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> ®ion) 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 }