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
174
175 void fastCopyArea(QPolygonF areaToCopy) {
176 QRect boundRect = areaToCopy.boundingRect().toAlignedRect();
177 if (boundRect.isEmpty()) return;
178
179 bool isItRect = KisAlgebra2D::isPolygonRect(areaToCopy, m_epsilon); // no need for lower tolerance
180 if (isItRect) {
181 fastCopyArea(boundRect);
182 return;
183 }
184
185 KisSequentialIterator dstIt(m_dstDev, boundRect);
186 KisSequentialIterator srcIt(m_srcDev, boundRect);
187
188 // this can possibly be optimized with scanlining the polygon
189 // (use intersectLineConvexPolygon to get a line at every height)
190 // but it doesn't matter much because in the vast majority of cases
191 // it should go straight to the rect area copying
192
193 while (dstIt.nextPixel() && srcIt.nextPixel()) {
194 if (areaToCopy.containsPoint(QPoint(dstIt.x(), dstIt.y()), Qt::OddEvenFill)) {
195 memcpy(dstIt.rawData(), srcIt.oldRawData(), m_dstDev->pixelSize());
196 }
197 }
198 }
199
200 void fastCopyArea(QRect areaToCopy) {
201 fastCopyArea(areaToCopy, m_canMergeRects);
202 }
203
204 void fastCopyArea(QRect areaToCopy, bool lazy) {
205 if (lazy) {
206 m_rectsToCopy.append(areaToCopy.adjusted(0, 0, -1, -1));
207 } else {
208 KisPainter::copyAreaOptimized(areaToCopy.topLeft(), m_srcDev, m_dstDev, areaToCopy);
209 }
210 }
211
212 void operator() (const QPolygonF &srcPolygon, const QPolygonF &dstPolygon) {
213 operator() (srcPolygon, dstPolygon, dstPolygon);
214 }
215
216 void operator() (const QPolygonF &srcPolygon, const QPolygonF &dstPolygon, const QPolygonF &clipDstPolygon) {
217 QRect boundRect = clipDstPolygon.boundingRect().toAlignedRect();
218 if (boundRect.isEmpty()) return;
219
220 bool samePolygon = (m_dstDev->colorSpace() == m_srcDev->colorSpace()) && KisAlgebra2D::fuzzyPointCompare(srcPolygon, dstPolygon, m_epsilon);
221
222 if (samePolygon) {
223 // we can use clipDstPolygon here, because it will be smaller than dstPolygon and srcPolygon, because of how IncompletePolicy works
224 // we could also calculate intersection here if we're worried whether that fact is always true
225 fastCopyArea(clipDstPolygon);
226 return;
227 }
228
229
230 KisSequentialIterator dstIt(m_dstDev, boundRect);
232
233 KisFourPointInterpolatorBackward interp(srcPolygon, dstPolygon);
234
240 if (interp.isValid(0.1)) {
241 int y = boundRect.top();
242 interp.setY(y);
243
244 while (dstIt.nextPixel()) {
245 int newY = dstIt.y();
246
247 if (y != newY) {
248 y = newY;
249 interp.setY(y);
250 }
251
252 QPointF srcPoint(dstIt.x(), y);
253
254 if (clipDstPolygon.containsPoint(srcPoint, Qt::OddEvenFill)) {
255
256 interp.setX(srcPoint.x());
257 QPointF dstPoint = interp.getValue();
258
259 // brain-blowing part:
260 //
261 // since the interpolator does the inverted
262 // transformation we read data from "dstPoint"
263 // (which is non-transformed) and write it into
264 // "srcPoint" (which is transformed position)
265
266 srcAcc->moveTo(dstPoint);
267 quint8* rawData = dstIt.rawData();
268 srcAcc->sampledOldRawData(rawData);
269 }
270 }
271
272 } else {
273 srcAcc->moveTo(interp.fallbackSourcePoint());
274
275 while (dstIt.nextPixel()) {
276 QPointF srcPoint(dstIt.x(), dstIt.y());
277
278 if (clipDstPolygon.containsPoint(srcPoint, Qt::OddEvenFill)) {
279 srcAcc->sampledOldRawData(dstIt.rawData());
280 }
281 }
282 }
283
284 }
285
286 void finalize() {
287
289
290 for (QVector<QRect>::iterator it = m_rectsToCopy.begin(); it < end; it++) {
291 QRect areaToCopy = *it;
292 fastCopyArea(areaToCopy.adjusted(0, 0, 1, 1), false);
293 }
295 }
296
297 inline void setCanMergeRects(bool newCanMergeRects) {
298 m_canMergeRects = newCanMergeRects;
299 }
300
303 const qreal m_epsilon {0.1};
304
305private:
306 bool m_canMergeRects {true};
308
309};
310
312{
313 QImagePolygonOp(const QImage &srcImage, QImage &dstImage,
314 const QPointF &srcImageOffset,
315 const QPointF &dstImageOffset)
316 : m_srcImage(srcImage), m_dstImage(dstImage),
317 m_srcImageOffset(srcImageOffset),
318 m_dstImageOffset(dstImageOffset),
321 {
322 }
323
324 void fastCopyArea(QPolygonF areaToCopy) {
325 QRect boundRect = areaToCopy.boundingRect().toAlignedRect();
326
327 if (boundRect.isEmpty()) return;
328
329 bool isItRect = KisAlgebra2D::isPolygonRect(areaToCopy, m_epsilon); // no need for lower tolerance
330 if (isItRect) {
331 fastCopyArea(boundRect);
332 return;
333 }
334
335 // this can possibly be optimized with scanlining the polygon
336 // (use intersectLineConvexPolygon to get a line at every height)
337 // but it doesn't matter much because in the vast majority of cases
338 // it should go straight to the rect area copying
339
340 for (int y = boundRect.top(); y <= boundRect.bottom(); y++) {
341 for (int x = boundRect.left(); x <= boundRect.right(); x++) {
342 QPointF dstPoint = QPointF(x, y);
343 QPointF srcPoint = dstPoint;
344
345 if (areaToCopy.containsPoint(srcPoint, Qt::OddEvenFill)) {
346
347 // about srcPoint/dstPoint hell please see a
348 // comment in PaintDevicePolygonOp::operator() ()
349
350 srcPoint -= m_dstImageOffset;
352
353 QPoint srcPointI = srcPoint.toPoint();
354 QPoint dstPointI = dstPoint.toPoint();
355
356 if (!m_dstImageRect.contains(srcPointI)) continue;
357 if (!m_srcImageRect.contains(dstPointI)) continue;
358
359 m_dstImage.setPixel(srcPointI, m_srcImage.pixel(dstPointI));
360 }
361
362 }
363 }
364
365 }
366
367 void fastCopyArea(QRect areaToCopy) {
368 fastCopyArea(areaToCopy, m_canMergeRects);
369 }
370
371 void fastCopyArea(QRect areaToCopy, bool lazy) {
372 if (lazy) {
373 m_rectsToCopy.append(areaToCopy.adjusted(0, 0, -1, -1));
374 return;
375 }
376
377 // only handling saved offsets
378 QRect srcArea = areaToCopy.translated(-m_srcImageOffset.toPoint());
379 QRect dstArea = areaToCopy.translated(-m_dstImageOffset.toPoint());
380
381 srcArea = srcArea.intersected(m_srcImageRect);
382 dstArea = dstArea.intersected(m_dstImageRect);
383
384 // it might look pointless but it cuts off unneeded areas on both rects based on where they end up
385 // since *I know* they are the same rectangle before translation
386 // TODO: I'm pretty sure this logic is correct, but let's check it when I'm less sleepy
387 QRect srcAreaUntranslated = srcArea.translated(m_srcImageOffset.toPoint());
388 QRect dstAreaUntranslated = dstArea.translated(m_dstImageOffset.toPoint());
389
390 QRect actualCopyArea = srcAreaUntranslated.intersected(dstAreaUntranslated);
391 srcArea = actualCopyArea.translated(-m_srcImageOffset.toPoint());
392 dstArea = actualCopyArea.translated(-m_dstImageOffset.toPoint());
393
394 int bytesPerPixel = m_srcImage.sizeInBytes()/m_srcImage.height()/m_srcImage.width();
395
396 int srcX = srcArea.left()*bytesPerPixel;
397 int dstX = dstArea.left()*bytesPerPixel;
398
399 for (int srcY = srcArea.top(); srcY <= srcArea.bottom(); ++srcY) {
400
401 int dstY = dstArea.top() + srcY - srcArea.top();
402 const uchar *srcLine = m_srcImage.constScanLine(srcY);
403 uchar *dstLine = m_dstImage.scanLine(dstY);
404 memcpy(dstLine + dstX, srcLine + srcX, srcArea.width()*bytesPerPixel);
405
406 }
407 }
408
409 void operator() (const QPolygonF &srcPolygon, const QPolygonF &dstPolygon) {
410 this->operator() (srcPolygon, dstPolygon, dstPolygon);
411 }
412
413 void operator() (const QPolygonF &srcPolygon, const QPolygonF &dstPolygon, const QPolygonF &clipDstPolygon) {
414 QRect boundRect = clipDstPolygon.boundingRect().toAlignedRect();
415
416 bool samePolygon = m_dstImage.format() == m_srcImage.format() && KisAlgebra2D::fuzzyPointCompare(srcPolygon, dstPolygon, m_epsilon);
417
418 if (samePolygon) {
419 // we can use clipDstPolygon here, because it will be smaller than dstPolygon and srcPolygon, because of how IncompletePolicy works
420 // we could also calculate intersection here if we're worried whether that fact is always true
421 fastCopyArea(clipDstPolygon);
422 return;
423 }
424
425 KisFourPointInterpolatorBackward interp(srcPolygon, dstPolygon);
426
427 for (int y = boundRect.top(); y <= boundRect.bottom(); y++) {
428 interp.setY(y);
429 for (int x = boundRect.left(); x <= boundRect.right(); x++) {
430
431 QPointF srcPoint(x, y);
432 if (clipDstPolygon.containsPoint(srcPoint, Qt::OddEvenFill)) {
433
434 interp.setX(srcPoint.x());
435 QPointF dstPoint = interp.getValue();
436
437 // about srcPoint/dstPoint hell please see a
438 // comment in PaintDevicePolygonOp::operator() ()
439
440 srcPoint -= m_dstImageOffset;
442
443 QPoint srcPointI = srcPoint.toPoint();
444 QPoint dstPointI = dstPoint.toPoint();
445
446 if (!m_dstImageRect.contains(srcPointI)) continue;
447 if (!m_srcImageRect.contains(dstPointI)) continue;
448
449 m_dstImage.setPixel(srcPointI, m_srcImage.pixel(dstPointI));
450 }
451 }
452 }
453
454#ifdef DEBUG_PAINTING_POLYGONS
455 QPainter gc(&m_dstImage);
456 gc.setPen(Qt::red);
457 gc.setOpacity(0.5);
458
459 gc.setBrush(Qt::green);
460 gc.drawPolygon(clipDstPolygon.translated(-m_dstImageOffset));
461
462 gc.setBrush(Qt::blue);
463 //gc.drawPolygon(dstPolygon.translated(-m_dstImageOffset));
464
465#endif /* DEBUG_PAINTING_POLYGONS */
466
467 }
468
469 void finalize() {
470
472
473 for (QVector<QRect>::iterator it = m_rectsToCopy.begin(); it < end; it++) {
474 QRect areaToCopy = *it;
475 fastCopyArea(areaToCopy.adjusted(0, 0, 1, 1), false);
476 }
477
479 }
480
481 inline void setCanMergeRects(bool canMergeRects) {
482 m_canMergeRects = canMergeRects;
483 }
484
485
486 const QImage &m_srcImage;
487 QImage &m_dstImage;
490
493
494 const qreal m_epsilon {0.1};
495
496private:
497 bool m_canMergeRects {true};
499};
500
501/*************************************************************/
502/* Iteration through precalculated grid */
503/*************************************************************/
504
511inline QVector<int> calculateCellIndexes(int col, int row, const QSize &gridSize)
512{
513 const int tl = col + row * gridSize.width();
514 const int tr = tl + 1;
515 const int bl = tl + gridSize.width();
516 const int br = bl + 1;
517
518 QVector<int> cellIndexes;
519 cellIndexes << tl;
520 cellIndexes << tr;
521 cellIndexes << br;
522 cellIndexes << bl;
523
524 return cellIndexes;
525}
526
527inline int pointToIndex(const QPoint &cellPt, const QSize &gridSize)
528{
529 return cellPt.x() +
530 cellPt.y() * gridSize.width();
531}
532
533namespace Private {
534 inline QPoint pointPolygonIndexToColRow(QPoint baseColRow, int index)
535 {
536 static QVector<QPoint> pointOffsets;
537 if (pointOffsets.isEmpty()) {
538 pointOffsets << QPoint(0,0);
539 pointOffsets << QPoint(1,0);
540 pointOffsets << QPoint(1,1);
541 pointOffsets << QPoint(0,1);
542 }
543
544 return baseColRow + pointOffsets[index];
545 }
546
548 int near;
549 int far;
550 };
551}
552
553inline QRect calculateCorrectSubGrid(QRect originalBoundsForGrid, int pixelPrecision, QRectF currentBounds, QSize gridSize) {
554
555 if (!QRectF(originalBoundsForGrid).intersects(currentBounds)) {
556 return QRect();
557 }
558
559 QPoint offsetB = originalBoundsForGrid.topLeft();
560
561 QPointF startPointB = currentBounds.topLeft() - offsetB;
562 QPoint startPointG = QPoint(startPointB.x()/pixelPrecision, startPointB.y()/pixelPrecision);
563 startPointG = QPoint(kisBoundFast(0, startPointG.x(), gridSize.width()), kisBoundFast(0, startPointG.y(), gridSize.height()));
564
565 QPointF endPointB = currentBounds.bottomRight() + QPoint(1, 1) - offsetB; // *true* bottomRight
566 QPoint endPointG = QPoint(std::ceil(endPointB.x()/pixelPrecision), std::ceil(endPointB.y()/pixelPrecision));
567 QPoint endPointPotential = endPointG;
568
569 QPoint trueEndPoint = QPoint(kisBoundFast(0, endPointPotential.x(), gridSize.width()), kisBoundFast(0, endPointPotential.y(), gridSize.height()));
570
571 QPoint size = trueEndPoint - startPointG;
572
573 return QRect(startPointG, QSize(size.x(), size.y()));
574}
575
576inline QList<QRectF> cutOutSubgridFromBounds(QRect subGrid, QRect srcBounds, const QSize &gridSize, const QVector<QPointF> &originalPoints) {
577 if (subGrid.width() == 0 || subGrid.height() == 0) {
578 return QList<QRectF> {srcBounds};
579 }
580 QPoint topLeft = subGrid.topLeft();
581 QPoint bottomRight = subGrid.topLeft() + QPoint(subGrid.width() - 1, subGrid.height() - 1);
582
583 int topLeftIndex = pointToIndex(topLeft, gridSize);
584 int bottomRightIndex = pointToIndex(bottomRight, gridSize);
585
586 topLeftIndex = qMax(0, qMin(topLeftIndex, originalPoints.length() - 1));
587 bottomRightIndex = qMax(0, qMin(bottomRightIndex, originalPoints.length() - 1));
588
589 QPointF topLeftReal = originalPoints[topLeftIndex];
590 QPointF bottomRightReal = originalPoints[bottomRightIndex];
591 QRectF cutOut = QRectF(topLeftReal, bottomRightReal);
592
593 QList<QRectF> response;
594 // *-----------*
595 // | top |
596 // |-----------|
597 // | l |xxx| r |
598 // | e |xxx| i |
599 // | f |xxx| g |
600 // | t |xxx| h |
601 // | |xxx| t |
602 // |-----------|
603 // | bottom |
604 // *-----------*
605
606
607 QRectF top = QRectF(srcBounds.topLeft(), QPointF(srcBounds.right() + 1, topLeftReal.y()));
608 QRectF bottom = QRectF(QPointF(srcBounds.left(), bottomRightReal.y()), srcBounds.bottomRight() + QPointF(1, 1));
609 QRectF left = QRectF(QPointF(srcBounds.left(), cutOut.top()), QPointF(cutOut.left(), cutOut.bottom()));
610 QRectF right = QRectF(QPointF(cutOut.right(), cutOut.top()), QPointF(srcBounds.right() + 1, cutOut.bottom()));
611 QList<QRectF> rects = {top, left, right, bottom};
612 for (int i = 0; i < rects.length(); i++) {
613 if (!rects[i].isEmpty()) {
614 response << rects[i];
615 }
616 }
617 return response;
618
619}
620
621
622
623
624template <class IndexesOp>
625bool getOrthogonalPointApproximation(const QPoint &cellPt,
626 const QVector<QPointF> &originalPoints,
627 const QVector<QPointF> &transformedPoints,
628 IndexesOp indexesOp,
629 QPointF *srcPoint,
630 QPointF *dstPoint)
631{
632 QVector<Private::PointExtension> extensionPoints;
634
635 // left
636 if ((ext.near = indexesOp.tryGetValidIndex(cellPt + QPoint(-1, 0))) >= 0 &&
637 (ext.far = indexesOp.tryGetValidIndex(cellPt + QPoint(-2, 0))) >= 0) {
638
639 extensionPoints << ext;
640 }
641 // top
642 if ((ext.near = indexesOp.tryGetValidIndex(cellPt + QPoint(0, -1))) >= 0 &&
643 (ext.far = indexesOp.tryGetValidIndex(cellPt + QPoint(0, -2))) >= 0) {
644
645 extensionPoints << ext;
646 }
647 // right
648 if ((ext.near = indexesOp.tryGetValidIndex(cellPt + QPoint(1, 0))) >= 0 &&
649 (ext.far = indexesOp.tryGetValidIndex(cellPt + QPoint(2, 0))) >= 0) {
650
651 extensionPoints << ext;
652 }
653 // bottom
654 if ((ext.near = indexesOp.tryGetValidIndex(cellPt + QPoint(0, 1))) >= 0 &&
655 (ext.far = indexesOp.tryGetValidIndex(cellPt + QPoint(0, 2))) >= 0) {
656
657 extensionPoints << ext;
658 }
659
660 if (extensionPoints.isEmpty()) {
661 // top-left
662 if ((ext.near = indexesOp.tryGetValidIndex(cellPt + QPoint(-1, -1))) >= 0 &&
663 (ext.far = indexesOp.tryGetValidIndex(cellPt + QPoint(-2, -2))) >= 0) {
664
665 extensionPoints << ext;
666 }
667 // top-right
668 if ((ext.near = indexesOp.tryGetValidIndex(cellPt + QPoint(1, -1))) >= 0 &&
669 (ext.far = indexesOp.tryGetValidIndex(cellPt + QPoint(2, -2))) >= 0) {
670
671 extensionPoints << ext;
672 }
673 // bottom-right
674 if ((ext.near = indexesOp.tryGetValidIndex(cellPt + QPoint(1, 1))) >= 0 &&
675 (ext.far = indexesOp.tryGetValidIndex(cellPt + QPoint(2, 2))) >= 0) {
676
677 extensionPoints << ext;
678 }
679 // bottom-left
680 if ((ext.near = indexesOp.tryGetValidIndex(cellPt + QPoint(-1, 1))) >= 0 &&
681 (ext.far = indexesOp.tryGetValidIndex(cellPt + QPoint(-2, 2))) >= 0) {
682
683 extensionPoints << ext;
684 }
685 }
686
687 if (extensionPoints.isEmpty()) {
688 return false;
689 }
690
691 int numResultPoints = 0;
692 *srcPoint = indexesOp.getSrcPointForce(cellPt);
693 *dstPoint = QPointF();
694
695 Q_FOREACH (const Private::PointExtension &ext, extensionPoints) {
696 QPointF near = transformedPoints[ext.near];
697 QPointF far = transformedPoints[ext.far];
698
699 QPointF nearSrc = originalPoints[ext.near];
700 QPointF farSrc = originalPoints[ext.far];
701
702 QPointF base1 = nearSrc - farSrc;
703 QPointF base2 = near - far;
704
705 QPointF pt = near +
706 KisAlgebra2D::transformAsBase(*srcPoint - nearSrc, base1, base2);
707
708 *dstPoint += pt;
709 numResultPoints++;
710 }
711
712 *dstPoint /= numResultPoints;
713
714 return true;
715}
716
717template <class PolygonOp, class IndexesOp>
719
720 static inline bool tryProcessPolygon(int col, int row,
721 int numExistingPoints,
722 PolygonOp &polygonOp,
723 IndexesOp &indexesOp,
724 const QVector<int> &polygonPoints,
725 const QVector<QPointF> &originalPoints,
726 const QVector<QPointF> &transformedPoints)
727 {
728 if (numExistingPoints >= 4) return false;
729 if (numExistingPoints == 0) return true;
730
731 QPolygonF srcPolygon;
732 QPolygonF dstPolygon;
733
734 for (int i = 0; i < 4; i++) {
735 const int index = polygonPoints[i];
736
737 if (index >= 0) {
738 srcPolygon << originalPoints[index];
739 dstPolygon << transformedPoints[index];
740 } else {
741 QPoint cellPt = Private::pointPolygonIndexToColRow(QPoint(col, row), i);
742 QPointF srcPoint;
743 QPointF dstPoint;
744 bool result =
746 originalPoints,
747 transformedPoints,
748 indexesOp,
749 &srcPoint,
750 &dstPoint);
751
752 if (!result) {
753 //dbgKrita << "*NOT* found any valid point" << allSrcPoints[pointToIndex(cellPt)] << "->" << ppVar(pt);
754 break;
755 } else {
756 srcPolygon << srcPoint;
757 dstPolygon << dstPoint;
758 }
759 }
760 }
761
762 if (dstPolygon.size() == 4) {
763 QPolygonF srcClipPolygon(srcPolygon.intersected(indexesOp.srcCropPolygon()));
764
765 KisFourPointInterpolatorForward forwardTransform(srcPolygon, dstPolygon);
766 for (int i = 0; i < srcClipPolygon.size(); i++) {
767 const QPointF newPt = forwardTransform.map(srcClipPolygon[i]);
768 srcClipPolygon[i] = newPt;
769 }
770
771 polygonOp(srcPolygon, dstPolygon, srcClipPolygon);
772 }
773
774 return true;
775 }
776};
777
778template <class PolygonOp, class IndexesOp>
780
781 static inline bool tryProcessPolygon(int col, int row,
782 int numExistingPoints,
783 PolygonOp &polygonOp,
784 IndexesOp &indexesOp,
785 const QVector<int> &polygonPoints,
786 const QVector<QPointF> &originalPoints,
787 const QVector<QPointF> &transformedPoints)
788 {
789 Q_UNUSED(col);
790 Q_UNUSED(row);
791 Q_UNUSED(polygonOp);
792 Q_UNUSED(indexesOp);
793 Q_UNUSED(polygonPoints);
794 Q_UNUSED(originalPoints);
795 Q_UNUSED(transformedPoints);
796
797 KIS_ASSERT_RECOVER_NOOP(numExistingPoints == 4);
798 return false;
799 }
800};
801
803
804 RegularGridIndexesOp(const QSize &gridSize)
805 : m_gridSize(gridSize)
806 {
807 }
808
809 inline QVector<int> calculateMappedIndexes(int col, int row,
810 int *numExistingPoints) const {
811
812 *numExistingPoints = 4;
813 QVector<int> cellIndexes =
815
816 return cellIndexes;
817 }
818
819 inline int tryGetValidIndex(const QPoint &cellPt) const {
820 Q_UNUSED(cellPt);
821
822 KIS_ASSERT_RECOVER_NOOP(0 && "Not applicable");
823 return -1;
824 }
825
826 inline QPointF getSrcPointForce(const QPoint &cellPt) const {
827 Q_UNUSED(cellPt);
828
829 KIS_ASSERT_RECOVER_NOOP(0 && "Not applicable");
830 return QPointF();
831 }
832
833 inline const QPolygonF srcCropPolygon() const {
834 KIS_ASSERT_RECOVER_NOOP(0 && "Not applicable");
835 return QPolygonF();
836 }
837
839};
840
849inline void adjustAlignedPolygon(QPolygonF &polygon)
850{
851 static const qreal eps = 1e-5;
852 static const QPointF p1(eps, 0.0);
853 static const QPointF p2(eps, eps);
854 static const QPointF p3(0.0, eps);
855
856 polygon[1] += p1;
857 polygon[2] += p2;
858 polygon[3] += p3;
859}
860
861template <class IndexesOp>
862bool canProcessRectsInRandomOrder(IndexesOp &indexesOp, const QVector<QPointF> &transformedPoints, QSize grid) {
863 return canProcessRectsInRandomOrder(indexesOp, transformedPoints, QRect(QPoint(0, 0), grid));
864}
865
866template <class IndexesOp>
867bool canProcessRectsInRandomOrder(IndexesOp &indexesOp, const QVector<QPointF> &transformedPoints, QRect subgrid) {
868 QVector<int> polygonPoints(4);
869 QPoint startPoint = subgrid.topLeft();
870 QPoint endPoint = subgrid.bottomRight();
871
872
873 int polygonsChecked = 0;
874
875 for (int row = startPoint.y(); row < endPoint.y(); row++) {
876 for (int col = startPoint.x(); col < endPoint.x(); col++) {
877 int numExistingPoints = 0;
878
879 polygonPoints = indexesOp.calculateMappedIndexes(col, row, &numExistingPoints);
880
881 QPolygonF dstPolygon;
882
883 for (int i = 0; i < polygonPoints.count(); i++) {
884 const int index = polygonPoints[i];
885 dstPolygon << transformedPoints[index];
886 }
887
888
889 adjustAlignedPolygon(dstPolygon);
890
891
892 if (!KisAlgebra2D::isPolygonTrulyConvex(dstPolygon)) {
893 return false;
894 }
895
896 }
897 }
898 return true;
899}
900
901
902
903template <template <class PolygonOp, class IndexesOp> class IncompletePolygonPolicy,
904 class PolygonOp,
905 class IndexesOp>
906void iterateThroughGrid(PolygonOp &polygonOp,
907 IndexesOp &indexesOp,
908 const QSize &gridSize,
909 const QVector<QPointF> &originalPoints,
910 const QVector<QPointF> &transformedPoints)
911{
912 iterateThroughGrid<IncompletePolygonPolicy, PolygonOp, IndexesOp>(polygonOp, indexesOp, gridSize, originalPoints, transformedPoints, QRect(QPoint(0, 0), gridSize));
913}
914
915template <template <class PolygonOp, class IndexesOp> class IncompletePolygonPolicy,
916 class PolygonOp,
917 class IndexesOp>
918void iterateThroughGrid(PolygonOp &polygonOp,
919 IndexesOp &indexesOp,
920 const QSize &gridSize,
921 const QVector<QPointF> &originalPoints,
922 const QVector<QPointF> &transformedPoints,
923 const QRect subGrid)
924{
925 QVector<int> polygonPoints(4);
926 QPoint startPoint = subGrid.topLeft();
927 QPoint endPoint = subGrid.bottomRight(); // it's weird but bottomRight on QRect point does give us one unit of margin on both x and y
928 // when start is on (0, 0), and size is (500, 500), bottomRight is on (499, 499)
929 // but remember that it also only needs a top left corner of the polygon
930
931 KIS_SAFE_ASSERT_RECOVER(startPoint.x() >= 0 && startPoint.y() >= 0 && endPoint.x() <= gridSize.width() - 1 && endPoint.y() <= gridSize.height() - 1) {
932 startPoint = QPoint(qMax(startPoint.x(), 0), qMax(startPoint.y(), 0));
933 endPoint = QPoint(qMin(endPoint.x(), gridSize.width() - 1), qMin(startPoint.y(), gridSize.height() - 1));
934 }
935
936 for (int row = startPoint.y(); row < endPoint.y(); row++) {
937 for (int col = startPoint.x(); col < endPoint.x(); col++) {
938 int numExistingPoints = 0;
939
940 polygonPoints = indexesOp.calculateMappedIndexes(col, row, &numExistingPoints);
941
943 tryProcessPolygon(col, row,
944 numExistingPoints,
945 polygonOp,
946 indexesOp,
947 polygonPoints,
948 originalPoints,
949 transformedPoints)) {
950
951 QPolygonF srcPolygon;
952 QPolygonF dstPolygon;
953
954 for (int i = 0; i < 4; i++) {
955 const int index = polygonPoints[i];
956 srcPolygon << originalPoints[index];
957 dstPolygon << transformedPoints[index];
958 }
959
960 adjustAlignedPolygon(srcPolygon);
961 adjustAlignedPolygon(dstPolygon);
962
963 polygonOp(srcPolygon, dstPolygon);
964 }
965 }
966 }
967
968 polygonOp.finalize();
969}
970
971}
972
973#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
#define KIS_SAFE_ASSERT_RECOVER(cond)
Definition kis_assert.h:126
#define KIS_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:97
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 isPolygonRect(const Polygon &poly, Difference tolerance)
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