Krita Source Code Documentation
Loading...
Searching...
No Matches
krita_utils.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2011 Dmitry Kazakov <dimula73@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
7#include "krita_utils.h"
8
9#include <QtCore/qmath.h>
10
11#include <QRect>
12#include <QRegion>
13#include <QPainterPath>
14#include <QPolygonF>
15#include <QPen>
16#include <QPainter>
17
18#include "kis_algebra_2d.h"
19
21
22#include "kis_image.h"
23#include "kis_image_config.h"
24#include "kis_debug.h"
25#include "kis_node.h"
28
29#include <KisRenderedDab.h>
30
31
32namespace KritaUtils
33{
34
36 {
37 KisImageConfig cfg(true);
38 return QSize(cfg.updatePatchWidth(),
39 cfg.updatePatchHeight());
40 }
41
42 QVector<QRect> splitRectIntoPatches(const QRect &rc, const QSize &patchSize)
43 {
44 using namespace KisAlgebra2D;
45
46
47 QVector<QRect> patches;
48
49 const qint32 firstCol = divideFloor(rc.x(), patchSize.width());
50 const qint32 firstRow = divideFloor(rc.y(), patchSize.height());
51
52 // TODO: check if -1 is needed here
53 const qint32 lastCol = divideFloor(rc.x() + rc.width(), patchSize.width());
54 const qint32 lastRow = divideFloor(rc.y() + rc.height(), patchSize.height());
55
56 for(qint32 i = firstRow; i <= lastRow; i++) {
57 for(qint32 j = firstCol; j <= lastCol; j++) {
58 QRect maxPatchRect(j * patchSize.width(), i * patchSize.height(),
59 patchSize.width(), patchSize.height());
60 QRect patchRect = rc & maxPatchRect;
61
62 if (!patchRect.isEmpty()) {
63 patches.append(patchRect);
64 }
65 }
66 }
67
68 return patches;
69 }
70
71 QVector<QRect> splitRectIntoPatchesTight(const QRect &rc, const QSize &patchSize)
72 {
73 QVector<QRect> patches;
74
75 for (qint32 y = rc.y(); y < rc.y() + rc.height(); y += patchSize.height()) {
76 for (qint32 x = rc.x(); x < rc.x() + rc.width(); x += patchSize.width()) {
77 patches << QRect(x, y,
78 qMin(rc.x() + rc.width() - x, patchSize.width()),
79 qMin(rc.y() + rc.height() - y, patchSize.height()));
80 }
81 }
82
83 return patches;
84 }
85
86 QVector<QRect> splitRegionIntoPatches(const KisRegion &region, const QSize &patchSize)
87 {
88 QVector<QRect> patches;
89
90 Q_FOREACH (const QRect rect, region.rects()) {
91 patches << KritaUtils::splitRectIntoPatches(rect, patchSize);
92 }
93
94 return patches;
95 }
96
97 bool checkInTriangle(const QRectF &rect,
98 const QPolygonF &triangle)
99 {
100 return triangle.intersected(rect).boundingRect().isValid();
101 }
102
103
104 KisRegion splitTriangles(const QPointF &center,
105 const QVector<QPointF> &points)
106 {
107
108 Q_ASSERT(points.size());
109 Q_ASSERT(!(points.size() & 1));
110
111 QVector<QPolygonF> triangles;
112 QRect totalRect;
113
114 for (int i = 0; i < points.size(); i += 2) {
115 QPolygonF triangle;
116 triangle << center;
117 triangle << points[i];
118 triangle << points[i+1];
119
120 totalRect |= triangle.boundingRect().toAlignedRect();
121 triangles << triangle;
122 }
123
124
125 const int step = 64;
126 const int right = totalRect.x() + totalRect.width();
127 const int bottom = totalRect.y() + totalRect.height();
128
129 QVector<QRect> dirtyRects;
130
131 for (int y = totalRect.y(); y < bottom;) {
132 int nextY = qMin((y + step) & ~(step-1), bottom);
133
134 for (int x = totalRect.x(); x < right;) {
135 int nextX = qMin((x + step) & ~(step-1), right);
136
137 QRect rect(x, y, nextX - x, nextY - y);
138
139 Q_FOREACH (const QPolygonF &triangle, triangles) {
140 if(checkInTriangle(rect, triangle)) {
141 dirtyRects << rect;
142 break;
143 }
144 }
145
146 x = nextX;
147 }
148 y = nextY;
149 }
150 return KisRegion(std::move(dirtyRects));
151 }
152
153 KisRegion splitPath(const QPainterPath &path)
154 {
155 QVector<QRect> dirtyRects;
156 QRect totalRect = path.boundingRect().toAlignedRect();
157
158 // adjust the rect for antialiasing to work
159 totalRect = totalRect.adjusted(-1,-1,1,1);
160
161 const int step = 64;
162 const int right = totalRect.x() + totalRect.width();
163 const int bottom = totalRect.y() + totalRect.height();
164
165 for (int y = totalRect.y(); y < bottom;) {
166 int nextY = qMin((y + step) & ~(step-1), bottom);
167
168 for (int x = totalRect.x(); x < right;) {
169 int nextX = qMin((x + step) & ~(step-1), right);
170
171 QRect rect(x, y, nextX - x, nextY - y);
172
173 if(path.intersects(rect)) {
174 dirtyRects << rect;
175 }
176
177 x = nextX;
178 }
179 y = nextY;
180 }
181
182 return KisRegion(std::move(dirtyRects));
183 }
184
185 QString KRITAIMAGE_EXPORT prettyFormatReal(qreal value)
186 {
187 return QLocale().toString(value, 'f', 1);
188 }
189
190 qreal KRITAIMAGE_EXPORT maxDimensionPortion(const QRectF &bounds, qreal portion, qreal minValue)
191 {
192 qreal maxDimension = qMax(bounds.width(), bounds.height());
193 return qMax(portion * maxDimension, minValue);
194 }
195
196 QList<QPainterPath> splitDisjointPaths(const QPainterPath &path)
197 {
198 QList<QPainterPath> resultList;
199 QList<QPolygonF> inputPolygons = path.toSubpathPolygons();
200
201 Q_FOREACH (const QPolygonF &poly, inputPolygons) {
202 QPainterPath testPath;
203 testPath.addPolygon(poly);
204
205 if (resultList.isEmpty()) {
206 resultList.append(testPath);
207 continue;
208 }
209
210 QPainterPath mergedPath = testPath;
211
212 for (auto it = resultList.begin(); it != resultList.end(); /*noop*/) {
213 if (it->intersects(testPath)) {
214 mergedPath.addPath(*it);
215 it = resultList.erase(it);
216 } else {
217 ++it;
218 }
219 }
220
221 resultList.append(mergedPath);
222 }
223
224 return resultList;
225 }
226
227 quint8 mergeOpacityU8(quint8 opacity, quint8 parentOpacity)
228 {
229 if (parentOpacity != OPACITY_OPAQUE_U8) {
230 opacity = (int(opacity) * parentOpacity) / OPACITY_OPAQUE_U8;
231 }
232 return opacity;
233 }
234
235 qreal mergeOpacityF(qreal opacity, qreal parentOpacity)
236 {
237 if (!qFuzzyCompare(parentOpacity, OPACITY_OPAQUE_F)) {
238 opacity *= parentOpacity;
239 }
240 return opacity;
241 }
242
243 QBitArray mergeChannelFlags(const QBitArray &childFlags, const QBitArray &parentFlags)
244 {
245 QBitArray flags = childFlags;
246
247 if (!flags.isEmpty() &&
248 !parentFlags.isEmpty() &&
249 flags.size() == parentFlags.size()) {
250
251 flags &= parentFlags;
252
253 } else if (!parentFlags.isEmpty()) {
254 flags = parentFlags;
255 }
256
257 return flags;
258 }
259
260 bool compareChannelFlags(QBitArray f1, QBitArray f2)
261 {
262 if (f1.isNull() && f2.isNull()) return true;
263
264 if (f1.isNull()) {
265 f1.fill(true, f2.size());
266 }
267
268 if (f2.isNull()) {
269 f2.fill(true, f1.size());
270 }
271
272 return f1 == f2;
273 }
274
275 QString KRITAIMAGE_EXPORT toLocalizedOnOff(bool value) {
276 return value ? i18n("on") : i18n("off");
277 }
278
280 {
281 KisNodeSP newNode = node->nextSibling();
282
283 if (!newNode) {
284 newNode = node->prevSibling();
285 }
286
287 if (!newNode) {
288 newNode = node->parent();
289 }
290
291 return newNode;
292 }
293
294 void renderExactRect(QPainter *p, const QRect &rc)
295 {
296 p->drawRect(rc.adjusted(0,0,-1,-1));
297 }
298
299 void renderExactRect(QPainter *p, const QRect &rc, const QPen &pen)
300 {
301 QPen oldPen = p->pen();
302 p->setPen(pen);
303 renderExactRect(p, rc);
304 p->setPen(oldPen);
305 }
306
307 QImage convertQImageToGrayA(const QImage &image)
308 {
309 QImage dstImage(image.size(), QImage::Format_ARGB32);
310
311 // TODO: if someone feel bored, a more optimized version of this would be welcome
312 const QSize size = image.size();
313 for(int y = 0; y < size.height(); ++y) {
314 for(int x = 0; x < size.width(); ++x) {
315 const QRgb pixel = image.pixel(x,y);
316 const int gray = qGray(pixel);
317 dstImage.setPixel(x, y, qRgba(gray, gray, gray, qAlpha(pixel)));
318 }
319 }
320
321 return dstImage;
322 }
323
324 void applyToAlpha8Device(KisPaintDeviceSP dev, const QRect &rc, std::function<void(quint8)> func) {
325 KisSequentialConstIterator dstIt(dev, rc);
326 while (dstIt.nextPixel()) {
327 const quint8 *dstPtr = dstIt.rawDataConst();
328 func(*dstPtr);
329 }
330 }
331
332 void filterAlpha8Device(KisPaintDeviceSP dev, const QRect &rc, std::function<quint8(quint8)> func) {
333 KisSequentialIterator dstIt(dev, rc);
334 while (dstIt.nextPixel()) {
335 quint8 *dstPtr = dstIt.rawData();
336 *dstPtr = func(*dstPtr);
337 }
338 }
339
340 qreal estimatePortionOfTransparentPixels(KisPaintDeviceSP dev, const QRect &rect, qreal samplePortion) {
341 const KoColorSpace *cs = dev->colorSpace();
342
343 const qreal linearPortion = std::sqrt(samplePortion);
344 const qreal ratio = qreal(rect.width()) / rect.height();
345 const int xStep = qMax(1, qRound(1.0 / linearPortion * ratio));
346 const int yStep = qMax(1, qRound(1.0 / linearPortion / ratio));
347
348 int numTransparentPixels = 0;
349 int numPixels = 0;
350
352 for (int y = rect.y(); y <= rect.bottom(); y += yStep) {
353 for (int x = rect.x(); x <= rect.right(); x += xStep) {
354 it->moveTo(x, y);
355 const quint8 alpha = cs->opacityU8(it->rawDataConst());
356
357 if (alpha != OPACITY_OPAQUE_U8) {
358 numTransparentPixels++;
359 }
360
361 numPixels++;
362 }
363 }
364
365 if (numPixels == 0) {
366 return 0; // avoid dividing by 0
367 }
368 return qreal(numTransparentPixels) / numPixels;
369 }
370
371 void mirrorDab(Qt::Orientation dir, const QPoint &center, KisRenderedDab *dab, bool skipMirrorPixels)
372 {
373 const QRect rc = dab->realBounds();
374
375 if (dir == Qt::Horizontal) {
376 const int mirrorX = -((rc.x() + rc.width()) - center.x()) + center.x();
377
378 if (!skipMirrorPixels) {
379 dab->device->mirror(true, false);
380 }
381 dab->offset.rx() = mirrorX;
382 } else /* if (dir == Qt::Vertical) */ {
383 const int mirrorY = -((rc.y() + rc.height()) - center.y()) + center.y();
384
385 if (!skipMirrorPixels) {
386 dab->device->mirror(false, true);
387 }
388 dab->offset.ry() = mirrorY;
389 }
390 }
391
392 void mirrorDab(Qt::Orientation dir, const QPointF &center, KisRenderedDab *dab, bool skipMirrorPixels)
393 {
394 const QRect rc = dab->realBounds();
395
396 if (dir == Qt::Horizontal) {
397 const int mirrorX = -((rc.x() + rc.width()) - center.x()) + center.x();
398
399 if (!skipMirrorPixels) {
400 dab->device->mirror(true, false);
401 }
402 dab->offset.rx() = mirrorX;
403 } else /* if (dir == Qt::Vertical) */ {
404 const int mirrorY = -((rc.y() + rc.height()) - center.y()) + center.y();
405
406 if (!skipMirrorPixels) {
407 dab->device->mirror(false, true);
408 }
409 dab->offset.ry() = mirrorY;
410 }
411 }
412
413 void mirrorRect(Qt::Orientation dir, const QPoint &center, QRect *rc)
414 {
415 if (dir == Qt::Horizontal) {
416 const int mirrorX = -((rc->x() + rc->width()) - center.x()) + center.x();
417 rc->moveLeft(mirrorX);
418 } else /* if (dir == Qt::Vertical) */ {
419 const int mirrorY = -((rc->y() + rc->height()) - center.y()) + center.y();
420 rc->moveTop(mirrorY);
421 }
422 }
423
424 void mirrorRect(Qt::Orientation dir, const QPointF &center, QRect *rc)
425 {
426 if (dir == Qt::Horizontal) {
427 const int mirrorX = -((rc->x() + rc->width()) - center.x()) + center.x();
428 rc->moveLeft(mirrorX);
429 } else /* if (dir == Qt::Vertical) */ {
430 const int mirrorY = -((rc->y() + rc->height()) - center.y()) + center.y();
431 rc->moveTop(mirrorY);
432 }
433 }
434
435 void mirrorPoint(Qt::Orientation dir, const QPoint &center, QPointF *pt)
436 {
437 if (dir == Qt::Horizontal) {
438 pt->rx() = -(pt->x() - qreal(center.x())) + center.x();
439 } else /* if (dir == Qt::Vertical) */ {
440 pt->ry() = -(pt->y() - qreal(center.y())) + center.y();
441 }
442 }
443
444 void mirrorPoint(Qt::Orientation dir, const QPointF &center, QPointF *pt)
445 {
446 if (dir == Qt::Horizontal) {
447 pt->rx() = -(pt->x() - qreal(center.x())) + center.x();
448 } else /* if (dir == Qt::Vertical) */ {
449 pt->ry() = -(pt->y() - qreal(center.y())) + center.y();
450 }
451 }
452
454 {
455 return QTransform::fromScale(image->xRes(), image->yRes());
456 }
457
458 QPainterPath tryCloseTornSubpathsAfterIntersection(QPainterPath path)
459 {
460 path.setFillRule(Qt::WindingFill);
461 QList<QPolygonF> polys = path.toSubpathPolygons();
462
463 path = QPainterPath();
464 path.setFillRule(Qt::WindingFill);
465 Q_FOREACH (QPolygonF poly, polys) {
466 ENTER_FUNCTION() << ppVar(poly.isClosed());
467 if (!poly.isClosed()) {
468 poly.append(poly.first());
469 }
470 path.addPolygon(poly);
471 }
472 return path;
473 }
474
475 void thresholdOpacity(KisPaintDeviceSP device, const QRect &rect, ThresholdMode mode)
476 {
477 const KoColorSpace *cs = device->colorSpace();
478
479 if (mode == ThresholdCeil) {
480 KisSequentialIterator it(device, rect);
481 while (it.nextPixel()) {
482 if (cs->opacityU8(it.rawDataConst()) > 0) {
483 cs->setOpacity(it.rawData(), quint8(255), 1);
484 }
485 }
486 } else if (mode == ThresholdFloor) {
487 KisSequentialIterator it(device, rect);
488 while (it.nextPixel()) {
489 if (cs->opacityU8(it.rawDataConst()) < 255) {
490 cs->setOpacity(it.rawData(), quint8(0), 1);
491 }
492 }
493 } else if (mode == ThresholdMaxOut) {
494 KisSequentialIterator it(device, rect);
495 int numConseqPixels = it.nConseqPixels();
496 while (it.nextPixels(numConseqPixels)) {
497 numConseqPixels = it.nConseqPixels();
498 cs->setOpacity(it.rawData(), quint8(255), numConseqPixels);
499 }
500 }
501 }
502
504 {
505 if (mode == ThresholdCeil) {
506 filterAlpha8Device(device, rect,
507 [] (quint8 value) {
508 return value > 0 ? 255 : value;
509 });
510 } else if (mode == ThresholdFloor) {
511 filterAlpha8Device(device, rect,
512 [] (quint8 value) {
513 return value < 255 ? 0 : value;
514 });
515 } else if (mode == ThresholdMaxOut) {
516 device->fill(rect, KoColor(Qt::white, device->colorSpace()));
517 }
518 }
519
520 QVector<QPoint> rasterizeHLine(const QPoint &startPoint, const QPoint &endPoint)
521 {
522 QVector<QPoint> points;
523 rasterizeHLine(startPoint, endPoint, [&points](const QPoint &point) { points.append(point); });
524 return points;
525 }
526
527 QVector<QPoint> rasterizeVLine(const QPoint &startPoint, const QPoint &endPoint)
528 {
529 QVector<QPoint> points;
530 rasterizeVLine(startPoint, endPoint, [&points](const QPoint &point) { points.append(point); });
531 return points;
532 }
533
534 QVector<QPoint> rasterizeLineDDA(const QPoint &startPoint, const QPoint &endPoint)
535 {
536 QVector<QPoint> points;
537 rasterizeLineDDA(startPoint, endPoint, [&points](const QPoint &point) { points.append(point); });
538 return points;
539 }
540
542 {
543 QVector<QPoint> points;
544 rasterizePolylineDDA(polylinePoints, [&points](const QPoint &point) { points.append(point); });
545 return points;
546 }
547
549 {
550 QVector<QPoint> points;
551 rasterizePolygonDDA(polygonPoints, [&points](const QPoint &point) { points.append(point); });
552 return points;
553 }
554
555}
float value(const T *src, size_t ch)
const Params2D p
const qreal OPACITY_OPAQUE_F
const quint8 OPACITY_OPAQUE_U8
virtual const quint8 * rawDataConst() const =0
void mirror(bool horizontal, bool vertical)
int updatePatchWidth() const
int updatePatchHeight() const
double xRes() const
double yRes() const
KisRandomConstAccessorSP createRandomConstAccessorNG() const
void fill(const QRect &rc, const KoColor &color)
const KoColorSpace * colorSpace() const
virtual void moveTo(qint32 x, qint32 y)=0
QVector< QRect > rects() const
ALWAYS_INLINE quint8 * rawData()
ALWAYS_INLINE const quint8 * rawDataConst() const
virtual void setOpacity(quint8 *pixels, quint8 alpha, qint32 nPixels) const =0
virtual quint8 opacityU8(const quint8 *pixel) const =0
static bool qFuzzyCompare(half p1, half p2)
unsigned int QRgb
#define bounds(x, a, b)
#define ENTER_FUNCTION()
Definition kis_debug.h:178
#define ppVar(var)
Definition kis_debug.h:155
qreal KRITAIMAGE_EXPORT maxDimensionPortion(const QRectF &bounds, qreal portion, qreal minValue)
QVector< QRect > splitRectIntoPatches(const QRect &rc, const QSize &patchSize)
quint8 mergeOpacityU8(quint8 opacity, quint8 parentOpacity)
void filterAlpha8Device(KisPaintDeviceSP dev, const QRect &rc, std::function< quint8(quint8)> func)
bool compareChannelFlags(QBitArray f1, QBitArray f2)
QString KRITAIMAGE_EXPORT toLocalizedOnOff(bool value)
void thresholdOpacity(KisPaintDeviceSP device, const QRect &rect, ThresholdMode mode)
QVector< QPoint > rasterizeLineDDA(const QPoint &startPoint, const QPoint &endPoint)
QString KRITAIMAGE_EXPORT prettyFormatReal(qreal value)
KisRegion splitPath(const QPainterPath &path)
QVector< QPoint > rasterizePolygonDDA(const QVector< QPoint > &polygonPoints)
QTransform pathShapeBooleanSpaceWorkaround(KisImageSP image)
KisNodeSP nearestNodeAfterRemoval(KisNodeSP node)
qreal mergeOpacityF(qreal opacity, qreal parentOpacity)
QImage convertQImageToGrayA(const QImage &image)
QPainterPath tryCloseTornSubpathsAfterIntersection(QPainterPath path)
QVector< QRect > splitRegionIntoPatches(const KisRegion &region, const QSize &patchSize)
qreal estimatePortionOfTransparentPixels(KisPaintDeviceSP dev, const QRect &rect, qreal samplePortion)
void mirrorDab(Qt::Orientation dir, const QPoint &center, KisRenderedDab *dab, bool skipMirrorPixels)
bool checkInTriangle(const QRectF &rect, const QPolygonF &triangle)
QList< QPainterPath > splitDisjointPaths(const QPainterPath &path)
void applyToAlpha8Device(KisPaintDeviceSP dev, const QRect &rc, std::function< void(quint8)> func)
QBitArray mergeChannelFlags(const QBitArray &childFlags, const QBitArray &parentFlags)
QVector< QPoint > rasterizeVLine(const QPoint &startPoint, const QPoint &endPoint)
KisRegion splitTriangles(const QPointF &center, const QVector< QPointF > &points)
QVector< QRect > splitRectIntoPatchesTight(const QRect &rc, const QSize &patchSize)
void thresholdOpacityAlpha8(KisPaintDeviceSP device, const QRect &rect, ThresholdMode mode)
void mirrorPoint(Qt::Orientation dir, const QPoint &center, QPointF *pt)
QSize optimalPatchSize()
void mirrorRect(Qt::Orientation dir, const QPoint &center, QRect *rc)
void renderExactRect(QPainter *p, const QRect &rc)
QVector< QPoint > rasterizeHLine(const QPoint &startPoint, const QPoint &endPoint)
QVector< QPoint > rasterizePolylineDDA(const QVector< QPoint > &polylinePoints)
KisNodeSP prevSibling() const
Definition kis_node.cpp:402
KisNodeWSP parent
Definition kis_node.cpp:86
KisNodeSP nextSibling() const
Definition kis_node.cpp:408
KisFixedPaintDeviceSP device
QRect realBounds() const