Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_pixel_selection.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2004 Boudewijn Rempt <boud@valdyas.org>
3 * SPDX-FileCopyrightText: 2007 Sven Langkamp <sven.langkamp@gmail.com>
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
9
10
11#include <QImage>
12#include <QVector>
13
14#include <QMutex>
15#include <QPoint>
16#include <QPolygon>
17
18#include <KoColorSpace.h>
21#include <KoIntegerMaths.h>
23
24#include "kis_layer.h"
25#include "kis_debug.h"
26#include "kis_image.h"
27#include "kis_fill_painter.h"
29#include <kis_iterator_ng.h>
30#include "kis_lod_transform.h"
31#include "krita_utils.h"
32#include "kundo2command.h"
33
34
35struct Q_DECL_HIDDEN KisPixelSelection::Private {
37
38 QPainterPath outlineCache;
41
45
47
49 thumbnailImageValid = false;
50 thumbnailImage = QImage();
51 thumbnailImageTransform = QTransform();
52 }
53};
54
56 : KisPaintDevice(0, KoColorSpaceRegistry::instance()->alpha8(), defaultBounds)
57 , m_d(new Private)
58{
59 m_d->outlineCacheValid = true;
60 m_d->invalidateThumbnailImage();
61
62 m_d->parentSelection = parentSelection;
63}
64
66 : KisPaintDevice(rhs, copyMode)
68 , m_d(new Private)
69{
70 // parent selection is not supposed to be shared
71 m_d->outlineCache = rhs.m_d->outlineCache;
72 m_d->outlineCacheValid = rhs.m_d->outlineCacheValid;
73
74 m_d->thumbnailImageValid = rhs.m_d->thumbnailImageValid;
75 m_d->thumbnailImage = rhs.m_d->thumbnailImage;
76 m_d->thumbnailImageTransform = rhs.m_d->thumbnailImageTransform;
77}
78
80 : KisPaintDevice(0, KoColorSpaceRegistry::instance()->alpha8(), copySource->defaultBounds())
81 , m_d(new Private)
82{
83 KisPaintDeviceSP tmpDevice = new KisPaintDevice(*copySource, copyMode, 0);
84 tmpDevice->convertTo(this->colorSpace());
85
86 this->makeFullCopyFrom(*tmpDevice, copyMode, 0);
87
88 m_d->parentSelection = parentSelection;
89 m_d->outlineCacheValid = false;
90 m_d->invalidateThumbnailImage();
91}
92
97
102
110
111bool KisPixelSelection::read(QIODevice *stream)
112{
113 bool retval = KisPaintDevice::read(stream);
114 m_d->outlineCacheValid = false;
115 m_d->invalidateThumbnailImage();
116 return retval;
117}
118
119void KisPixelSelection::select(const QRect & rc, quint8 selectedness)
120{
121 QRect r = rc.normalized();
122 if (r.isEmpty()) return;
123
124 KisFillPainter painter(KisPaintDeviceSP(this));
126 painter.fillRect(r, KoColor(Qt::white, cs), selectedness);
127
128 if (m_d->outlineCacheValid) {
129 QPainterPath path;
130 path.addRect(r);
131
132 if (selectedness != MIN_SELECTED) {
133 m_d->outlineCache += path;
134 } else {
135 m_d->outlineCache -= path;
136 }
137 }
138 m_d->invalidateThumbnailImage();
139}
140
142{
143 switch (action) {
145 clear();
146 addSelection(selection);
147 break;
148 case SELECTION_ADD:
149 addSelection(selection);
150 break;
152 subtractSelection(selection);
153 break;
155 intersectSelection(selection);
156 break;
159 break;
160 default:
161 break;
162 }
163}
164
165void KisPixelSelection::copyAlphaFrom(KisPaintDeviceSP src, const QRect &processRect)
166{
167 const KoColorSpace *srcCS = src->colorSpace();
168
169 KisSequentialConstIterator srcIt(src, processRect);
170 KisSequentialIterator dstIt(this, processRect);
171
172 while (srcIt.nextPixel() && dstIt.nextPixel()) {
173 const quint8 *srcPtr = srcIt.rawDataConst();
174 quint8 *alpha8Ptr = dstIt.rawData();
175
176 *alpha8Ptr = srcCS->opacityU8(srcPtr);
177 }
178
179 m_d->outlineCacheValid = false;
180 m_d->outlineCache = QPainterPath();
181 m_d->invalidateThumbnailImage();
182}
183
185{
186 QRect r = selection->selectedRect();
187 if (r.isEmpty()) return;
188
189 KisHLineIteratorSP dst = createHLineIteratorNG(r.x(), r.y(), r.width());
190 KisHLineConstIteratorSP src = selection->createHLineConstIteratorNG(r.x(), r.y(), r.width());
191 for (int i = 0; i < r.height(); ++i) {
192 do {
193 if (*src->oldRawData() + *dst->rawData() < MAX_SELECTED)
194 *dst->rawData() = *src->oldRawData() + *dst->rawData();
195 else
196 *dst->rawData() = MAX_SELECTED;
197
198 } while (src->nextPixel() && dst->nextPixel());
199 dst->nextRow();
200 src->nextRow();
201 }
202
203 const quint8 defPixel = qMax(*defaultPixel().data(), *selection->defaultPixel().data());
204 setDefaultPixel(KoColor(&defPixel, colorSpace()));
205
206 m_d->outlineCacheValid &= selection->outlineCacheValid();
207
208 if (m_d->outlineCacheValid) {
209 m_d->outlineCache += selection->outlineCache();
210 }
211
212 m_d->invalidateThumbnailImage();
213}
214
216{
217 QRect r = selection->selectedRect();
218 if (r.isEmpty()) return;
219
220
221 KisHLineIteratorSP dst = createHLineIteratorNG(r.x(), r.y(), r.width());
222 KisHLineConstIteratorSP src = selection->createHLineConstIteratorNG(r.x(), r.y(), r.width());
223 for (int i = 0; i < r.height(); ++i) {
224 do {
225 if (*dst->rawData() - *src->oldRawData() > MIN_SELECTED)
226 *dst->rawData() = *dst->rawData() - *src->oldRawData();
227 else
228 *dst->rawData() = MIN_SELECTED;
229
230 } while (src->nextPixel() && dst->nextPixel());
231 dst->nextRow();
232 src->nextRow();
233 }
234
235 const quint8 defPixel = *selection->defaultPixel().data() > *defaultPixel().data()
237 : *defaultPixel().data() - *selection->defaultPixel().data();
238 setDefaultPixel(KoColor(&defPixel, colorSpace()));
239
240 m_d->outlineCacheValid &= selection->outlineCacheValid();
241
242 if (m_d->outlineCacheValid) {
243 m_d->outlineCache -= selection->outlineCache();
244 }
245
246 m_d->invalidateThumbnailImage();
247}
248
250{
251 const QRect r = selection->selectedRect().united(selectedRect());
252 if (r.isEmpty()) {
253 clear();
254 return;
255 }
256
257 KisHLineIteratorSP dst = createHLineIteratorNG(r.x(), r.y(), r.width());
258 KisHLineConstIteratorSP src = selection->createHLineConstIteratorNG(r.x(), r.y(), r.width());
259 for (int i = 0; i < r.height(); ++i) {
260 do {
261 *dst->rawData() = qMin(*dst->rawData(), *src->oldRawData());
262 } while (src->nextPixel() && dst->nextPixel());
263 dst->nextRow();
264 src->nextRow();
265 }
266
267 const quint8 defPixel = qMin(*defaultPixel().data(), *selection->defaultPixel().data());
268 setDefaultPixel(KoColor(&defPixel, colorSpace()));
269
270 crop(r);
271
272 m_d->outlineCacheValid &= selection->outlineCacheValid();
273
274 if (m_d->outlineCacheValid) {
275 m_d->outlineCache = KritaUtils::tryCloseTornSubpathsAfterIntersection(m_d->outlineCache & selection->outlineCache());
276 }
277
278 m_d->invalidateThumbnailImage();
279}
280
282{
283 QRect r = selection->selectedRect().united(selectedRect());
284 if (r.isEmpty()) return;
285
286 KisHLineIteratorSP dst = createHLineIteratorNG(r.x(), r.y(), r.width());
287 KisHLineConstIteratorSP src = selection->createHLineConstIteratorNG(r.x(), r.y(), r.width());
288 for (int i = 0; i < r.height(); ++i) {
289
290 do {
291 *dst->rawData() = abs(*dst->rawData() - *src->oldRawData());
292 } while (src->nextPixel() && dst->nextPixel());
293
294 dst->nextRow();
295 src->nextRow();
296 }
297
298 const quint8 defPixel = abs(*defaultPixel().data() - *selection->defaultPixel().data());
299 setDefaultPixel(KoColor(&defPixel, colorSpace()));
300
301 m_d->outlineCacheValid &= selection->outlineCacheValid();
302
303 if (m_d->outlineCacheValid) {
304 m_d->outlineCache = (m_d->outlineCache | selection->outlineCache()) - (m_d->outlineCache & selection->outlineCache());
305 }
306
307 m_d->invalidateThumbnailImage();
308}
309
310void KisPixelSelection::clear(const QRect & r)
311{
312 if (*defaultPixel().data() != MIN_SELECTED) {
313 KisFillPainter painter(KisPaintDeviceSP(this));
315 painter.fillRect(r, KoColor(Qt::white, cs), MIN_SELECTED);
316 } else {
318 }
319
320 if (m_d->outlineCacheValid) {
321 QPainterPath path;
322 path.addRect(r);
323
324 m_d->outlineCache -= path;
325 }
326
327 m_d->invalidateThumbnailImage();
328}
329
331{
334
335 m_d->outlineCacheValid = true;
336 m_d->outlineCache = QPainterPath();
337
338 // Empty the thumbnail image. It is a valid state.
339 m_d->invalidateThumbnailImage();
340 m_d->thumbnailImageValid = true;
341}
342
344{
345 // Region is needed here (not exactBounds or extent), because
346 // unselected but existing pixels need to be inverted too
347 QRect rc = region().boundingRect();
348
349 if (!rc.isEmpty()) {
350 KisSequentialIterator it(this, rc);
351 while(it.nextPixel()) {
352 *(it.rawData()) = MAX_SELECTED - *(it.rawData());
353 }
354 }
355 quint8 defPixel = MAX_SELECTED - *defaultPixel().data();
356 setDefaultPixel(KoColor(&defPixel, colorSpace()));
357
358 if (m_d->outlineCacheValid) {
359 QPainterPath path;
360 path.addRect(defaultBounds()->bounds());
361
362 m_d->outlineCache = path - m_d->outlineCache;
363 }
364
365 m_d->invalidateThumbnailImage();
366}
367
368void KisPixelSelection::moveTo(const QPoint &pt)
369{
370 const int lod = defaultBounds()->currentLevelOfDetail();
371 const QPoint lod0Point = !lod ? pt :
373
374 const QPoint offset = lod0Point - m_d->lod0CachesOffset;
375
376 if (m_d->outlineCacheValid) {
377 m_d->outlineCache.translate(offset);
378 }
379
380 if (m_d->thumbnailImageValid) {
381 m_d->thumbnailImageTransform =
382 QTransform::fromTranslate(offset.x(), offset.y()) *
383 m_d->thumbnailImageTransform;
384 }
385
386 m_d->lod0CachesOffset = lod0Point;
387
389}
390
391bool KisPixelSelection::isTotallyUnselected(const QRect & r) const
392{
393 if (*defaultPixel().data() != MIN_SELECTED)
394 return false;
395 QRect sr = selectedExactRect();
396 return ! r.intersects(sr);
397}
398
400{
401 return extent();
402}
403
405{
406 return exactBounds();
407}
408
410{
411 QRect selectionExtent = selectedExactRect();
412
419 if (*defaultPixel().data() != MIN_SELECTED) {
420 selectionExtent &= defaultBounds()->bounds();
421 }
422
423 qint32 xOffset = selectionExtent.x();
424 qint32 yOffset = selectionExtent.y();
425 qint32 width = selectionExtent.width();
426 qint32 height = selectionExtent.height();
427
429 // If the selection is small using a buffer is much faster
430 try {
431 quint8* buffer = new quint8[width*height];
432 readBytes(buffer, xOffset, yOffset, width, height);
433
434 QVector<QPolygon> paths = generator.outline(buffer, xOffset, yOffset, width, height);
435
436 delete[] buffer;
437 return paths;
438 }
439 catch(const std::bad_alloc&) {
440 // Allocating so much memory failed, so we fall through to the slow option.
441 warnKrita << "KisPixelSelection::outline ran out of memory allocating" << width << "*" << height << "bytes.";
442 }
443
444 return generator.outline(this, xOffset, yOffset, width, height);
445}
446
448{
449 return *defaultPixel().data() == MIN_SELECTED && selectedRect().isEmpty();
450}
451
452QPainterPath KisPixelSelection::outlineCache() const
453{
454 QMutexLocker locker(&m_d->outlineCacheMutex);
455 return m_d->outlineCache;
456}
457
458void KisPixelSelection::setOutlineCache(const QPainterPath &cache)
459{
460 QMutexLocker locker(&m_d->outlineCacheMutex);
461 m_d->outlineCache = cache;
462 m_d->outlineCacheValid = true;
463 m_d->thumbnailImageValid = false;
464}
465
467{
468 QMutexLocker locker(&m_d->outlineCacheMutex);
469 return m_d->outlineCacheValid;
470}
471
473{
474 QMutexLocker locker(&m_d->outlineCacheMutex);
475 m_d->outlineCacheValid = false;
476 m_d->thumbnailImageValid = false;
477}
478
480{
481 QMutexLocker locker(&m_d->outlineCacheMutex);
482
483 m_d->outlineCache = QPainterPath();
484
485 Q_FOREACH (const QPolygon &polygon, outline()) {
486 m_d->outlineCache.addPolygon(polygon);
487
497 m_d->outlineCache.closeSubpath();
498 }
499
500 m_d->outlineCacheValid = true;
501}
502
504{
505 return m_d->thumbnailImageValid;
506}
507
509{
510 return m_d->thumbnailImage;
511}
512
514{
515 return m_d->thumbnailImageTransform;
516}
517
519 const QRect &rc,
520 const QColor &maskColor)
521{
522 QImage image(rc.size(), QImage::Format_ARGB32);
523
524 QColor color = maskColor;
525 const qreal alphaScale = maskColor.alphaF();
526
527 KisSequentialConstIterator it(device, rc);
528 while(it.nextPixel()) {
529 quint8 value = (MAX_SELECTED - *(it.rawDataConst())) * alphaScale;
530 color.setAlpha(value);
531
532 QPoint pt(it.x(), it.y());
533 pt -= rc.topLeft();
534
535 image.setPixel(pt.x(), pt.y(), color.rgba());
536 }
537
538 return image;
539}
540
542{
543 QRect rc = selectedExactRect();
544 const int maxPreviewSize = 2000;
545
546 if (rc.isEmpty()) {
547 m_d->thumbnailImageTransform = QTransform();
548 m_d->thumbnailImage = QImage();
549 return;
550 }
551
552
553 if (rc.width() > maxPreviewSize ||
554 rc.height() > maxPreviewSize) {
555
556 qreal factor = 1.0;
557
558 if (rc.width() > rc.height()) {
559 factor = qreal(maxPreviewSize) / rc.width();
560 } else {
561 factor = qreal(maxPreviewSize) / rc.height();
562 }
563
564 int newWidth = qRound(rc.width() * factor);
565 int newHeight = qRound(rc.height() * factor);
566
567 m_d->thumbnailImageTransform =
568 QTransform::fromScale(qreal(rc.width()) / newWidth,
569 qreal(rc.height()) / newHeight) *
570 QTransform::fromTranslate(rc.x(), rc.y());
571
572 KisPaintDeviceSP thumbDevice =
573 createThumbnailDevice(newWidth, newHeight, rc);
574
575 QRect thumbRect(0, 0, newWidth, newHeight);
576 m_d->thumbnailImage = deviceToQImage(thumbDevice, thumbRect, maskColor);
577
578 } else {
579 m_d->thumbnailImageTransform = QTransform::fromTranslate(rc.x(), rc.y());
580 m_d->thumbnailImage = deviceToQImage(this, rc, maskColor);
581 }
582
583 m_d->thumbnailImageValid = true;
584}
585
587{
588 m_d->parentSelection = selection;
589}
590
592{
593 return m_d->parentSelection;
594}
595
600
602{
603 QRect updateRect = rc & selectedExactRect();
604
605 if (updateRect.isValid()) {
606 KisPainter::copyAreaOptimized(updateRect.topLeft(), KisPaintDeviceSP(this), projection, updateRect);
607 }
608}
float value(const T *src, size_t ch)
SelectionAction
@ SELECTION_REPLACE
@ SELECTION_INTERSECT
@ SELECTION_SYMMETRICDIFFERENCE
@ SELECTION_SUBTRACT
@ SELECTION_ADD
const KoID GrayAColorModelID("GRAYA", ki18n("Grayscale/Alpha"))
const KoID Integer8BitsColorDepthID("U8", ki18n("8-bit integer/channel"))
PythonPluginManager * instance
virtual int currentLevelOfDetail() const =0
virtual QRect bounds() const =0
void fillRect(qint32 x, qint32 y, qint32 w, qint32 h, const KoColor &c, quint8 opacity)
static qreal lodToInvScale(int levelOfDetail)
QVector< QPolygon > outline(quint8 *buffer, qint32 xOffset, qint32 yOffset, qint32 width, qint32 height)
void crop(qint32 x, qint32 y, qint32 w, qint32 h)
bool read(QIODevice *stream)
virtual void clear()
KisRegion region() const
void setDefaultPixel(const KoColor &defPixel)
KisPaintDevice(const KoColorSpace *colorSpace, const QString &name=QString())
KisPaintDeviceSP createThumbnailDevice(qint32 w, qint32 h, QRect rect=QRect(), QRect outputRect=QRect()) const
KisHLineIteratorSP createHLineIteratorNG(qint32 x, qint32 y, qint32 w)
const KoColorSpace * colorSpace() const
KoColor defaultPixel() const
KisDefaultBoundsBaseSP defaultBounds() const
void moveTo(qint32 x, qint32 y)
void makeFullCopyFrom(const KisPaintDevice &rhs, KritaUtils::DeviceCopyMode copyMode=KritaUtils::CopySnapshot, KisNode *newParentNode=0)
void convertTo(const KoColorSpace *dstColorSpace, KoColorConversionTransformation::Intent renderingIntent=KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::ConversionFlags conversionFlags=KoColorConversionTransformation::internalConversionFlags(), KUndo2Command *parentCommand=nullptr, KoUpdater *progressUpdater=nullptr)
void readBytes(quint8 *data, qint32 x, qint32 y, qint32 w, qint32 h) const
KisHLineConstIteratorSP createHLineConstIteratorNG(qint32 x, qint32 y, qint32 w) const
QPoint offset() const
static void copyAreaOptimized(const QPoint &dstPt, KisPaintDeviceSP src, KisPaintDeviceSP dst, const QRect &originalSrcRect)
QRect boundingRect() const
ALWAYS_INLINE quint8 * rawData()
ALWAYS_INLINE int x() const
ALWAYS_INLINE int y() const
ALWAYS_INLINE const quint8 * rawDataConst() const
virtual quint8 opacityU8(const quint8 *pixel) const =0
static KoColor createTransparent(const KoColorSpace *cs)
Definition KoColor.cpp:681
quint8 * data()
Definition KoColor.h:144
QString id() const
Definition KoID.cpp:63
#define bounds(x, a, b)
#define warnKrita
Definition kis_debug.h:87
const quint8 MAX_SELECTED
Definition kis_global.h:32
const quint8 MIN_SELECTED
Definition kis_global.h:33
QImage deviceToQImage(KisPaintDeviceSP device, const QRect &rc, const QColor &maskColor)
KisSharedPtr< KisPaintDevice > KisPaintDeviceSP
Definition kis_types.h:73
QPainterPath tryCloseTornSubpathsAfterIntersection(QPainterPath path)
bool isEmpty() const override
void intersectSelection(KisPixelSelectionSP selection)
KisSelectionComponent * clone(KisSelection *) override
bool isTotallyUnselected(const QRect &r) const
Tests if the rect is totally outside the selection.
void recalculateOutlineCache() override
KisSelectionWSP parentSelection
void copyAlphaFrom(KisPaintDeviceSP src, const QRect &processRect)
void recalculateThumbnailImage(const QColor &maskColor)
void symmetricdifferenceSelection(KisPixelSelectionSP selection)
void subtractSelection(KisPixelSelectionSP selection)
void setParentSelection(KisSelectionWSP selection)
QRect exactBounds() const
void renderToProjection(KisPaintDeviceSP projection) override
bool read(QIODevice *stream)
QRect selectedExactRect() const
const KoColorSpace * compositionSourceColorSpace() const override
void moveTo(const QPoint &pt) override
QVector< QPolygon > outline() const
outline returns the outline of the current selection
KisPixelSelection(KisDefaultBoundsBaseSP defaultBounds=KisDefaultBoundsBaseSP(), KisSelectionWSP parentSelection=KisSelectionWSP())
void setOutlineCache(const QPainterPath &cache)
void select(const QRect &r, quint8 selectedness=MAX_SELECTED)
void addSelection(KisPixelSelectionSP selection)
void applySelection(KisPixelSelectionSP selection, SelectionAction action)
static KoColorSpaceRegistry * instance()
const KoColorSpace * rgb8(const QString &profileName=QString())