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