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 ®ion, const QSize &patchSize); 0035 QVector<QRect> KRITAIMAGE_EXPORT splitRegionIntoPatches(const KisRegion ®ion, const QSize &patchSize); 0036 0037 KRITAIMAGE_EXPORT KisRegion splitTriangles(const QPointF ¢er, 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 ¢er, KisRenderedDab *dab, bool skipMirrorPixels = false); 0092 void KRITAIMAGE_EXPORT mirrorDab(Qt::Orientation dir, const QPointF ¢er, KisRenderedDab *dab, bool skipMirrorPixels = false); 0093 0094 void KRITAIMAGE_EXPORT mirrorRect(Qt::Orientation dir, const QPoint ¢er, QRect *rc); 0095 void KRITAIMAGE_EXPORT mirrorRect(Qt::Orientation dir, const QPointF ¢er, QRect *rc); 0096 void KRITAIMAGE_EXPORT mirrorPoint(Qt::Orientation dir, const QPoint ¢er, QPointF *pt); 0097 void KRITAIMAGE_EXPORT mirrorPoint(Qt::Orientation dir, const QPointF ¢er, 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 */