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 }