File indexing completed on 2024-05-12 16:01:54

0001 /*
0002  * SPDX-FileCopyrightText: 2016 Boudewijn Rempt <boud@valdyas.org>
0003  *
0004  * SPDX-License-Identifier: LGPL-2.0-or-later
0005  */
0006 
0007 #include "KisReferenceImagesDecoration.h"
0008 
0009 #include "KoShapeManager.h"
0010 
0011 #include "kis_algebra_2d.h"
0012 #include "KisDocument.h"
0013 #include "KisReferenceImagesLayer.h"
0014 #include "kis_layer_utils.h"
0015 
0016 struct KisReferenceImagesDecoration::Private {
0017     struct Buffer
0018     {
0019         /// Top left corner of the buffer relative to the viewport
0020         QPointF position;
0021         QImage image;
0022 
0023         QRectF bounds() const
0024         {
0025             return QRectF(position, image.size() / image.devicePixelRatio());
0026         }
0027     };
0028 
0029     KisReferenceImagesDecoration *q;
0030 
0031     KisWeakSharedPtr<KisReferenceImagesLayer> layer;
0032     Buffer buffer;
0033     QTransform previousTransform;
0034     QSizeF previousViewSize;
0035 
0036     explicit Private(KisReferenceImagesDecoration *q)
0037         : q(q)
0038     {}
0039 
0040     void updateBufferByImageCoordinates(const QRectF &dirtyImageRect)
0041     {
0042         QRectF dirtyWidgetRect = q->view()->viewConverter()->imageToWidget(dirtyImageRect);
0043         updateBuffer(dirtyWidgetRect, dirtyImageRect);
0044     }
0045 
0046     void updateBufferByWidgetCoordinates(const QRectF &dirtyWidgetRect)
0047     {
0048         QRectF dirtyImageRect = q->view()->viewConverter()->widgetToImage(dirtyWidgetRect);
0049         updateBuffer(dirtyWidgetRect, dirtyImageRect);
0050     }
0051 
0052 private:
0053     void updateBuffer(QRectF widgetRect, QRectF imageRect)
0054     {
0055         KisCoordinatesConverter *viewConverter = q->view()->viewConverter();
0056         QTransform transform = viewConverter->imageToWidgetTransform();
0057 
0058         qreal devicePixelRatioF = q->view()->devicePixelRatioF();
0059         if (buffer.image.isNull() || !buffer.bounds().contains(widgetRect)) {
0060             const QRectF boundingImageRect = layer->boundingImageRect();
0061             const QRectF boundingWidgetRect = q->view()->viewConverter()->imageToWidget(boundingImageRect);
0062             widgetRect = boundingWidgetRect.intersected(q->view()->rect());
0063 
0064             if (widgetRect.isNull()) return;
0065 
0066             buffer.position = widgetRect.topLeft();
0067             // to ensure that buffer is big enough for all the pixels on high dpi displays
0068             // BUG 411118
0069             buffer.image = QImage((widgetRect.size()*devicePixelRatioF).toSize(), QImage::Format_ARGB32);
0070             buffer.image.setDevicePixelRatio(devicePixelRatioF);
0071 
0072             imageRect = q->view()->viewConverter()->widgetToImage(widgetRect);
0073 
0074         }
0075 
0076         QPainter gc(&buffer.image);
0077 
0078         gc.translate(-buffer.position);
0079         gc.setTransform(transform, true);
0080 
0081         gc.save();
0082         gc.setCompositionMode(QPainter::CompositionMode_Source);
0083         gc.fillRect(imageRect, Qt::transparent);
0084         gc.restore();
0085 
0086         // to ensure that clipping rect is also big enough for all the pixels
0087         // BUG 411118
0088         gc.setClipRect(QRectF(imageRect.topLeft(), imageRect.size()*devicePixelRatioF));
0089         layer->paintReferences(gc);
0090     }
0091 };
0092 
0093 KisReferenceImagesDecoration::KisReferenceImagesDecoration(QPointer<KisView> parent, KisDocument *document, bool viewReady)
0094     : KisCanvasDecoration("referenceImagesDecoration", parent)
0095     , d(new Private(this))
0096 {
0097     connect(document->image().data(), SIGNAL(sigNodeAddedAsync(KisNodeSP)), this, SLOT(slotNodeAdded(KisNodeSP)));
0098     connect(document->image().data(), SIGNAL(sigRemoveNodeAsync(KisNodeSP)), this, SLOT(slotNodeRemoved(KisNodeSP)));
0099     connect(document->image().data(), SIGNAL(sigLayersChangedAsync()), this, SLOT(slotLayersChanged()));
0100     connect(document, &KisDocument::sigReferenceImagesLayerChanged, this, &KisReferenceImagesDecoration::slotNodeAdded);
0101 
0102     auto referenceImageLayer = document->referenceImagesLayer();
0103     if (referenceImageLayer) {
0104         setReferenceImageLayer(referenceImageLayer, /* updateCanvas = */ viewReady);
0105     }
0106 }
0107 
0108 KisReferenceImagesDecoration::~KisReferenceImagesDecoration()
0109 {}
0110 
0111 void KisReferenceImagesDecoration::addReferenceImage(KisReferenceImage *referenceImage)
0112 {
0113     KUndo2Command *cmd = KisReferenceImagesLayer::addReferenceImages(view()->document(), {referenceImage});
0114     view()->canvasBase()->addCommand(cmd);
0115 }
0116 
0117 bool KisReferenceImagesDecoration::documentHasReferenceImages() const
0118 {
0119     return view()->document()->referenceImagesLayer() != nullptr;
0120 }
0121 
0122 void KisReferenceImagesDecoration::drawDecoration(QPainter &gc, const QRectF &/*updateRect*/, const KisCoordinatesConverter *converter, KisCanvas2 */*canvas*/)
0123 {
0124     // TODO: can we use partial updates here?
0125 
0126     KisSharedPtr<KisReferenceImagesLayer> layer = d->layer.toStrongRef();
0127 
0128     if (!layer.isNull()) {
0129         QSizeF viewSize = view()->size();
0130 
0131         QTransform transform = converter->imageToWidgetTransform();
0132         if (d->previousViewSize != viewSize || !KisAlgebra2D::fuzzyMatrixCompare(transform, d->previousTransform, 1e-4)) {
0133             d->previousViewSize = viewSize;
0134             d->previousTransform = transform;
0135             d->buffer.image = QImage();
0136             d->updateBufferByWidgetCoordinates(QRectF(QPointF(0,0), viewSize));
0137         }
0138 
0139         if (!d->buffer.image.isNull()) {
0140             gc.drawImage(d->buffer.position, d->buffer.image);
0141         }
0142     }
0143 }
0144 
0145 void KisReferenceImagesDecoration::slotNodeAdded(KisNodeSP node)
0146 {
0147     KisReferenceImagesLayer *referenceImagesLayer =
0148         dynamic_cast<KisReferenceImagesLayer*>(node.data());
0149 
0150     if (referenceImagesLayer) {
0151         setReferenceImageLayer(referenceImagesLayer, /* updateCanvas = */ true);
0152     }
0153 }
0154 
0155 void KisReferenceImagesDecoration::slotNodeRemoved(KisNodeSP node)
0156 {
0157     KisReferenceImagesLayer *referenceImagesLayer =
0158         dynamic_cast<KisReferenceImagesLayer*>(node.data());
0159 
0160     if (referenceImagesLayer && referenceImagesLayer == d->layer) {
0161         setReferenceImageLayer(0, true);
0162     }
0163 }
0164 
0165 void KisReferenceImagesDecoration::slotLayersChanged()
0166 {
0167     KisImageSP image = view()->image();
0168 
0169     KisReferenceImagesLayer *referenceImagesLayer =
0170         KisLayerUtils::findNodeByType<KisReferenceImagesLayer>(image->root());
0171 
0172     setReferenceImageLayer(referenceImagesLayer, true);
0173 }
0174 
0175 void KisReferenceImagesDecoration::slotReferenceImagesChanged(const QRectF &dirtyRect)
0176 {
0177     d->updateBufferByImageCoordinates(dirtyRect);
0178 
0179     QRectF documentRect = view()->viewConverter()->imageToDocument(dirtyRect);
0180     view()->canvasBase()->updateCanvasDecorations(documentRect);
0181 }
0182 
0183 void KisReferenceImagesDecoration::setReferenceImageLayer(KisSharedPtr<KisReferenceImagesLayer> layer, bool updateCanvas)
0184 {
0185     if (d->layer != layer.data()) {
0186         KisSharedPtr<KisReferenceImagesLayer> oldLayer = d->layer.toStrongRef();
0187         if (oldLayer) {
0188             oldLayer->disconnect(this);
0189         }
0190 
0191         d->layer = layer;
0192 
0193         if (layer) {
0194             connect(layer.data(), SIGNAL(sigUpdateCanvas(QRectF)),
0195                     this, SLOT(slotReferenceImagesChanged(QRectF)));
0196 
0197             const QRectF dirtyRect = layer->boundingImageRect();
0198 
0199             // If the view is not ready yet (because this is being constructed
0200             // from view.d's ctor and thus view.d is not available now),
0201             // do not update canvas because it will lead to a crash.
0202             if (updateCanvas && !dirtyRect.isEmpty()) { // in case the reference layer is just being loaded from the .kra file
0203                 slotReferenceImagesChanged(dirtyRect);
0204             }
0205         }
0206     }
0207 }