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 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
7#ifndef __KIS_GRID_INTERPOLATION_TOOLS_H
8#define __KIS_GRID_INTERPOLATION_TOOLS_H
9
10#include <limits>
11#include <algorithm>
12
13#include <QImage>
14
15#include "kis_algebra_2d.h"
18#include "kis_iterator_ng.h"
20
21//#define DEBUG_PAINTING_POLYGONS
22
23#ifdef DEBUG_PAINTING_POLYGONS
24#include <QPainter>
25#endif /* DEBUG_PAINTING_POLYGONS */
26
28
29inline int calcGridDimension(int start, int end, const int pixelPrecision)
30{
31 const int alignmentMask = ~(pixelPrecision - 1);
32
33 int alignedStart = (start + pixelPrecision - 1) & alignmentMask;
34 int alignedEnd = end & alignmentMask;
35
36 int size = 0;
37
38 if (alignedEnd > alignedStart) {
39 size = (alignedEnd - alignedStart) / pixelPrecision + 1;
40 size += alignedStart != start;
41 size += alignedEnd != end;
42 } else {
43 size = 2 + (end - start >= pixelPrecision);
44 }
45
46 return size;
47}
48
49inline QSize calcGridSize(const QRect &srcBounds, const int pixelPrecision) {
50 return QSize(calcGridDimension(srcBounds.x(), srcBounds.right(), pixelPrecision),
51 calcGridDimension(srcBounds.y(), srcBounds.bottom(), pixelPrecision));
52}
53
54template <class ProcessPolygon, class ForwardTransform>
55struct CellOp
56{
57 CellOp(ProcessPolygon &_polygonOp, ForwardTransform &_transformOp)
58 : polygonOp(_polygonOp),
59 transformOp(_transformOp)
60 {
61 }
62
63 inline void processPoint(int col, int row,
64 int prevCol, int prevRow,
65 int colIndex, int rowIndex) {
66
67 QPointF dstPosF = transformOp(QPointF(col, row));
68 currLinePoints << dstPosF;
69
70 if (rowIndex >= 1 && colIndex >= 1) {
71 QPolygonF srcPolygon;
72
73 srcPolygon << QPointF(prevCol, prevRow);
74 srcPolygon << QPointF(col, prevRow);
75 srcPolygon << QPointF(col, row);
76 srcPolygon << QPointF(prevCol, row);
77
78 QPolygonF dstPolygon;
79
80 dstPolygon << prevLinePoints.at(colIndex - 1);
81 dstPolygon << prevLinePoints.at(colIndex);
82 dstPolygon << currLinePoints.at(colIndex);
83 dstPolygon << currLinePoints.at(colIndex - 1);
84
85 polygonOp(srcPolygon, dstPolygon);
86 }
87
88 }
89
90 inline void nextLine() {
92
93 // we are erasing elements for not freeing the occupied
94 // memory, which is more efficient since we are going to fill
95 // the vector again
96 currLinePoints.erase(currLinePoints.begin(), currLinePoints.end());
97 }
98
101 ProcessPolygon &polygonOp;
102 ForwardTransform &transformOp;
103};
104
105template <class ProcessCell>
106void processGrid(ProcessCell &cellOp,
107 const QRect &srcBounds,
108 const int pixelPrecision)
109{
110 if (srcBounds.isEmpty()) return;
111
112 const int alignmentMask = ~(pixelPrecision - 1);
113
114 int prevRow = std::numeric_limits<int>::max();
115 int prevCol = std::numeric_limits<int>::max();
116
117 int rowIndex = 0;
118 int colIndex = 0;
119
120 for (int row = srcBounds.top(); row <= srcBounds.bottom();) {
121 for (int col = srcBounds.left(); col <= srcBounds.right();) {
122
123 cellOp.processPoint(col, row,
124 prevCol, prevRow,
125 colIndex, rowIndex);
126
127 prevCol = col;
128 col += pixelPrecision;
129 colIndex++;
130
131 if (col > srcBounds.right() &&
132 col <= srcBounds.right() + pixelPrecision - 1) {
133
134 col = srcBounds.right();
135 } else {
136 col &= alignmentMask;
137 }
138 }
139
140 cellOp.nextLine();
141 colIndex = 0;
142
143 prevRow = row;
144 row += pixelPrecision;
145 rowIndex++;
146
147 if (row > srcBounds.bottom() &&
148 row <= srcBounds.bottom() + pixelPrecision - 1) {
149
150 row = srcBounds.bottom();
151 } else {
152 row &= alignmentMask;
153 }
154 }
155}
156
157template <class ProcessPolygon, class ForwardTransform>
158void processGrid(ProcessPolygon &polygonOp, ForwardTransform &transformOp,
159 const QRect &srcBounds, const int pixelPrecision)
160{
161 CellOp<ProcessPolygon, ForwardTransform> cellOp(polygonOp, transformOp);
162 processGrid(cellOp, srcBounds, pixelPrecision);
163}
164
166{
168 : m_srcDev(srcDev), m_dstDev(dstDev) {}
169
170 void operator() (const QPolygonF &srcPolygon, const QPolygonF &dstPolygon) {
171 this->operator() (srcPolygon, dstPolygon, dstPolygon);
172 }
173
174 void operator() (const QPolygonF &srcPolygon, const QPolygonF &dstPolygon, const QPolygonF &clipDstPolygon) {
175 QRect boundRect = clipDstPolygon.boundingRect().toAlignedRect();
176 if (boundRect.isEmpty()) return;
177
178 KisSequentialIterator dstIt(m_dstDev, boundRect);
180
181 KisFourPointInterpolatorBackward interp(srcPolygon, dstPolygon);
182
188 if (interp.isValid(0.1)) {
189 int y = boundRect.top();
190 interp.setY(y);
191
192 while (dstIt.nextPixel()) {
193 int newY = dstIt.y();
194
195 if (y != newY) {
196 y = newY;
197 interp.setY(y);
198 }
199
200 QPointF srcPoint(dstIt.x(), y);
201
202 if (clipDstPolygon.containsPoint(srcPoint, Qt::OddEvenFill)) {
203
204 interp.setX(srcPoint.x());
205 QPointF dstPoint = interp.getValue();
206
207 // brain-blowing part:
208 //
209 // since the interpolator does the inverted
210 // transformation we read data from "dstPoint"
211 // (which is non-transformed) and write it into
212 // "srcPoint" (which is transformed position)
213
214 srcAcc->moveTo(dstPoint);
215 srcAcc->sampledOldRawData(dstIt.rawData());
216 }
217 }
218
219 } else {
220 srcAcc->moveTo(interp.fallbackSourcePoint());
221
222 while (dstIt.nextPixel()) {
223 QPointF srcPoint(dstIt.x(), dstIt.y());
224
225 if (clipDstPolygon.containsPoint(srcPoint, Qt::OddEvenFill)) {
226 srcAcc->sampledOldRawData(dstIt.rawData());
227 }
228 }
229 }
230
231 }
232
235};
236
238{
239 QImagePolygonOp(const QImage &srcImage, QImage &dstImage,
240 const QPointF &srcImageOffset,
241 const QPointF &dstImageOffset)
242 : m_srcImage(srcImage), m_dstImage(dstImage),
243 m_srcImageOffset(srcImageOffset),
244 m_dstImageOffset(dstImageOffset),
247 {
248 }
249
250 void operator() (const QPolygonF &srcPolygon, const QPolygonF &dstPolygon) {
251 this->operator() (srcPolygon, dstPolygon, dstPolygon);
252 }
253
254 void operator() (const QPolygonF &srcPolygon, const QPolygonF &dstPolygon, const QPolygonF &clipDstPolygon) {
255 QRect boundRect = clipDstPolygon.boundingRect().toAlignedRect();
256 KisFourPointInterpolatorBackward interp(srcPolygon, dstPolygon);
257
258 for (int y = boundRect.top(); y <= boundRect.bottom(); y++) {
259 interp.setY(y);
260 for (int x = boundRect.left(); x <= boundRect.right(); x++) {
261
262 QPointF srcPoint(x, y);
263 if (clipDstPolygon.containsPoint(srcPoint, Qt::OddEvenFill)) {
264
265 interp.setX(srcPoint.x());
266 QPointF dstPoint = interp.getValue();
267
268 // about srcPoint/dstPoint hell please see a
269 // comment in PaintDevicePolygonOp::operator() ()
270
271 srcPoint -= m_dstImageOffset;
273
274 QPoint srcPointI = srcPoint.toPoint();
275 QPoint dstPointI = dstPoint.toPoint();
276
277 if (!m_dstImageRect.contains(srcPointI)) continue;
278 if (!m_srcImageRect.contains(dstPointI)) continue;
279
280 m_dstImage.setPixel(srcPointI, m_srcImage.pixel(dstPointI));
281 }
282 }
283 }
284
285#ifdef DEBUG_PAINTING_POLYGONS
286 QPainter gc(&m_dstImage);
287 gc.setPen(Qt::red);
288 gc.setOpacity(0.5);
289
290 gc.setBrush(Qt::green);
291 gc.drawPolygon(clipDstPolygon.translated(-m_dstImageOffset));
292
293 gc.setBrush(Qt::blue);
294 //gc.drawPolygon(dstPolygon.translated(-m_dstImageOffset));
295
296#endif /* DEBUG_PAINTING_POLYGONS */
297
298 }
299
300 const QImage &m_srcImage;
301 QImage &m_dstImage;
304
307};
308
309/*************************************************************/
310/* Iteration through precalculated grid */
311/*************************************************************/
312
319inline QVector<int> calculateCellIndexes(int col, int row, const QSize &gridSize)
320{
321 const int tl = col + row * gridSize.width();
322 const int tr = tl + 1;
323 const int bl = tl + gridSize.width();
324 const int br = bl + 1;
325
326 QVector<int> cellIndexes;
327 cellIndexes << tl;
328 cellIndexes << tr;
329 cellIndexes << br;
330 cellIndexes << bl;
331
332 return cellIndexes;
333}
334
335inline int pointToIndex(const QPoint &cellPt, const QSize &gridSize)
336{
337 return cellPt.x() +
338 cellPt.y() * gridSize.width();
339}
340
341namespace Private {
342 inline QPoint pointPolygonIndexToColRow(QPoint baseColRow, int index)
343 {
344 static QVector<QPoint> pointOffsets;
345 if (pointOffsets.isEmpty()) {
346 pointOffsets << QPoint(0,0);
347 pointOffsets << QPoint(1,0);
348 pointOffsets << QPoint(1,1);
349 pointOffsets << QPoint(0,1);
350 }
351
352 return baseColRow + pointOffsets[index];
353 }
354
356 int near;
357 int far;
358 };
359}
360
361template <class IndexesOp>
362bool getOrthogonalPointApproximation(const QPoint &cellPt,
363 const QVector<QPointF> &originalPoints,
364 const QVector<QPointF> &transformedPoints,
365 IndexesOp indexesOp,
366 QPointF *srcPoint,
367 QPointF *dstPoint)
368{
369 QVector<Private::PointExtension> extensionPoints;
371
372 // left
373 if ((ext.near = indexesOp.tryGetValidIndex(cellPt + QPoint(-1, 0))) >= 0 &&
374 (ext.far = indexesOp.tryGetValidIndex(cellPt + QPoint(-2, 0))) >= 0) {
375
376 extensionPoints << ext;
377 }
378 // top
379 if ((ext.near = indexesOp.tryGetValidIndex(cellPt + QPoint(0, -1))) >= 0 &&
380 (ext.far = indexesOp.tryGetValidIndex(cellPt + QPoint(0, -2))) >= 0) {
381
382 extensionPoints << ext;
383 }
384 // right
385 if ((ext.near = indexesOp.tryGetValidIndex(cellPt + QPoint(1, 0))) >= 0 &&
386 (ext.far = indexesOp.tryGetValidIndex(cellPt + QPoint(2, 0))) >= 0) {
387
388 extensionPoints << ext;
389 }
390 // bottom
391 if ((ext.near = indexesOp.tryGetValidIndex(cellPt + QPoint(0, 1))) >= 0 &&
392 (ext.far = indexesOp.tryGetValidIndex(cellPt + QPoint(0, 2))) >= 0) {
393
394 extensionPoints << ext;
395 }
396
397 if (extensionPoints.isEmpty()) {
398 // top-left
399 if ((ext.near = indexesOp.tryGetValidIndex(cellPt + QPoint(-1, -1))) >= 0 &&
400 (ext.far = indexesOp.tryGetValidIndex(cellPt + QPoint(-2, -2))) >= 0) {
401
402 extensionPoints << ext;
403 }
404 // top-right
405 if ((ext.near = indexesOp.tryGetValidIndex(cellPt + QPoint(1, -1))) >= 0 &&
406 (ext.far = indexesOp.tryGetValidIndex(cellPt + QPoint(2, -2))) >= 0) {
407
408 extensionPoints << ext;
409 }
410 // bottom-right
411 if ((ext.near = indexesOp.tryGetValidIndex(cellPt + QPoint(1, 1))) >= 0 &&
412 (ext.far = indexesOp.tryGetValidIndex(cellPt + QPoint(2, 2))) >= 0) {
413
414 extensionPoints << ext;
415 }
416 // bottom-left
417 if ((ext.near = indexesOp.tryGetValidIndex(cellPt + QPoint(-1, 1))) >= 0 &&
418 (ext.far = indexesOp.tryGetValidIndex(cellPt + QPoint(-2, 2))) >= 0) {
419
420 extensionPoints << ext;
421 }
422 }
423
424 if (extensionPoints.isEmpty()) {
425 return false;
426 }
427
428 int numResultPoints = 0;
429 *srcPoint = indexesOp.getSrcPointForce(cellPt);
430 *dstPoint = QPointF();
431
432 Q_FOREACH (const Private::PointExtension &ext, extensionPoints) {
433 QPointF near = transformedPoints[ext.near];
434 QPointF far = transformedPoints[ext.far];
435
436 QPointF nearSrc = originalPoints[ext.near];
437 QPointF farSrc = originalPoints[ext.far];
438
439 QPointF base1 = nearSrc - farSrc;
440 QPointF base2 = near - far;
441
442 QPointF pt = near +
443 KisAlgebra2D::transformAsBase(*srcPoint - nearSrc, base1, base2);
444
445 *dstPoint += pt;
446 numResultPoints++;
447 }
448
449 *dstPoint /= numResultPoints;
450
451 return true;
452}
453
454template <class PolygonOp, class IndexesOp>
456
457 static inline bool tryProcessPolygon(int col, int row,
458 int numExistingPoints,
459 PolygonOp &polygonOp,
460 IndexesOp &indexesOp,
461 const QVector<int> &polygonPoints,
462 const QVector<QPointF> &originalPoints,
463 const QVector<QPointF> &transformedPoints)
464 {
465 if (numExistingPoints >= 4) return false;
466 if (numExistingPoints == 0) return true;
467
468 QPolygonF srcPolygon;
469 QPolygonF dstPolygon;
470
471 for (int i = 0; i < 4; i++) {
472 const int index = polygonPoints[i];
473
474 if (index >= 0) {
475 srcPolygon << originalPoints[index];
476 dstPolygon << transformedPoints[index];
477 } else {
478 QPoint cellPt = Private::pointPolygonIndexToColRow(QPoint(col, row), i);
479 QPointF srcPoint;
480 QPointF dstPoint;
481 bool result =
483 originalPoints,
484 transformedPoints,
485 indexesOp,
486 &srcPoint,
487 &dstPoint);
488
489 if (!result) {
490 //dbgKrita << "*NOT* found any valid point" << allSrcPoints[pointToIndex(cellPt)] << "->" << ppVar(pt);
491 break;
492 } else {
493 srcPolygon << srcPoint;
494 dstPolygon << dstPoint;
495 }
496 }
497 }
498
499 if (dstPolygon.size() == 4) {
500 QPolygonF srcClipPolygon(srcPolygon.intersected(indexesOp.srcCropPolygon()));
501
502 KisFourPointInterpolatorForward forwardTransform(srcPolygon, dstPolygon);
503 for (int i = 0; i < srcClipPolygon.size(); i++) {
504 const QPointF newPt = forwardTransform.map(srcClipPolygon[i]);
505 srcClipPolygon[i] = newPt;
506 }
507
508 polygonOp(srcPolygon, dstPolygon, srcClipPolygon);
509 }
510
511 return true;
512 }
513};
514
515template <class PolygonOp, class IndexesOp>
517
518 static inline bool tryProcessPolygon(int col, int row,
519 int numExistingPoints,
520 PolygonOp &polygonOp,
521 IndexesOp &indexesOp,
522 const QVector<int> &polygonPoints,
523 const QVector<QPointF> &originalPoints,
524 const QVector<QPointF> &transformedPoints)
525 {
526 Q_UNUSED(col);
527 Q_UNUSED(row);
528 Q_UNUSED(polygonOp);
529 Q_UNUSED(indexesOp);
530 Q_UNUSED(polygonPoints);
531 Q_UNUSED(originalPoints);
532 Q_UNUSED(transformedPoints);
533
534 KIS_ASSERT_RECOVER_NOOP(numExistingPoints == 4);
535 return false;
536 }
537};
538
540
541 RegularGridIndexesOp(const QSize &gridSize)
542 : m_gridSize(gridSize)
543 {
544 }
545
546 inline QVector<int> calculateMappedIndexes(int col, int row,
547 int *numExistingPoints) const {
548
549 *numExistingPoints = 4;
550 QVector<int> cellIndexes =
552
553 return cellIndexes;
554 }
555
556 inline int tryGetValidIndex(const QPoint &cellPt) const {
557 Q_UNUSED(cellPt);
558
559 KIS_ASSERT_RECOVER_NOOP(0 && "Not applicable");
560 return -1;
561 }
562
563 inline QPointF getSrcPointForce(const QPoint &cellPt) const {
564 Q_UNUSED(cellPt);
565
566 KIS_ASSERT_RECOVER_NOOP(0 && "Not applicable");
567 return QPointF();
568 }
569
570 inline const QPolygonF srcCropPolygon() const {
571 KIS_ASSERT_RECOVER_NOOP(0 && "Not applicable");
572 return QPolygonF();
573 }
574
576};
577
586inline void adjustAlignedPolygon(QPolygonF &polygon)
587{
588 static const qreal eps = 1e-5;
589 static const QPointF p1(eps, 0.0);
590 static const QPointF p2(eps, eps);
591 static const QPointF p3(0.0, eps);
592
593 polygon[1] += p1;
594 polygon[2] += p2;
595 polygon[3] += p3;
596}
597
598template <template <class PolygonOp, class IndexesOp> class IncompletePolygonPolicy,
599 class PolygonOp,
600 class IndexesOp>
601void iterateThroughGrid(PolygonOp &polygonOp,
602 IndexesOp &indexesOp,
603 const QSize &gridSize,
604 const QVector<QPointF> &originalPoints,
605 const QVector<QPointF> &transformedPoints)
606{
607 QVector<int> polygonPoints(4);
608
609 for (int row = 0; row < gridSize.height() - 1; row++) {
610 for (int col = 0; col < gridSize.width() - 1; col++) {
611 int numExistingPoints = 0;
612
613 polygonPoints = indexesOp.calculateMappedIndexes(col, row, &numExistingPoints);
614
616 tryProcessPolygon(col, row,
617 numExistingPoints,
618 polygonOp,
619 indexesOp,
620 polygonPoints,
621 originalPoints,
622 transformedPoints)) {
623
624 QPolygonF srcPolygon;
625 QPolygonF dstPolygon;
626
627 for (int i = 0; i < 4; i++) {
628 const int index = polygonPoints[i];
629 srcPolygon << originalPoints[index];
630 dstPolygon << transformedPoints[index];
631 }
632
633 adjustAlignedPolygon(srcPolygon);
634 adjustAlignedPolygon(dstPolygon);
635
636 polygonOp(srcPolygon, dstPolygon);
637 }
638 }
639 }
640}
641
642}
643
644#endif /* __KIS_GRID_INTERPOLATION_TOOLS_H */
QPointF dstPoint
QPointF p2
QPointF p3
QPointF p1
KisRandomSubAccessorSP createRandomSubAccessor() const
void sampledOldRawData(quint8 *dst)
void moveTo(qreal x, qreal y)
ALWAYS_INLINE quint8 * rawData()
ALWAYS_INLINE int x() const
ALWAYS_INLINE int y() const
#define KIS_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:97
const qreal eps
qreal interp(qreal r, qreal a, qreal b)
private functions
QPoint pointPolygonIndexToColRow(QPoint baseColRow, int index)
void iterateThroughGrid(PolygonOp &polygonOp, IndexesOp &indexesOp, const QSize &gridSize, const QVector< QPointF > &originalPoints, const QVector< QPointF > &transformedPoints)
void adjustAlignedPolygon(QPolygonF &polygon)
QVector< int > calculateCellIndexes(int col, int row, const 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)
QPointF transformAsBase(const QPointF &pt, const QPointF &base1, const QPointF &base2)
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 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