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

0001 /*
0002  *  SPDX-FileCopyrightText: 2007 Boudewijn Rempt <boud@valdyas.org>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "kis_shape_layer_canvas.h"
0008 
0009 #include <QPainter>
0010 #include <QMutexLocker>
0011 
0012 #include <KoShapeManager.h>
0013 #include <KoSelectedShapesProxySimple.h>
0014 #include <KoViewConverter.h>
0015 #include <KoColorSpace.h>
0016 
0017 #include <kis_paint_device.h>
0018 #include <kis_image.h>
0019 #include <kis_layer.h>
0020 #include <kis_painter.h>
0021 #include <flake/kis_shape_layer.h>
0022 #include <KoCompositeOpRegistry.h>
0023 #include <KoSelection.h>
0024 #include <KoUnit.h>
0025 #include "kis_image_view_converter.h"
0026 
0027 #include <kis_debug.h>
0028 
0029 #include <QThread>
0030 #include <QApplication>
0031 
0032 #include <kis_spontaneous_job.h>
0033 #include "kis_global.h"
0034 #include "krita_utils.h"
0035 #include "kis_image_view_converter.h"
0036 #include "kis_default_bounds.h"
0037 #include "kis_do_something_command.h"
0038 
0039 
0040 KisShapeLayerCanvasBase::KisShapeLayerCanvasBase(KisShapeLayer *parent)
0041     : KoCanvasBase(0)
0042     , m_shapeManager(new KoShapeManager(this))
0043     , m_selectedShapesProxy(new KoSelectedShapesProxySimple(m_shapeManager.data()))
0044     , m_viewConverter()
0045 {
0046     m_shapeManager->selection()->setActiveLayer(parent);
0047 }
0048 
0049 KisShapeLayerCanvasBase::KisShapeLayerCanvasBase(const KisShapeLayerCanvasBase &rhs, KisShapeLayer *parent)
0050     : KoCanvasBase(0)
0051     , m_shapeManager(new KoShapeManager(this))
0052     , m_selectedShapesProxy(new KoSelectedShapesProxySimple(m_shapeManager.data()))
0053     , m_viewConverter(rhs.m_viewConverter)
0054 {
0055     m_viewConverter.setImage(nullptr);
0056     m_shapeManager->selection()->setActiveLayer(parent);
0057 }
0058 
0059 void KisShapeLayerCanvasBase::setImage(KisImageWSP image)
0060 {
0061     m_viewConverter.setImage(image);
0062 }
0063 
0064 KoShapeManager *KisShapeLayerCanvasBase::shapeManager() const
0065 {
0066     return m_shapeManager.data();
0067 }
0068 
0069 KoSelectedShapesProxy *KisShapeLayerCanvasBase::selectedShapesProxy() const
0070 {
0071     return m_selectedShapesProxy.data();
0072 }
0073 
0074 const KoViewConverter *KisShapeLayerCanvasBase::viewConverter() const
0075 {
0076     return &m_viewConverter;
0077 }
0078 
0079 KoViewConverter *KisShapeLayerCanvasBase::viewConverter()
0080 {
0081     return &m_viewConverter;
0082 }
0083 
0084 void KisShapeLayerCanvasBase::gridSize(QPointF *offset, QSizeF *spacing) const
0085 {
0086     KIS_SAFE_ASSERT_RECOVER_NOOP(false); // This should never be called as this canvas should have no tools.
0087     Q_UNUSED(offset);
0088     Q_UNUSED(spacing);
0089 }
0090 
0091 bool KisShapeLayerCanvasBase::snapToGrid() const
0092 {
0093     KIS_SAFE_ASSERT_RECOVER_NOOP(false); // This should never be called as this canvas should have no tools.
0094     return false;
0095 }
0096 
0097 void KisShapeLayerCanvasBase::addCommand(KUndo2Command *)
0098 {
0099     KIS_SAFE_ASSERT_RECOVER_NOOP(false); // This should never be called as this canvas should have no tools.
0100 }
0101 
0102 
0103 KoToolProxy * KisShapeLayerCanvasBase::toolProxy() const
0104 {
0105 //     KIS_SAFE_ASSERT_RECOVER_NOOP(false); // This should never be called as this canvas should have no tools.
0106     return 0;
0107 }
0108 
0109 QWidget* KisShapeLayerCanvasBase::canvasWidget()
0110 {
0111     return 0;
0112 }
0113 
0114 const QWidget* KisShapeLayerCanvasBase::canvasWidget() const
0115 {
0116     return 0;
0117 }
0118 
0119 KoUnit KisShapeLayerCanvasBase::unit() const
0120 {
0121     KIS_SAFE_ASSERT_RECOVER_NOOP(false); // This should never be called as this canvas should have no tools.
0122     return KoUnit(KoUnit::Point);
0123 }
0124 
0125 void KisShapeLayerCanvasBase::prepareForDestroying()
0126 {
0127     m_isDestroying = true;
0128 }
0129 
0130 bool KisShapeLayerCanvasBase::hasChangedWhileBeingInvisible()
0131 {
0132     return m_hasChangedWhileBeingInvisible;
0133 }
0134 
0135 
0136 KisShapeLayerCanvas::KisShapeLayerCanvas(const KoColorSpace *cs, KisDefaultBoundsBaseSP defaultBounds, KisShapeLayer *parent)
0137         : KisShapeLayerCanvasBase(parent)
0138         , m_projection(new KisPaintDevice(parent, cs, defaultBounds))
0139         , m_parentLayer(parent)
0140         , m_asyncUpdateSignalCompressor(25, KisSignalCompressor::FIRST_ACTIVE)
0141         , m_safeForcedConnection(std::bind(&KisShapeLayerCanvas::slotStartAsyncRepaint, this))
0142 {
0143     /**
0144      * The layer should also add itself to its own shape manager, so that the canvas
0145      * would track its changes/transformations
0146      */
0147     m_shapeManager->addShape(parent, KoShapeManager::AddWithoutRepaint);
0148     m_shapeManager->selection()->setActiveLayer(parent);
0149 
0150     connect(&m_asyncUpdateSignalCompressor, SIGNAL(timeout()), SLOT(slotStartAsyncRepaint()));
0151 }
0152 
0153 KisShapeLayerCanvas::KisShapeLayerCanvas(const KisShapeLayerCanvas &rhs, KisShapeLayer *parent)
0154         : KisShapeLayerCanvasBase(rhs, parent)
0155         , m_projection(new KisPaintDevice(*rhs.m_projection))
0156         , m_parentLayer(parent)
0157         , m_asyncUpdateSignalCompressor(25, KisSignalCompressor::FIRST_ACTIVE)
0158         , m_safeForcedConnection(std::bind(&KisShapeLayerCanvas::slotStartAsyncRepaint, this))
0159 {
0160     /**
0161      * The layer should also add itself to its own shape manager, so that the canvas
0162      * would track its changes/transformations
0163      */
0164     m_shapeManager->addShape(parent, KoShapeManager::AddWithoutRepaint);
0165     m_shapeManager->selection()->setActiveLayer(parent);
0166 
0167     connect(&m_asyncUpdateSignalCompressor, SIGNAL(timeout()), SLOT(slotStartAsyncRepaint()));
0168     m_projection->setParentNode(parent);
0169 }
0170 
0171 KisShapeLayerCanvas::~KisShapeLayerCanvas()
0172 {
0173     m_shapeManager->remove(m_parentLayer);
0174 }
0175 
0176 void KisShapeLayerCanvas::setProjection(KisPaintDeviceSP projection)
0177 {
0178     m_projection = projection;
0179 }
0180 
0181 KisPaintDeviceSP KisShapeLayerCanvas::projection() const
0182 {
0183     return m_projection;
0184 }
0185 
0186 void KisShapeLayerCanvas::setImage(KisImageWSP image)
0187 {
0188     m_imageConnections.clear();
0189 
0190     KisShapeLayerCanvasBase::setImage(image);
0191     m_image = image;
0192 
0193     if (image) {
0194         m_imageConnections.addUniqueConnection(m_image, SIGNAL(sigSizeChanged(QPointF,QPointF)), this, SLOT(slotImageSizeChanged()));
0195         m_cachedImageRect = m_image->bounds();
0196         m_projection->convertTo(image->colorSpace());
0197     }
0198     m_projection->setDefaultBounds(new KisDefaultBounds(image));
0199     if (image && m_hasUpdateOnSetImage) {
0200         m_hasUpdateOnSetImage = false;
0201         updateCanvas(m_cachedImageRect);
0202     }
0203 }
0204 
0205 class KisRepaintShapeLayerLayerJob : public KisSpontaneousJob
0206 {
0207 public:
0208     KisRepaintShapeLayerLayerJob(KisShapeLayerSP layer, KisShapeLayerCanvas *canvas)
0209         : m_layer(layer),
0210           m_canvas(canvas)
0211     {
0212     }
0213 
0214     bool overrides(const KisSpontaneousJob *_otherJob) override {
0215         const KisRepaintShapeLayerLayerJob *otherJob =
0216             dynamic_cast<const KisRepaintShapeLayerLayerJob*>(_otherJob);
0217 
0218         return otherJob && otherJob->m_canvas == m_canvas;
0219     }
0220 
0221     void run() override {
0222         m_canvas->repaint();
0223     }
0224 
0225     int levelOfDetail() const override {
0226         return 0;
0227     }
0228 
0229     QString debugName() const override {
0230         QString result;
0231         QDebug dbg(&result);
0232         dbg << "KisRepaintShapeLayerLayerJob" << m_layer;
0233         return result;
0234     }
0235 
0236 private:
0237 
0238     // we store a pointer to the layer just
0239     // to keep the lifetime of the canvas!
0240     KisShapeLayerSP m_layer;
0241 
0242     KisShapeLayerCanvas *m_canvas;
0243 };
0244 
0245 
0246 void KisShapeLayerCanvas::updateCanvas(const QVector<QRectF> &region)
0247 {
0248     if (!m_image){
0249         m_hasUpdateOnSetImage = true;
0250         return;
0251     }
0252     if (!m_parentLayer->image() || m_isDestroying) {
0253         return;
0254     }
0255 
0256     {
0257         QMutexLocker locker(&m_dirtyRegionMutex);
0258         Q_FOREACH (const QRectF &rc, region) {
0259             // grow for antialiasing
0260             const QRect imageRect = kisGrowRect(viewConverter()->documentToView(rc).toAlignedRect(), 2);
0261             m_dirtyRegion += imageRect;
0262         }
0263     }
0264 
0265     m_asyncUpdateSignalCompressor.start();
0266     m_hasUpdateInCompressor = true;
0267 }
0268 
0269 
0270 void KisShapeLayerCanvas::updateCanvas(const QRectF& rc)
0271 {
0272     updateCanvas(QVector<QRectF>({rc}));
0273 }
0274 
0275 void KisShapeLayerCanvas::slotStartAsyncRepaint()
0276 {
0277     KisImageSP image = m_image;
0278     if (!image || !m_parentLayer->image()) {
0279         return;
0280     }
0281 
0282     /**
0283      * Don't try to start a regeneration stroke while image
0284      * is locked. It may happen on loading, when all necessary
0285      * conversions are not yet finished.
0286      */
0287     if (image->locked()) {
0288         m_asyncUpdateSignalCompressor.start();
0289         return;
0290     }
0291 
0292     QRect repaintRect;
0293     QRect uncroppedRepaintRect;
0294     bool forceUpdateHiddenAreasOnly = false;
0295     const qint32 MASK_IMAGE_WIDTH = 256;
0296     const qint32 MASK_IMAGE_HEIGHT = 256;
0297     {
0298         QMutexLocker locker(&m_dirtyRegionMutex);
0299 
0300         repaintRect = m_dirtyRegion.boundingRect();
0301         forceUpdateHiddenAreasOnly = m_forceUpdateHiddenAreasOnly;
0302 
0303         /// Since we are going to override the previous jobs, we should fetch
0304         /// all the area covered by it. Otherwise we'll get dirty leftovers of
0305         /// the layer on the projection
0306         Q_FOREACH (const KoShapeManager::PaintJob &job, m_paintJobsOrder.jobs) {
0307             repaintRect |= viewConverter()->documentToView().mapRect(job.docUpdateRect).toAlignedRect();
0308         }
0309         m_paintJobsOrder.clear();
0310 
0311         m_dirtyRegion = QRegion();
0312         m_forceUpdateHiddenAreasOnly = false;
0313     }
0314 
0315     if (!forceUpdateHiddenAreasOnly) {
0316         if (repaintRect.isEmpty()) {
0317             return;
0318         }
0319 
0320         // Crop the update rect by the image bounds. We keep the cache consistent
0321         // by tracking the size of the image in slotImageSizeChanged()
0322         uncroppedRepaintRect = repaintRect;
0323         repaintRect = repaintRect.intersected(image->bounds());
0324     } else {
0325         const QRectF shapesBounds = KoShape::boundingRect(m_shapeManager->shapes());
0326         repaintRect |= kisGrowRect(viewConverter()->documentToView(shapesBounds).toAlignedRect(), 2);
0327         uncroppedRepaintRect = repaintRect;
0328     }
0329 
0330     /**
0331      * Vector shapes are not thread-safe against concurrent read-writes, so we
0332      * need to utilize rather complicated policy on accessing them:
0333      *
0334      * 1) All shape writes happen in GUI thread (right in the tools)
0335      * 2) No concurrent reads from the shapes may happen in other threads
0336      *    while the user is modifying them.
0337      *
0338      * That is why our shape rendering code is split into two parts:
0339      *
0340      * 1) First we just fetch a shallow copy of the shapes of the layer (it
0341      *    takes about 1ms for complicated vector layers) and pack them into
0342      *    KoShapeManager::PaintJobsList jobs. It happens here, in
0343      *    slotStartAsyncRepaint(), which runs in the GUI thread. It guarantees
0344      *    that no one is accessing the shapes during the copy operation.
0345      *
0346      * 2) The rendering itself happens in the worker thread in repaint(). But
0347      *    repaint() doesn't access original shapes anymore. It accesses only they
0348      *    shallow copies, which means that there is no concurrent
0349      *    access to anything (*).
0350      *
0351      * (*) "no concurrent access to anything" is a rather fragile term :) There
0352      *     will still be concurrent access to it, on detaching... But(!), when detaching,
0353      *     the original data is kept unchanged, so "it should be safe enough"(c). Especially
0354      *     if we guarantee that rendering thread may not cause a detach (?), and the detach
0355      *     can happen only from a single GUI thread.
0356      */
0357 
0358     const QVector<QRect> updateRects =
0359         KritaUtils::splitRectIntoPatchesTight(repaintRect,
0360                                               QSize(MASK_IMAGE_WIDTH, MASK_IMAGE_HEIGHT));
0361 
0362     KoShapeManager::PaintJobsOrder jobsOrder;
0363     Q_FOREACH (const QRect &viewUpdateRect, updateRects) {
0364         jobsOrder.jobs << KoShapeManager::PaintJob(viewConverter()->viewToDocument().mapRect(QRectF(viewUpdateRect)),
0365                                               viewUpdateRect);
0366     }
0367     jobsOrder.uncroppedViewUpdateRect = uncroppedRepaintRect;
0368 
0369     m_shapeManager->preparePaintJobs(jobsOrder, m_parentLayer);
0370 
0371     {
0372         QMutexLocker locker(&m_dirtyRegionMutex);
0373 
0374         // check if it is still empty! It should be true, because GUI thread is
0375         // the only actor that can add stuff to it.
0376         KIS_SAFE_ASSERT_RECOVER_NOOP(m_paintJobsOrder.isEmpty());
0377         m_paintJobsOrder = jobsOrder;
0378     }
0379 
0380     m_hasUpdateInCompressor = false;
0381     image->addSpontaneousJob(new KisRepaintShapeLayerLayerJob(m_parentLayer, this));
0382 }
0383 
0384 void KisShapeLayerCanvas::slotImageSizeChanged()
0385 {
0386     QRegion dirtyCacheRegion;
0387     dirtyCacheRegion += m_image->bounds();
0388     dirtyCacheRegion += m_cachedImageRect;
0389     dirtyCacheRegion -= m_image->bounds() & m_cachedImageRect;
0390 
0391     QVector<QRectF> dirtyRects;
0392     auto rc = dirtyCacheRegion.begin();
0393     while (rc != dirtyCacheRegion.end()) {
0394         dirtyRects.append(viewConverter()->viewToDocument(*rc));
0395         rc++;
0396     }
0397     updateCanvas(dirtyRects);
0398 
0399     m_cachedImageRect = m_image->bounds();
0400 }
0401 
0402 void KisShapeLayerCanvas::repaint()
0403 {
0404 
0405     KoShapeManager::PaintJobsOrder paintJobsOrder;
0406 
0407     {
0408         QMutexLocker locker(&m_dirtyRegionMutex);
0409         std::swap(paintJobsOrder, m_paintJobsOrder);
0410     }
0411 
0412     /**
0413      * Sometimes two update jobs might not override and the second one
0414      * will arrive right after the first one
0415      */
0416     if (paintJobsOrder.isEmpty()) return;
0417 
0418     const qint32 MASK_IMAGE_WIDTH = 256;
0419     const qint32 MASK_IMAGE_HEIGHT = 256;
0420 
0421     QImage image(MASK_IMAGE_WIDTH, MASK_IMAGE_HEIGHT, QImage::Format_ARGB32);
0422     QPainter tempPainter(&image);
0423 
0424     tempPainter.setRenderHint(QPainter::Antialiasing);
0425     tempPainter.setRenderHint(QPainter::TextAntialiasing);
0426 
0427     quint8 * dstData = new quint8[MASK_IMAGE_WIDTH * MASK_IMAGE_HEIGHT * m_projection->pixelSize()];
0428 
0429     QRect repaintRect = paintJobsOrder.uncroppedViewUpdateRect;
0430     m_projection->clear(repaintRect);
0431 
0432     Q_FOREACH (const KoShapeManager::PaintJob &job, paintJobsOrder.jobs) {
0433         if (job.isEmpty()) {
0434             m_projection->clear(job.viewUpdateRect);
0435             continue;
0436         }
0437 
0438         KIS_SAFE_ASSERT_RECOVER(job.viewUpdateRect.width() <= MASK_IMAGE_WIDTH &&
0439                                 job.viewUpdateRect.height() <= MASK_IMAGE_HEIGHT) {
0440             continue;
0441         }
0442 
0443         image.fill(0);
0444 
0445         tempPainter.setTransform(QTransform());
0446         tempPainter.setClipRect(QRect(0,0,job.viewUpdateRect.width(), job.viewUpdateRect.height()));
0447         tempPainter.setTransform(viewConverter()->documentToView() *
0448                                  QTransform::fromTranslate(-job.viewUpdateRect.x(), -job.viewUpdateRect.y()));
0449 
0450         m_shapeManager->paintJob(tempPainter, job);
0451 
0452         if (image.size() != job.viewUpdateRect.size()) {
0453             const quint8 *imagePtr = image.constBits();
0454             const int imageRowStride = 4 * image.width();
0455 
0456             for (int y = 0; y < job.viewUpdateRect.height(); y++) {
0457 
0458                 KoColorSpaceRegistry::instance()->rgb8()
0459                         ->convertPixelsTo(imagePtr, dstData, m_projection->colorSpace(),
0460                                           job.viewUpdateRect.width(),
0461                                           KoColorConversionTransformation::internalRenderingIntent(),
0462                                           KoColorConversionTransformation::internalConversionFlags());
0463 
0464                 m_projection->writeBytes(dstData,
0465                                          job.viewUpdateRect.x(),
0466                                          job.viewUpdateRect.y() + y,
0467                                          job.viewUpdateRect.width(),
0468                                          1);
0469 
0470                 imagePtr += imageRowStride;
0471             }
0472         } else {
0473             KoColorSpaceRegistry::instance()->rgb8()
0474                     ->convertPixelsTo(image.constBits(), dstData, m_projection->colorSpace(),
0475                                       MASK_IMAGE_WIDTH * MASK_IMAGE_HEIGHT,
0476                                       KoColorConversionTransformation::internalRenderingIntent(),
0477                                       KoColorConversionTransformation::internalConversionFlags());
0478 
0479             m_projection->writeBytes(dstData,
0480                                      job.viewUpdateRect.x(),
0481                                      job.viewUpdateRect.y(),
0482                                      MASK_IMAGE_WIDTH,
0483                                      MASK_IMAGE_HEIGHT);
0484 
0485         }
0486         repaintRect |= job.viewUpdateRect;
0487     }
0488 
0489     delete[] dstData;
0490     m_projection->purgeDefaultPixels();
0491     m_parentLayer->setDirty(repaintRect);
0492 
0493     m_hasChangedWhileBeingInvisible |= !m_parentLayer->visible(true);
0494 }
0495 
0496 void KisShapeLayerCanvas::forceRepaint()
0497 {
0498     /**
0499      * WARNING! Although forceRepaint() may be called from different threads, it is
0500      * not entirely safe. If the user plays with shapes at the same time (vector tools are
0501      * not ported to strokes yet), the shapes my be accessed from two different places at
0502      * the same time, which will cause a crash.
0503      *
0504      * The only real solution to this is to port vector tools to strokes framework.
0505      */
0506 
0507     if (hasPendingUpdates()) {
0508         m_asyncUpdateSignalCompressor.stop();
0509         m_safeForcedConnection.start();
0510     }
0511 }
0512 
0513 bool KisShapeLayerCanvas::hasPendingUpdates() const
0514 {
0515     return m_hasUpdateInCompressor;
0516 }
0517 
0518 void KisShapeLayerCanvas::forceRepaintWithHiddenAreas()
0519 {
0520     KIS_SAFE_ASSERT_RECOVER_RETURN(m_image);
0521     KIS_SAFE_ASSERT_RECOVER_RETURN(m_parentLayer->image());
0522     KIS_SAFE_ASSERT_RECOVER_RETURN(!m_isDestroying);
0523 
0524     {
0525         QMutexLocker locker(&m_dirtyRegionMutex);
0526         m_forceUpdateHiddenAreasOnly = true;
0527     }
0528 
0529     m_asyncUpdateSignalCompressor.stop();
0530     m_safeForcedConnection.start();
0531 }
0532 
0533 void KisShapeLayerCanvas::resetCache()
0534 {
0535     m_projection->clear();
0536 
0537     QList<KoShape*> shapes = m_shapeManager->shapes();
0538     Q_FOREACH (const KoShape* shape, shapes) {
0539         shape->update();
0540     }
0541 }
0542 
0543 void KisShapeLayerCanvas::rerenderAfterBeingInvisible()
0544 {
0545     KIS_SAFE_ASSERT_RECOVER_RETURN(m_parentLayer->visible(true));
0546 
0547     m_hasChangedWhileBeingInvisible = false;
0548     resetCache();
0549 }