File indexing completed on 2025-01-05 03:51:41

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2017-06-15
0007  * Description : a brush for use with tool to replace part of the image using another
0008  *
0009  * SPDX-FileCopyrightText: 2017 by Shaza Ismail Kaoud <shaza dot ismail dot k at gmail dot com>
0010  * SPDX-FileCopyrightText: 2019 by Ahmed Fathi <ahmed dot fathi dot abdelmageed at gmail dot com>
0011  *
0012  * SPDX-License-Identifier: GPL-2.0-or-later
0013  *
0014  * ============================================================ */
0015 
0016 #include "healingclonetoolwidget.h"
0017 
0018 // Qt includes
0019 
0020 #include <QScrollBar>
0021 #include <QPainter>
0022 #include <QCursor>
0023 
0024 // KDE includes
0025 
0026 #include <klocalizedstring.h>
0027 
0028 // Local includes
0029 
0030 #include "digikam_debug.h"
0031 #include "overlaywidget.h"
0032 #include "previewlayout.h"
0033 
0034 namespace DigikamEditorHealingCloneToolPlugin
0035 {
0036 
0037 class Q_DECL_HIDDEN HealingCloneToolWidget::Private
0038 {
0039 public:
0040 
0041     explicit Private()
0042       : srcSet                  (true),
0043         isLassoPointsVectorEmpty(true),
0044         src                     (QPoint(0, 0)),
0045         amIFocused              (false),
0046         proceedInMoveEvent      (false),
0047         cloneVectorChanged      (true),
0048         brushRadius             (1),
0049         brushValue              (1),
0050         currentState            (HealingCloneState::SELECT_SOURCE),
0051         previousState           (HealingCloneState::DO_NOTHING),
0052         drawCursor              (nullptr),
0053         sourceCursor            (nullptr),
0054         sourceCursorCenter      (nullptr)
0055     {
0056     }
0057 
0058     bool                  srcSet;
0059     bool                  isLassoPointsVectorEmpty;
0060     QPointF               lastCursorPosition;
0061     QPoint                src;
0062     QPoint                dst;
0063     bool                  amIFocused;
0064     bool                  proceedInMoveEvent;
0065     bool                  cloneVectorChanged;
0066     int                   brushRadius;
0067     int                   brushValue;
0068     HealingCloneState     currentState;
0069     HealingCloneState     previousState;
0070     QGraphicsEllipseItem* drawCursor;
0071     QGraphicsEllipseItem* sourceCursor;
0072     QGraphicsEllipseItem* sourceCursorCenter;
0073 };
0074 
0075 HealingCloneToolWidget::HealingCloneToolWidget(QWidget* const parent)
0076     : ImageRegionWidget(parent, false),
0077       d                (new Private)
0078 {
0079     activateState(HealingCloneState::SELECT_SOURCE);
0080     updateSourceCursor(d->src, 10);
0081 
0082     connect(this, SIGNAL(viewportRectChanged(QRectF)),
0083             this, SLOT(slotImageRegionChanged()));
0084 }
0085 
0086 HealingCloneToolWidget::~HealingCloneToolWidget()
0087 {
0088     delete d->drawCursor;
0089     delete d->sourceCursor;
0090     delete d->sourceCursorCenter;
0091     delete d;
0092 }
0093 
0094 void HealingCloneToolWidget::mousePressEvent(QMouseEvent* e)
0095 {
0096     if      (!d->amIFocused                                 &&
0097              ((d->currentState == HealingCloneState::PAINT) ||
0098               (d->currentState == HealingCloneState::LASSO_CLONE)))
0099     {
0100         d->amIFocused = true;
0101         return;
0102     }
0103     else if (!d->amIFocused)
0104     {
0105         d->amIFocused = true;
0106     }
0107 
0108     d->proceedInMoveEvent = true;
0109 
0110     if (d->currentState == HealingCloneState::DO_NOTHING)
0111     {
0112         ImageRegionWidget::mousePressEvent(e);
0113         return;
0114     }
0115 
0116     if ((d->currentState == HealingCloneState::PAINT) ||
0117         (d->currentState == HealingCloneState::LASSO_CLONE))
0118     {
0119         if (d->cloneVectorChanged)
0120         {
0121             setCloneVectorChanged(false);
0122 
0123             Q_EMIT signalPushToUndoStack();
0124         }
0125     }
0126 
0127     if      ((d->currentState == HealingCloneState::MOVE_IMAGE) &&
0128              (e->buttons() & Qt::LeftButton))
0129     {
0130         ImageRegionWidget::mousePressEvent(e);
0131     }
0132     else if (d->srcSet)
0133     {
0134         ImageRegionWidget::mousePressEvent(e);
0135     }
0136     else if ((d->currentState == HealingCloneState::LASSO_DRAW_BOUNDARY) &&
0137              (e->buttons() & Qt::LeftButton))
0138     {
0139 
0140 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
0141 
0142         QPoint dst = QPoint(e->position().toPoint().x(), e->position().toPoint().y());
0143 
0144 #else
0145 
0146         QPoint dst = QPoint(e->x(), e->y());
0147 
0148 #endif
0149 
0150         Q_EMIT signalLasso(mapToImageCoordinates(dst));
0151     }
0152     else
0153     {
0154         if (e->button() == Qt::LeftButton)
0155         {
0156             d->dst = mapToImageCoordinates(e->pos());
0157 
0158             Q_EMIT signalClone(d->src, d->dst);
0159         }
0160     }
0161 }
0162 
0163 void HealingCloneToolWidget::mouseMoveEvent(QMouseEvent* e)
0164 {
0165     bool cursorOutsideScene = checkPointOutsideScene(e->pos());
0166     d->lastCursorPosition   = mapToScene(e->pos());
0167 
0168     if      (cursorOutsideScene &&
0169              (d->currentState != HealingCloneState::DO_NOTHING))
0170     {
0171         activateState(HealingCloneState::DO_NOTHING);
0172     }
0173     else if (!cursorOutsideScene &&
0174              (d->currentState == HealingCloneState::DO_NOTHING))
0175     {
0176         activateState(d->previousState);
0177     }
0178 
0179     setDrawCursorPosition(d->lastCursorPosition);
0180 
0181     if (d->currentState == HealingCloneState::DO_NOTHING)
0182     {
0183         ImageRegionWidget::mouseMoveEvent(e);
0184         return;
0185     }
0186 
0187     if (!d->proceedInMoveEvent)
0188     {
0189         return;
0190     }
0191 
0192     if      ((d->currentState == HealingCloneState::MOVE_IMAGE) &&
0193              (e->buttons() & Qt::LeftButton))
0194     {
0195         ImageRegionWidget::mouseMoveEvent(e);
0196     }
0197     else if ((d->currentState == HealingCloneState::LASSO_DRAW_BOUNDARY) &&
0198              (e->buttons() & Qt::LeftButton))
0199     {
0200 
0201 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
0202 
0203         QPoint dst = QPoint(e->position().toPoint().x(), e->position().toPoint().y());
0204 
0205 #else
0206 
0207         QPoint dst = QPoint(e->x(), e->y());
0208 
0209 #endif
0210 
0211         Q_EMIT signalLasso(mapToImageCoordinates(dst));
0212     }
0213     else if ((e->buttons() & Qt::LeftButton) && !d->srcSet)
0214     {
0215         QPoint currentDst = mapToImageCoordinates(e->pos());
0216         QPoint currentSrc = d->src;
0217         QPoint orgDst     = d->dst;
0218         currentSrc        = QPoint(currentSrc.x() + currentDst.x() - orgDst.x(),
0219                                    currentSrc.y() + currentDst.y() - orgDst.y());
0220 
0221         // Source Cursor Update in scene coordinates
0222 
0223         QPointF tempCursorPosition = mapToScene(mapFromImageCoordinates(currentSrc));
0224         setSourceCursorPosition(tempCursorPosition);
0225 
0226         Q_EMIT signalClone(currentSrc, currentDst);
0227     }
0228 
0229     if (d->srcSet)
0230     {
0231         ImageRegionWidget::mouseMoveEvent(e);
0232     }
0233 }
0234 
0235 void HealingCloneToolWidget::mouseReleaseEvent(QMouseEvent* e)
0236 {
0237     ImageRegionWidget::mouseReleaseEvent(e);
0238 
0239     if (d->currentState == HealingCloneState::DO_NOTHING)
0240     {
0241         return;
0242     }
0243 
0244     if      (d->currentState == HealingCloneState::MOVE_IMAGE)
0245     {
0246 /*
0247         setCursor(Qt::OpenHandCursor);
0248 */
0249         ImageRegionWidget::mouseReleaseEvent(e);
0250     }
0251 
0252     else if (d->srcSet)
0253     {
0254         d->src = mapToImageCoordinates(e->pos());
0255         setSourceCursorPosition(mapToScene(e->pos()));
0256 
0257         undoSlotSetSourcePoint();
0258     }
0259     else
0260     {
0261         QPointF tempCursorPosition = mapToScene(mapFromImageCoordinates(d->src));
0262         setSourceCursorPosition(tempCursorPosition);
0263     }
0264 }
0265 
0266 void HealingCloneToolWidget::mouseDoubleClickEvent(QMouseEvent* e)
0267 {
0268     if (e->button() == Qt::LeftButton)
0269     {
0270         if (d->currentState == HealingCloneState::LASSO_DRAW_BOUNDARY)
0271         {
0272             slotLassoSelect();
0273         }
0274     }
0275 }
0276 
0277 void HealingCloneToolWidget::keyPressEvent(QKeyEvent* e)
0278 {
0279     if      (e->key() == Qt::Key_M)
0280     {
0281         slotMoveImage();
0282     }
0283     else if (e->key() == Qt::Key_L)
0284     {
0285         slotLassoSelect();
0286     }
0287 
0288     if (e->key() == Qt::Key_BracketLeft)
0289     {
0290         Q_EMIT signalDecreaseBrushRadius();
0291     }
0292 
0293     if (e->key() == Qt::Key_BracketRight)
0294     {
0295         Q_EMIT signalIncreaseBrushRadius();
0296     }
0297 
0298     if (e->matches(QKeySequence::Undo))
0299     {
0300         Q_EMIT signalUndoClone();
0301     }
0302 
0303     if (e->matches(QKeySequence::Redo))
0304     {
0305         Q_EMIT signalRedoClone();
0306     }
0307 
0308     ImageRegionWidget::keyPressEvent(e);
0309 }
0310 
0311 bool HealingCloneToolWidget::event(QEvent* e)
0312 {
0313     QKeyEvent* const keyEvent = static_cast<QKeyEvent*>(e);
0314 
0315     if (keyEvent && (keyEvent->key() == Qt::Key_Escape) &&
0316         (d->currentState != HealingCloneState::PAINT))
0317     {
0318         keyEvent->accept();
0319 
0320         if      (d->currentState == HealingCloneState::LASSO_DRAW_BOUNDARY)
0321         {
0322             if (!d->isLassoPointsVectorEmpty)
0323             {
0324                 slotLassoSelect();
0325             }
0326 
0327             slotLassoSelect();
0328         }
0329         else if (d->currentState == HealingCloneState::LASSO_CLONE)
0330         {
0331             slotLassoSelect();
0332         }
0333 
0334         return true;
0335     }
0336 
0337     return ImageRegionWidget::event(e);
0338 }
0339 
0340 void HealingCloneToolWidget::keyReleaseEvent(QKeyEvent* e)
0341 {
0342     if (e->key() == Qt::Key_S)
0343     {
0344         if (d->currentState == HealingCloneState::SELECT_SOURCE)
0345         {
0346             undoSlotSetSourcePoint();
0347         }
0348         else
0349         {
0350             slotSetSourcePoint();
0351         }
0352     }
0353 }
0354 
0355 void HealingCloneToolWidget::focusOutEvent(QFocusEvent*)
0356 {
0357     d->amIFocused         = false;
0358     d->proceedInMoveEvent = false;
0359 }
0360 
0361 void HealingCloneToolWidget::focusInEvent(QFocusEvent*)
0362 {
0363 }
0364 
0365 void HealingCloneToolWidget::slotSetSourcePoint()
0366 {
0367     d->srcSet = true;
0368     activateState(HealingCloneState::SELECT_SOURCE);
0369 }
0370 
0371 void HealingCloneToolWidget::slotMoveImage()
0372 {
0373     if (d->currentState == HealingCloneState::MOVE_IMAGE)
0374     {
0375 
0376         if (d->isLassoPointsVectorEmpty)
0377         {
0378             activateState(HealingCloneState::PAINT);
0379         }
0380         else
0381         {
0382             activateState(HealingCloneState::LASSO_CLONE);
0383             Q_EMIT signalContinuePolygon();
0384         }
0385     }
0386     else
0387     {
0388         activateState(HealingCloneState::MOVE_IMAGE);
0389     }
0390 }
0391 
0392 void HealingCloneToolWidget::slotLassoSelect()
0393 {
0394     if      ((d->currentState != HealingCloneState::LASSO_DRAW_BOUNDARY) &&
0395              (d->currentState != HealingCloneState::LASSO_CLONE))
0396     {
0397         activateState(HealingCloneState::LASSO_DRAW_BOUNDARY);
0398 
0399         Q_EMIT signalResetLassoPoint();
0400     }
0401     else if (d->currentState == HealingCloneState::LASSO_DRAW_BOUNDARY)
0402     {
0403         if (d->isLassoPointsVectorEmpty)
0404         {
0405             activateState(HealingCloneState::PAINT);
0406         }
0407         else
0408         {
0409             activateState(HealingCloneState::LASSO_CLONE);
0410 
0411             Q_EMIT signalContinuePolygon();
0412         }
0413     }
0414     else if (d->currentState == HealingCloneState::LASSO_CLONE)
0415     {
0416         activateState(HealingCloneState::PAINT);
0417 
0418         Q_EMIT signalResetLassoPoint();
0419     }
0420 }
0421 
0422 void HealingCloneToolWidget::undoSlotSetSourcePoint()
0423 {
0424     d->srcSet = false;
0425 
0426     if (d->isLassoPointsVectorEmpty)
0427     {
0428         activateState(HealingCloneState::PAINT);
0429     }
0430     else
0431     {
0432         activateState(HealingCloneState::LASSO_CLONE);
0433 
0434         Q_EMIT signalContinuePolygon();
0435     }
0436 }
0437 
0438 void HealingCloneToolWidget::changeCursorShape(const QColor& color)
0439 {
0440     if (d->drawCursor)
0441     {
0442         scene()->removeItem(d->drawCursor);
0443         delete d->drawCursor;
0444     }
0445 
0446     int diameter = d->brushRadius * 2;
0447 
0448     d->drawCursor = new QGraphicsEllipseItem(0, 0, diameter, diameter);
0449     d->drawCursor->setFlag(QGraphicsItem::ItemClipsChildrenToShape, true);
0450 
0451     QPen pen(Qt::SolidLine);
0452     pen.setWidth(2);
0453     pen.setColor(color);
0454     d->drawCursor->setPen(pen);
0455     d->drawCursor->setBrush(QBrush(Qt::transparent));
0456     d->drawCursor->setOpacity(1);
0457     scene()->addItem(d->drawCursor);
0458 
0459     QPointF tempCursorPosition = mapToScene(mapFromImageCoordinates(d->src));
0460     updateSourceCursor(tempCursorPosition, diameter);
0461 }
0462 
0463 void HealingCloneToolWidget::setBrushValue(int value)
0464 {
0465     d->brushValue = value;
0466     slotImageRegionChanged();
0467 }
0468 
0469 void HealingCloneToolWidget::setIsLassoPointsVectorEmpty(bool isEmpty)
0470 {
0471     d->isLassoPointsVectorEmpty = isEmpty;
0472 }
0473 
0474 void HealingCloneToolWidget::activateState(HealingCloneState state)
0475 {
0476     d->previousState = d->currentState;
0477 
0478     if (state != HealingCloneState::MOVE_IMAGE)
0479     {
0480         setDragMode(QGraphicsView::NoDrag);
0481     }
0482 
0483     if ((d->currentState == HealingCloneState::LASSO_DRAW_BOUNDARY) &&
0484         (state != HealingCloneState::LASSO_CLONE))
0485     {
0486         Q_EMIT signalContinuePolygon();
0487     }
0488 
0489     d->currentState = state;
0490 
0491     if      (state == HealingCloneState::PAINT)
0492     {
0493         changeCursorShape(Qt::blue);
0494         setCursor(QCursor(Qt::BlankCursor));
0495         setDrawCursorPosition(d->lastCursorPosition);
0496     }
0497     else if (state == HealingCloneState::MOVE_IMAGE)
0498     {
0499         if (QGraphicsView::dragMode() != QGraphicsView::ScrollHandDrag)
0500         {
0501             setDragMode(QGraphicsView::ScrollHandDrag);
0502         }
0503     }
0504     else if (state == HealingCloneState::LASSO_DRAW_BOUNDARY)
0505     {
0506         setCursor(QCursor(Qt::PointingHandCursor));
0507     }
0508     else if (state == HealingCloneState::LASSO_CLONE)
0509     {
0510         changeCursorShape(Qt::blue);
0511         setCursor(QCursor(Qt::BlankCursor));
0512         setDrawCursorPosition(d->lastCursorPosition);
0513     }
0514     else if (state == HealingCloneState::SELECT_SOURCE)
0515     {
0516         setCursor(QCursor(Qt::CrossCursor));
0517     }
0518     else if (state == HealingCloneState::DO_NOTHING)
0519     {
0520         setCursor(QCursor(Qt::ArrowCursor));
0521     }
0522 }
0523 
0524 void HealingCloneToolWidget::setCloneVectorChanged(bool changed)
0525 {
0526     d->cloneVectorChanged = changed;
0527 }
0528 
0529 QPoint HealingCloneToolWidget::mapToImageCoordinates(const QPoint& point) const
0530 {
0531     QPoint ret;
0532     ImageRegionItem* const region = dynamic_cast<ImageRegionItem*>(item());
0533 
0534     if (region)
0535     {
0536         QPointF temp = region->zoomSettings()->mapZoomToImage(mapToScene(point)) ;
0537         ret          = QPoint((int)temp.x(), (int)temp.y());
0538     }
0539 
0540     return ret;
0541 }
0542 
0543 QPoint HealingCloneToolWidget::mapFromImageCoordinates(const QPoint& point) const
0544 {
0545     QPoint ret;
0546     ImageRegionItem* const region = dynamic_cast<ImageRegionItem*>(item());
0547 
0548     if (region)
0549     {
0550         ret = mapFromScene(region->zoomSettings()->mapImageToZoom(point));
0551     }
0552 
0553     return ret;
0554 }
0555 
0556 void HealingCloneToolWidget::updateSourceCursor(const QPointF& pos, int diameter)
0557 {
0558     if (d->sourceCursor)
0559     {
0560         scene()->removeItem(d->sourceCursor);
0561         scene()->removeItem(d->sourceCursorCenter);
0562         delete d->sourceCursor;
0563         delete d->sourceCursorCenter;
0564     }
0565 
0566     d->sourceCursor       = new QGraphicsEllipseItem(0, 0, diameter, diameter);
0567     d->sourceCursorCenter = new QGraphicsEllipseItem(0, 0, 2, 2);
0568     d->sourceCursor->setFlag(QGraphicsItem::ItemClipsChildrenToShape, true);
0569     d->sourceCursorCenter->setFlag(QGraphicsItem::ItemClipsChildrenToShape, true);
0570 
0571     QPen pen(Qt::DashDotDotLine);
0572     pen.setWidth(2);
0573     pen.setColor(Qt::black);
0574     d->sourceCursor->setPen(pen);
0575     d->sourceCursor->setBrush(QBrush(Qt::transparent));
0576     d->sourceCursor->setOpacity(1);
0577     scene()->addItem(d->sourceCursor);
0578 
0579     pen.setStyle(Qt::SolidLine);
0580     d->sourceCursorCenter->setPen(pen);
0581     d->sourceCursorCenter->setBrush(QBrush(Qt::black));
0582     d->sourceCursorCenter->setOpacity(1);
0583     scene()->addItem(d->sourceCursorCenter);
0584 
0585     setSourceCursorPosition(pos);
0586 }
0587 
0588 void HealingCloneToolWidget::setDrawCursorPosition(const QPointF& topLeftPos)
0589 {
0590     if (!d->drawCursor)
0591     {
0592         return;
0593     }
0594 
0595     double d1          = d->drawCursor->rect().width() / 2.0;
0596     QPointF shiftedPos = QPointF(topLeftPos.x() - d1, topLeftPos.y() - d1);
0597 
0598     // Check if source is outside scene
0599 
0600     bool drawCursorOutsideScene = ((topLeftPos.x() < 0)                ||
0601                                    (topLeftPos.x() > scene()->width()) ||
0602                                    (topLeftPos.y() < 0)                ||
0603                                    (topLeftPos.y() > scene()->height()));
0604 
0605     if (drawCursorOutsideScene                         ||
0606         ((d->currentState != HealingCloneState::PAINT) &&
0607          (d->currentState != HealingCloneState::LASSO_CLONE)))
0608     {
0609         d->drawCursor->setVisible(false);
0610     }
0611     else
0612     {
0613         d->drawCursor->setPos(shiftedPos);
0614 
0615         d->drawCursor->setVisible(true);
0616     }
0617 }
0618 
0619 void HealingCloneToolWidget::setSourceCursorPosition(const QPointF& topLeftPos)
0620 {
0621     double d1           = d->sourceCursor->rect().width() / 2.0;
0622     QPointF shiftedPos  = QPointF(topLeftPos.x() - d1, topLeftPos.y() - d1);
0623 
0624     double d2           = d->sourceCursorCenter->rect().width() / 2.0;
0625     QPointF shiftedPos2 = QPointF(topLeftPos.x() - d2, topLeftPos.y() - d2);
0626 
0627     // Check if source is outside scene
0628 
0629     bool sourceCursorOutsideScene = ((topLeftPos.x() < 0)                ||
0630                                      (topLeftPos.x() > scene()->width()) ||
0631                                      (topLeftPos.y() < 0)                ||
0632                                      (topLeftPos.y() > scene()->height()));
0633 
0634     if (sourceCursorOutsideScene)
0635     {
0636         d->sourceCursor->setVisible(false);
0637         d->sourceCursorCenter->setVisible(false);
0638     }
0639     else
0640     {
0641         d->sourceCursor->setPos(shiftedPos);
0642         d->sourceCursorCenter->setPos(shiftedPos2);
0643 
0644         d->sourceCursor->setVisible(true);
0645         d->sourceCursorCenter->setVisible(true);
0646     }
0647 }
0648 
0649 bool HealingCloneToolWidget::checkPointOutsideScene(const QPoint& globalPoint) const
0650 {
0651     bool pointOutsideScene;
0652     QPointF temp = mapToScene(globalPoint);
0653 
0654     if (viewport()->width() > scene()->width())
0655     {
0656         pointOutsideScene  = ((temp.x() < 0)                ||
0657                               (temp.x() > scene()->width()) ||
0658                               (temp.y() < 0)                ||
0659                               (temp.y() > scene()->height()));
0660     }
0661     else
0662     {
0663         QPoint bottomRight = QPoint(viewport()->width(),
0664                                     viewport()->height());
0665 
0666         int right          = mapToScene(bottomRight).x();
0667         int left           = right - viewport()->width();
0668         int bottom         = mapToScene(bottomRight).y();
0669         int top            = bottom - viewport()->height();
0670 
0671         pointOutsideScene  = ((temp.x()     < left)  ||
0672                               (temp.x() + 1 > right) ||
0673                               (temp.y()     < top)   ||
0674                               (temp.y() + 1 > bottom));
0675     }
0676 
0677     return pointOutsideScene;
0678 }
0679 
0680 void HealingCloneToolWidget::slotImageRegionChanged()
0681 {
0682     double zoom    = layout()->realZoomFactor();
0683     d->brushRadius = qRound(d->brushValue * zoom);
0684 
0685     activateState(d->currentState);
0686 
0687     if (!d->lastCursorPosition.isNull())
0688     {
0689         setDrawCursorPosition(d->lastCursorPosition);
0690     }
0691 }
0692 
0693 } // namespace DigikamEditorHealingCloneToolPlugin
0694 
0695 #include "moc_healingclonetoolwidget.cpp"