File indexing completed on 2024-05-12 15:58:53

0001 /*
0002  *  SPDX-FileCopyrightText: 2011 Dmitry Kazakov <dimula73@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "krita_utils.h"
0008 
0009 #include <QtCore/qmath.h>
0010 
0011 #include <QRect>
0012 #include <QRegion>
0013 #include <QPainterPath>
0014 #include <QPolygonF>
0015 #include <QPen>
0016 #include <QPainter>
0017 
0018 #include "kis_algebra_2d.h"
0019 
0020 #include <KoColorSpaceRegistry.h>
0021 
0022 #include "kis_image.h"
0023 #include "kis_image_config.h"
0024 #include "kis_debug.h"
0025 #include "kis_node.h"
0026 #include "kis_sequential_iterator.h"
0027 #include "kis_random_accessor_ng.h"
0028 
0029 #include <KisRenderedDab.h>
0030 
0031 
0032 namespace KritaUtils
0033 {
0034 
0035     QSize optimalPatchSize()
0036     {
0037         KisImageConfig cfg(true);
0038         return QSize(cfg.updatePatchWidth(),
0039                      cfg.updatePatchHeight());
0040     }
0041 
0042     QVector<QRect> splitRectIntoPatches(const QRect &rc, const QSize &patchSize)
0043     {
0044         using namespace KisAlgebra2D;
0045 
0046 
0047         QVector<QRect> patches;
0048 
0049         const qint32 firstCol = divideFloor(rc.x(), patchSize.width());
0050         const qint32 firstRow = divideFloor(rc.y(), patchSize.height());
0051 
0052         // TODO: check if -1 is needed here
0053         const qint32 lastCol = divideFloor(rc.x() + rc.width(), patchSize.width());
0054         const qint32 lastRow = divideFloor(rc.y() + rc.height(), patchSize.height());
0055 
0056         for(qint32 i = firstRow; i <= lastRow; i++) {
0057             for(qint32 j = firstCol; j <= lastCol; j++) {
0058                 QRect maxPatchRect(j * patchSize.width(), i * patchSize.height(),
0059                                    patchSize.width(), patchSize.height());
0060                 QRect patchRect = rc & maxPatchRect;
0061 
0062                 if (!patchRect.isEmpty()) {
0063                     patches.append(patchRect);
0064                 }
0065             }
0066         }
0067 
0068         return patches;
0069     }
0070 
0071     QVector<QRect> splitRectIntoPatchesTight(const QRect &rc, const QSize &patchSize)
0072     {
0073         QVector<QRect> patches;
0074 
0075         for (qint32 y = rc.y(); y < rc.y() + rc.height(); y += patchSize.height()) {
0076             for (qint32 x = rc.x(); x < rc.x() + rc.width(); x += patchSize.width()) {
0077                 patches << QRect(x, y,
0078                                  qMin(rc.x() + rc.width() - x, patchSize.width()),
0079                                  qMin(rc.y() + rc.height() - y, patchSize.height()));
0080             }
0081         }
0082 
0083         return patches;
0084     }
0085 
0086     QVector<QRect> splitRegionIntoPatches(const KisRegion &region, const QSize &patchSize)
0087     {
0088         QVector<QRect> patches;
0089 
0090         Q_FOREACH (const QRect rect, region.rects()) {
0091             patches << KritaUtils::splitRectIntoPatches(rect, patchSize);
0092         }
0093 
0094         return patches;
0095     }
0096 
0097     bool checkInTriangle(const QRectF &rect,
0098                          const QPolygonF &triangle)
0099     {
0100         return triangle.intersected(rect).boundingRect().isValid();
0101     }
0102 
0103 
0104     KisRegion splitTriangles(const QPointF &center,
0105                                                const QVector<QPointF> &points)
0106     {
0107 
0108         Q_ASSERT(points.size());
0109         Q_ASSERT(!(points.size() & 1));
0110 
0111         QVector<QPolygonF> triangles;
0112         QRect totalRect;
0113 
0114         for (int i = 0; i < points.size(); i += 2) {
0115             QPolygonF triangle;
0116             triangle << center;
0117             triangle << points[i];
0118             triangle << points[i+1];
0119 
0120             totalRect |= triangle.boundingRect().toAlignedRect();
0121             triangles << triangle;
0122         }
0123 
0124 
0125         const int step = 64;
0126         const int right = totalRect.x() + totalRect.width();
0127         const int bottom = totalRect.y() + totalRect.height();
0128 
0129         QVector<QRect> dirtyRects;
0130 
0131         for (int y = totalRect.y(); y < bottom;) {
0132             int nextY = qMin((y + step) & ~(step-1), bottom);
0133 
0134             for (int x = totalRect.x(); x < right;) {
0135                 int nextX = qMin((x + step) & ~(step-1), right);
0136 
0137                 QRect rect(x, y, nextX - x, nextY - y);
0138 
0139                 Q_FOREACH (const QPolygonF &triangle, triangles) {
0140                     if(checkInTriangle(rect, triangle)) {
0141                         dirtyRects << rect;
0142                         break;
0143                     }
0144                 }
0145 
0146                 x = nextX;
0147             }
0148             y = nextY;
0149         }
0150         return KisRegion(std::move(dirtyRects));
0151     }
0152 
0153     KisRegion splitPath(const QPainterPath &path)
0154     {
0155         QVector<QRect> dirtyRects;
0156         QRect totalRect = path.boundingRect().toAlignedRect();
0157 
0158         // adjust the rect for antialiasing to work
0159         totalRect = totalRect.adjusted(-1,-1,1,1);
0160 
0161         const int step = 64;
0162         const int right = totalRect.x() + totalRect.width();
0163         const int bottom = totalRect.y() + totalRect.height();
0164 
0165         for (int y = totalRect.y(); y < bottom;) {
0166             int nextY = qMin((y + step) & ~(step-1), bottom);
0167 
0168             for (int x = totalRect.x(); x < right;) {
0169                 int nextX = qMin((x + step) & ~(step-1), right);
0170 
0171                 QRect rect(x, y, nextX - x, nextY - y);
0172 
0173                 if(path.intersects(rect)) {
0174                     dirtyRects << rect;
0175                 }
0176 
0177                 x = nextX;
0178             }
0179             y = nextY;
0180         }
0181 
0182         return KisRegion(std::move(dirtyRects));
0183     }
0184 
0185     QString KRITAIMAGE_EXPORT prettyFormatReal(qreal value)
0186     {
0187         return QLocale().toString(value, 'f', 1);
0188     }
0189 
0190     qreal KRITAIMAGE_EXPORT maxDimensionPortion(const QRectF &bounds, qreal portion, qreal minValue)
0191     {
0192         qreal maxDimension = qMax(bounds.width(), bounds.height());
0193         return qMax(portion * maxDimension, minValue);
0194     }
0195 
0196     bool tryMergePoints(QPainterPath &path,
0197                         const QPointF &startPoint,
0198                         const QPointF &endPoint,
0199                         qreal &distance,
0200                         qreal distanceThreshold,
0201                         bool lastSegment)
0202     {
0203         qreal length = (endPoint - startPoint).manhattanLength();
0204 
0205         if (lastSegment || length > distanceThreshold) {
0206             if (lastSegment) {
0207                 qreal wrappedLength =
0208                     (endPoint - QPointF(path.elementAt(0))).manhattanLength();
0209 
0210                 if (length < distanceThreshold ||
0211                     wrappedLength < distanceThreshold) {
0212 
0213                     return true;
0214                 }
0215             }
0216 
0217             distance = 0;
0218             return false;
0219         }
0220 
0221         distance += length;
0222 
0223         if (distance > distanceThreshold) {
0224             path.lineTo(endPoint);
0225             distance = 0;
0226         }
0227 
0228         return true;
0229     }
0230 
0231     QPainterPath trySimplifyPath(const QPainterPath &path, qreal lengthThreshold)
0232     {
0233         QPainterPath newPath;
0234         QPointF startPoint;
0235         qreal distance = 0;
0236 
0237         int count = path.elementCount();
0238         for (int i = 0; i < count; i++) {
0239             QPainterPath::Element e = path.elementAt(i);
0240             QPointF endPoint = QPointF(e.x, e.y);
0241 
0242             switch (e.type) {
0243             case QPainterPath::MoveToElement:
0244                 newPath.moveTo(endPoint);
0245                 break;
0246             case QPainterPath::LineToElement:
0247                 if (!tryMergePoints(newPath, startPoint, endPoint,
0248                                     distance, lengthThreshold, i == count - 1)) {
0249 
0250                     newPath.lineTo(endPoint);
0251                 }
0252                 break;
0253             case QPainterPath::CurveToElement: {
0254                 Q_ASSERT(i + 2 < count);
0255 
0256                 if (!tryMergePoints(newPath, startPoint, endPoint,
0257                                     distance, lengthThreshold, i == count - 1)) {
0258 
0259                     e = path.elementAt(i + 1);
0260                     Q_ASSERT(e.type == QPainterPath::CurveToDataElement);
0261                     QPointF ctrl1 = QPointF(e.x, e.y);
0262                     e = path.elementAt(i + 2);
0263                     Q_ASSERT(e.type == QPainterPath::CurveToDataElement);
0264                     QPointF ctrl2 = QPointF(e.x, e.y);
0265                     newPath.cubicTo(ctrl1, ctrl2, endPoint);
0266                 }
0267 
0268                 i += 2;
0269             }
0270             default:
0271                 ;
0272             }
0273             startPoint = endPoint;
0274         }
0275 
0276         return newPath;
0277     }
0278 
0279     QList<QPainterPath> splitDisjointPaths(const QPainterPath &path)
0280     {
0281         QList<QPainterPath> resultList;
0282         QList<QPolygonF> inputPolygons = path.toSubpathPolygons();
0283 
0284         Q_FOREACH (const QPolygonF &poly, inputPolygons) {
0285             QPainterPath testPath;
0286             testPath.addPolygon(poly);
0287 
0288             if (resultList.isEmpty()) {
0289                 resultList.append(testPath);
0290                 continue;
0291             }
0292 
0293             QPainterPath mergedPath = testPath;
0294 
0295             for (auto it = resultList.begin(); it != resultList.end(); /*noop*/) {
0296                 if (it->intersects(testPath)) {
0297                     mergedPath.addPath(*it);
0298                     it = resultList.erase(it);
0299                 } else {
0300                     ++it;
0301                 }
0302             }
0303 
0304             resultList.append(mergedPath);
0305         }
0306 
0307         return resultList;
0308     }
0309 
0310     quint8 mergeOpacity(quint8 opacity, quint8 parentOpacity)
0311     {
0312         if (parentOpacity != OPACITY_OPAQUE_U8) {
0313             opacity = (int(opacity) * parentOpacity) / OPACITY_OPAQUE_U8;
0314         }
0315         return opacity;
0316     }
0317 
0318     QBitArray mergeChannelFlags(const QBitArray &childFlags, const QBitArray &parentFlags)
0319     {
0320         QBitArray flags = childFlags;
0321 
0322         if (!flags.isEmpty() &&
0323             !parentFlags.isEmpty() &&
0324             flags.size() == parentFlags.size()) {
0325 
0326             flags &= parentFlags;
0327 
0328         } else if (!parentFlags.isEmpty()) {
0329             flags = parentFlags;
0330         }
0331 
0332         return flags;
0333     }
0334 
0335     bool compareChannelFlags(QBitArray f1, QBitArray f2)
0336     {
0337         if (f1.isNull() && f2.isNull()) return true;
0338 
0339         if (f1.isNull()) {
0340             f1.fill(true, f2.size());
0341         }
0342 
0343         if (f2.isNull()) {
0344             f2.fill(true, f1.size());
0345         }
0346 
0347         return f1 == f2;
0348     }
0349 
0350     QString KRITAIMAGE_EXPORT toLocalizedOnOff(bool value) {
0351         return value ? i18n("on") : i18n("off");
0352     }
0353 
0354     KisNodeSP nearestNodeAfterRemoval(KisNodeSP node)
0355     {
0356         KisNodeSP newNode = node->nextSibling();
0357 
0358         if (!newNode) {
0359             newNode = node->prevSibling();
0360         }
0361 
0362         if (!newNode) {
0363             newNode = node->parent();
0364         }
0365 
0366         return newNode;
0367     }
0368 
0369     void renderExactRect(QPainter *p, const QRect &rc)
0370     {
0371         p->drawRect(rc.adjusted(0,0,-1,-1));
0372     }
0373 
0374     void renderExactRect(QPainter *p, const QRect &rc, const QPen &pen)
0375     {
0376         QPen oldPen = p->pen();
0377         p->setPen(pen);
0378         renderExactRect(p, rc);
0379         p->setPen(oldPen);
0380     }
0381 
0382     QImage convertQImageToGrayA(const QImage &image)
0383     {
0384         QImage dstImage(image.size(), QImage::Format_ARGB32);
0385 
0386         // TODO: if someone feel bored, a more optimized version of this would be welcome
0387         const QSize size = image.size();
0388         for(int y = 0; y < size.height(); ++y) {
0389             for(int x = 0; x < size.width(); ++x) {
0390                 const QRgb pixel = image.pixel(x,y);
0391                 const int gray = qGray(pixel);
0392                 dstImage.setPixel(x, y, qRgba(gray, gray, gray, qAlpha(pixel)));
0393             }
0394         }
0395 
0396         return dstImage;
0397     }
0398 
0399     void applyToAlpha8Device(KisPaintDeviceSP dev, const QRect &rc, std::function<void(quint8)> func) {
0400         KisSequentialConstIterator dstIt(dev, rc);
0401         while (dstIt.nextPixel()) {
0402             const quint8 *dstPtr = dstIt.rawDataConst();
0403             func(*dstPtr);
0404         }
0405     }
0406 
0407     void filterAlpha8Device(KisPaintDeviceSP dev, const QRect &rc, std::function<quint8(quint8)> func) {
0408         KisSequentialIterator dstIt(dev, rc);
0409         while (dstIt.nextPixel()) {
0410             quint8 *dstPtr = dstIt.rawData();
0411             *dstPtr = func(*dstPtr);
0412         }
0413     }
0414 
0415     qreal estimatePortionOfTransparentPixels(KisPaintDeviceSP dev, const QRect &rect, qreal samplePortion) {
0416         const KoColorSpace *cs = dev->colorSpace();
0417 
0418         const qreal linearPortion = std::sqrt(samplePortion);
0419         const qreal ratio = qreal(rect.width()) / rect.height();
0420         const int xStep = qMax(1, qRound(1.0 / linearPortion * ratio));
0421         const int yStep = qMax(1, qRound(1.0 / linearPortion / ratio));
0422 
0423         int numTransparentPixels = 0;
0424         int numPixels = 0;
0425 
0426         KisRandomConstAccessorSP it = dev->createRandomConstAccessorNG();
0427         for (int y = rect.y(); y <= rect.bottom(); y += yStep) {
0428             for (int x = rect.x(); x <= rect.right(); x += xStep) {
0429                 it->moveTo(x, y);
0430                 const quint8 alpha = cs->opacityU8(it->rawDataConst());
0431 
0432                 if (alpha != OPACITY_OPAQUE_U8) {
0433                     numTransparentPixels++;
0434                 }
0435 
0436                 numPixels++;
0437             }
0438         }
0439 
0440         if (numPixels == 0) {
0441             return 0; // avoid dividing by 0
0442         }
0443         return qreal(numTransparentPixels) / numPixels;
0444     }
0445 
0446     void mirrorDab(Qt::Orientation dir, const QPoint &center, KisRenderedDab *dab, bool skipMirrorPixels)
0447     {
0448         const QRect rc = dab->realBounds();
0449 
0450         if (dir == Qt::Horizontal) {
0451             const int mirrorX = -((rc.x() + rc.width()) - center.x()) + center.x();
0452 
0453             if (!skipMirrorPixels) {
0454                 dab->device->mirror(true, false);
0455             }
0456             dab->offset.rx() = mirrorX;
0457         } else /* if (dir == Qt::Vertical) */ {
0458             const int mirrorY = -((rc.y() + rc.height()) - center.y()) + center.y();
0459 
0460             if (!skipMirrorPixels) {
0461                 dab->device->mirror(false, true);
0462             }
0463             dab->offset.ry() = mirrorY;
0464         }
0465     }
0466 
0467     void mirrorDab(Qt::Orientation dir, const QPointF &center, KisRenderedDab *dab, bool skipMirrorPixels)
0468     {
0469         const QRect rc = dab->realBounds();
0470 
0471         if (dir == Qt::Horizontal) {
0472             const int mirrorX = -((rc.x() + rc.width()) - center.x()) + center.x();
0473 
0474             if (!skipMirrorPixels) {
0475                 dab->device->mirror(true, false);
0476             }
0477             dab->offset.rx() = mirrorX;
0478         } else /* if (dir == Qt::Vertical) */ {
0479             const int mirrorY = -((rc.y() + rc.height()) - center.y()) + center.y();
0480 
0481             if (!skipMirrorPixels) {
0482                 dab->device->mirror(false, true);
0483             }
0484             dab->offset.ry() = mirrorY;
0485         }
0486     }
0487 
0488     void mirrorRect(Qt::Orientation dir, const QPoint &center, QRect *rc)
0489     {
0490         if (dir == Qt::Horizontal) {
0491             const int mirrorX = -((rc->x() + rc->width()) - center.x()) + center.x();
0492             rc->moveLeft(mirrorX);
0493         } else /* if (dir == Qt::Vertical) */ {
0494             const int mirrorY = -((rc->y() + rc->height()) - center.y()) + center.y();
0495             rc->moveTop(mirrorY);
0496         }
0497     }
0498 
0499     void mirrorRect(Qt::Orientation dir, const QPointF &center, QRect *rc)
0500     {
0501         if (dir == Qt::Horizontal) {
0502             const int mirrorX = -((rc->x() + rc->width()) - center.x()) + center.x();
0503             rc->moveLeft(mirrorX);
0504         } else /* if (dir == Qt::Vertical) */ {
0505             const int mirrorY = -((rc->y() + rc->height()) - center.y()) + center.y();
0506             rc->moveTop(mirrorY);
0507         }
0508     }
0509 
0510     void mirrorPoint(Qt::Orientation dir, const QPoint &center, QPointF *pt)
0511     {
0512         if (dir == Qt::Horizontal) {
0513             pt->rx() = -(pt->x() - qreal(center.x())) + center.x();
0514         } else /* if (dir == Qt::Vertical) */ {
0515             pt->ry() = -(pt->y() - qreal(center.y())) + center.y();
0516         }
0517     }
0518 
0519     void mirrorPoint(Qt::Orientation dir, const QPointF &center, QPointF *pt)
0520     {
0521         if (dir == Qt::Horizontal) {
0522             pt->rx() = -(pt->x() - qreal(center.x())) + center.x();
0523         } else /* if (dir == Qt::Vertical) */ {
0524             pt->ry() = -(pt->y() - qreal(center.y())) + center.y();
0525         }
0526     }
0527 
0528     QTransform pathShapeBooleanSpaceWorkaround(KisImageSP image)
0529     {
0530         return QTransform::fromScale(image->xRes(), image->yRes());
0531     }
0532 
0533     void thresholdOpacity(KisPaintDeviceSP device, const QRect &rect, ThresholdMode mode)
0534     {
0535         const KoColorSpace *cs = device->colorSpace();
0536 
0537         if (mode == ThresholdCeil) {
0538             KisSequentialIterator it(device, rect);
0539             while (it.nextPixel()) {
0540                 if (cs->opacityU8(it.rawDataConst()) > 0) {
0541                     cs->setOpacity(it.rawData(), quint8(255), 1);
0542                 }
0543             }
0544         } else if (mode == ThresholdFloor) {
0545             KisSequentialIterator it(device, rect);
0546             while (it.nextPixel()) {
0547                 if (cs->opacityU8(it.rawDataConst()) < 255) {
0548                     cs->setOpacity(it.rawData(), quint8(0), 1);
0549                 }
0550             }
0551         } else if (mode == ThresholdMaxOut) {
0552             KisSequentialIterator it(device, rect);
0553             int numConseqPixels = it.nConseqPixels();
0554             while (it.nextPixels(numConseqPixels)) {
0555                 numConseqPixels = it.nConseqPixels();
0556                 cs->setOpacity(it.rawData(), quint8(255), numConseqPixels);
0557             }
0558         }
0559     }
0560 
0561     void thresholdOpacityAlpha8(KisPaintDeviceSP device, const QRect &rect, ThresholdMode mode)
0562     {
0563         if (mode == ThresholdCeil) {
0564             filterAlpha8Device(device, rect,
0565                 [] (quint8 value) {
0566                     return value > 0 ? 255 : value;
0567                 });
0568         } else if (mode == ThresholdFloor) {
0569             filterAlpha8Device(device, rect,
0570                 [] (quint8 value) {
0571                     return value < 255 ? 0 : value;
0572                 });
0573         } else if (mode == ThresholdMaxOut) {
0574             device->fill(rect, KoColor(Qt::white, device->colorSpace()));
0575         }
0576     }
0577 
0578     QVector<QPoint> rasterizeHLine(const QPoint &startPoint, const QPoint &endPoint)
0579     {
0580         QVector<QPoint> points;
0581         rasterizeHLine(startPoint, endPoint, [&points](const QPoint &point) { points.append(point); });
0582         return points;
0583     }
0584 
0585     QVector<QPoint> rasterizeVLine(const QPoint &startPoint, const QPoint &endPoint)
0586     {
0587         QVector<QPoint> points;
0588         rasterizeVLine(startPoint, endPoint, [&points](const QPoint &point) { points.append(point); });
0589         return points;
0590     }
0591 
0592     QVector<QPoint> rasterizeLineDDA(const QPoint &startPoint, const QPoint &endPoint)
0593     {
0594         QVector<QPoint> points;
0595         rasterizeLineDDA(startPoint, endPoint, [&points](const QPoint &point) { points.append(point); });
0596         return points;
0597     }
0598 
0599     QVector<QPoint> rasterizePolylineDDA(const QVector<QPoint> &polylinePoints)
0600     {
0601         QVector<QPoint> points;
0602         rasterizePolylineDDA(polylinePoints, [&points](const QPoint &point) { points.append(point); });
0603         return points;
0604     }
0605 
0606     QVector<QPoint> rasterizePolygonDDA(const QVector<QPoint> &polygonPoints)
0607     {
0608         QVector<QPoint> points;
0609         rasterizePolygonDDA(polygonPoints, [&points](const QPoint &point) { points.append(point); });
0610         return points;
0611     }
0612 
0613 }