File indexing completed on 2024-12-22 04:12:34

0001 /*
0002  * SPDX-FileCopyrightText: 2017 Jouni Pentikäinen <joupent@gmail.com>
0003  *
0004  * SPDX-License-Identifier: LGPL-2.0-or-later
0005  */
0006 
0007 #include <KoShapeCreateCommand.h>
0008 #include <KoShapeDeleteCommand.h>
0009 #include <KoKeepShapesSelectedCommand.h>
0010 #include <KoSelection.h>
0011 #include <kis_node_visitor.h>
0012 #include <kis_processing_visitor.h>
0013 #include <kis_shape_layer_canvas.h>
0014 
0015 #include "kis_default_bounds.h"
0016 #include "KisReferenceImagesLayer.h"
0017 #include "KisReferenceImage.h"
0018 #include "KisDocument.h"
0019 #include <KoViewConverter.h>
0020 
0021 struct AddReferenceImagesCommand : KoShapeCreateCommand
0022 {
0023     AddReferenceImagesCommand(KisDocument *document, KisSharedPtr<KisReferenceImagesLayer> layer, const QList<KoShape*> referenceImages, KUndo2Command *parent = nullptr)
0024         : KoShapeCreateCommand(layer->shapeController(), referenceImages, layer.data(), parent, kundo2_i18n("Add reference image"))
0025         , m_document(document)
0026         , m_layer(layer)
0027     {}
0028 
0029     void redo() override {
0030         auto layer = m_document->referenceImagesLayer();
0031         KIS_SAFE_ASSERT_RECOVER_NOOP(!layer || layer == m_layer);
0032 
0033         if (!layer) {
0034             m_document->setReferenceImagesLayer(m_layer, true);
0035         }
0036 
0037         KoShapeCreateCommand::redo();
0038     }
0039 
0040     void undo() override {
0041         KoShapeCreateCommand::undo();
0042 
0043         if (m_layer->shapeCount() == 0) {
0044             m_document->setReferenceImagesLayer(nullptr, true);
0045         }
0046     }
0047 
0048 private:
0049     KisDocument *m_document;
0050     KisSharedPtr<KisReferenceImagesLayer> m_layer;
0051 };
0052 
0053 struct RemoveReferenceImagesCommand : KoShapeDeleteCommand
0054 {
0055     RemoveReferenceImagesCommand(KisDocument *document, KisSharedPtr<KisReferenceImagesLayer> layer, QList<KoShape *> referenceImages, KUndo2Command *parent = nullptr)
0056         : KoShapeDeleteCommand(layer->shapeController(), referenceImages, parent)
0057         , m_document(document)
0058         , m_layer(layer)
0059     {}
0060 
0061 
0062     void redo() override {
0063         KoShapeDeleteCommand::redo();
0064 
0065         if (m_layer->shapeCount() == 0) {
0066             m_document->setReferenceImagesLayer(nullptr, true);
0067         }
0068     }
0069 
0070     void undo() override {
0071         auto layer = m_document->referenceImagesLayer();
0072         KIS_SAFE_ASSERT_RECOVER_NOOP(!layer || layer == m_layer);
0073 
0074         if (!layer) {
0075             m_document->setReferenceImagesLayer(m_layer, true);
0076         }
0077 
0078         KoShapeDeleteCommand::undo();
0079     }
0080 
0081 private:
0082     KisDocument *m_document;
0083     KisSharedPtr<KisReferenceImagesLayer> m_layer;
0084 };
0085 
0086 class ReferenceImagesCanvas : public KisShapeLayerCanvasBase
0087 {
0088     Q_OBJECT
0089 public:
0090     ReferenceImagesCanvas(const KoColorSpace *cs, KisDefaultBoundsBaseSP defaultBounds, KisReferenceImagesLayer *parent)
0091         : KisShapeLayerCanvasBase(parent)
0092         , m_layer(parent)
0093         , m_fallbackProjection(new KisPaintDevice(parent, cs, defaultBounds))
0094         , m_compressor(KisThreadSafeSignalCompressor(25, KisSignalCompressor::FIRST_ACTIVE))
0095     {
0096         connect(&m_compressor, SIGNAL(timeout()), this, SLOT(slotAsyncRepaint()));
0097     }
0098 
0099     ReferenceImagesCanvas(const ReferenceImagesCanvas &rhs, KisReferenceImagesLayer *parent)
0100         : KisShapeLayerCanvasBase(rhs, parent)
0101         , m_layer(parent)
0102         , m_fallbackProjection(new KisPaintDevice(*rhs.m_fallbackProjection))
0103         , m_compressor(KisThreadSafeSignalCompressor(25, KisSignalCompressor::FIRST_ACTIVE))
0104     {
0105         connect(&m_compressor, SIGNAL(timeout()), this, SLOT(slotAsyncRepaint()));
0106     }
0107 
0108     void updateCanvas(const QRectF &rect) override
0109     {
0110         if (!m_layer->image() || m_isDestroying) {
0111             return;
0112         }
0113 
0114         m_dirtyRect |= rect;
0115 
0116         m_compressor.start();
0117         m_hasUpdateInCompressor = true;
0118     }
0119 
0120     void forceRepaint() override
0121     {
0122         m_layer->signalUpdate(m_layer->boundingImageRect());
0123     }
0124 
0125     bool hasPendingUpdates() const override
0126     {
0127         return m_hasUpdateInCompressor;
0128     }
0129 
0130     void rerenderAfterBeingInvisible() override {}
0131     void resetCache() override {}
0132 
0133     KisPaintDeviceSP projection() const override {
0134         return m_fallbackProjection;
0135     }
0136 private Q_SLOTS:
0137     void slotAsyncRepaint() {
0138         QRectF r = viewConverter()->documentToView(m_dirtyRect);
0139         m_layer->signalUpdate(r);
0140         m_dirtyRect = QRectF();
0141         m_hasUpdateInCompressor = false;
0142     }
0143 
0144 private:
0145     KisReferenceImagesLayer *m_layer;
0146     KisPaintDeviceSP m_fallbackProjection;
0147     KisThreadSafeSignalCompressor m_compressor;
0148     QRectF m_dirtyRect;
0149     volatile bool m_hasUpdateInCompressor = false;
0150 };
0151 
0152 KisReferenceImagesLayer::KisReferenceImagesLayer(KoShapeControllerBase* shapeController, KisImageWSP image)
0153     : KisShapeLayer(shapeController, image, i18n("Reference images"), OPACITY_OPAQUE_U8,
0154                     [&] () { return new ReferenceImagesCanvas(image->colorSpace(), new KisDefaultBounds(image), this); })
0155 {}
0156 
0157 KisReferenceImagesLayer::KisReferenceImagesLayer(const KisReferenceImagesLayer &rhs)
0158     : KisShapeLayer(rhs, rhs.shapeController(),
0159                     [&] () {
0160                             const ReferenceImagesCanvas* referenceImagesCanvas = dynamic_cast<const ReferenceImagesCanvas*>(rhs.canvas());
0161                             KIS_ASSERT(referenceImagesCanvas);
0162                             return new ReferenceImagesCanvas(*referenceImagesCanvas, this); })
0163 {}
0164 
0165 KUndo2Command * KisReferenceImagesLayer::addReferenceImages(KisDocument *document, const QList<KoShape*> referenceImages)
0166 {
0167     KisSharedPtr<KisReferenceImagesLayer> layer = document->referenceImagesLayer();
0168     if (!layer) {
0169         layer = new KisReferenceImagesLayer(document->shapeController(), document->image());
0170     }
0171 
0172     KUndo2Command *parentCommand = new KUndo2Command();
0173 
0174     new KoKeepShapesSelectedCommand(layer->shapeManager()->selection()->selectedShapes(), {}, layer->selectedShapesProxy(), KisCommandUtils::FlipFlopCommand::State::INITIALIZING, parentCommand);
0175     AddReferenceImagesCommand *cmd = new AddReferenceImagesCommand(document, layer, referenceImages, parentCommand);
0176     parentCommand->setText(cmd->text());
0177     new KoKeepShapesSelectedCommand({}, referenceImages, layer->selectedShapesProxy(), KisCommandUtils::FlipFlopCommand::State::FINALIZING, parentCommand);
0178 
0179     return parentCommand;
0180 }
0181 
0182 KUndo2Command * KisReferenceImagesLayer::removeReferenceImages(KisDocument *document, QList<KoShape*> referenceImages)
0183 {
0184     return new RemoveReferenceImagesCommand(document, this, referenceImages);
0185 }
0186 
0187 QVector<KisReferenceImage*> KisReferenceImagesLayer::referenceImages() const
0188 {
0189     QVector<KisReferenceImage*> references;
0190 
0191     Q_FOREACH(auto shape, shapes()) {
0192         KisReferenceImage *referenceImage = dynamic_cast<KisReferenceImage*>(shape);
0193         if (referenceImage) {
0194             references.append(referenceImage);
0195         }
0196     }
0197     return references;
0198 }
0199 
0200 void KisReferenceImagesLayer::paintReferences(QPainter &painter) {
0201     painter.setTransform(converter()->documentToView(), true);
0202     shapeManager()->paint(painter);
0203 }
0204 
0205 bool KisReferenceImagesLayer::allowAsChild(KisNodeSP) const
0206 {
0207     return false;
0208 }
0209 
0210 bool KisReferenceImagesLayer::accept(KisNodeVisitor &visitor)
0211 {
0212     return visitor.visit(this);
0213 }
0214 
0215 void KisReferenceImagesLayer::accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter)
0216 {
0217     visitor.visit(this, undoAdapter);
0218 }
0219 
0220 bool KisReferenceImagesLayer::isFakeNode() const
0221 {
0222     return true;
0223 }
0224 
0225 KUndo2Command *KisReferenceImagesLayer::setProfile(const KoColorProfile *profile)
0226 {
0227     // references should not be converted with the image
0228     Q_UNUSED(profile);
0229     return 0;
0230 }
0231 
0232 KUndo2Command *KisReferenceImagesLayer::convertTo(const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags)
0233 {
0234     // references should not be converted with the image
0235     Q_UNUSED(dstColorSpace);
0236     Q_UNUSED(renderingIntent);
0237     Q_UNUSED(conversionFlags);
0238     return 0;
0239 }
0240 
0241 void KisReferenceImagesLayer::signalUpdate(const QRectF &rect)
0242 {
0243     emit sigUpdateCanvas(rect);
0244 }
0245 
0246 QRectF KisReferenceImagesLayer::boundingImageRect() const
0247 {
0248     return converter()->documentToView(boundingRect());
0249 }
0250 
0251 QColor KisReferenceImagesLayer::getPixel(QPointF position) const
0252 {
0253     const QPointF docPoint = converter()->viewToDocument(position);
0254 
0255     KoShape *shape = shapeManager()->shapeAt(docPoint);
0256 
0257     if (shape) {
0258         auto *reference = dynamic_cast<KisReferenceImage*>(shape);
0259         KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(reference, QColor());
0260 
0261         return reference->getPixel(docPoint);
0262     }
0263 
0264     return QColor();
0265 }
0266 
0267 #include "KisReferenceImagesLayer.moc"