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 #ifndef __KRITA_UTILS_H
0008 #define __KRITA_UTILS_H
0009 
0010 class QRect;
0011 class QRectF;
0012 class QSize;
0013 class QPen;
0014 class QPointF;
0015 class QPainterPath;
0016 class QBitArray;
0017 class QPainter;
0018 struct KisRenderedDab;
0019 class KisRegion;
0020 
0021 #include <QVector>
0022 #include "kritaimage_export.h"
0023 #include "kis_types.h"
0024 #include "krita_container_utils.h"
0025 #include <functional>
0026 
0027 
0028 namespace KritaUtils
0029 {
0030     QSize KRITAIMAGE_EXPORT optimalPatchSize();
0031 
0032     QVector<QRect> KRITAIMAGE_EXPORT splitRectIntoPatches(const QRect &rc, const QSize &patchSize);
0033     QVector<QRect> KRITAIMAGE_EXPORT splitRectIntoPatchesTight(const QRect &rc, const QSize &patchSize);
0034     QVector<QRect> KRITAIMAGE_EXPORT splitRegionIntoPatches(const QRegion &region, const QSize &patchSize);
0035     QVector<QRect> KRITAIMAGE_EXPORT splitRegionIntoPatches(const KisRegion &region, const QSize &patchSize);
0036 
0037     KRITAIMAGE_EXPORT KisRegion splitTriangles(const QPointF &center,
0038                                              const QVector<QPointF> &points);
0039     KRITAIMAGE_EXPORT KisRegion splitPath(const QPainterPath &path);
0040 
0041     QString KRITAIMAGE_EXPORT prettyFormatReal(qreal value);
0042 
0043     qreal KRITAIMAGE_EXPORT maxDimensionPortion(const QRectF &bounds, qreal portion, qreal minValue);
0044     QPainterPath KRITAIMAGE_EXPORT trySimplifyPath(const QPainterPath &path, qreal lengthThreshold);
0045 
0046     /**
0047      * Split a path \p path into a set of disjoint (non-intersectable)
0048      * paths if possible.
0049      *
0050      * It tries to follow odd-even fill rule, but has a small problem:
0051      * If you have three selections included into each other twice,
0052      * then the smallest selection will be included into the final subpath,
0053      * although it shouldn't according to odd-even-fill rule. It is still
0054      * to be fixed.
0055      */
0056     QList<QPainterPath> KRITAIMAGE_EXPORT splitDisjointPaths(const QPainterPath &path);
0057 
0058 
0059     quint8 KRITAIMAGE_EXPORT mergeOpacity(quint8 opacity, quint8 parentOpacity);
0060     QBitArray KRITAIMAGE_EXPORT mergeChannelFlags(const QBitArray &flags, const QBitArray &parentFlags);
0061 
0062     bool KRITAIMAGE_EXPORT compareChannelFlags(QBitArray f1, QBitArray f2);
0063     QString KRITAIMAGE_EXPORT toLocalizedOnOff(bool value);
0064 
0065     KisNodeSP KRITAIMAGE_EXPORT nearestNodeAfterRemoval(KisNodeSP node);
0066 
0067     /**
0068      * When drawing a rect Qt uses quite a weird algorithm. It
0069      * draws 4 lines:
0070      *  o at X-es: rect.x() and rect.right() + 1
0071      *  o at Y-s: rect.y() and rect.bottom() + 1
0072      *
0073      *  Which means that bottom and right lines of the rect are painted
0074      *  outside the virtual rectangle the rect defines. This methods overcome this issue by
0075      *  painting the adjusted rect.
0076      */
0077     void KRITAIMAGE_EXPORT renderExactRect(QPainter *p, const QRect &rc);
0078 
0079     /**
0080      * \see renderExactRect(QPainter *p, const QRect &rc)
0081      */
0082     void KRITAIMAGE_EXPORT renderExactRect(QPainter *p, const QRect &rc, const QPen &pen);
0083 
0084     QImage KRITAIMAGE_EXPORT convertQImageToGrayA(const QImage &image);
0085 
0086     void KRITAIMAGE_EXPORT applyToAlpha8Device(KisPaintDeviceSP dev, const QRect &rc, std::function<void(quint8)> func);
0087     void KRITAIMAGE_EXPORT filterAlpha8Device(KisPaintDeviceSP dev, const QRect &rc, std::function<quint8(quint8)> func);
0088 
0089     qreal KRITAIMAGE_EXPORT estimatePortionOfTransparentPixels(KisPaintDeviceSP dev, const QRect &rect, qreal samplePortion);
0090 
0091     void KRITAIMAGE_EXPORT mirrorDab(Qt::Orientation dir, const QPoint &center, KisRenderedDab *dab, bool skipMirrorPixels = false);
0092     void KRITAIMAGE_EXPORT mirrorDab(Qt::Orientation dir, const QPointF &center, KisRenderedDab *dab, bool skipMirrorPixels = false);
0093 
0094     void KRITAIMAGE_EXPORT mirrorRect(Qt::Orientation dir, const QPoint &center, QRect *rc);
0095     void KRITAIMAGE_EXPORT mirrorRect(Qt::Orientation dir, const QPointF &center, QRect *rc);
0096     void KRITAIMAGE_EXPORT mirrorPoint(Qt::Orientation dir, const QPoint &center, QPointF *pt);
0097     void KRITAIMAGE_EXPORT mirrorPoint(Qt::Orientation dir, const QPointF &center, QPointF *pt);
0098 
0099 
0100     /**
0101      * Returns a special transformation that converts vector shape coordinates
0102      * ('pt') into a special coordinate space, where all path boolean operations
0103      * should happen.
0104      *
0105      * The problem is that Qt's path boolean operation do not support curves,
0106      * therefore all the curves are converted into lines
0107      * (see QPathSegments::addPath()). The curves are split into lines using
0108      * absolute size of the curve for the threshold. Therefore, when applying
0109      * boolean operations we should convert them into 'image pixel' coordinate
0110      * space first.
0111      *
0112      * See https://bugs.kde.org/show_bug.cgi?id=411056
0113      */
0114     QTransform KRITAIMAGE_EXPORT pathShapeBooleanSpaceWorkaround(KisImageSP image);
0115 
0116     enum ThresholdMode {
0117         ThresholdNone = 0,
0118         ThresholdFloor,
0119         ThresholdCeil,
0120         ThresholdMaxOut
0121     };
0122 
0123     void thresholdOpacity(KisPaintDeviceSP device, const QRect &rect, ThresholdMode mode);
0124     void thresholdOpacityAlpha8(KisPaintDeviceSP device, const QRect &rect, ThresholdMode mode);
0125 
0126     template <typename Visitor>
0127     void rasterizeHLine(const QPoint &startPoint, const QPoint &endPoint, Visitor visitor)
0128     {
0129         QVector<QPoint> points;
0130         int startX, endX;
0131         if (startPoint.x() < endPoint.x()) {
0132             startX = startPoint.x();
0133             endX = endPoint.x();
0134         } else {
0135             startX = endPoint.x();
0136             endX = startPoint.x();
0137         }
0138         for (int x = startX; x <= endX; ++x) {
0139             visitor(QPoint(x, startPoint.y()));
0140         }
0141     }
0142 
0143     template <typename Visitor>
0144     void rasterizeVLine(const QPoint &startPoint, const QPoint &endPoint, Visitor visitor)
0145     {
0146         QVector<QPoint> points;
0147         int startY, endY;
0148         if (startPoint.y() < endPoint.y()) {
0149             startY = startPoint.y();
0150             endY = endPoint.y();
0151         } else {
0152             startY = endPoint.y();
0153             endY = startPoint.y();
0154         }
0155         for (int y = startY; y <= endY; ++y) {
0156             visitor(QPoint(startPoint.x(), y));
0157         }
0158     }
0159 
0160     template <typename Visitor>
0161     void rasterizeLineDDA(const QPoint &startPoint, const QPoint &endPoint, Visitor visitor)
0162     {
0163         QVector<QPoint> points;
0164 
0165         if (startPoint == endPoint) {
0166             visitor(startPoint);
0167             return;
0168         }
0169         if (startPoint.y() == endPoint.y()) {
0170             rasterizeHLine(startPoint, endPoint, visitor);
0171             return;
0172         }
0173         if (startPoint.x() == endPoint.x()) {
0174             rasterizeVLine(startPoint, endPoint, visitor);
0175             return;
0176         }
0177 
0178         const QPoint delta = endPoint - startPoint;
0179         QPoint currentPosition = startPoint;
0180         QPointF currentPositionF = startPoint;
0181         qreal m = static_cast<qreal>(delta.y()) / static_cast<qreal>(delta.x());
0182         int increment;
0183 
0184         if (std::abs(m) > 1.0) {
0185             if (delta.y() > 0) {
0186                 m = 1.0 / m;
0187                 increment = 1;
0188             } else {
0189                 m = -1.0 / m;
0190                 increment = -1;
0191             }
0192             while (currentPosition.y() != endPoint.y()) {
0193                 currentPositionF.setX(currentPositionF.x() + m);
0194                 currentPosition = QPoint(static_cast<int>(qRound(currentPositionF.x())),
0195                                         currentPosition.y() + increment);
0196                 visitor(currentPosition);
0197             }
0198         } else {
0199             if (delta.x() > 0) {
0200                 increment = 1;
0201             } else {
0202                 increment = -1;
0203                 m = -m;
0204             }
0205             while (currentPosition.x() != endPoint.x()) {
0206                 currentPositionF.setY(currentPositionF.y() + m);
0207                 currentPosition = QPoint(currentPosition.x() + increment,
0208                                         static_cast<int>(qRound(currentPositionF.y())));
0209                 visitor(currentPosition);
0210             }
0211         }
0212     }
0213 
0214     template <typename Visitor>
0215     void rasterizePolylineDDA(const QVector<QPoint> &polylinePoints, Visitor visitor)
0216     {
0217         if (polylinePoints.size() == 0) {
0218             return;
0219         }
0220         if (polylinePoints.size() == 1) {
0221             visitor(polylinePoints.first());
0222             return;
0223         }
0224 
0225         // copy all points from the first segment
0226         rasterizeLineDDA(polylinePoints[0], polylinePoints[1], visitor);
0227         // for the rest of the segments, copy all points except the first one
0228         // (it is the same as the last point in the previous segment)
0229         for (int i = 1; i < polylinePoints.size() - 1; ++i) {
0230             int pointIndex = 0;
0231             rasterizeLineDDA(
0232                 polylinePoints[i], polylinePoints[i + 1],
0233                 [&pointIndex, &visitor](const QPoint &point) -> void
0234                 {
0235                     if (pointIndex > 0) {
0236                         visitor(point);
0237                     }
0238                     ++pointIndex;
0239                 }
0240             );
0241         }
0242     }
0243 
0244     template <typename Visitor>
0245     void rasterizePolygonDDA(const QVector<QPoint> &polygonPoints, Visitor visitor)
0246     {
0247         // this is a line
0248         if (polygonPoints.size() < 3) {
0249             rasterizeLineDDA(polygonPoints.first(), polygonPoints.last(), visitor);
0250             return;
0251         }
0252         // rasterize all segments except the last one
0253         QPoint lastSegmentStart;
0254         if (polygonPoints.first() == polygonPoints.last()) {
0255             rasterizePolylineDDA(polygonPoints.mid(0, polygonPoints.size() - 1), visitor);
0256             lastSegmentStart = polygonPoints[polygonPoints.size() - 2];
0257         } else {
0258             rasterizePolylineDDA(polygonPoints, visitor);
0259             lastSegmentStart = polygonPoints[polygonPoints.size() - 1];
0260         }
0261         // close the polygon
0262         {
0263             QVector<QPoint> points;
0264             auto addPoint = [&points](const QPoint &point) -> void { points.append(point); };
0265             rasterizeLineDDA(lastSegmentStart, polygonPoints.first(), addPoint);
0266             for (int i = 1; i < points.size() - 1; ++i) {
0267                 visitor(points[i]);
0268             }
0269         }
0270     }
0271 
0272     // Convenience functions
0273     QVector<QPoint> KRITAIMAGE_EXPORT rasterizeHLine(const QPoint &startPoint, const QPoint &endPoint);
0274     QVector<QPoint> KRITAIMAGE_EXPORT rasterizeVLine(const QPoint &startPoint, const QPoint &endPoint);
0275     QVector<QPoint> KRITAIMAGE_EXPORT rasterizeLineDDA(const QPoint &startPoint, const QPoint &endPoint);
0276     QVector<QPoint> KRITAIMAGE_EXPORT rasterizePolylineDDA(const QVector<QPoint> &polylinePoints);
0277     QVector<QPoint> KRITAIMAGE_EXPORT rasterizePolygonDDA(const QVector<QPoint> &polygonPoints);
0278 }
0279 
0280 #endif /* __KRITA_UTILS_H */