Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_shape_layer_canvas.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2007 Boudewijn Rempt <boud@valdyas.org>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
8
9#include <QPainter>
10#include <QMutexLocker>
11
12#include <KoShapeManager.h>
14#include <KoViewConverter.h>
15#include <KoColorSpace.h>
16
17#include <kis_paint_device.h>
18#include <kis_image.h>
19#include <kis_layer.h>
20#include <kis_painter.h>
23#include <KoSelection.h>
24#include <KoUnit.h>
26
27#include <kis_debug.h>
28
29#include <QThread>
30#include <QApplication>
31
32#include <kis_spontaneous_job.h>
33#include "kis_global.h"
34#include "krita_utils.h"
36#include "kis_default_bounds.h"
38
39
41 : KoCanvasBase(0)
42 , m_shapeManager(new KoShapeManager(this))
43 , m_selectedShapesProxy(new KoSelectedShapesProxySimple(m_shapeManager.data()))
44 , m_viewConverter()
45{
46 m_shapeManager->selection()->setActiveLayer(parent);
47}
48
50 : KoCanvasBase(0)
51 , m_shapeManager(new KoShapeManager(this))
52 , m_selectedShapesProxy(new KoSelectedShapesProxySimple(m_shapeManager.data()))
53 , m_viewConverter(rhs.m_viewConverter)
54{
56 m_shapeManager->selection()->setActiveLayer(parent);
57}
58
63
68
73
78
83
84void KisShapeLayerCanvasBase::gridSize(QPointF *offset, QSizeF *spacing) const
85{
86 KIS_SAFE_ASSERT_RECOVER_NOOP(false); // This should never be called as this canvas should have no tools.
87 Q_UNUSED(offset);
88 Q_UNUSED(spacing);
89}
90
92{
93 KIS_SAFE_ASSERT_RECOVER_NOOP(false); // This should never be called as this canvas should have no tools.
94 return false;
95}
96
98{
99 KIS_SAFE_ASSERT_RECOVER_NOOP(false); // This should never be called as this canvas should have no tools.
100}
101
102
104{
105// KIS_SAFE_ASSERT_RECOVER_NOOP(false); // This should never be called as this canvas should have no tools.
106 return 0;
107}
108
110{
111 return 0;
112}
113
115{
116 return 0;
117}
118
120{
121 KIS_SAFE_ASSERT_RECOVER_NOOP(false); // This should never be called as this canvas should have no tools.
122 return KoUnit(KoUnit::Point);
123}
124
129
134
135
138 , m_projection(new KisPaintDevice(parent, cs, defaultBounds))
139 , m_parentLayer(parent)
140 , m_asyncUpdateSignalCompressor(25, KisSignalCompressor::FIRST_ACTIVE)
141 , m_safeForcedConnection(std::bind(&KisShapeLayerCanvas::slotStartAsyncRepaint, this))
142{
148 m_shapeManager->selection()->setActiveLayer(parent);
149
150 connect(&m_asyncUpdateSignalCompressor, SIGNAL(timeout()), SLOT(slotStartAsyncRepaint()));
151}
152
154 : KisShapeLayerCanvasBase(rhs, parent)
155 , m_projection(new KisPaintDevice(*rhs.m_projection))
156 , m_parentLayer(parent)
157 , m_asyncUpdateSignalCompressor(25, KisSignalCompressor::FIRST_ACTIVE)
158 , m_safeForcedConnection(std::bind(&KisShapeLayerCanvas::slotStartAsyncRepaint, this))
159{
165 m_shapeManager->selection()->setActiveLayer(parent);
166
167 connect(&m_asyncUpdateSignalCompressor, SIGNAL(timeout()), SLOT(slotStartAsyncRepaint()));
169}
170
175
180
185
187{
189
191 m_image = image;
192
193 if (image) {
194 m_imageConnections.addUniqueConnection(m_image, SIGNAL(sigSizeChanged(QPointF,QPointF)), this, SLOT(slotImageSizeChanged()));
197 }
199 if (image && m_hasUpdateOnSetImage) {
200 m_hasUpdateOnSetImage = false;
201 const QRectF documentRect = viewConverter()->viewToDocument(m_cachedImageRect);
202 updateCanvas(documentRect);
203 }
204}
205
207{
208public:
210 : m_layer(layer),
211 m_canvas(canvas)
212 {
213 }
214
215 bool overrides(const KisSpontaneousJob *_otherJob) override {
216 const KisRepaintShapeLayerLayerJob *otherJob =
217 dynamic_cast<const KisRepaintShapeLayerLayerJob*>(_otherJob);
218
219 return otherJob && otherJob->m_canvas == m_canvas;
220 }
221
222 void run() override {
223 m_canvas->repaint();
224 }
225
226 int levelOfDetail() const override {
227 return 0;
228 }
229
230 QString debugName() const override {
231 QString result;
232 QDebug dbg(&result);
233 dbg << "KisRepaintShapeLayerLayerJob" << m_layer;
234 return result;
235 }
236
237private:
238
239 // we store a pointer to the layer just
240 // to keep the lifetime of the canvas!
242
244};
245
246
248{
249 if (!m_image){
251 return;
252 }
254 return;
255 }
256
257 {
258 QMutexLocker locker(&m_dirtyRegionMutex);
259 Q_FOREACH (const QRectF &rc, region) {
260 // grow for antialiasing
261 const QRect imageRect = kisGrowRect(viewConverter()->documentToView(rc).toAlignedRect(), 2);
262 m_dirtyRegion += imageRect;
263 }
264 }
265
268}
269
270
272{
274}
275
277{
278 KisImageSP image = m_image;
279 if (!image || !m_parentLayer->image()) {
280 return;
281 }
282
288 if (image->locked()) {
290 return;
291 }
292
293 QRect repaintRect;
294 QRect uncroppedRepaintRect;
295 bool forceUpdateHiddenAreasOnly = false;
296 const qint32 MASK_IMAGE_WIDTH = 256;
297 const qint32 MASK_IMAGE_HEIGHT = 256;
298 {
299 QMutexLocker locker(&m_dirtyRegionMutex);
300
301 repaintRect = m_dirtyRegion.boundingRect();
302 forceUpdateHiddenAreasOnly = m_forceUpdateHiddenAreasOnly;
303
307 Q_FOREACH (const KoShapeManager::PaintJob &job, m_paintJobsOrder.jobs) {
308 repaintRect |= viewConverter()->documentToView().mapRect(job.docUpdateRect).toAlignedRect();
309 }
311
312 m_dirtyRegion = QRegion();
314 }
315
316 if (!forceUpdateHiddenAreasOnly) {
317 if (repaintRect.isEmpty()) {
318 return;
319 }
320
321 // Crop the update rect by the image bounds. We keep the cache consistent
322 // by tracking the size of the image in slotImageSizeChanged()
323 uncroppedRepaintRect = repaintRect;
324 repaintRect = repaintRect.intersected(image->bounds());
325 } else {
326 const QRectF shapesBounds = KoShape::boundingRect(m_shapeManager->shapes());
327 repaintRect |= kisGrowRect(viewConverter()->documentToView(shapesBounds).toAlignedRect(), 2);
328 uncroppedRepaintRect = repaintRect;
329 }
330
359 const QVector<QRect> updateRects =
361 QSize(MASK_IMAGE_WIDTH, MASK_IMAGE_HEIGHT));
362
364 Q_FOREACH (const QRect &viewUpdateRect, updateRects) {
365 jobsOrder.jobs << KoShapeManager::PaintJob(viewConverter()->viewToDocument().mapRect(QRectF(viewUpdateRect)),
366 viewUpdateRect);
367 }
368 jobsOrder.uncroppedViewUpdateRect = uncroppedRepaintRect;
369
370 m_shapeManager->preparePaintJobs(jobsOrder, m_parentLayer);
371
372 {
373 QMutexLocker locker(&m_dirtyRegionMutex);
374
375 // check if it is still empty! It should be true, because GUI thread is
376 // the only actor that can add stuff to it.
378 m_paintJobsOrder = jobsOrder;
379 }
380
383}
384
386{
387 QRegion dirtyCacheRegion;
388 dirtyCacheRegion += m_image->bounds();
389 dirtyCacheRegion += m_cachedImageRect;
390 dirtyCacheRegion -= m_image->bounds() & m_cachedImageRect;
391
392 QVector<QRectF> dirtyRects;
393 auto rc = dirtyCacheRegion.begin();
394 while (rc != dirtyCacheRegion.end()) {
395 dirtyRects.append(viewConverter()->viewToDocument(*rc));
396 rc++;
397 }
398 updateCanvas(dirtyRects);
399
401}
402
404{
405
406 KoShapeManager::PaintJobsOrder paintJobsOrder;
407
408 {
409 QMutexLocker locker(&m_dirtyRegionMutex);
410 std::swap(paintJobsOrder, m_paintJobsOrder);
411 }
412
417 if (paintJobsOrder.isEmpty()) return;
418
419 const qint32 MASK_IMAGE_WIDTH = 256;
420 const qint32 MASK_IMAGE_HEIGHT = 256;
421
422 QImage image(MASK_IMAGE_WIDTH, MASK_IMAGE_HEIGHT, QImage::Format_ARGB32);
423 QPainter tempPainter(&image);
424
426 tempPainter.setRenderHint(QPainter::Antialiasing);
427 tempPainter.setRenderHint(QPainter::TextAntialiasing);
428 }
429
430 quint8 * dstData = new quint8[MASK_IMAGE_WIDTH * MASK_IMAGE_HEIGHT * m_projection->pixelSize()];
431
432 QRect repaintRect = paintJobsOrder.uncroppedViewUpdateRect;
433 m_projection->clear(repaintRect);
434
435 Q_FOREACH (const KoShapeManager::PaintJob &job, paintJobsOrder.jobs) {
436 if (job.isEmpty()) {
438 continue;
439 }
440
441 KIS_SAFE_ASSERT_RECOVER(job.viewUpdateRect.width() <= MASK_IMAGE_WIDTH &&
442 job.viewUpdateRect.height() <= MASK_IMAGE_HEIGHT) {
443 continue;
444 }
445
446 image.fill(0);
447
448 tempPainter.setTransform(QTransform());
449 tempPainter.setClipRect(QRect(0,0,job.viewUpdateRect.width(), job.viewUpdateRect.height()));
450 tempPainter.setTransform(viewConverter()->documentToView() *
451 QTransform::fromTranslate(-job.viewUpdateRect.x(), -job.viewUpdateRect.y()));
452
453 m_shapeManager->paintJob(tempPainter, job);
454
455 if (image.size() != job.viewUpdateRect.size()) {
456 const quint8 *imagePtr = image.constBits();
457 const int imageRowStride = 4 * image.width();
458
459 for (int y = 0; y < job.viewUpdateRect.height(); y++) {
460
462 ->convertPixelsTo(imagePtr, dstData, m_projection->colorSpace(),
463 job.viewUpdateRect.width(),
466
467 m_projection->writeBytes(dstData,
468 job.viewUpdateRect.x(),
469 job.viewUpdateRect.y() + y,
470 job.viewUpdateRect.width(),
471 1);
472
473 imagePtr += imageRowStride;
474 }
475 } else {
477 ->convertPixelsTo(image.constBits(), dstData, m_projection->colorSpace(),
478 MASK_IMAGE_WIDTH * MASK_IMAGE_HEIGHT,
481
482 m_projection->writeBytes(dstData,
483 job.viewUpdateRect.x(),
484 job.viewUpdateRect.y(),
485 MASK_IMAGE_WIDTH,
486 MASK_IMAGE_HEIGHT);
487
488 }
489 repaintRect |= job.viewUpdateRect;
490 }
491
492 delete[] dstData;
494 m_parentLayer->setDirty(repaintRect);
495
497}
498
515
520
535
537{
538 Q_UNUSED(colorSpace);
540
541 QList<KoShape*> shapes = m_shapeManager->shapes();
542 Q_FOREACH (const KoShape* shape, shapes) {
543 shape->update();
544 }
545}
546
void setImage(KisImageWSP image)
const KoColorSpace * colorSpace() const
QRect bounds() const override
bool locked() const
Definition kis_image.cc:752
void addSpontaneousJob(KisSpontaneousJob *spontaneousJob)
quint32 pixelSize() const
virtual void clear()
void setDefaultBounds(KisDefaultBoundsBaseSP bounds)
const KoColorSpace * colorSpace() const
void setParentNode(KisNodeWSP parent)
void convertTo(const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent=KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::ConversionFlags conversionFlags=KoColorConversionTransformation::internalConversionFlags(), KUndo2Command *parentCommand=nullptr, KoUpdater *progressUpdater=nullptr)
void writeBytes(const quint8 *data, qint32 x, qint32 y, qint32 w, qint32 h)
KisRepaintShapeLayerLayerJob(KisShapeLayerSP layer, KisShapeLayerCanvas *canvas)
bool overrides(const KisSpontaneousJob *_otherJob) override
QScopedPointer< KoSelectedShapesProxy > m_selectedShapesProxy
KisShapeLayerCanvasBase(KisShapeLayer *parent)
void gridSize(QPointF *offset, QSizeF *spacing) const override
const KoViewConverter * viewConverter() const override
virtual void setImage(KisImageWSP image)
KoToolProxy * toolProxy() const override
bool snapToGrid() const override
KisImageViewConverter m_viewConverter
QScopedPointer< KoShapeManager > m_shapeManager
KoShapeManager * shapeManager() const override
KoSelectedShapesProxy * selectedShapesProxy() const override
selectedShapesProxy() is a special interface for keeping a persistent connections to selectionChanged...
void addCommand(KUndo2Command *command) override
bool hasPendingUpdates() const override
void forceRepaintWithHiddenAreas() override
void setProjection(KisPaintDeviceSP projection)
This canvas won't render onto a widget, but a projection.
friend class KisRepaintShapeLayerLayerJob
KisSignalAutoConnectionsStore m_imageConnections
KoShapeManager::PaintJobsOrder m_paintJobsOrder
void setImage(KisImageWSP image) override
KisPaintDeviceSP projection() const override
void rerenderAfterBeingInvisible() override
KisSafeBlockingQueueConnectionProxy< void > m_safeForcedConnection
KisThreadSafeSignalCompressor m_asyncUpdateSignalCompressor
KisShapeLayerCanvas(const KoColorSpace *cs, KisDefaultBoundsBaseSP defaultBounds, KisShapeLayer *parent)
volatile bool m_hasUpdateInCompressor
void resetCache(const KoColorSpace *colorSpace=0) override
void updateCanvas(const QRectF &rc) override
bool antialiased() const
bool visible(bool recursive=false) const override
void addUniqueConnection(Sender sender, Signal signal, Receiver receiver, Method method)
virtual QPointF viewToDocument(const QPointF &viewPoint) const
virtual bool convertPixelsTo(const quint8 *src, quint8 *dst, const KoColorSpace *dstColorSpace, quint32 numPixels, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const
The KoSelectedShapesProxy class is a special interface of KoCanvasBase to have a stable connection to...
@ AddWithoutRepaint
Avoids each shapes 'update()' to be called for faster addition when its possible.
virtual QRectF boundingRect() const
Get the bounding box of the shape.
Definition KoShape.cpp:300
virtual void update() const
Definition KoShape.cpp:529
@ Point
Postscript point, 1/72th of an Inco.
Definition KoUnit.h:76
virtual QPointF viewToDocument(const QPointF &viewPoint) const
virtual QPointF documentToView(const QPointF &documentPoint) const
#define KIS_SAFE_ASSERT_RECOVER(cond)
Definition kis_assert.h:126
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:130
T kisGrowRect(const T &rect, U offset)
Definition kis_global.h:186
QVector< QRect > splitRectIntoPatchesTight(const QRect &rc, const QSize &patchSize)
KisImageWSP image
virtual void setDirty()
Definition kis_node.cpp:577
static KoColorSpaceRegistry * instance()
const KoColorSpace * rgb8(const QString &profileName=QString())