Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_grid_interpolation_tools.h
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2014 Dmitry Kazakov <dimula73@gmail.com>
3 * SPDX-FileCopyrightText: 2025 Agata Cacko <cacko.azh@gmail.com>
4 *
5 *
6 * SPDX-License-Identifier: GPL-2.0-or-later
7 */
8
9#ifndef __KIS_GRID_INTERPOLATION_TOOLS_H
10#define __KIS_GRID_INTERPOLATION_TOOLS_H
11
12#include <limits>
13#include <algorithm>
14
15#include <QImage>
16
17#include "kis_algebra_2d.h"
20#include "kis_iterator_ng.h"
22#include "kis_painter.h"
23#include "KisRegion.h"
24
25//#define DEBUG_PAINTING_POLYGONS
26
27#ifdef DEBUG_PAINTING_POLYGONS
28#include <QPainter>
29#endif /* DEBUG_PAINTING_POLYGONS */
30
32
33inline int calcGridDimension(int start, int end, const int pixelPrecision)
34{
35 const int alignmentMask = ~(pixelPrecision - 1);
36
37 int alignedStart = (start + pixelPrecision - 1) & alignmentMask;
38 int alignedEnd = end & alignmentMask;
39
40 int size = 0;
41
42 if (alignedEnd > alignedStart) {
43 size = (alignedEnd - alignedStart) / pixelPrecision + 1;
44 size += alignedStart != start;
45 size += alignedEnd != end;
46 } else {
47 size = 2 + (end - start >= pixelPrecision);
48 }
49
50 return size;
51}
52
53inline QSize calcGridSize(const QRect &srcBounds, const int pixelPrecision) {
54 return QSize(calcGridDimension(srcBounds.x(), srcBounds.right(), pixelPrecision),
55 calcGridDimension(srcBounds.y(), srcBounds.bottom(), pixelPrecision));
56}
57
58template <class ProcessPolygon, class ForwardTransform>
59struct CellOp
60{
61 CellOp(ProcessPolygon &_polygonOp, ForwardTransform &_transformOp)
62 : polygonOp(_polygonOp),
63 transformOp(_transformOp)
64 {
65 }
66
67 inline void processPoint(int col, int row,
68 int prevCol, int prevRow,
69 int colIndex, int rowIndex) {
70
71 QPointF dstPosF = transformOp(QPointF(col, row));
72 currLinePoints << dstPosF;
73
74 if (rowIndex >= 1 && colIndex >= 1) {
75 QPolygonF srcPolygon;
76
77 srcPolygon << QPointF(prevCol, prevRow);
78 srcPolygon << QPointF(col, prevRow);
79 srcPolygon << QPointF(col, row);
80 srcPolygon << QPointF(prevCol, row);
81
82 QPolygonF dstPolygon;
83
84 dstPolygon << prevLinePoints.at(colIndex - 1);
85 dstPolygon << prevLinePoints.at(colIndex);
86 dstPolygon << currLinePoints.at(colIndex);
87 dstPolygon << currLinePoints.at(colIndex - 1);
88
89 polygonOp(srcPolygon, dstPolygon);
90 }
91
92 }
93
94 inline void nextLine() {
96
97 // we are erasing elements for not freeing the occupied
98 // memory, which is more efficient since we are going to fill
99 // the vector again
100 currLinePoints.erase(currLinePoints.begin(), currLinePoints.end());
101 }
102
105 ProcessPolygon &polygonOp;
106 ForwardTransform &transformOp;
107};
108
109template <class ProcessCell>
110void processGrid(ProcessCell &cellOp,
111 const QRect &srcBounds,
112 const int pixelPrecision)
113{
114 if (srcBounds.isEmpty()) return;
115
116 const int alignmentMask = ~(pixelPrecision - 1);
117
118 int prevRow = std::numeric_limits<int>::max();
119 int prevCol = std::numeric_limits<int>::max();
120
121 int rowIndex = 0;
122 int colIndex = 0;
123
124 for (int row = srcBounds.top(); row <= srcBounds.bottom();) {
125 for (int col = srcBounds.left(); col <= srcBounds.right();) {
126
127 cellOp.processPoint(col, row,
128 prevCol, prevRow,
129 colIndex, rowIndex);
130
131 prevCol = col;
132 col += pixelPrecision;
133 colIndex++;
134
135 if (col > srcBounds.right() &&
136 col <= srcBounds.right() + pixelPrecision - 1) {
137
138 col = srcBounds.right();
139 } else {
140 col &= alignmentMask;
141 }
142 }
143
144 cellOp.nextLine();
145 colIndex = 0;
146
147 prevRow = row;
148 row += pixelPrecision;
149 rowIndex++;
150
151 if (row > srcBounds.bottom() &&
152 row <= srcBounds.bottom() + pixelPrecision - 1) {
153
154 row = srcBounds.bottom();
155 } else {
156 row &= alignmentMask;
157 }
158 }
159}
160
161template <class ProcessPolygon, class ForwardTransform>
162void processGrid(ProcessPolygon &polygonOp, ForwardTransform &transformOp,
163 const QRect &srcBounds, const int pixelPrecision)
164{
165 CellOp<ProcessPolygon, ForwardTransform> cellOp(polygonOp, transformOp);
166 processGrid(cellOp, srcBounds, pixelPrecision);
167}
168
170{
172 : m_srcDev(srcDev), m_dstDev(dstDev) {}
173
183
184 void fastCopyArea(QRect areaToCopy) {
185 fastCopyArea(areaToCopy, m_canMergeRects);
186 }
187
188 void fastCopyArea(QRect areaToCopy, bool lazy) {
189#ifdef DEBUG_PAINTING_POLYGONS
190
191 QRect boundRect = areaToCopy;
192 KisSequentialIterator dstIt(m_dstDev, boundRect);
193 KisSequentialIterator srcIt(m_srcDev, boundRect);
194
195 // this can possibly be optimized with scanlining the polygon
196 // (use intersectLineConvexPolygon to get a line at every height)
197 // but it doesn't matter much because in the vast majority of cases
198 // it should go straight to the rect area copying
199
200 while (dstIt.nextPixel() && srcIt.nextPixel()) {
201 memcpy(dstIt.rawData(), srcIt.oldRawData(), m_dstDev->pixelSize());
202 QColor color = m_debugColor;
203 color.setHsl(KisAlgebra2D::wrapValue(m_debugColor.hslHue() + 20, 0, 360), m_debugColor.hslSaturation(), m_debugColor.lightness());
204 m_dstDev->colorSpace()->fromQColor(color, dstIt.rawData());
205 }
206 return;
207#endif
208 if (lazy) {
209 m_rectsToCopy.append(areaToCopy.adjusted(0, 0, -1, -1));
210 } else {
211 KisPainter::copyAreaOptimized(areaToCopy.topLeft(), m_srcDev, m_dstDev, areaToCopy);
212 }
213 }
214
215 void operator() (const QPolygonF &srcPolygon, const QPolygonF &dstPolygon) {
216 operator() (srcPolygon, dstPolygon, dstPolygon);
217 }
218
219 void operator() (const QPolygonF &srcPolygon, const QPolygonF &dstPolygon, const QPolygonF &clipDstPolygon) {
220#ifdef DEBUG_PAINTING_POLYGONS
221 //m_rectId++;
222#endif
223 QRect boundRect = clipDstPolygon.boundingRect().toAlignedRect();
224 if (boundRect.isEmpty()) return;
225
226 bool samePolygon = (m_dstDev->colorSpace() == m_srcDev->colorSpace())
227 && KisAlgebra2D::fuzzyPointCompare(srcPolygon, dstPolygon, m_epsilon)
228 && KisAlgebra2D::fuzzyPointCompare(srcPolygon, clipDstPolygon, m_epsilon);
229
230 if (samePolygon && KisAlgebra2D::isPolygonPixelAlignedRect(dstPolygon, m_epsilon)) {
231 QRect boundRect = dstPolygon.boundingRect().toAlignedRect();
232 fastCopyArea(boundRect);
233 return;
234 }
235
236
237 // provess previous rects so they are all processed
238 // in the same order as without any performance improvements
239 // (according to the grid processing order)
241
242
243 KisSequentialIterator dstIt(m_dstDev, boundRect);
245
246 KisFourPointInterpolatorBackward interp(srcPolygon, dstPolygon);
247#ifdef DEBUG_PAINTING_POLYGONS
248 int pixelId = 0;
249#endif
250
256 if (interp.isValid(0.1)) {
257 int y = boundRect.top();
258 interp.setY(y);
259
260 while (dstIt.nextPixel()) {
261 int newY = dstIt.y();
262
263 if (y != newY) {
264 y = newY;
265 interp.setY(y);
266 }
267
268 QPointF srcPoint(dstIt.x(), y);
269
270 if (clipDstPolygon.containsPoint(srcPoint, Qt::OddEvenFill)) {
271
272 interp.setX(srcPoint.x());
273 QPointF dstPoint = interp.getValue();
274
275 // brain-blowing part:
276 //
277 // since the interpolator does the inverted
278 // transformation we read data from "dstPoint"
279 // (which is non-transformed) and write it into
280 // "srcPoint" (which is transformed position)
281
282 srcAcc->moveTo(dstPoint);
283 quint8* rawData = dstIt.rawData();
284 srcAcc->sampledOldRawData(rawData);
285#ifdef DEBUG_PAINTING_POLYGONS
286 QColor color = m_debugColor;
287 color.setHsl(KisAlgebra2D::wrapValue(m_debugColor.hslHue() + m_rectId, 0, 360), m_debugColor.hslSaturation(), qBound(0, m_debugColor.lightness() - 50 - pixelId, 100));
288 pixelId++;
289 m_dstDev->colorSpace()->fromQColor(color, rawData);
290#endif
291 }
292 }
293
294 } else {
295 srcAcc->moveTo(interp.fallbackSourcePoint());
296
297 while (dstIt.nextPixel()) {
298 QPointF srcPoint(dstIt.x(), dstIt.y());
299
300 if (clipDstPolygon.containsPoint(srcPoint, Qt::OddEvenFill)) {
301 srcAcc->sampledOldRawData(dstIt.rawData());
302#ifdef DEBUG_PAINTING_POLYGONS
303 QColor color = m_debugColor;
304 color.setHsl(KisAlgebra2D::wrapValue(m_debugColor.hslHue() + m_rectId, 0, 360), m_debugColor.hslSaturation(), qBound(0, m_debugColor.lightness() + 50, 100));
305 m_dstDev->colorSpace()->fromQColor(color, dstIt.rawData());
306#endif
307 }
308 }
309 }
310
311 }
312
315
316 for (QVector<QRect>::iterator it = m_rectsToCopy.begin(); it < end; it++) {
317 QRect areaToCopy = *it;
318 fastCopyArea(areaToCopy.adjusted(0, 0, 1, 1), false);
319 }
321 }
322
323 void finalize() {
325 }
326
333 inline void setCanMergeRects(bool newCanMergeRects) {
334 m_canMergeRects = newCanMergeRects;
335 }
336
339 const qreal m_epsilon {0.001};
340
341#ifdef DEBUG_PAINTING_POLYGONS
342 QColor m_debugColor {Qt::red};
343 int m_rectId {0};
344 inline void setDebugColor(QColor color) {
345 m_debugColor = color;
346 }
347#endif
348
349private:
350 bool m_canMergeRects {false};
352
353};
354
356{
357 QImagePolygonOp(const QImage &srcImage, QImage &dstImage,
358 const QPointF &srcImageOffset,
359 const QPointF &dstImageOffset)
360 : m_srcImage(srcImage), m_dstImage(dstImage),
361 m_srcImageOffset(srcImageOffset),
362 m_dstImageOffset(dstImageOffset),
365 {
366 }
367
377
378 void fastCopyArea(QRect areaToCopy) {
379 fastCopyArea(areaToCopy, m_canMergeRects);
380 }
381
382 void fastCopyArea(QRect areaToCopy, bool lazy) {
383 if (lazy) {
384 m_rectsToCopy.append(areaToCopy.adjusted(0, 0, -1, -1));
385 return;
386 }
387
388 // only handling saved offsets
389 QRect srcArea = areaToCopy.translated(-m_srcImageOffset.toPoint());
390 QRect dstArea = areaToCopy.translated(-m_dstImageOffset.toPoint());
391
392 srcArea = srcArea.intersected(m_srcImageRect);
393 dstArea = dstArea.intersected(m_dstImageRect);
394
395 // it might look pointless but it cuts off unneeded areas on both rects based on where they end up
396 // since *I know* they are the same rectangle before translation
397 // TODO: I'm pretty sure this logic is correct, but let's check it when I'm less sleepy
398 QRect srcAreaUntranslated = srcArea.translated(m_srcImageOffset.toPoint());
399 QRect dstAreaUntranslated = dstArea.translated(m_dstImageOffset.toPoint());
400
401 QRect actualCopyArea = srcAreaUntranslated.intersected(dstAreaUntranslated);
402 srcArea = actualCopyArea.translated(-m_srcImageOffset.toPoint());
403 dstArea = actualCopyArea.translated(-m_dstImageOffset.toPoint());
404
405 int bytesPerPixel = m_srcImage.sizeInBytes()/m_srcImage.height()/m_srcImage.width();
406
407 int srcX = srcArea.left()*bytesPerPixel;
408 int dstX = dstArea.left()*bytesPerPixel;
409
410 for (int srcY = srcArea.top(); srcY <= srcArea.bottom(); ++srcY) {
411
412 int dstY = dstArea.top() + srcY - srcArea.top();
413 const uchar *srcLine = m_srcImage.constScanLine(srcY);
414 uchar *dstLine = m_dstImage.scanLine(dstY);
415 memcpy(dstLine + dstX, srcLine + srcX, srcArea.width()*bytesPerPixel);
416
417 }
418 }
419
420 void operator() (const QPolygonF &srcPolygon, const QPolygonF &dstPolygon) {
421 this->operator() (srcPolygon, dstPolygon, dstPolygon);
422 }
423
424 void operator() (const QPolygonF &srcPolygon, const QPolygonF &dstPolygon, const QPolygonF &clipDstPolygon) {
425 QRect boundRect = clipDstPolygon.boundingRect().toAlignedRect();
426
427 bool samePolygon = (m_dstImage.format() == m_srcImage.format())
428 && KisAlgebra2D::fuzzyPointCompare(srcPolygon, dstPolygon, m_epsilon)
429 && KisAlgebra2D::fuzzyPointCompare(srcPolygon, clipDstPolygon, m_epsilon);
430
431 if (samePolygon && KisAlgebra2D::isPolygonPixelAlignedRect(dstPolygon, m_epsilon)) {
432 QRect boundRect = dstPolygon.boundingRect().toAlignedRect();
433 fastCopyArea(boundRect);
434 return;
435 }
436
437 // provess previous rects so they are all processed
438 // in the same order as without any performance improvements
440
441 KisFourPointInterpolatorBackward interp(srcPolygon, dstPolygon);
442
443 for (int y = boundRect.top(); y <= boundRect.bottom(); y++) {
444 interp.setY(y);
445 for (int x = boundRect.left(); x <= boundRect.right(); x++) {
446
447 QPointF srcPoint(x, y);
448 if (clipDstPolygon.containsPoint(srcPoint, Qt::OddEvenFill)) {
449
450 interp.setX(srcPoint.x());
451 QPointF dstPoint = interp.getValue();
452
453 // about srcPoint/dstPoint hell please see a
454 // comment in PaintDevicePolygonOp::operator() ()
455
456 srcPoint -= m_dstImageOffset;
458
459 QPoint srcPointI = srcPoint.toPoint();
460 QPoint dstPointI = dstPoint.toPoint();
461
462 if (!m_dstImageRect.contains(srcPointI)) continue;
463 if (!m_srcImageRect.contains(dstPointI)) continue;
464
465 m_dstImage.setPixel(srcPointI, m_srcImage.pixel(dstPointI));
466 }
467 }
468 }
469
470#ifdef DEBUG_PAINTING_POLYGONS
471 QPainter gc(&m_dstImage);
472 gc.setPen(Qt::red);
473 gc.setOpacity(0.5);
474
475 gc.setBrush(Qt::green);
476 gc.drawPolygon(clipDstPolygon.translated(-m_dstImageOffset));
477
478 gc.setBrush(Qt::blue);
479 //gc.drawPolygon(dstPolygon.translated(-m_dstImageOffset));
480
481#endif /* DEBUG_PAINTING_POLYGONS */
482
483 }
484
486
488
489 for (QVector<QRect>::iterator it = m_rectsToCopy.begin(); it < end; it++) {
490 QRect areaToCopy = *it;
491 fastCopyArea(areaToCopy.adjusted(0, 0, 1, 1), false);
492 }
493
495 }
496
497 void finalize() {
499 }
500
507 inline void setCanMergeRects(bool canMergeRects) {
508 m_canMergeRects = canMergeRects;
509 }
510
511 const QImage &m_srcImage;
512 QImage &m_dstImage;
515
518
519 const qreal m_epsilon {0.001};
520
521private:
522 bool m_canMergeRects {false};
524};
525
526/*************************************************************/
527/* Iteration through precalculated grid */
528/*************************************************************/
529
536inline QVector<int> calculateCellIndexes(int col, int row, const QSize &gridSize)
537{
538 const int tl = col + row * gridSize.width();
539 const int tr = tl + 1;
540 const int bl = tl + gridSize.width();
541 const int br = bl + 1;
542
543 QVector<int> cellIndexes;
544 cellIndexes << tl;
545 cellIndexes << tr;
546 cellIndexes << br;
547 cellIndexes << bl;
548
549 return cellIndexes;
550}
551
552inline int pointToIndex(const QPoint &cellPt, const QSize &gridSize)
553{
554 return cellPt.x() +
555 cellPt.y() * gridSize.width();
556}
557
558namespace Private {
559 inline QPoint pointPolygonIndexToColRow(QPoint baseColRow, int index)
560 {
561 static QVector<QPoint> pointOffsets;
562 if (pointOffsets.isEmpty()) {
563 pointOffsets << QPoint(0,0);
564 pointOffsets << QPoint(1,0);
565 pointOffsets << QPoint(1,1);
566 pointOffsets << QPoint(0,1);
567 }
568
569 return baseColRow + pointOffsets[index];
570 }
571
573 int near;
574 int far;
575 };
576}
577
578inline QRect calculateCorrectSubGrid(QRect originalBoundsForGrid, int pixelPrecision, QRectF currentBounds, QSize gridSize) {
579
580 if (!QRectF(originalBoundsForGrid).intersects(currentBounds)) {
581 return QRect();
582 }
583
584 QPointF imaginaryGridStartF = QPoint(originalBoundsForGrid.x()/pixelPrecision, originalBoundsForGrid.y()/pixelPrecision)*pixelPrecision;
585
586 QPointF startPointB = currentBounds.topLeft() - imaginaryGridStartF;
587 QPoint startPointG = QPoint(startPointB.x()/pixelPrecision, startPointB.y()/pixelPrecision);
588 startPointG = QPoint(kisBoundFast(0, startPointG.x(), gridSize.width()), kisBoundFast(0, startPointG.y(), gridSize.height()));
589
590 QPointF endPointB = currentBounds.bottomRight() + QPoint(1, 1) - imaginaryGridStartF;
591 QPoint endPointG = QPoint(std::ceil(endPointB.x()/pixelPrecision), std::ceil(endPointB.y()/pixelPrecision)) + QPoint(1, 1);
592 QPoint endPointPotential = endPointG;
593
594 QPoint trueEndPoint = QPoint(kisBoundFast(0, endPointPotential.x(), gridSize.width()), kisBoundFast(0, endPointPotential.y(), gridSize.height()));
595
596 QPoint size = trueEndPoint - startPointG;
597
598 return QRect(startPointG, QSize(size.x(), size.y()));
599}
600
601inline QList<QRectF> cutOutSubgridFromBounds(QRect subGrid, QRect srcBounds, const QSize &gridSize, const QVector<QPointF> &originalPoints) {
602 if (subGrid.width() == 0 || subGrid.height() == 0) {
603 return QList<QRectF> {srcBounds};
604 }
605 QPoint topLeft = subGrid.topLeft();
606 QPoint bottomRight = subGrid.topLeft() + QPoint(subGrid.width() - 1, subGrid.height() - 1);
607
608 int topLeftIndex = pointToIndex(topLeft, gridSize);
609 int bottomRightIndex = pointToIndex(bottomRight, gridSize);
610
611 topLeftIndex = qMax(0, qMin(topLeftIndex, originalPoints.length() - 1));
612 bottomRightIndex = qMax(0, qMin(bottomRightIndex, originalPoints.length() - 1));
613
614 QPointF topLeftReal = originalPoints[topLeftIndex];
615 QPointF bottomRightReal = originalPoints[bottomRightIndex];
616 QRectF cutOut = QRectF(topLeftReal, bottomRightReal);
617
618 QList<QRectF> response;
619 // *-----------*
620 // | top |
621 // |-----------|
622 // | l |xxx| r |
623 // | e |xxx| i |
624 // | f |xxx| g |
625 // | t |xxx| h |
626 // | |xxx| t |
627 // |-----------|
628 // | bottom |
629 // *-----------*
630
631
632 QRectF top = QRectF(srcBounds.topLeft(), QPointF(srcBounds.right() + 1, topLeftReal.y()));
633 QRectF bottom = QRectF(QPointF(srcBounds.left(), bottomRightReal.y() + 1), srcBounds.bottomRight() + QPointF(1, 1));
634 QRectF left = QRectF(QPointF(srcBounds.left(), cutOut.top()), QPointF(cutOut.left(), cutOut.bottom() + 1));
635 QRectF right = QRectF(QPointF(cutOut.right() + 1, cutOut.top()), QPointF(srcBounds.right() + 1, cutOut.bottom() + 1));
636 QList<QRectF> rects = {top, left, right, bottom};
637 for (int i = 0; i < rects.length(); i++) {
638 if (!rects[i].isEmpty()) {
639 response << rects[i];
640 }
641 }
642 return response;
643
644}
645
646
647
648
649template <class IndexesOp>
650bool getOrthogonalPointApproximation(const QPoint &cellPt,
651 const QVector<QPointF> &originalPoints,
652 const QVector<QPointF> &transformedPoints,
653 IndexesOp indexesOp,
654 QPointF *srcPoint,
655 QPointF *dstPoint)
656{
657 QVector<Private::PointExtension> extensionPoints;
659
660 // left
661 if ((ext.near = indexesOp.tryGetValidIndex(cellPt + QPoint(-1, 0))) >= 0 &&
662 (ext.far = indexesOp.tryGetValidIndex(cellPt + QPoint(-2, 0))) >= 0) {
663
664 extensionPoints << ext;
665 }
666 // top
667 if ((ext.near = indexesOp.tryGetValidIndex(cellPt + QPoint(0, -1))) >= 0 &&
668 (ext.far = indexesOp.tryGetValidIndex(cellPt + QPoint(0, -2))) >= 0) {
669
670 extensionPoints << ext;
671 }
672 // right
673 if ((ext.near = indexesOp.tryGetValidIndex(cellPt + QPoint(1, 0))) >= 0 &&
674 (ext.far = indexesOp.tryGetValidIndex(cellPt + QPoint(2, 0))) >= 0) {
675
676 extensionPoints << ext;
677 }
678 // bottom
679 if ((ext.near = indexesOp.tryGetValidIndex(cellPt + QPoint(0, 1))) >= 0 &&
680 (ext.far = indexesOp.tryGetValidIndex(cellPt + QPoint(0, 2))) >= 0) {
681
682 extensionPoints << ext;
683 }
684
685 if (extensionPoints.isEmpty()) {
686 // top-left
687 if ((ext.near = indexesOp.tryGetValidIndex(cellPt + QPoint(-1, -1))) >= 0 &&
688 (ext.far = indexesOp.tryGetValidIndex(cellPt + QPoint(-2, -2))) >= 0) {
689
690 extensionPoints << ext;
691 }
692 // top-right
693 if ((ext.near = indexesOp.tryGetValidIndex(cellPt + QPoint(1, -1))) >= 0 &&
694 (ext.far = indexesOp.tryGetValidIndex(cellPt + QPoint(2, -2))) >= 0) {
695
696 extensionPoints << ext;
697 }
698 // bottom-right
699 if ((ext.near = indexesOp.tryGetValidIndex(cellPt + QPoint(1, 1))) >= 0 &&
700 (ext.far = indexesOp.tryGetValidIndex(cellPt + QPoint(2, 2))) >= 0) {
701
702 extensionPoints << ext;
703 }
704 // bottom-left
705 if ((ext.near = indexesOp.tryGetValidIndex(cellPt + QPoint(-1, 1))) >= 0 &&
706 (ext.far = indexesOp.tryGetValidIndex(cellPt + QPoint(-2, 2))) >= 0) {
707
708 extensionPoints << ext;
709 }
710 }
711
712 if (extensionPoints.isEmpty()) {
713 return false;
714 }
715
716 int numResultPoints = 0;
717 *srcPoint = indexesOp.getSrcPointForce(cellPt);
718 *dstPoint = QPointF();
719
720 Q_FOREACH (const Private::PointExtension &ext, extensionPoints) {
721 QPointF near = transformedPoints[ext.near];
722 QPointF far = transformedPoints[ext.far];
723
724 QPointF nearSrc = originalPoints[ext.near];
725 QPointF farSrc = originalPoints[ext.far];
726
727 QPointF base1 = nearSrc - farSrc;
728 QPointF base2 = near - far;
729
730 QPointF pt = near +
731 KisAlgebra2D::transformAsBase(*srcPoint - nearSrc, base1, base2);
732
733 *dstPoint += pt;
734 numResultPoints++;
735 }
736
737 *dstPoint /= numResultPoints;
738
739 return true;
740}
741
742template <class PolygonOp, class IndexesOp>
744
745 static inline bool tryProcessPolygon(int col, int row,
746 int numExistingPoints,
747 PolygonOp &polygonOp,
748 IndexesOp &indexesOp,
749 const QVector<int> &polygonPoints,
750 const QVector<QPointF> &originalPoints,
751 const QVector<QPointF> &transformedPoints)
752 {
753 if (numExistingPoints >= 4) return false;
754 if (numExistingPoints == 0) return true;
755
756 QPolygonF srcPolygon;
757 QPolygonF dstPolygon;
758
759 for (int i = 0; i < 4; i++) {
760 const int index = polygonPoints[i];
761
762 if (index >= 0) {
763 srcPolygon << originalPoints[index];
764 dstPolygon << transformedPoints[index];
765 } else {
766 QPoint cellPt = Private::pointPolygonIndexToColRow(QPoint(col, row), i);
767 QPointF srcPoint;
768 QPointF dstPoint;
769 bool result =
771 originalPoints,
772 transformedPoints,
773 indexesOp,
774 &srcPoint,
775 &dstPoint);
776
777 if (!result) {
778 //dbgKrita << "*NOT* found any valid point" << allSrcPoints[pointToIndex(cellPt)] << "->" << ppVar(pt);
779 break;
780 } else {
781 srcPolygon << srcPoint;
782 dstPolygon << dstPoint;
783 }
784 }
785 }
786
787 if (dstPolygon.size() == 4) {
788 QPolygonF srcClipPolygon(srcPolygon.intersected(indexesOp.srcCropPolygon()));
789
790 KisFourPointInterpolatorForward forwardTransform(srcPolygon, dstPolygon);
791 for (int i = 0; i < srcClipPolygon.size(); i++) {
792 const QPointF newPt = forwardTransform.map(srcClipPolygon[i]);
793 srcClipPolygon[i] = newPt;
794 }
795
796 polygonOp(srcPolygon, dstPolygon, srcClipPolygon);
797 }
798
799 return true;
800 }
801};
802
803template <class PolygonOp, class IndexesOp>
805
806 static inline bool tryProcessPolygon(int col, int row,
807 int numExistingPoints,
808 PolygonOp &polygonOp,
809 IndexesOp &indexesOp,
810 const QVector<int> &polygonPoints,
811 const QVector<QPointF> &originalPoints,
812 const QVector<QPointF> &transformedPoints)
813 {
814 Q_UNUSED(col);
815 Q_UNUSED(row);
816 Q_UNUSED(polygonOp);
817 Q_UNUSED(indexesOp);
818 Q_UNUSED(polygonPoints);
819 Q_UNUSED(originalPoints);
820 Q_UNUSED(transformedPoints);
821
822 KIS_ASSERT_RECOVER_NOOP(numExistingPoints == 4);
823 return false;
824 }
825};
826
828
829 RegularGridIndexesOp(const QSize &gridSize)
830 : m_gridSize(gridSize)
831 {
832 }
833
834 inline QVector<int> calculateMappedIndexes(int col, int row,
835 int *numExistingPoints) const {
836
837 *numExistingPoints = 4;
838 QVector<int> cellIndexes =
840
841 return cellIndexes;
842 }
843
844 inline int tryGetValidIndex(const QPoint &cellPt) const {
845 Q_UNUSED(cellPt);
846
847 KIS_ASSERT_RECOVER_NOOP(0 && "Not applicable");
848 return -1;
849 }
850
851 inline QPointF getSrcPointForce(const QPoint &cellPt) const {
852 Q_UNUSED(cellPt);
853
854 KIS_ASSERT_RECOVER_NOOP(0 && "Not applicable");
855 return QPointF();
856 }
857
858 inline const QPolygonF srcCropPolygon() const {
859 KIS_ASSERT_RECOVER_NOOP(0 && "Not applicable");
860 return QPolygonF();
861 }
862
864};
865
874inline void adjustAlignedPolygon(QPolygonF &polygon)
875{
876 static const qreal eps = 1e-5;
877 static const QPointF p1(eps, 0.0);
878 static const QPointF p2(eps, eps);
879 static const QPointF p3(0.0, eps);
880
881 polygon[1] += p1;
882 polygon[2] += p2;
883 polygon[3] += p3;
884}
885
886template <class IndexesOp>
887bool canProcessRectsInRandomOrder(IndexesOp &indexesOp, const QVector<QPointF> &transformedPoints, QSize grid) {
888 return canProcessRectsInRandomOrder(indexesOp, transformedPoints, QRect(QPoint(0, 0), grid));
889}
890
891template <class IndexesOp>
892bool canProcessRectsInRandomOrder(IndexesOp &indexesOp, const QVector<QPointF> &transformedPoints, QRect subgrid) {
893 QVector<int> polygonPoints(4);
894 QPoint startPoint = subgrid.topLeft();
895 QPoint endPoint = subgrid.bottomRight();
896
897 for (int row = startPoint.y(); row < endPoint.y(); row++) {
898 for (int col = startPoint.x(); col < endPoint.x(); col++) {
899 int numExistingPoints = 0;
900
901 polygonPoints = indexesOp.calculateMappedIndexes(col, row, &numExistingPoints);
902
903 QPolygonF dstPolygon;
904
905 for (int i = 0; i < polygonPoints.count(); i++) {
906 const int index = polygonPoints[i];
907 dstPolygon << transformedPoints[index];
908 }
909
910
911 adjustAlignedPolygon(dstPolygon);
912
913
914 if (!KisAlgebra2D::isPolygonTrulyConvex(dstPolygon)) {
915 return false;
916 }
917
918 }
919 }
920 return true;
921}
922
923
924
925template <template <class PolygonOp, class IndexesOp> class IncompletePolygonPolicy,
926 class PolygonOp,
927 class IndexesOp>
928void iterateThroughGrid(PolygonOp &polygonOp,
929 IndexesOp &indexesOp,
930 const QSize &gridSize,
931 const QVector<QPointF> &originalPoints,
932 const QVector<QPointF> &transformedPoints)
933{
934 iterateThroughGrid<IncompletePolygonPolicy, PolygonOp, IndexesOp>(polygonOp, indexesOp, gridSize, originalPoints, transformedPoints, QRect(QPoint(0, 0), gridSize));
935}
936
937template <template <class PolygonOp, class IndexesOp> class IncompletePolygonPolicy,
938 class PolygonOp,
939 class IndexesOp>
940void iterateThroughGrid(PolygonOp &polygonOp,
941 IndexesOp &indexesOp,
942 const QSize &gridSize,
943 const QVector<QPointF> &originalPoints,
944 const QVector<QPointF> &transformedPoints,
945 const QRect subGrid)
946{
947 QVector<int> polygonPoints(4);
948 QPoint startPoint = subGrid.topLeft();
949 QPoint endPoint = subGrid.bottomRight(); // it's weird but bottomRight on QRect point does give us one unit of margin on both x and y
950 // when start is on (0, 0), and size is (500, 500), bottomRight is on (499, 499)
951 // but remember that it also only needs a top left corner of the polygon
952
953 KIS_SAFE_ASSERT_RECOVER(startPoint.x() >= 0 && startPoint.y() >= 0 && endPoint.x() <= gridSize.width() - 1 && endPoint.y() <= gridSize.height() - 1) {
954 startPoint = QPoint(qMax(startPoint.x(), 0), qMax(startPoint.y(), 0));
955 endPoint = QPoint(qMin(endPoint.x(), gridSize.width() - 1), qMin(startPoint.y(), gridSize.height() - 1));
956 }
957
958 for (int row = startPoint.y(); row < endPoint.y(); row++) {
959 for (int col = startPoint.x(); col < endPoint.x(); col++) {
960 int numExistingPoints = 0;
961
962 polygonPoints = indexesOp.calculateMappedIndexes(col, row, &numExistingPoints);
963
965 tryProcessPolygon(col, row,
966 numExistingPoints,
967 polygonOp,
968 indexesOp,
969 polygonPoints,
970 originalPoints,
971 transformedPoints)) {
972
973 QPolygonF srcPolygon;
974 QPolygonF dstPolygon;
975
976 for (int i = 0; i < 4; i++) {
977 const int index = polygonPoints[i];
978 srcPolygon << originalPoints[index];
979 dstPolygon << transformedPoints[index];
980 }
981
982 adjustAlignedPolygon(srcPolygon);
983 adjustAlignedPolygon(dstPolygon);
984
985 polygonOp(srcPolygon, dstPolygon);
986 }
987 }
988 }
989
990 polygonOp.finalize();
991}
992
993}
994
995#endif /* __KIS_GRID_INTERPOLATION_TOOLS_H */
QPointF dstPoint
QPointF p2
QPointF p3
QPointF p1
quint32 pixelSize() const
const KoColorSpace * colorSpace() const
KisRandomSubAccessorSP createRandomSubAccessor() const
static void copyAreaOptimized(const QPoint &dstPt, KisPaintDeviceSP src, KisPaintDeviceSP dst, const QRect &originalSrcRect)
void sampledOldRawData(quint8 *dst)
void moveTo(qreal x, qreal y)
static QVector< QRect >::iterator mergeSparseRects(QVector< QRect >::iterator beginIt, QVector< QRect >::iterator endIt)
merge a set of rectangles into a smaller set of bigger rectangles
ALWAYS_INLINE quint8 * rawData()
ALWAYS_INLINE int x() const
ALWAYS_INLINE const quint8 * oldRawData() const
ALWAYS_INLINE int y() const
virtual void fromQColor(const QColor &color, quint8 *dst) const =0
#define KIS_SAFE_ASSERT_RECOVER(cond)
Definition kis_assert.h:126
#define KIS_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:97
#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:130
const qreal eps
qreal interp(qreal r, qreal a, qreal b)
private functions
constexpr const T & kisBoundFast(const T &min, const T &val, const T &max)
Definition kis_global.h:37
QPoint pointPolygonIndexToColRow(QPoint baseColRow, int index)
void iterateThroughGrid(PolygonOp &polygonOp, IndexesOp &indexesOp, const QSize &gridSize, const QVector< QPointF > &originalPoints, const QVector< QPointF > &transformedPoints)
bool canProcessRectsInRandomOrder(IndexesOp &indexesOp, const QVector< QPointF > &transformedPoints, QSize grid)
QList< QRectF > cutOutSubgridFromBounds(QRect subGrid, QRect srcBounds, const QSize &gridSize, const QVector< QPointF > &originalPoints)
void adjustAlignedPolygon(QPolygonF &polygon)
QVector< int > calculateCellIndexes(int col, int row, const QSize &gridSize)
QRect calculateCorrectSubGrid(QRect originalBoundsForGrid, int pixelPrecision, QRectF currentBounds, QSize gridSize)
int pointToIndex(const QPoint &cellPt, const QSize &gridSize)
void processGrid(ProcessCell &cellOp, const QRect &srcBounds, const int pixelPrecision)
int calcGridDimension(int start, int end, const int pixelPrecision)
QSize calcGridSize(const QRect &srcBounds, const int pixelPrecision)
bool getOrthogonalPointApproximation(const QPoint &cellPt, const QVector< QPointF > &originalPoints, const QVector< QPointF > &transformedPoints, IndexesOp indexesOp, QPointF *srcPoint, QPointF *dstPoint)
bool isPolygonPixelAlignedRect(const Polygon &poly, Difference tolerance)
T wrapValue(T value, T wrapBounds)
bool isPolygonTrulyConvex(const QVector< T > &polygon)
QPointF transformAsBase(const QPointF &pt, const QPointF &base1, const QPointF &base2)
bool fuzzyPointCompare(const QPointF &p1, const QPointF &p2)
static bool tryProcessPolygon(int col, int row, int numExistingPoints, PolygonOp &polygonOp, IndexesOp &indexesOp, const QVector< int > &polygonPoints, const QVector< QPointF > &originalPoints, const QVector< QPointF > &transformedPoints)
CellOp(ProcessPolygon &_polygonOp, ForwardTransform &_transformOp)
void processPoint(int col, int row, int prevCol, int prevRow, int colIndex, int rowIndex)
static bool tryProcessPolygon(int col, int row, int numExistingPoints, PolygonOp &polygonOp, IndexesOp &indexesOp, const QVector< int > &polygonPoints, const QVector< QPointF > &originalPoints, const QVector< QPointF > &transformedPoints)
void operator()(const QPolygonF &srcPolygon, const QPolygonF &dstPolygon)
PaintDevicePolygonOp(KisPaintDeviceSP srcDev, KisPaintDeviceSP dstDev)
void fastCopyArea(QRect areaToCopy, bool lazy)
void operator()(const QPolygonF &srcPolygon, const QPolygonF &dstPolygon)
QImagePolygonOp(const QImage &srcImage, QImage &dstImage, const QPointF &srcImageOffset, const QPointF &dstImageOffset)
QVector< int > calculateMappedIndexes(int col, int row, int *numExistingPoints) const
QPointF getSrcPointForce(const QPoint &cellPt) const