File indexing completed on 2024-05-19 04:26:33

0001 /*
0002  *  SPDX-FileCopyrightText: 2004 Boudewijn Rempt <boud@valdyas.org>
0003  *  SPDX-FileCopyrightText: 2007 Sven Langkamp <sven.langkamp@gmail.com>
0004  *
0005  *
0006  *  SPDX-License-Identifier: GPL-2.0-or-later
0007  */
0008 
0009 #include "kis_selection.h"
0010 
0011 #include "kundo2command.h"
0012 
0013 #include "kis_selection_component.h"
0014 #include "kis_pixel_selection.h"
0015 #include "kis_node_graph_listener.h"
0016 #include "kis_node.h"
0017 #include "kis_image.h"
0018 
0019 #include "KisImageResolutionProxy.h"
0020 #include "kis_default_bounds.h"
0021 #include "kis_iterator_ng.h"
0022 #include "KisLazyStorage.h"
0023 #include "KisSelectionUpdateCompressor.h"
0024 #include "kis_simple_stroke_strategy.h"
0025 #include "KisDeleteLaterWrapper.h"
0026 #include "kis_command_utils.h"
0027 
0028 #include <QReadWriteLock>
0029 #include <QReadLocker>
0030 #include <QWriteLocker>
0031 
0032 
0033 struct Q_DECL_HIDDEN KisSelection::Private {
0034     Private(KisSelection *q)
0035         : isVisible(true),
0036           shapeSelection(0),
0037           updateCompressor(q)
0038 
0039     {
0040     }
0041 
0042     template <typename T>
0043     static void safeDeleteShapeSelection(T *object, KisSelection *selection);
0044 
0045     // used for forwarding setDirty signals only
0046     KisNodeWSP parentNode;
0047 
0048     bool isVisible; //false is the selection decoration should not be displayed
0049     KisImageResolutionProxySP resolutionProxy;
0050     KisPixelSelectionSP pixelSelection;
0051     KisSelectionComponent *shapeSelection;
0052     KisLazyStorage<KisSelectionUpdateCompressor, KisSelection*> updateCompressor;
0053 
0054     /**
0055      * This lock makes sure that the shape selection is not reincarnated,
0056      * while some update jobs still access it via KisSelection::updateProjection().
0057      */
0058     QReadWriteLock shapeSelectionPointerLock;
0059 };
0060 
0061 template <typename T>
0062 void KisSelection::Private::safeDeleteShapeSelection(T *object, KisSelection *selection)
0063 {
0064     struct ShapeSelectionReleaseStroke : public KisSimpleStrokeStrategy {
0065         ShapeSelectionReleaseStroke(T *object)
0066             : KisSimpleStrokeStrategy(QLatin1String("ShapeSelectionReleaseStroke")),
0067               m_objectWrapper(makeKisDeleteLaterWrapper(object))
0068         {
0069             setRequestsOtherStrokesToEnd(false);
0070             setClearsRedoOnStart(false);
0071             setNeedsExplicitCancel(true);
0072 
0073             this->enableJob(JOB_FINISH, true, KisStrokeJobData::BARRIER);
0074             this->enableJob(JOB_CANCEL, true, KisStrokeJobData::BARRIER);
0075         }
0076 
0077         ~ShapeSelectionReleaseStroke()
0078         {
0079             /// it looks like the strategy has not been executed,
0080             /// the object will leak...
0081             KIS_SAFE_ASSERT_RECOVER_NOOP(!m_objectWrapper);
0082         }
0083 
0084         void finishStrokeCallback() override
0085         {
0086             m_objectWrapper->deleteLater();
0087             m_objectWrapper = 0;
0088         }
0089 
0090         void cancelStrokeCallback() override
0091         {
0092             finishStrokeCallback();
0093         }
0094 
0095     private:
0096         KisDeleteLaterWrapper<T*> *m_objectWrapper = 0;
0097     };
0098 
0099     /**
0100      * Yes, you see it right. We create a fake object, then put it into the GUI
0101      * events queue using delete later. This object, on destruction creates a
0102      * stroke, whose only purpose of life is to give birth to another fake
0103      * object, which will be put into delete-later queue again. This final
0104      * object, on destruction will finally release the shape selection.
0105      *
0106      * If you don't understand that, relax, it is impossible.
0107      *
0108      * This trickery is needed to satisfy the following requirements:
0109      *
0110      * 1) KisShapeSelection object should be deleted in the GUI thread (all
0111      * shape manipulations should happen in the GUI thread). Hence we have the
0112      * last step of wrapping m_shapeSelection into KisDeleteLaterWrapper.
0113      *
0114      * 2) KisShapeSelection cannot be deleted before all the updates using this
0115      * layer/selection has completed its execution. Hence we create of the
0116      * stroke that uses a BARRIER job of wait until all the updates are
0117      * finished.
0118      *
0119      * 3) We cannot call image->startStroke() from within
0120      * safeDeleteShapeSelection() directly, because it may be called in the
0121      * destructor of KisTransactionData, which may theoretically be called from
0122      * the destructor of KisStrokeStrategy, which will cause a deadlock in the
0123      * strokes queue. Hence we do the first layer of wrapping into
0124      * GuiStrokeWrapper.
0125      */
0126     struct GuiStrokeWrapper
0127     {
0128         GuiStrokeWrapper(KisImageSP image, T *object)
0129             : m_image(image), m_object(object)
0130         {
0131         }
0132 
0133         ~GuiStrokeWrapper()
0134         {
0135             KisImageSP image = m_image;
0136 
0137             if (image) {
0138                 KisStrokeId strokeId = image->startStroke(new ShapeSelectionReleaseStroke(m_object));
0139                 image->endStroke(strokeId);
0140             } else {
0141                 delete m_object;
0142             }
0143         }
0144 
0145         KisImageWSP m_image;
0146         T *m_object;
0147     };
0148 
0149     if (selection) {
0150         KisImageSP image = 0;
0151 
0152         KisNodeSP parentNode = selection->parentNode();
0153         if (parentNode) {
0154             image = parentNode->image();
0155         }
0156 
0157         if (image) {
0158             makeKisDeleteLaterWrapper(new GuiStrokeWrapper(image, object))->deleteLater();
0159             object = 0;
0160         }
0161     }
0162 
0163     if (object) {
0164         makeKisDeleteLaterWrapper(object)->deleteLater();
0165         object = 0;
0166     }
0167 }
0168 
0169 struct KisSelection::ChangeShapeSelectionCommand : public KUndo2Command
0170 {
0171     ChangeShapeSelectionCommand(KisSelectionWSP selection, KisSelectionComponent *shapeSelection)
0172         : m_selection(selection),
0173           m_shapeSelection(shapeSelection)
0174     {
0175         m_isFlatten = !shapeSelection;
0176     }
0177 
0178     ~ChangeShapeSelectionCommand() override
0179     {
0180         if (m_shapeSelection) {
0181             Private::safeDeleteShapeSelection(m_shapeSelection, m_selection ? m_selection.data() : 0);
0182         }
0183 
0184         if (m_reincarnationCommand) {
0185             Private::safeDeleteShapeSelection(m_reincarnationCommand.take(), m_selection ? m_selection.data() : 0);
0186         }
0187     }
0188 
0189     void undo() override
0190     {
0191         KIS_SAFE_ASSERT_RECOVER_RETURN(m_selection);
0192 
0193         if (m_reincarnationCommand) {
0194             m_reincarnationCommand->undo();
0195         }
0196 
0197         {
0198             QWriteLocker l(&m_selection->m_d->shapeSelectionPointerLock);
0199             std::swap(m_selection->m_d->shapeSelection, m_shapeSelection);
0200         }
0201 
0202         if (!m_isFlatten) {
0203             m_selection->requestCompressedProjectionUpdate(QRect());
0204         }
0205     }
0206 
0207     void redo() override
0208     {
0209         KIS_SAFE_ASSERT_RECOVER_RETURN(m_selection);
0210 
0211         if (m_firstRedo) {
0212             QReadLocker l(&m_selection->m_d->shapeSelectionPointerLock);
0213 
0214             if (bool(m_selection->m_d->shapeSelection) != bool(m_shapeSelection)) {
0215                 m_reincarnationCommand.reset(
0216                     m_selection->m_d->pixelSelection->reincarnateWithDetachedHistory(m_isFlatten));
0217             }
0218             m_firstRedo = false;
0219 
0220         }
0221 
0222         if (m_reincarnationCommand) {
0223             m_reincarnationCommand->redo();
0224         }
0225 
0226         {
0227             QWriteLocker l(&m_selection->m_d->shapeSelectionPointerLock);
0228             std::swap(m_selection->m_d->shapeSelection, m_shapeSelection);
0229         }
0230 
0231         if (!m_isFlatten) {
0232             m_selection->requestCompressedProjectionUpdate(QRect());
0233         }
0234     }
0235 
0236 private:
0237     KisSelectionWSP m_selection;
0238     KisSelectionComponent *m_shapeSelection = 0;
0239     QScopedPointer<KUndo2Command> m_reincarnationCommand;
0240     bool m_firstRedo = true;
0241     bool m_isFlatten = false;
0242 };
0243 
0244 KisSelection::KisSelection()
0245     : KisSelection(nullptr, nullptr)
0246 {
0247 }
0248 
0249 KisSelection::KisSelection(KisDefaultBoundsBaseSP defaultBounds, KisImageResolutionProxySP resolutionProxy)
0250     : m_d(new Private(this))
0251 {
0252     if (!defaultBounds) {
0253         defaultBounds = new KisSelectionEmptyBounds(nullptr);
0254     }
0255 
0256     if (!resolutionProxy) {
0257         resolutionProxy.reset(new KisImageResolutionProxy(nullptr));
0258     }
0259 
0260     m_d->resolutionProxy = resolutionProxy;
0261 
0262     m_d->pixelSelection = new KisPixelSelection(defaultBounds, this);
0263     m_d->pixelSelection->setParentNode(m_d->parentNode);
0264 }
0265 
0266 KisSelection::KisSelection(const KisSelection& rhs)
0267     : KisShared(),
0268       m_d(new Private(this))
0269 {
0270     copyFrom(rhs);
0271 }
0272 
0273 KisSelection::KisSelection(const KisPaintDeviceSP source, KritaUtils::DeviceCopyMode copyMode,
0274                            KisDefaultBoundsBaseSP defaultBounds, KisImageResolutionProxySP resolutionProxy)
0275     : m_d(new Private(this))
0276 {
0277     if (!defaultBounds) {
0278         defaultBounds = new KisSelectionEmptyBounds(0);
0279     }
0280 
0281     m_d->resolutionProxy = resolutionProxy;
0282     m_d->pixelSelection = new KisPixelSelection(source, copyMode);
0283     m_d->pixelSelection->setParentSelection(this);
0284     m_d->pixelSelection->setParentNode(m_d->parentNode);
0285     m_d->pixelSelection->setDefaultBounds(defaultBounds);
0286 }
0287 
0288 KisSelection &KisSelection::operator=(const KisSelection &rhs)
0289 {
0290     if (&rhs != this) {
0291         copyFrom(rhs);
0292     }
0293     return *this;
0294 }
0295 
0296 void KisSelection::copyFrom(const KisSelection &rhs)
0297 {
0298     m_d->isVisible = rhs.m_d->isVisible;
0299     m_d->resolutionProxy = rhs.m_d->resolutionProxy;
0300     m_d->parentNode = 0; // not supposed to be shared
0301 
0302     Q_ASSERT(rhs.m_d->pixelSelection);
0303     m_d->pixelSelection = new KisPixelSelection(*rhs.m_d->pixelSelection, KritaUtils::CopyAllFrames);
0304     m_d->pixelSelection->setParentSelection(this);
0305 
0306     QReadLocker l1(&rhs.m_d->shapeSelectionPointerLock);
0307     QWriteLocker l2(&m_d->shapeSelectionPointerLock);
0308 
0309     if (rhs.m_d->shapeSelection && !rhs.m_d->shapeSelection->isEmpty()) {
0310         m_d->shapeSelection = rhs.m_d->shapeSelection->clone(this);
0311         KIS_SAFE_ASSERT_RECOVER_NOOP(m_d->shapeSelection);
0312         KIS_SAFE_ASSERT_RECOVER(m_d->shapeSelection &&
0313                                 m_d->shapeSelection != rhs.m_d->shapeSelection) {
0314             m_d->shapeSelection = 0;
0315         }
0316     }
0317     else {
0318         if (m_d->shapeSelection) {
0319             Private::safeDeleteShapeSelection(m_d->shapeSelection, this);
0320             m_d->shapeSelection = 0;
0321         }
0322     }
0323 }
0324 
0325 KisSelection::~KisSelection()
0326 {
0327     delete m_d->shapeSelection;
0328     delete m_d;
0329 }
0330 
0331 void KisSelection::setParentNode(KisNodeWSP node)
0332 {
0333     m_d->parentNode = node;
0334     m_d->pixelSelection->setParentNode(node);
0335 
0336     // the updates come through the parent image, so all the updates
0337     // that happened in the meantime are considered "stalled"
0338     if (node) {
0339         m_d->updateCompressor->tryProcessStalledUpdate();
0340     }
0341 }
0342 
0343 // for testing purposes only
0344 KisNodeWSP KisSelection::parentNode() const
0345 {
0346     return m_d->parentNode;
0347 }
0348 
0349 bool KisSelection::outlineCacheValid() const
0350 {
0351     QReadLocker l(&m_d->shapeSelectionPointerLock);
0352     return m_d->shapeSelection ||
0353         m_d->pixelSelection->outlineCacheValid();
0354 }
0355 
0356 QPainterPath KisSelection::outlineCache() const
0357 {
0358     QReadLocker l(&m_d->shapeSelectionPointerLock);
0359 
0360     QPainterPath outline;
0361 
0362     if (m_d->shapeSelection) {
0363         outline += m_d->shapeSelection->outlineCache();
0364     } else if (m_d->pixelSelection->outlineCacheValid()) {
0365         outline += m_d->pixelSelection->outlineCache();
0366     }
0367 
0368     return outline;
0369 }
0370 
0371 void KisSelection::recalculateOutlineCache()
0372 {
0373     QReadLocker l(&m_d->shapeSelectionPointerLock);
0374 
0375     Q_ASSERT(m_d->pixelSelection);
0376 
0377     if (m_d->shapeSelection) {
0378         m_d->shapeSelection->recalculateOutlineCache();
0379     } else if (!m_d->pixelSelection->outlineCacheValid()) {
0380         m_d->pixelSelection->recalculateOutlineCache();
0381     }
0382 }
0383 
0384 bool KisSelection::thumbnailImageValid() const
0385 {
0386     return m_d->pixelSelection->thumbnailImageValid();
0387 }
0388 
0389 void KisSelection::recalculateThumbnailImage(const QColor &maskColor)
0390 {
0391     m_d->pixelSelection->recalculateThumbnailImage(maskColor);
0392 }
0393 
0394 QImage KisSelection::thumbnailImage() const
0395 {
0396     return m_d->pixelSelection->thumbnailImage();
0397 }
0398 
0399 QTransform KisSelection::thumbnailImageTransform() const
0400 {
0401     return m_d->pixelSelection->thumbnailImageTransform();
0402 }
0403 
0404 bool KisSelection::hasNonEmptyPixelSelection() const
0405 {
0406     return m_d->pixelSelection && !m_d->pixelSelection->isEmpty();
0407 }
0408 
0409 bool KisSelection::hasNonEmptyShapeSelection() const
0410 {
0411     QReadLocker l(&m_d->shapeSelectionPointerLock);
0412     return m_d->shapeSelection && !m_d->shapeSelection->isEmpty();
0413 }
0414 
0415 bool KisSelection::hasShapeSelection() const
0416 {
0417     QReadLocker l(&m_d->shapeSelectionPointerLock);
0418     return m_d->shapeSelection;
0419 }
0420 
0421 KisPixelSelectionSP KisSelection::pixelSelection() const
0422 {
0423     return m_d->pixelSelection;
0424 }
0425 
0426 KisSelectionComponent* KisSelection::shapeSelection() const
0427 {
0428     return m_d->shapeSelection;
0429 }
0430 
0431 void KisSelection::convertToVectorSelectionNoUndo(KisSelectionComponent* shapeSelection)
0432 {
0433     KIS_SAFE_ASSERT_RECOVER_RETURN(shapeSelection);
0434 
0435     shapeSelection->setResolutionProxy(m_d->resolutionProxy);
0436     QScopedPointer<KUndo2Command> cmd(new ChangeShapeSelectionCommand(this, shapeSelection));
0437     cmd->redo();
0438 }
0439 
0440 KUndo2Command *KisSelection::convertToVectorSelection(KisSelectionComponent *shapeSelection)
0441 {
0442     KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!m_d->shapeSelection, nullptr);
0443 
0444     shapeSelection->setResolutionProxy(m_d->resolutionProxy);
0445     return new ChangeShapeSelectionCommand(this, shapeSelection);
0446 }
0447 
0448 KisPixelSelectionSP KisSelection::projection() const
0449 {
0450     return m_d->pixelSelection;
0451 }
0452 
0453 void KisSelection::updateProjection(const QRect &rc)
0454 {
0455     QReadLocker l(&m_d->shapeSelectionPointerLock);
0456 
0457     if(m_d->shapeSelection) {
0458         m_d->shapeSelection->renderToProjection(m_d->pixelSelection, rc);
0459         m_d->pixelSelection->setOutlineCache(m_d->shapeSelection->outlineCache());
0460     }
0461 }
0462 
0463 void KisSelection::updateProjection()
0464 {
0465     QReadLocker l(&m_d->shapeSelectionPointerLock);
0466 
0467     if(m_d->shapeSelection) {
0468         m_d->pixelSelection->clear();
0469         m_d->shapeSelection->renderToProjection(m_d->pixelSelection);
0470         m_d->pixelSelection->setOutlineCache(m_d->shapeSelection->outlineCache());
0471     }
0472 }
0473 
0474 void KisSelection::setVisible(bool visible)
0475 {
0476     bool needsNotification = visible != m_d->isVisible;
0477 
0478     m_d->isVisible = visible;
0479 
0480     if (needsNotification) {
0481         notifySelectionChanged();
0482     }
0483 }
0484 
0485 bool KisSelection::isVisible()
0486 {
0487     return m_d->isVisible;
0488 }
0489 
0490 bool KisSelection::isTotallyUnselected(const QRect & r) const
0491 {
0492     return m_d->pixelSelection->isTotallyUnselected(r);
0493 }
0494 
0495 QRect KisSelection::selectedRect() const
0496 {
0497     return m_d->pixelSelection->selectedRect();
0498 }
0499 
0500 QRect KisSelection::selectedExactRect() const
0501 {
0502     return m_d->pixelSelection->selectedExactRect();
0503 }
0504 
0505 qint32 KisSelection::x() const
0506 {
0507     return m_d->pixelSelection->x();
0508 }
0509 
0510 qint32 KisSelection::y() const
0511 {
0512     return m_d->pixelSelection->y();
0513 }
0514 
0515 void KisSelection::setX(qint32 x)
0516 {
0517     QReadLocker l(&m_d->shapeSelectionPointerLock);
0518 
0519     Q_ASSERT(m_d->pixelSelection);
0520 
0521     qint32 delta = x - m_d->pixelSelection->x();
0522     m_d->pixelSelection->setX(x);
0523     if (m_d->shapeSelection) {
0524         m_d->shapeSelection->moveX(delta);
0525     }
0526 }
0527 
0528 void KisSelection::setY(qint32 y)
0529 {
0530     QReadLocker l(&m_d->shapeSelectionPointerLock);
0531 
0532     Q_ASSERT(m_d->pixelSelection);
0533 
0534     qint32 delta = y - m_d->pixelSelection->y();
0535     m_d->pixelSelection->setY(y);
0536     if (m_d->shapeSelection) {
0537         m_d->shapeSelection->moveY(delta);
0538     }
0539 }
0540 
0541 void KisSelection::setDefaultBounds(KisDefaultBoundsBaseSP bounds)
0542 {
0543     m_d->pixelSelection->setDefaultBounds(bounds);
0544 }
0545 
0546 void KisSelection::setResolutionProxy(KisImageResolutionProxySP proxy)
0547 {
0548     m_d->resolutionProxy = proxy;
0549     if (m_d->shapeSelection) {
0550         m_d->shapeSelection->setResolutionProxy(proxy);
0551     }
0552 }
0553 
0554 KisImageResolutionProxySP KisSelection::resolutionProxy() const
0555 {
0556     return m_d->resolutionProxy;
0557 }
0558 
0559 void KisSelection::clear()
0560 {
0561     QReadLocker readLocker(&m_d->shapeSelectionPointerLock);
0562 
0563     if (m_d->shapeSelection) {
0564         readLocker.unlock();
0565         QWriteLocker writeLocker(&m_d->shapeSelectionPointerLock);
0566         if (m_d->shapeSelection) {
0567             Private::safeDeleteShapeSelection(m_d->shapeSelection, this);
0568             m_d->shapeSelection = 0;
0569         }
0570     }
0571 
0572     m_d->pixelSelection->clear();
0573 }
0574 
0575 KUndo2Command* KisSelection::flatten()
0576 {
0577     QReadLocker readLocker(&m_d->shapeSelectionPointerLock);
0578 
0579     KUndo2Command *command = 0;
0580 
0581     if (m_d->shapeSelection) {
0582         command = m_d->shapeSelection->resetToEmpty();
0583         readLocker.unlock();
0584 
0585         if (command) {
0586             KisCommandUtils::CompositeCommand *cmd = new KisCommandUtils::CompositeCommand();
0587             cmd->addCommand(command);
0588             cmd->addCommand(new ChangeShapeSelectionCommand(this, nullptr));
0589             command = cmd;
0590         } else {
0591             command = new ChangeShapeSelectionCommand(this, nullptr);
0592         }
0593     }
0594 
0595     return command;
0596 }
0597 
0598 void KisSelection::notifySelectionChanged()
0599 {
0600     KisNodeWSP parentNode;
0601     if (!(parentNode = this->parentNode())) return;
0602 
0603     KisNodeGraphListener *listener;
0604     if (!(listener = parentNode->graphListener())) return;
0605 
0606     listener->notifySelectionChanged();
0607 }
0608 
0609 void KisSelection::requestCompressedProjectionUpdate(const QRect &rc)
0610 {
0611     m_d->updateCompressor->requestUpdate(rc);
0612 }
0613 
0614 quint8 KisSelection::selected(qint32 x, qint32 y) const
0615 {
0616     KisHLineConstIteratorSP iter = m_d->pixelSelection->createHLineConstIteratorNG(x, y, 1);
0617 
0618     const quint8 *pix = iter->oldRawData();
0619 
0620     return *pix;
0621 }
0622