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;
202 }
203}
204
206{
207public:
209 : m_layer(layer),
210 m_canvas(canvas)
211 {
212 }
213
214 bool overrides(const KisSpontaneousJob *_otherJob) override {
215 const KisRepaintShapeLayerLayerJob *otherJob =
216 dynamic_cast<const KisRepaintShapeLayerLayerJob*>(_otherJob);
217
218 return otherJob && otherJob->m_canvas == m_canvas;
219 }
220
221 void run() override {
222 m_canvas->repaint();
223 }
224
225 int levelOfDetail() const override {
226 return 0;
227 }
228
229 QString debugName() const override {
230 QString result;
231 QDebug dbg(&result);
232 dbg << "KisRepaintShapeLayerLayerJob" << m_layer;
233 return result;
234 }
235
236private:
237
238 // we store a pointer to the layer just
239 // to keep the lifetime of the canvas!
241
243};
244
245
247{
248 if (!m_image){
250 return;
251 }
253 return;
254 }
255
256 {
257 QMutexLocker locker(&m_dirtyRegionMutex);
258 Q_FOREACH (const QRectF &rc, region) {
259 // grow for antialiasing
260 const QRect imageRect = kisGrowRect(viewConverter()->documentToView(rc).toAlignedRect(), 2);
261 m_dirtyRegion += imageRect;
262 }
263 }
264
267}
268
269
271{
273}
274
276{
277 KisImageSP image = m_image;
278 if (!image || !m_parentLayer->image()) {
279 return;
280 }
281
287 if (image->locked()) {
289 return;
290 }
291
292 QRect repaintRect;
293 QRect uncroppedRepaintRect;
294 bool forceUpdateHiddenAreasOnly = false;
295 const qint32 MASK_IMAGE_WIDTH = 256;
296 const qint32 MASK_IMAGE_HEIGHT = 256;
297 {
298 QMutexLocker locker(&m_dirtyRegionMutex);
299
300 repaintRect = m_dirtyRegion.boundingRect();
301 forceUpdateHiddenAreasOnly = m_forceUpdateHiddenAreasOnly;
302
306 Q_FOREACH (const KoShapeManager::PaintJob &job, m_paintJobsOrder.jobs) {
307 repaintRect |= viewConverter()->documentToView().mapRect(job.docUpdateRect).toAlignedRect();
308 }
310
311 m_dirtyRegion = QRegion();
313 }
314
315 if (!forceUpdateHiddenAreasOnly) {
316 if (repaintRect.isEmpty()) {
317 return;
318 }
319
320 // Crop the update rect by the image bounds. We keep the cache consistent
321 // by tracking the size of the image in slotImageSizeChanged()
322 uncroppedRepaintRect = repaintRect;
323 repaintRect = repaintRect.intersected(image->bounds());
324 } else {
325 const QRectF shapesBounds = KoShape::boundingRect(m_shapeManager->shapes());
326 repaintRect |= kisGrowRect(viewConverter()->documentToView(shapesBounds).toAlignedRect(), 2);
327 uncroppedRepaintRect = repaintRect;
328 }
329
358 const QVector<QRect> updateRects =
360 QSize(MASK_IMAGE_WIDTH, MASK_IMAGE_HEIGHT));
361
363 Q_FOREACH (const QRect &viewUpdateRect, updateRects) {
364 jobsOrder.jobs << KoShapeManager::PaintJob(viewConverter()->viewToDocument().mapRect(QRectF(viewUpdateRect)),
365 viewUpdateRect);
366 }
367 jobsOrder.uncroppedViewUpdateRect = uncroppedRepaintRect;
368
369 m_shapeManager->preparePaintJobs(jobsOrder, m_parentLayer);
370
371 {
372 QMutexLocker locker(&m_dirtyRegionMutex);
373
374 // check if it is still empty! It should be true, because GUI thread is
375 // the only actor that can add stuff to it.
377 m_paintJobsOrder = jobsOrder;
378 }
379
382}
383
385{
386 QRegion dirtyCacheRegion;
387 dirtyCacheRegion += m_image->bounds();
388 dirtyCacheRegion += m_cachedImageRect;
389 dirtyCacheRegion -= m_image->bounds() & m_cachedImageRect;
390
391 QVector<QRectF> dirtyRects;
392 auto rc = dirtyCacheRegion.begin();
393 while (rc != dirtyCacheRegion.end()) {
394 dirtyRects.append(viewConverter()->viewToDocument(*rc));
395 rc++;
396 }
397 updateCanvas(dirtyRects);
398
400}
401
403{
404
405 KoShapeManager::PaintJobsOrder paintJobsOrder;
406
407 {
408 QMutexLocker locker(&m_dirtyRegionMutex);
409 std::swap(paintJobsOrder, m_paintJobsOrder);
410 }
411
416 if (paintJobsOrder.isEmpty()) return;
417
418 const qint32 MASK_IMAGE_WIDTH = 256;
419 const qint32 MASK_IMAGE_HEIGHT = 256;
420
421 QImage image(MASK_IMAGE_WIDTH, MASK_IMAGE_HEIGHT, QImage::Format_ARGB32);
422 QPainter tempPainter(&image);
423
425 tempPainter.setRenderHint(QPainter::Antialiasing);
426 tempPainter.setRenderHint(QPainter::TextAntialiasing);
427 }
428
429 quint8 * dstData = new quint8[MASK_IMAGE_WIDTH * MASK_IMAGE_HEIGHT * m_projection->pixelSize()];
430
431 QRect repaintRect = paintJobsOrder.uncroppedViewUpdateRect;
432 m_projection->clear(repaintRect);
433
434 Q_FOREACH (const KoShapeManager::PaintJob &job, paintJobsOrder.jobs) {
435 if (job.isEmpty()) {
437 continue;
438 }
439
440 KIS_SAFE_ASSERT_RECOVER(job.viewUpdateRect.width() <= MASK_IMAGE_WIDTH &&
441 job.viewUpdateRect.height() <= MASK_IMAGE_HEIGHT) {
442 continue;
443 }
444
445 image.fill(0);
446
447 tempPainter.setTransform(QTransform());
448 tempPainter.setClipRect(QRect(0,0,job.viewUpdateRect.width(), job.viewUpdateRect.height()));
449 tempPainter.setTransform(viewConverter()->documentToView() *
450 QTransform::fromTranslate(-job.viewUpdateRect.x(), -job.viewUpdateRect.y()));
451
452 m_shapeManager->paintJob(tempPainter, job);
453
454 if (image.size() != job.viewUpdateRect.size()) {
455 const quint8 *imagePtr = image.constBits();
456 const int imageRowStride = 4 * image.width();
457
458 for (int y = 0; y < job.viewUpdateRect.height(); y++) {
459
461 ->convertPixelsTo(imagePtr, dstData, m_projection->colorSpace(),
462 job.viewUpdateRect.width(),
465
466 m_projection->writeBytes(dstData,
467 job.viewUpdateRect.x(),
468 job.viewUpdateRect.y() + y,
469 job.viewUpdateRect.width(),
470 1);
471
472 imagePtr += imageRowStride;
473 }
474 } else {
476 ->convertPixelsTo(image.constBits(), dstData, m_projection->colorSpace(),
477 MASK_IMAGE_WIDTH * MASK_IMAGE_HEIGHT,
480
481 m_projection->writeBytes(dstData,
482 job.viewUpdateRect.x(),
483 job.viewUpdateRect.y(),
484 MASK_IMAGE_WIDTH,
485 MASK_IMAGE_HEIGHT);
486
487 }
488 repaintRect |= job.viewUpdateRect;
489 }
490
491 delete[] dstData;
493 m_parentLayer->setDirty(repaintRect);
494
496}
497
514
519
534
536{
537 Q_UNUSED(colorSpace);
539
540 QList<KoShape*> shapes = m_shapeManager->shapes();
541 Q_FOREACH (const KoShape* shape, shapes) {
542 shape->update();
543 }
544}
545
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
void setImage(KisImageWSP image)
const KoColorSpace * colorSpace() const
QRect bounds() const override
bool locked() const
Definition kis_image.cc:751
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:335
virtual void update() const
Definition KoShape.cpp:605
@ Point
Postscript point, 1/72th of an Inco.
Definition KoUnit.h:76
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())