File indexing completed on 2024-05-12 15:58:36
0001 /* 0002 * SPDX-FileCopyrightText: 2004 Boudewijn Rempt <boud@valdyas.org> 0003 * SPDX-FileCopyrightText: 2007 Sven Langkamp <sven.langkamp@gmail.com> 0004 * 0005 * SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "kis_pixel_selection.h" 0009 0010 0011 #include <QImage> 0012 #include <QVector> 0013 0014 #include <QMutex> 0015 #include <QPoint> 0016 #include <QPolygon> 0017 0018 #include <KoColorSpace.h> 0019 #include <KoColorSpaceRegistry.h> 0020 #include <KoColorModelStandardIds.h> 0021 #include <KoIntegerMaths.h> 0022 #include <KoCompositeOpRegistry.h> 0023 0024 #include "kis_layer.h" 0025 #include "kis_debug.h" 0026 #include "kis_image.h" 0027 #include "kis_fill_painter.h" 0028 #include "kis_outline_generator.h" 0029 #include <kis_iterator_ng.h> 0030 #include "kis_lod_transform.h" 0031 #include "kundo2command.h" 0032 0033 0034 struct Q_DECL_HIDDEN KisPixelSelection::Private { 0035 KisSelectionWSP parentSelection; 0036 0037 QPainterPath outlineCache; 0038 bool outlineCacheValid; 0039 QMutex outlineCacheMutex; 0040 0041 bool thumbnailImageValid; 0042 QImage thumbnailImage; 0043 QTransform thumbnailImageTransform; 0044 0045 QPoint lod0CachesOffset; 0046 0047 void invalidateThumbnailImage() { 0048 thumbnailImageValid = false; 0049 thumbnailImage = QImage(); 0050 thumbnailImageTransform = QTransform(); 0051 } 0052 }; 0053 0054 KisPixelSelection::KisPixelSelection(KisDefaultBoundsBaseSP defaultBounds, KisSelectionWSP parentSelection) 0055 : KisPaintDevice(0, KoColorSpaceRegistry::instance()->alpha8(), defaultBounds) 0056 , m_d(new Private) 0057 { 0058 m_d->outlineCacheValid = true; 0059 m_d->invalidateThumbnailImage(); 0060 0061 m_d->parentSelection = parentSelection; 0062 } 0063 0064 KisPixelSelection::KisPixelSelection(const KisPixelSelection& rhs, KritaUtils::DeviceCopyMode copyMode) 0065 : KisPaintDevice(rhs, copyMode) 0066 , KisSelectionComponent(rhs) 0067 , m_d(new Private) 0068 { 0069 // parent selection is not supposed to be shared 0070 m_d->outlineCache = rhs.m_d->outlineCache; 0071 m_d->outlineCacheValid = rhs.m_d->outlineCacheValid; 0072 0073 m_d->thumbnailImageValid = rhs.m_d->thumbnailImageValid; 0074 m_d->thumbnailImage = rhs.m_d->thumbnailImage; 0075 m_d->thumbnailImageTransform = rhs.m_d->thumbnailImageTransform; 0076 } 0077 0078 KisPixelSelection::KisPixelSelection(const KisPaintDeviceSP copySource, KritaUtils::DeviceCopyMode copyMode, KisSelectionWSP parentSelection) 0079 : KisPaintDevice(0, KoColorSpaceRegistry::instance()->alpha8(), copySource->defaultBounds()) 0080 , m_d(new Private) 0081 { 0082 KisPaintDeviceSP tmpDevice = new KisPaintDevice(*copySource, copyMode, 0); 0083 tmpDevice->convertTo(this->colorSpace()); 0084 0085 this->makeFullCopyFrom(*tmpDevice, copyMode, 0); 0086 0087 m_d->parentSelection = parentSelection; 0088 m_d->outlineCacheValid = false; 0089 m_d->invalidateThumbnailImage(); 0090 } 0091 0092 KisSelectionComponent* KisPixelSelection::clone(KisSelection*) 0093 { 0094 return new KisPixelSelection(*this); 0095 } 0096 0097 KisPixelSelection::~KisPixelSelection() 0098 { 0099 delete m_d; 0100 } 0101 0102 const KoColorSpace *KisPixelSelection::compositionSourceColorSpace() const 0103 { 0104 return KoColorSpaceRegistry::instance()-> 0105 colorSpace(GrayAColorModelID.id(), 0106 Integer8BitsColorDepthID.id(), 0107 QString()); 0108 } 0109 0110 bool KisPixelSelection::read(QIODevice *stream) 0111 { 0112 bool retval = KisPaintDevice::read(stream); 0113 m_d->outlineCacheValid = false; 0114 m_d->invalidateThumbnailImage(); 0115 return retval; 0116 } 0117 0118 void KisPixelSelection::select(const QRect & rc, quint8 selectedness) 0119 { 0120 QRect r = rc.normalized(); 0121 if (r.isEmpty()) return; 0122 0123 KisFillPainter painter(KisPaintDeviceSP(this)); 0124 const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); 0125 painter.fillRect(r, KoColor(Qt::white, cs), selectedness); 0126 0127 if (m_d->outlineCacheValid) { 0128 QPainterPath path; 0129 path.addRect(r); 0130 0131 if (selectedness != MIN_SELECTED) { 0132 m_d->outlineCache += path; 0133 } else { 0134 m_d->outlineCache -= path; 0135 } 0136 } 0137 m_d->invalidateThumbnailImage(); 0138 } 0139 0140 void KisPixelSelection::applySelection(KisPixelSelectionSP selection, SelectionAction action) 0141 { 0142 switch (action) { 0143 case SELECTION_REPLACE: 0144 clear(); 0145 addSelection(selection); 0146 break; 0147 case SELECTION_ADD: 0148 addSelection(selection); 0149 break; 0150 case SELECTION_SUBTRACT: 0151 subtractSelection(selection); 0152 break; 0153 case SELECTION_INTERSECT: 0154 intersectSelection(selection); 0155 break; 0156 case SELECTION_SYMMETRICDIFFERENCE: 0157 symmetricdifferenceSelection(selection); 0158 break; 0159 default: 0160 break; 0161 } 0162 } 0163 0164 void KisPixelSelection::copyAlphaFrom(KisPaintDeviceSP src, const QRect &processRect) 0165 { 0166 const KoColorSpace *srcCS = src->colorSpace(); 0167 0168 KisSequentialConstIterator srcIt(src, processRect); 0169 KisSequentialIterator dstIt(this, processRect); 0170 0171 while (srcIt.nextPixel() && dstIt.nextPixel()) { 0172 const quint8 *srcPtr = srcIt.rawDataConst(); 0173 quint8 *alpha8Ptr = dstIt.rawData(); 0174 0175 *alpha8Ptr = srcCS->opacityU8(srcPtr); 0176 } 0177 0178 m_d->outlineCacheValid = false; 0179 m_d->outlineCache = QPainterPath(); 0180 m_d->invalidateThumbnailImage(); 0181 } 0182 0183 void KisPixelSelection::addSelection(KisPixelSelectionSP selection) 0184 { 0185 QRect r = selection->selectedRect(); 0186 if (r.isEmpty()) return; 0187 0188 KisHLineIteratorSP dst = createHLineIteratorNG(r.x(), r.y(), r.width()); 0189 KisHLineConstIteratorSP src = selection->createHLineConstIteratorNG(r.x(), r.y(), r.width()); 0190 for (int i = 0; i < r.height(); ++i) { 0191 do { 0192 if (*src->oldRawData() + *dst->rawData() < MAX_SELECTED) 0193 *dst->rawData() = *src->oldRawData() + *dst->rawData(); 0194 else 0195 *dst->rawData() = MAX_SELECTED; 0196 0197 } while (src->nextPixel() && dst->nextPixel()); 0198 dst->nextRow(); 0199 src->nextRow(); 0200 } 0201 0202 const quint8 defPixel = qMax(*defaultPixel().data(), *selection->defaultPixel().data()); 0203 setDefaultPixel(KoColor(&defPixel, colorSpace())); 0204 0205 m_d->outlineCacheValid &= selection->outlineCacheValid(); 0206 0207 if (m_d->outlineCacheValid) { 0208 m_d->outlineCache += selection->outlineCache(); 0209 } 0210 0211 m_d->invalidateThumbnailImage(); 0212 } 0213 0214 void KisPixelSelection::subtractSelection(KisPixelSelectionSP selection) 0215 { 0216 QRect r = selection->selectedRect(); 0217 if (r.isEmpty()) return; 0218 0219 0220 KisHLineIteratorSP dst = createHLineIteratorNG(r.x(), r.y(), r.width()); 0221 KisHLineConstIteratorSP src = selection->createHLineConstIteratorNG(r.x(), r.y(), r.width()); 0222 for (int i = 0; i < r.height(); ++i) { 0223 do { 0224 if (*dst->rawData() - *src->oldRawData() > MIN_SELECTED) 0225 *dst->rawData() = *dst->rawData() - *src->oldRawData(); 0226 else 0227 *dst->rawData() = MIN_SELECTED; 0228 0229 } while (src->nextPixel() && dst->nextPixel()); 0230 dst->nextRow(); 0231 src->nextRow(); 0232 } 0233 0234 const quint8 defPixel = *selection->defaultPixel().data() > *defaultPixel().data() 0235 ? MIN_SELECTED 0236 : *defaultPixel().data() - *selection->defaultPixel().data(); 0237 setDefaultPixel(KoColor(&defPixel, colorSpace())); 0238 0239 m_d->outlineCacheValid &= selection->outlineCacheValid(); 0240 0241 if (m_d->outlineCacheValid) { 0242 m_d->outlineCache -= selection->outlineCache(); 0243 } 0244 0245 m_d->invalidateThumbnailImage(); 0246 } 0247 0248 void KisPixelSelection::intersectSelection(KisPixelSelectionSP selection) 0249 { 0250 const QRect r = selection->selectedRect().united(selectedRect()); 0251 if (r.isEmpty()) { 0252 clear(); 0253 return; 0254 } 0255 0256 KisHLineIteratorSP dst = createHLineIteratorNG(r.x(), r.y(), r.width()); 0257 KisHLineConstIteratorSP src = selection->createHLineConstIteratorNG(r.x(), r.y(), r.width()); 0258 for (int i = 0; i < r.height(); ++i) { 0259 do { 0260 *dst->rawData() = qMin(*dst->rawData(), *src->oldRawData()); 0261 } while (src->nextPixel() && dst->nextPixel()); 0262 dst->nextRow(); 0263 src->nextRow(); 0264 } 0265 0266 const quint8 defPixel = qMin(*defaultPixel().data(), *selection->defaultPixel().data()); 0267 setDefaultPixel(KoColor(&defPixel, colorSpace())); 0268 0269 crop(r); 0270 0271 m_d->outlineCacheValid &= selection->outlineCacheValid(); 0272 0273 if (m_d->outlineCacheValid) { 0274 m_d->outlineCache &= selection->outlineCache(); 0275 m_d->outlineCache.closeSubpath(); 0276 } 0277 0278 m_d->invalidateThumbnailImage(); 0279 } 0280 0281 void KisPixelSelection::symmetricdifferenceSelection(KisPixelSelectionSP selection) 0282 { 0283 QRect r = selection->selectedRect().united(selectedRect()); 0284 if (r.isEmpty()) return; 0285 0286 KisHLineIteratorSP dst = createHLineIteratorNG(r.x(), r.y(), r.width()); 0287 KisHLineConstIteratorSP src = selection->createHLineConstIteratorNG(r.x(), r.y(), r.width()); 0288 for (int i = 0; i < r.height(); ++i) { 0289 0290 do { 0291 *dst->rawData() = abs(*dst->rawData() - *src->oldRawData()); 0292 } while (src->nextPixel() && dst->nextPixel()); 0293 0294 dst->nextRow(); 0295 src->nextRow(); 0296 } 0297 0298 const quint8 defPixel = abs(*defaultPixel().data() - *selection->defaultPixel().data()); 0299 setDefaultPixel(KoColor(&defPixel, colorSpace())); 0300 0301 m_d->outlineCacheValid &= selection->outlineCacheValid(); 0302 0303 if (m_d->outlineCacheValid) { 0304 m_d->outlineCache = (m_d->outlineCache | selection->outlineCache()) - (m_d->outlineCache & selection->outlineCache()); 0305 } 0306 0307 m_d->invalidateThumbnailImage(); 0308 } 0309 0310 void KisPixelSelection::clear(const QRect & r) 0311 { 0312 if (*defaultPixel().data() != MIN_SELECTED) { 0313 KisFillPainter painter(KisPaintDeviceSP(this)); 0314 const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); 0315 painter.fillRect(r, KoColor(Qt::white, cs), MIN_SELECTED); 0316 } else { 0317 KisPaintDevice::clear(r); 0318 } 0319 0320 if (m_d->outlineCacheValid) { 0321 QPainterPath path; 0322 path.addRect(r); 0323 0324 m_d->outlineCache -= path; 0325 } 0326 0327 m_d->invalidateThumbnailImage(); 0328 } 0329 0330 void KisPixelSelection::clear() 0331 { 0332 setDefaultPixel(KoColor(Qt::transparent, colorSpace())); 0333 KisPaintDevice::clear(); 0334 0335 m_d->outlineCacheValid = true; 0336 m_d->outlineCache = QPainterPath(); 0337 0338 // Empty the thumbnail image. It is a valid state. 0339 m_d->invalidateThumbnailImage(); 0340 m_d->thumbnailImageValid = true; 0341 } 0342 0343 void KisPixelSelection::invert() 0344 { 0345 // Region is needed here (not exactBounds or extent), because 0346 // unselected but existing pixels need to be inverted too 0347 QRect rc = region().boundingRect(); 0348 0349 if (!rc.isEmpty()) { 0350 KisSequentialIterator it(this, rc); 0351 while(it.nextPixel()) { 0352 *(it.rawData()) = MAX_SELECTED - *(it.rawData()); 0353 } 0354 } 0355 quint8 defPixel = MAX_SELECTED - *defaultPixel().data(); 0356 setDefaultPixel(KoColor(&defPixel, colorSpace())); 0357 0358 if (m_d->outlineCacheValid) { 0359 QPainterPath path; 0360 path.addRect(defaultBounds()->bounds()); 0361 0362 m_d->outlineCache = path - m_d->outlineCache; 0363 } 0364 0365 m_d->invalidateThumbnailImage(); 0366 } 0367 0368 void KisPixelSelection::moveTo(const QPoint &pt) 0369 { 0370 const int lod = defaultBounds()->currentLevelOfDetail(); 0371 const QPoint lod0Point = !lod ? pt : 0372 pt * KisLodTransform::lodToInvScale(lod); 0373 0374 const QPoint offset = lod0Point - m_d->lod0CachesOffset; 0375 0376 if (m_d->outlineCacheValid) { 0377 m_d->outlineCache.translate(offset); 0378 } 0379 0380 if (m_d->thumbnailImageValid) { 0381 m_d->thumbnailImageTransform = 0382 QTransform::fromTranslate(offset.x(), offset.y()) * 0383 m_d->thumbnailImageTransform; 0384 } 0385 0386 m_d->lod0CachesOffset = lod0Point; 0387 0388 KisPaintDevice::moveTo(pt); 0389 } 0390 0391 bool KisPixelSelection::isTotallyUnselected(const QRect & r) const 0392 { 0393 if (*defaultPixel().data() != MIN_SELECTED) 0394 return false; 0395 QRect sr = selectedExactRect(); 0396 return ! r.intersects(sr); 0397 } 0398 0399 QRect KisPixelSelection::selectedRect() const 0400 { 0401 return extent(); 0402 } 0403 0404 QRect KisPixelSelection::selectedExactRect() const 0405 { 0406 return exactBounds(); 0407 } 0408 0409 QVector<QPolygon> KisPixelSelection::outline() const 0410 { 0411 QRect selectionExtent = selectedExactRect(); 0412 0413 /** 0414 * When the default pixel is not fully transparent, the 0415 * exactBounds() return extent of the device instead. To make this 0416 * value sane we should limit the calculated area by the bounds of 0417 * the image. 0418 */ 0419 if (*defaultPixel().data() != MIN_SELECTED) { 0420 selectionExtent &= defaultBounds()->bounds(); 0421 } 0422 0423 qint32 xOffset = selectionExtent.x(); 0424 qint32 yOffset = selectionExtent.y(); 0425 qint32 width = selectionExtent.width(); 0426 qint32 height = selectionExtent.height(); 0427 0428 KisOutlineGenerator generator(colorSpace(), MIN_SELECTED); 0429 // If the selection is small using a buffer is much faster 0430 try { 0431 quint8* buffer = new quint8[width*height]; 0432 readBytes(buffer, xOffset, yOffset, width, height); 0433 0434 QVector<QPolygon> paths = generator.outline(buffer, xOffset, yOffset, width, height); 0435 0436 delete[] buffer; 0437 return paths; 0438 } 0439 catch(const std::bad_alloc&) { 0440 // Allocating so much memory failed, so we fall through to the slow option. 0441 warnKrita << "KisPixelSelection::outline ran out of memory allocating" << width << "*" << height << "bytes."; 0442 } 0443 0444 return generator.outline(this, xOffset, yOffset, width, height); 0445 } 0446 0447 bool KisPixelSelection::isEmpty() const 0448 { 0449 return *defaultPixel().data() == MIN_SELECTED && selectedRect().isEmpty(); 0450 } 0451 0452 QPainterPath KisPixelSelection::outlineCache() const 0453 { 0454 QMutexLocker locker(&m_d->outlineCacheMutex); 0455 return m_d->outlineCache; 0456 } 0457 0458 void KisPixelSelection::setOutlineCache(const QPainterPath &cache) 0459 { 0460 QMutexLocker locker(&m_d->outlineCacheMutex); 0461 m_d->outlineCache = cache; 0462 m_d->outlineCacheValid = true; 0463 m_d->thumbnailImageValid = false; 0464 } 0465 0466 bool KisPixelSelection::outlineCacheValid() const 0467 { 0468 QMutexLocker locker(&m_d->outlineCacheMutex); 0469 return m_d->outlineCacheValid; 0470 } 0471 0472 void KisPixelSelection::invalidateOutlineCache() 0473 { 0474 QMutexLocker locker(&m_d->outlineCacheMutex); 0475 m_d->outlineCacheValid = false; 0476 m_d->thumbnailImageValid = false; 0477 } 0478 0479 void KisPixelSelection::recalculateOutlineCache() 0480 { 0481 QMutexLocker locker(&m_d->outlineCacheMutex); 0482 0483 m_d->outlineCache = QPainterPath(); 0484 0485 Q_FOREACH (const QPolygon &polygon, outline()) { 0486 m_d->outlineCache.addPolygon(polygon); 0487 0488 /** 0489 * The outline generation algorithm has a small bug, which 0490 * results in the starting point be repeated twice in the 0491 * beginning of the path, instead of being put to the 0492 * end. Here we just explicitly close the path to workaround 0493 * it. 0494 * 0495 * \see KisSelectionTest::testOutlineGeneration() 0496 */ 0497 m_d->outlineCache.closeSubpath(); 0498 } 0499 0500 m_d->outlineCacheValid = true; 0501 } 0502 0503 bool KisPixelSelection::thumbnailImageValid() const 0504 { 0505 return m_d->thumbnailImageValid; 0506 } 0507 0508 QImage KisPixelSelection::thumbnailImage() const 0509 { 0510 return m_d->thumbnailImage; 0511 } 0512 0513 QTransform KisPixelSelection::thumbnailImageTransform() const 0514 { 0515 return m_d->thumbnailImageTransform; 0516 } 0517 0518 QImage deviceToQImage(KisPaintDeviceSP device, 0519 const QRect &rc, 0520 const QColor &maskColor) 0521 { 0522 QImage image(rc.size(), QImage::Format_ARGB32); 0523 0524 QColor color = maskColor; 0525 const qreal alphaScale = maskColor.alphaF(); 0526 0527 KisSequentialConstIterator it(device, rc); 0528 while(it.nextPixel()) { 0529 quint8 value = (MAX_SELECTED - *(it.rawDataConst())) * alphaScale; 0530 color.setAlpha(value); 0531 0532 QPoint pt(it.x(), it.y()); 0533 pt -= rc.topLeft(); 0534 0535 image.setPixel(pt.x(), pt.y(), color.rgba()); 0536 } 0537 0538 return image; 0539 } 0540 0541 void KisPixelSelection::recalculateThumbnailImage(const QColor &maskColor) 0542 { 0543 QRect rc = selectedExactRect(); 0544 const int maxPreviewSize = 2000; 0545 0546 if (rc.isEmpty()) { 0547 m_d->thumbnailImageTransform = QTransform(); 0548 m_d->thumbnailImage = QImage(); 0549 return; 0550 } 0551 0552 0553 if (rc.width() > maxPreviewSize || 0554 rc.height() > maxPreviewSize) { 0555 0556 qreal factor = 1.0; 0557 0558 if (rc.width() > rc.height()) { 0559 factor = qreal(maxPreviewSize) / rc.width(); 0560 } else { 0561 factor = qreal(maxPreviewSize) / rc.height(); 0562 } 0563 0564 int newWidth = qRound(rc.width() * factor); 0565 int newHeight = qRound(rc.height() * factor); 0566 0567 m_d->thumbnailImageTransform = 0568 QTransform::fromScale(qreal(rc.width()) / newWidth, 0569 qreal(rc.height()) / newHeight) * 0570 QTransform::fromTranslate(rc.x(), rc.y()); 0571 0572 KisPaintDeviceSP thumbDevice = 0573 createThumbnailDevice(newWidth, newHeight, rc); 0574 0575 QRect thumbRect(0, 0, newWidth, newHeight); 0576 m_d->thumbnailImage = deviceToQImage(thumbDevice, thumbRect, maskColor); 0577 0578 } else { 0579 m_d->thumbnailImageTransform = QTransform::fromTranslate(rc.x(), rc.y()); 0580 m_d->thumbnailImage = deviceToQImage(this, rc, maskColor); 0581 } 0582 0583 m_d->thumbnailImageValid = true; 0584 } 0585 0586 void KisPixelSelection::setParentSelection(KisSelectionWSP selection) 0587 { 0588 m_d->parentSelection = selection; 0589 } 0590 0591 KisSelectionWSP KisPixelSelection::parentSelection() const 0592 { 0593 return m_d->parentSelection; 0594 } 0595 0596 void KisPixelSelection::renderToProjection(KisPaintDeviceSP projection) 0597 { 0598 renderToProjection(projection, selectedExactRect()); 0599 } 0600 0601 void KisPixelSelection::renderToProjection(KisPaintDeviceSP projection, const QRect& rc) 0602 { 0603 QRect updateRect = rc & selectedExactRect(); 0604 0605 if (updateRect.isValid()) { 0606 KisPainter::copyAreaOptimized(updateRect.topLeft(), KisPaintDeviceSP(this), projection, updateRect); 0607 } 0608 }