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