File indexing completed on 2025-01-05 03:59:43
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2013-08-04 0007 * Description : image editor canvas management class 0008 * 0009 * SPDX-FileCopyrightText: 2013-2014 by Yiou Wang <geow812 at gmail dot com> 0010 * SPDX-FileCopyrightText: 2004-2005 by Renchi Raju <renchi dot raju at gmail dot com> 0011 * SPDX-FileCopyrightText: 2004-2024 by Gilles Caulier <caulier dot gilles at gmail dot com> 0012 * 0013 * SPDX-License-Identifier: GPL-2.0-or-later 0014 * 0015 * ============================================================ */ 0016 0017 #include "canvas.h" 0018 0019 // Qt includes 0020 0021 #include <QFileInfo> 0022 #include <QClipboard> 0023 #include <QToolButton> 0024 #include <QScrollBar> 0025 #include <QMimeData> 0026 #include <QApplication> 0027 0028 // KDE includes 0029 0030 #include <klocalizedstring.h> 0031 0032 // Local includes 0033 0034 #include "imagehistogram.h" 0035 #include "iccsettingscontainer.h" 0036 #include "icctransform.h" 0037 #include "exposurecontainer.h" 0038 #include "iofilesettings.h" 0039 #include "loadingcacheinterface.h" 0040 #include "imagepreviewitem.h" 0041 #include "previewlayout.h" 0042 #include "imagezoomsettings.h" 0043 #include "clickdragreleaseitem.h" 0044 #include "rubberitem.h" 0045 0046 namespace Digikam 0047 { 0048 0049 class Q_DECL_HIDDEN Canvas::Private 0050 { 0051 0052 public: 0053 0054 explicit Private() 0055 : canvasItem(nullptr), 0056 rubber(nullptr), 0057 wrapItem(nullptr), 0058 core(nullptr) 0059 { 0060 } 0061 0062 QString errorMessage; 0063 0064 ImagePreviewItem* canvasItem; 0065 0066 RubberItem* rubber; 0067 ClickDragReleaseItem* wrapItem; 0068 EditorCore* core; 0069 }; 0070 0071 Canvas::Canvas(QWidget* const parent) 0072 : GraphicsDImgView(parent), 0073 d(new Private) 0074 { 0075 d->core = new EditorCore(); 0076 d->canvasItem = new ImagePreviewItem; 0077 setItem(d->canvasItem); 0078 0079 setFrameStyle(QFrame::NoFrame); 0080 addRubber(); 0081 layout()->fitToWindow(); 0082 installPanIcon(); 0083 0084 setAcceptDrops(true); 0085 viewport()->setAcceptDrops(true); 0086 0087 // ------------------------------------------------------------ 0088 0089 connect(d->core, SIGNAL(signalModified()), 0090 this, SLOT(slotModified())); 0091 0092 connect(d->core, SIGNAL(signalLoadingStarted(QString)), 0093 this, SIGNAL(signalLoadingStarted(QString))); 0094 0095 connect(d->core, SIGNAL(signalImageLoaded(QString,bool)), 0096 this, SLOT(slotImageLoaded(QString,bool))); 0097 0098 connect(d->core, SIGNAL(signalImageSaved(QString,bool)), 0099 this, SLOT(slotImageSaved(QString,bool))); 0100 0101 connect(d->core, SIGNAL(signalLoadingProgress(QString,float)), 0102 this, SIGNAL(signalLoadingProgress(QString,float))); 0103 0104 connect(d->core, SIGNAL(signalSavingStarted(QString)), 0105 this, SIGNAL(signalSavingStarted(QString))); 0106 0107 connect(d->core, SIGNAL(signalSavingProgress(QString,float)), 0108 this, SIGNAL(signalSavingProgress(QString,float))); 0109 0110 connect(this, SIGNAL(signalSelected(bool)), 0111 this, SLOT(slotSelected())); 0112 0113 connect(d->canvasItem, SIGNAL(showContextMenu(QGraphicsSceneContextMenuEvent*)), 0114 this, SIGNAL(signalRightButtonClicked())); 0115 0116 connect(layout(), SIGNAL(zoomFactorChanged(double)), 0117 this, SIGNAL(signalZoomChanged(double))); 0118 } 0119 0120 Canvas::~Canvas() 0121 { 0122 delete d->core; 0123 delete d->canvasItem; 0124 delete d; 0125 } 0126 0127 void Canvas::resetImage() 0128 { 0129 reset(); 0130 d->core->resetImage(); 0131 } 0132 0133 void Canvas::reset() 0134 { 0135 if (d->rubber && d->rubber->isVisible()) 0136 { 0137 d->rubber->setVisible(false); 0138 0139 if (d->core->isValid()) 0140 { 0141 Q_EMIT signalSelected(false); 0142 } 0143 } 0144 0145 addRubber(); 0146 d->errorMessage.clear(); 0147 } 0148 0149 void Canvas::load(const QString& filename, IOFileSettings* const IOFileSettings) 0150 { 0151 reset(); 0152 Q_EMIT signalPrepareToLoad(); 0153 d->core->load(filename, IOFileSettings); 0154 } 0155 0156 void Canvas::slotImageLoaded(const QString& filePath, bool success) 0157 { 0158 if (d->core->getImg()) 0159 { 0160 d->canvasItem->setImage(*d->core->getImg()); 0161 } 0162 0163 // Note: in showFoto, we using a null filename to clear canvas. 0164 0165 if (!success && !filePath.isEmpty()) 0166 { 0167 QFileInfo info(filePath); 0168 d->errorMessage = i18n("Failed to load image\n\"%1\"", info.fileName()); 0169 } 0170 else 0171 { 0172 d->errorMessage.clear(); 0173 } 0174 0175 viewport()->update(); 0176 0177 Q_EMIT signalLoadingFinished(filePath, success); 0178 } 0179 0180 void Canvas::fitToSelect() 0181 { 0182 QRect sel = d->core->getSelectedArea(); 0183 0184 if (!sel.size().isNull()) 0185 { 0186 // If selected area, use center of selection 0187 // and recompute zoom factor accordingly. 0188 0189 double cpx = sel.x() + sel.width() / 2.0; 0190 double cpy = sel.y() + sel.height() / 2.0; 0191 double srcWidth = sel.width(); 0192 double srcHeight = sel.height(); 0193 double dstWidth = contentsRect().width(); 0194 double dstHeight = contentsRect().height(); 0195 double zoom = qMin(dstWidth / srcWidth, dstHeight / srcHeight); 0196 0197 Q_EMIT signalToggleOffFitToWindow(); 0198 0199 layout()->setZoomFactor(zoom); 0200 0201 centerOn(cpx * zoom, cpy * zoom); 0202 viewport()->update(); 0203 } 0204 } 0205 0206 void Canvas::applyTransform(const IccTransform& t) 0207 { 0208 IccTransform transform(t); 0209 0210 if (transform.willHaveEffect()) 0211 { 0212 d->core->applyTransform(transform); 0213 } 0214 else 0215 { 0216 viewport()->update(); 0217 } 0218 } 0219 0220 void Canvas::preload(const QString& /*filename*/) 0221 { 0222 /* 0223 d->core->preload(filename); 0224 */ 0225 } 0226 0227 void Canvas::slotImageSaved(const QString& filePath, bool success) 0228 { 0229 Q_EMIT signalSavingFinished(filePath, success); 0230 } 0231 0232 void Canvas::abortSaving() 0233 { 0234 d->core->abortSaving(); 0235 } 0236 0237 void Canvas::setModified() 0238 { 0239 d->core->setModified(); 0240 } 0241 0242 QString Canvas::ensureHasCurrentUuid() const 0243 { 0244 return d->core->ensureHasCurrentUuid(); 0245 } 0246 0247 DImg Canvas::currentImage() const 0248 { 0249 DImg* const image = d->core->getImg(); 0250 0251 if (image) 0252 { 0253 return DImg(*image); 0254 } 0255 0256 return DImg(); 0257 } 0258 0259 QString Canvas::currentImageFileFormat() const 0260 { 0261 return d->core->getImageFormat(); 0262 } 0263 0264 QString Canvas::currentImageFilePath() const 0265 { 0266 return d->core->getImageFilePath(); 0267 } 0268 0269 int Canvas::imageWidth() const 0270 { 0271 return d->core->origWidth(); 0272 } 0273 0274 int Canvas::imageHeight() const 0275 { 0276 return d->core->origHeight(); 0277 } 0278 0279 bool Canvas::isReadOnly() const 0280 { 0281 return d->core->isReadOnly(); 0282 } 0283 0284 QRect Canvas::getSelectedArea() const 0285 { 0286 return d->core->getSelectedArea(); 0287 } 0288 0289 EditorCore* Canvas::interface() const 0290 { 0291 return d->core; 0292 } 0293 0294 void Canvas::makeDefaultEditingCanvas() 0295 { 0296 EditorCore::setDefaultInstance(d->core); 0297 } 0298 0299 bool Canvas::exifRotated() const 0300 { 0301 return d->core->exifRotated(); 0302 } 0303 0304 void Canvas::slotRotate90() 0305 { 0306 d->canvasItem->clearCache(); 0307 d->core->rotate90(); 0308 } 0309 0310 void Canvas::slotRotate180() 0311 { 0312 d->canvasItem->clearCache(); 0313 d->core->rotate180(); 0314 } 0315 0316 void Canvas::slotRotate270() 0317 { 0318 d->canvasItem->clearCache(); 0319 d->core->rotate270(); 0320 } 0321 0322 void Canvas::slotFlipHoriz() 0323 { 0324 d->canvasItem->clearCache(); 0325 d->core->flipHoriz(); 0326 } 0327 0328 void Canvas::slotFlipVert() 0329 { 0330 d->canvasItem->clearCache(); 0331 d->core->flipVert(); 0332 } 0333 0334 void Canvas::slotCrop() 0335 { 0336 d->canvasItem->clearCache(); 0337 QRect sel = d->core->getSelectedArea(); 0338 0339 if (sel.size().isNull()) // No current selection. 0340 { 0341 return; 0342 } 0343 0344 d->core->crop(sel); 0345 0346 if (d->rubber && d->rubber->isVisible()) 0347 { 0348 d->rubber->setVisible(false); 0349 } 0350 0351 Q_EMIT signalSelected(false); 0352 addRubber(); 0353 } 0354 0355 void Canvas::setICCSettings(const ICCSettingsContainer& cmSettings) 0356 { 0357 d->canvasItem->clearCache(); 0358 d->core->setICCSettings(cmSettings); 0359 viewport()->update(); 0360 } 0361 0362 void Canvas::setSoftProofingEnabled(bool enable) 0363 { 0364 d->canvasItem->clearCache(); 0365 d->core->setSoftProofingEnabled(enable); 0366 viewport()->update(); 0367 } 0368 0369 void Canvas::setExposureSettings(ExposureSettingsContainer* const expoSettings) 0370 { 0371 d->canvasItem->clearCache(); 0372 d->core->setExposureSettings(expoSettings); 0373 viewport()->update(); 0374 } 0375 0376 void Canvas::setExifOrient(bool exifOrient) 0377 { 0378 d->canvasItem->clearCache(); 0379 d->core->setExifOrient(exifOrient); 0380 viewport()->update(); 0381 } 0382 0383 void Canvas::slotRestore() 0384 { 0385 d->core->restore(); 0386 viewport()->update(); 0387 } 0388 0389 void Canvas::slotUndo(int steps) 0390 { 0391 Q_EMIT signalUndoSteps(steps); 0392 0393 d->canvasItem->clearCache(); 0394 0395 while (steps > 0) 0396 { 0397 d->core->undo(); 0398 --steps; 0399 } 0400 } 0401 0402 void Canvas::slotRedo(int steps) 0403 { 0404 Q_EMIT signalRedoSteps(steps); 0405 0406 d->canvasItem->clearCache(); 0407 0408 while (steps > 0) 0409 { 0410 d->core->redo(); 0411 --steps; 0412 } 0413 } 0414 0415 void Canvas::slotCopy() 0416 { 0417 QRect sel = d->core->getSelectedArea(); 0418 0419 if (sel.size().isNull()) // No current selection. 0420 { 0421 return; 0422 } 0423 0424 QApplication::setOverrideCursor(Qt::WaitCursor); 0425 0426 DImg selDImg = d->core->getImgSelection(); 0427 QImage selImg = selDImg.copyQImage(); 0428 QMimeData* const mimeData = new QMimeData(); 0429 mimeData->setImageData(selImg); 0430 QApplication::clipboard()->setMimeData(mimeData, QClipboard::Clipboard); 0431 0432 QApplication::restoreOverrideCursor(); 0433 } 0434 0435 void Canvas::slotSelected() 0436 { 0437 QRect sel = QRect(0, 0, 0, 0); 0438 0439 if (d->wrapItem) 0440 { 0441 cancelAddItem(); 0442 return; 0443 } 0444 0445 if (d->rubber) 0446 { 0447 sel = calcSelectedArea(); 0448 } 0449 0450 d->core->setSelectedArea(sel); 0451 Q_EMIT signalSelectionChanged(sel); 0452 } 0453 0454 void Canvas::slotSelectionMoved() 0455 { 0456 QRect sel = QRect(0, 0, 0, 0); 0457 0458 if (d->rubber) 0459 { 0460 sel = calcSelectedArea(); 0461 } 0462 0463 Q_EMIT signalSelectionSetText(sel); 0464 } 0465 0466 QRect Canvas::calcSelectedArea() const 0467 { 0468 int x = 0, y = 0, w = 0, h = 0; 0469 double z = layout()->realZoomFactor(); 0470 0471 if (d->rubber && d->rubber->isVisible()) 0472 { 0473 QRect r(d->rubber->boundingRect().toRect()); 0474 0475 if (r.isValid()) 0476 { 0477 r.translate((int)d->rubber->x(), 0478 (int)d->rubber->y()); 0479 0480 x = (int)((double)r.x() / z); 0481 y = (int)((double)r.y() / z); 0482 w = (int)((double)r.width() / z); 0483 h = (int)((double)r.height() / z); 0484 0485 x = qMin(imageWidth(), qMax(x, 0)); 0486 y = qMin(imageHeight(), qMax(y, 0)); 0487 w = qMin(imageWidth(), qMax(w, 0)); 0488 h = qMin(imageHeight(), qMax(h, 0)); 0489 0490 // Avoid empty selection by rubberband - at least mark one pixel 0491 // At high zoom factors, the rubberband may operate at subpixel level! 0492 0493 if (w == 0) 0494 { 0495 w = 1; 0496 } 0497 0498 if (h == 0) 0499 { 0500 h = 1; 0501 } 0502 } 0503 } 0504 0505 return QRect(x, y, w, h); 0506 } 0507 0508 void Canvas::slotModified() 0509 { 0510 d->canvasItem->setImage(currentImage()); 0511 0512 Q_EMIT signalChanged(); 0513 } 0514 0515 void Canvas::slotSelectAll() 0516 { 0517 if (d->rubber) 0518 { 0519 delete d->rubber; 0520 } 0521 0522 d->rubber = new RubberItem(d->canvasItem); 0523 d->rubber->setCanvas(this); 0524 d->rubber->setRectInSceneCoordinatesAdjusted(d->canvasItem->boundingRect()); 0525 viewport()->setMouseTracking(true); 0526 viewport()->update(); 0527 0528 if (d->core->isValid()) 0529 { 0530 Q_EMIT signalSelected(true); 0531 } 0532 } 0533 0534 void Canvas::slotSelectNone() 0535 { 0536 reset(); 0537 viewport()->update(); 0538 } 0539 0540 void Canvas::keyPressEvent(QKeyEvent* event) 0541 { 0542 if (!event) 0543 { 0544 return; 0545 } 0546 0547 int mult = 1; 0548 0549 if ((event->modifiers() & Qt::ControlModifier)) 0550 { 0551 mult = 10; 0552 } 0553 0554 switch (event->key()) 0555 { 0556 case Qt::Key_Right: 0557 { 0558 horizontalScrollBar()->setValue(horizontalScrollBar()->value() + horizontalScrollBar()->singleStep()*mult); 0559 break; 0560 } 0561 0562 case Qt::Key_Left: 0563 { 0564 horizontalScrollBar()->setValue(horizontalScrollBar()->value() - horizontalScrollBar()->singleStep()*mult); 0565 break; 0566 } 0567 0568 case Qt::Key_Up: 0569 { 0570 verticalScrollBar()->setValue(verticalScrollBar()->value() - verticalScrollBar()->singleStep()*mult); 0571 break; 0572 } 0573 0574 case Qt::Key_Down: 0575 { 0576 verticalScrollBar()->setValue(verticalScrollBar()->value() + verticalScrollBar()->singleStep()*mult); 0577 break; 0578 } 0579 0580 default: 0581 { 0582 event->ignore(); 0583 break; 0584 } 0585 } 0586 } 0587 0588 void Canvas::addRubber() 0589 { 0590 if (!d->wrapItem) 0591 { 0592 d->wrapItem = new ClickDragReleaseItem(d->canvasItem); 0593 } 0594 0595 d->wrapItem->setFocus(); 0596 setFocus(); 0597 0598 connect(d->wrapItem, SIGNAL(started(QPointF)), 0599 this, SLOT(slotAddItemStarted(QPointF))); 0600 0601 connect(d->wrapItem, SIGNAL(moving(QRectF)), 0602 this, SLOT(slotAddItemMoving(QRectF))); 0603 0604 connect(d->wrapItem, SIGNAL(finished(QRectF)), 0605 this, SLOT(slotAddItemFinished(QRectF))); 0606 0607 connect(d->wrapItem, SIGNAL(cancelled()), 0608 this, SLOT(cancelAddItem())); 0609 } 0610 0611 void Canvas::slotAddItemStarted(const QPointF& pos) 0612 { 0613 Q_UNUSED(pos); 0614 } 0615 0616 void Canvas::slotAddItemMoving(const QRectF& rect) 0617 { 0618 if (d->rubber) 0619 { 0620 delete d->rubber; 0621 } 0622 0623 d->rubber = new RubberItem(d->canvasItem); 0624 d->rubber->setCanvas(this); 0625 d->rubber->setRectInSceneCoordinatesAdjusted(rect); 0626 } 0627 0628 void Canvas::slotAddItemFinished(const QRectF& rect) 0629 { 0630 if (d->rubber) 0631 { 0632 d->rubber->setRectInSceneCoordinatesAdjusted(rect); 0633 /* 0634 d->wrapItem->stackBefore(d->canvasItem); 0635 */ 0636 } 0637 0638 cancelAddItem(); 0639 } 0640 0641 void Canvas::cancelAddItem() 0642 { 0643 if (d->wrapItem) 0644 { 0645 this->scene()->removeItem(d->wrapItem); 0646 d->wrapItem->deleteLater(); 0647 d->wrapItem = nullptr; 0648 } 0649 0650 Q_EMIT signalSelected(true); 0651 } 0652 0653 void Canvas::mousePressEvent(QMouseEvent* event) 0654 { 0655 GraphicsDImgView::mousePressEvent(event); 0656 0657 if (event->button() == Qt::LeftButton) 0658 { 0659 GraphicsDImgItem* const item = dynamic_cast<GraphicsDImgItem*>(itemAt(event->pos())); 0660 0661 if (item) 0662 { 0663 QLatin1String className(item->metaObject()->className()); 0664 0665 if (!(className == QLatin1String("Digikam::RubberItem") || className == QLatin1String("Digikam::ClickDragReleaseItem"))) 0666 { 0667 if (d->rubber && d->rubber->isVisible()) 0668 { 0669 d->rubber->setVisible(false); 0670 } 0671 0672 Q_EMIT signalSelected(false); 0673 addRubber(); 0674 } 0675 } 0676 } 0677 } 0678 0679 void Canvas::dragEnterEvent(QDragEnterEvent* e) 0680 { 0681 QGraphicsView::dragEnterEvent(e); 0682 0683 if (e->mimeData()->hasUrls()) 0684 { 0685 e->acceptProposedAction(); 0686 } 0687 } 0688 0689 void Canvas::dragMoveEvent(QDragMoveEvent* e) 0690 { 0691 QGraphicsView::dragMoveEvent(e); 0692 0693 if (e->mimeData()->hasUrls()) 0694 { 0695 e->acceptProposedAction(); 0696 } 0697 } 0698 0699 void Canvas::dropEvent(QDropEvent* e) 0700 { 0701 QGraphicsView::dropEvent(e); 0702 Q_EMIT signalAddedDropedItems(e); 0703 } 0704 0705 } // namespace Digikam 0706 0707 #include "moc_canvas.cpp"