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"