File indexing completed on 2024-05-12 15:58:18
0001 /* 0002 * SPDX-FileCopyrightText: 2014 Dmitry Kazakov <dimula73@gmail.com> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #ifndef __KIS_GRID_INTERPOLATION_TOOLS_H 0008 #define __KIS_GRID_INTERPOLATION_TOOLS_H 0009 0010 #include <limits> 0011 #include <algorithm> 0012 0013 #include <QImage> 0014 0015 #include "kis_algebra_2d.h" 0016 #include "kis_four_point_interpolator_forward.h" 0017 #include "kis_four_point_interpolator_backward.h" 0018 #include "kis_iterator_ng.h" 0019 #include "kis_random_sub_accessor.h" 0020 0021 //#define DEBUG_PAINTING_POLYGONS 0022 0023 #ifdef DEBUG_PAINTING_POLYGONS 0024 #include <QPainter> 0025 #endif /* DEBUG_PAINTING_POLYGONS */ 0026 0027 namespace GridIterationTools { 0028 0029 inline int calcGridDimension(int start, int end, const int pixelPrecision) 0030 { 0031 const int alignmentMask = ~(pixelPrecision - 1); 0032 0033 int alignedStart = (start + pixelPrecision - 1) & alignmentMask; 0034 int alignedEnd = end & alignmentMask; 0035 0036 int size = 0; 0037 0038 if (alignedEnd > alignedStart) { 0039 size = (alignedEnd - alignedStart) / pixelPrecision + 1; 0040 size += alignedStart != start; 0041 size += alignedEnd != end; 0042 } else { 0043 size = 2 + (end - start >= pixelPrecision); 0044 } 0045 0046 return size; 0047 } 0048 0049 inline QSize calcGridSize(const QRect &srcBounds, const int pixelPrecision) { 0050 return QSize(calcGridDimension(srcBounds.x(), srcBounds.right(), pixelPrecision), 0051 calcGridDimension(srcBounds.y(), srcBounds.bottom(), pixelPrecision)); 0052 } 0053 0054 template <class ProcessPolygon, class ForwardTransform> 0055 struct CellOp 0056 { 0057 CellOp(ProcessPolygon &_polygonOp, ForwardTransform &_transformOp) 0058 : polygonOp(_polygonOp), 0059 transformOp(_transformOp) 0060 { 0061 } 0062 0063 inline void processPoint(int col, int row, 0064 int prevCol, int prevRow, 0065 int colIndex, int rowIndex) { 0066 0067 QPointF dstPosF = transformOp(QPointF(col, row)); 0068 currLinePoints << dstPosF; 0069 0070 if (rowIndex >= 1 && colIndex >= 1) { 0071 QPolygonF srcPolygon; 0072 0073 srcPolygon << QPointF(prevCol, prevRow); 0074 srcPolygon << QPointF(col, prevRow); 0075 srcPolygon << QPointF(col, row); 0076 srcPolygon << QPointF(prevCol, row); 0077 0078 QPolygonF dstPolygon; 0079 0080 dstPolygon << prevLinePoints.at(colIndex - 1); 0081 dstPolygon << prevLinePoints.at(colIndex); 0082 dstPolygon << currLinePoints.at(colIndex); 0083 dstPolygon << currLinePoints.at(colIndex - 1); 0084 0085 polygonOp(srcPolygon, dstPolygon); 0086 } 0087 0088 } 0089 0090 inline void nextLine() { 0091 std::swap(prevLinePoints, currLinePoints); 0092 0093 // we are erasing elements for not free'ing the occupied 0094 // memory, which is more efficient since we are going to fill 0095 // the vector again 0096 currLinePoints.erase(currLinePoints.begin(), currLinePoints.end()); 0097 } 0098 0099 QVector<QPointF> prevLinePoints; 0100 QVector<QPointF> currLinePoints; 0101 ProcessPolygon &polygonOp; 0102 ForwardTransform &transformOp; 0103 }; 0104 0105 template <class ProcessCell> 0106 void processGrid(ProcessCell &cellOp, 0107 const QRect &srcBounds, 0108 const int pixelPrecision) 0109 { 0110 if (srcBounds.isEmpty()) return; 0111 0112 const int alignmentMask = ~(pixelPrecision - 1); 0113 0114 int prevRow = std::numeric_limits<int>::max(); 0115 int prevCol = std::numeric_limits<int>::max(); 0116 0117 int rowIndex = 0; 0118 int colIndex = 0; 0119 0120 for (int row = srcBounds.top(); row <= srcBounds.bottom();) { 0121 for (int col = srcBounds.left(); col <= srcBounds.right();) { 0122 0123 cellOp.processPoint(col, row, 0124 prevCol, prevRow, 0125 colIndex, rowIndex); 0126 0127 prevCol = col; 0128 col += pixelPrecision; 0129 colIndex++; 0130 0131 if (col > srcBounds.right() && 0132 col <= srcBounds.right() + pixelPrecision - 1) { 0133 0134 col = srcBounds.right(); 0135 } else { 0136 col &= alignmentMask; 0137 } 0138 } 0139 0140 cellOp.nextLine(); 0141 colIndex = 0; 0142 0143 prevRow = row; 0144 row += pixelPrecision; 0145 rowIndex++; 0146 0147 if (row > srcBounds.bottom() && 0148 row <= srcBounds.bottom() + pixelPrecision - 1) { 0149 0150 row = srcBounds.bottom(); 0151 } else { 0152 row &= alignmentMask; 0153 } 0154 } 0155 } 0156 0157 template <class ProcessPolygon, class ForwardTransform> 0158 void processGrid(ProcessPolygon &polygonOp, ForwardTransform &transformOp, 0159 const QRect &srcBounds, const int pixelPrecision) 0160 { 0161 CellOp<ProcessPolygon, ForwardTransform> cellOp(polygonOp, transformOp); 0162 processGrid(cellOp, srcBounds, pixelPrecision); 0163 } 0164 0165 struct PaintDevicePolygonOp 0166 { 0167 PaintDevicePolygonOp(KisPaintDeviceSP srcDev, KisPaintDeviceSP dstDev) 0168 : m_srcDev(srcDev), m_dstDev(dstDev) {} 0169 0170 void operator() (const QPolygonF &srcPolygon, const QPolygonF &dstPolygon) { 0171 this->operator() (srcPolygon, dstPolygon, dstPolygon); 0172 } 0173 0174 void operator() (const QPolygonF &srcPolygon, const QPolygonF &dstPolygon, const QPolygonF &clipDstPolygon) { 0175 QRect boundRect = clipDstPolygon.boundingRect().toAlignedRect(); 0176 if (boundRect.isEmpty()) return; 0177 0178 KisSequentialIterator dstIt(m_dstDev, boundRect); 0179 KisRandomSubAccessorSP srcAcc = m_srcDev->createRandomSubAccessor(); 0180 0181 KisFourPointInterpolatorBackward interp(srcPolygon, dstPolygon); 0182 0183 /** 0184 * We need to make sure that the destination polygon is not too small, 0185 * otherwise even small rounding will send the src-accessor into 0186 * infinity 0187 */ 0188 if (interp.isValid(0.1)) { 0189 int y = boundRect.top(); 0190 interp.setY(y); 0191 0192 while (dstIt.nextPixel()) { 0193 int newY = dstIt.y(); 0194 0195 if (y != newY) { 0196 y = newY; 0197 interp.setY(y); 0198 } 0199 0200 QPointF srcPoint(dstIt.x(), y); 0201 0202 if (clipDstPolygon.containsPoint(srcPoint, Qt::OddEvenFill)) { 0203 0204 interp.setX(srcPoint.x()); 0205 QPointF dstPoint = interp.getValue(); 0206 0207 // brain-blowing part: 0208 // 0209 // since the interpolator does the inverted 0210 // transformation we read data from "dstPoint" 0211 // (which is non-transformed) and write it into 0212 // "srcPoint" (which is transformed position) 0213 0214 srcAcc->moveTo(dstPoint); 0215 srcAcc->sampledOldRawData(dstIt.rawData()); 0216 } 0217 } 0218 0219 } else { 0220 srcAcc->moveTo(interp.fallbackSourcePoint()); 0221 0222 while (dstIt.nextPixel()) { 0223 QPointF srcPoint(dstIt.x(), dstIt.y()); 0224 0225 if (clipDstPolygon.containsPoint(srcPoint, Qt::OddEvenFill)) { 0226 srcAcc->sampledOldRawData(dstIt.rawData()); 0227 } 0228 } 0229 } 0230 0231 } 0232 0233 KisPaintDeviceSP m_srcDev; 0234 KisPaintDeviceSP m_dstDev; 0235 }; 0236 0237 struct QImagePolygonOp 0238 { 0239 QImagePolygonOp(const QImage &srcImage, QImage &dstImage, 0240 const QPointF &srcImageOffset, 0241 const QPointF &dstImageOffset) 0242 : m_srcImage(srcImage), m_dstImage(dstImage), 0243 m_srcImageOffset(srcImageOffset), 0244 m_dstImageOffset(dstImageOffset), 0245 m_srcImageRect(m_srcImage.rect()), 0246 m_dstImageRect(m_dstImage.rect()) 0247 { 0248 } 0249 0250 void operator() (const QPolygonF &srcPolygon, const QPolygonF &dstPolygon) { 0251 this->operator() (srcPolygon, dstPolygon, dstPolygon); 0252 } 0253 0254 void operator() (const QPolygonF &srcPolygon, const QPolygonF &dstPolygon, const QPolygonF &clipDstPolygon) { 0255 QRect boundRect = clipDstPolygon.boundingRect().toAlignedRect(); 0256 KisFourPointInterpolatorBackward interp(srcPolygon, dstPolygon); 0257 0258 for (int y = boundRect.top(); y <= boundRect.bottom(); y++) { 0259 interp.setY(y); 0260 for (int x = boundRect.left(); x <= boundRect.right(); x++) { 0261 0262 QPointF srcPoint(x, y); 0263 if (clipDstPolygon.containsPoint(srcPoint, Qt::OddEvenFill)) { 0264 0265 interp.setX(srcPoint.x()); 0266 QPointF dstPoint = interp.getValue(); 0267 0268 // about srcPoint/dstPoint hell please see a 0269 // comment in PaintDevicePolygonOp::operator() () 0270 0271 srcPoint -= m_dstImageOffset; 0272 dstPoint -= m_srcImageOffset; 0273 0274 QPoint srcPointI = srcPoint.toPoint(); 0275 QPoint dstPointI = dstPoint.toPoint(); 0276 0277 if (!m_dstImageRect.contains(srcPointI)) continue; 0278 if (!m_srcImageRect.contains(dstPointI)) continue; 0279 0280 m_dstImage.setPixel(srcPointI, m_srcImage.pixel(dstPointI)); 0281 } 0282 } 0283 } 0284 0285 #ifdef DEBUG_PAINTING_POLYGONS 0286 QPainter gc(&m_dstImage); 0287 gc.setPen(Qt::red); 0288 gc.setOpacity(0.5); 0289 0290 gc.setBrush(Qt::green); 0291 gc.drawPolygon(clipDstPolygon.translated(-m_dstImageOffset)); 0292 0293 gc.setBrush(Qt::blue); 0294 //gc.drawPolygon(dstPolygon.translated(-m_dstImageOffset)); 0295 0296 #endif /* DEBUG_PAINTING_POLYGONS */ 0297 0298 } 0299 0300 const QImage &m_srcImage; 0301 QImage &m_dstImage; 0302 QPointF m_srcImageOffset; 0303 QPointF m_dstImageOffset; 0304 0305 QRect m_srcImageRect; 0306 QRect m_dstImageRect; 0307 }; 0308 0309 /*************************************************************/ 0310 /* Iteration through precalculated grid */ 0311 /*************************************************************/ 0312 0313 /** 0314 * A-----B The polygons will be in the following order: 0315 * | | 0316 * | | polygon << A << B << D << C; 0317 * C-----D 0318 */ 0319 inline QVector<int> calculateCellIndexes(int col, int row, const QSize &gridSize) 0320 { 0321 const int tl = col + row * gridSize.width(); 0322 const int tr = tl + 1; 0323 const int bl = tl + gridSize.width(); 0324 const int br = bl + 1; 0325 0326 QVector<int> cellIndexes; 0327 cellIndexes << tl; 0328 cellIndexes << tr; 0329 cellIndexes << br; 0330 cellIndexes << bl; 0331 0332 return cellIndexes; 0333 } 0334 0335 inline int pointToIndex(const QPoint &cellPt, const QSize &gridSize) 0336 { 0337 return cellPt.x() + 0338 cellPt.y() * gridSize.width(); 0339 } 0340 0341 namespace Private { 0342 inline QPoint pointPolygonIndexToColRow(QPoint baseColRow, int index) 0343 { 0344 static QVector<QPoint> pointOffsets; 0345 if (pointOffsets.isEmpty()) { 0346 pointOffsets << QPoint(0,0); 0347 pointOffsets << QPoint(1,0); 0348 pointOffsets << QPoint(1,1); 0349 pointOffsets << QPoint(0,1); 0350 } 0351 0352 return baseColRow + pointOffsets[index]; 0353 } 0354 0355 struct PointExtension { 0356 int near; 0357 int far; 0358 }; 0359 } 0360 0361 template <class IndexesOp> 0362 bool getOrthogonalPointApproximation(const QPoint &cellPt, 0363 const QVector<QPointF> &originalPoints, 0364 const QVector<QPointF> &transformedPoints, 0365 IndexesOp indexesOp, 0366 QPointF *srcPoint, 0367 QPointF *dstPoint) 0368 { 0369 QVector<Private::PointExtension> extensionPoints; 0370 Private::PointExtension ext; 0371 0372 // left 0373 if ((ext.near = indexesOp.tryGetValidIndex(cellPt + QPoint(-1, 0))) >= 0 && 0374 (ext.far = indexesOp.tryGetValidIndex(cellPt + QPoint(-2, 0))) >= 0) { 0375 0376 extensionPoints << ext; 0377 } 0378 // top 0379 if ((ext.near = indexesOp.tryGetValidIndex(cellPt + QPoint(0, -1))) >= 0 && 0380 (ext.far = indexesOp.tryGetValidIndex(cellPt + QPoint(0, -2))) >= 0) { 0381 0382 extensionPoints << ext; 0383 } 0384 // right 0385 if ((ext.near = indexesOp.tryGetValidIndex(cellPt + QPoint(1, 0))) >= 0 && 0386 (ext.far = indexesOp.tryGetValidIndex(cellPt + QPoint(2, 0))) >= 0) { 0387 0388 extensionPoints << ext; 0389 } 0390 // bottom 0391 if ((ext.near = indexesOp.tryGetValidIndex(cellPt + QPoint(0, 1))) >= 0 && 0392 (ext.far = indexesOp.tryGetValidIndex(cellPt + QPoint(0, 2))) >= 0) { 0393 0394 extensionPoints << ext; 0395 } 0396 0397 if (extensionPoints.isEmpty()) { 0398 // top-left 0399 if ((ext.near = indexesOp.tryGetValidIndex(cellPt + QPoint(-1, -1))) >= 0 && 0400 (ext.far = indexesOp.tryGetValidIndex(cellPt + QPoint(-2, -2))) >= 0) { 0401 0402 extensionPoints << ext; 0403 } 0404 // top-right 0405 if ((ext.near = indexesOp.tryGetValidIndex(cellPt + QPoint(1, -1))) >= 0 && 0406 (ext.far = indexesOp.tryGetValidIndex(cellPt + QPoint(2, -2))) >= 0) { 0407 0408 extensionPoints << ext; 0409 } 0410 // bottom-right 0411 if ((ext.near = indexesOp.tryGetValidIndex(cellPt + QPoint(1, 1))) >= 0 && 0412 (ext.far = indexesOp.tryGetValidIndex(cellPt + QPoint(2, 2))) >= 0) { 0413 0414 extensionPoints << ext; 0415 } 0416 // bottom-left 0417 if ((ext.near = indexesOp.tryGetValidIndex(cellPt + QPoint(-1, 1))) >= 0 && 0418 (ext.far = indexesOp.tryGetValidIndex(cellPt + QPoint(-2, 2))) >= 0) { 0419 0420 extensionPoints << ext; 0421 } 0422 } 0423 0424 if (extensionPoints.isEmpty()) { 0425 return false; 0426 } 0427 0428 int numResultPoints = 0; 0429 *srcPoint = indexesOp.getSrcPointForce(cellPt); 0430 *dstPoint = QPointF(); 0431 0432 Q_FOREACH (const Private::PointExtension &ext, extensionPoints) { 0433 QPointF near = transformedPoints[ext.near]; 0434 QPointF far = transformedPoints[ext.far]; 0435 0436 QPointF nearSrc = originalPoints[ext.near]; 0437 QPointF farSrc = originalPoints[ext.far]; 0438 0439 QPointF base1 = nearSrc - farSrc; 0440 QPointF base2 = near - far; 0441 0442 QPointF pt = near + 0443 KisAlgebra2D::transformAsBase(*srcPoint - nearSrc, base1, base2); 0444 0445 *dstPoint += pt; 0446 numResultPoints++; 0447 } 0448 0449 *dstPoint /= numResultPoints; 0450 0451 return true; 0452 } 0453 0454 template <class PolygonOp, class IndexesOp> 0455 struct IncompletePolygonPolicy { 0456 0457 static inline bool tryProcessPolygon(int col, int row, 0458 int numExistingPoints, 0459 PolygonOp &polygonOp, 0460 IndexesOp &indexesOp, 0461 const QVector<int> &polygonPoints, 0462 const QVector<QPointF> &originalPoints, 0463 const QVector<QPointF> &transformedPoints) 0464 { 0465 if (numExistingPoints >= 4) return false; 0466 if (numExistingPoints == 0) return true; 0467 0468 QPolygonF srcPolygon; 0469 QPolygonF dstPolygon; 0470 0471 for (int i = 0; i < 4; i++) { 0472 const int index = polygonPoints[i]; 0473 0474 if (index >= 0) { 0475 srcPolygon << originalPoints[index]; 0476 dstPolygon << transformedPoints[index]; 0477 } else { 0478 QPoint cellPt = Private::pointPolygonIndexToColRow(QPoint(col, row), i); 0479 QPointF srcPoint; 0480 QPointF dstPoint; 0481 bool result = 0482 getOrthogonalPointApproximation(cellPt, 0483 originalPoints, 0484 transformedPoints, 0485 indexesOp, 0486 &srcPoint, 0487 &dstPoint); 0488 0489 if (!result) { 0490 //dbgKrita << "*NOT* found any valid point" << allSrcPoints[pointToIndex(cellPt)] << "->" << ppVar(pt); 0491 break; 0492 } else { 0493 srcPolygon << srcPoint; 0494 dstPolygon << dstPoint; 0495 } 0496 } 0497 } 0498 0499 if (dstPolygon.size() == 4) { 0500 QPolygonF srcClipPolygon(srcPolygon.intersected(indexesOp.srcCropPolygon())); 0501 0502 KisFourPointInterpolatorForward forwardTransform(srcPolygon, dstPolygon); 0503 for (int i = 0; i < srcClipPolygon.size(); i++) { 0504 const QPointF newPt = forwardTransform.map(srcClipPolygon[i]); 0505 srcClipPolygon[i] = newPt; 0506 } 0507 0508 polygonOp(srcPolygon, dstPolygon, srcClipPolygon); 0509 } 0510 0511 return true; 0512 } 0513 }; 0514 0515 template <class PolygonOp, class IndexesOp> 0516 struct AlwaysCompletePolygonPolicy { 0517 0518 static inline bool tryProcessPolygon(int col, int row, 0519 int numExistingPoints, 0520 PolygonOp &polygonOp, 0521 IndexesOp &indexesOp, 0522 const QVector<int> &polygonPoints, 0523 const QVector<QPointF> &originalPoints, 0524 const QVector<QPointF> &transformedPoints) 0525 { 0526 Q_UNUSED(col); 0527 Q_UNUSED(row); 0528 Q_UNUSED(polygonOp); 0529 Q_UNUSED(indexesOp); 0530 Q_UNUSED(polygonPoints); 0531 Q_UNUSED(originalPoints); 0532 Q_UNUSED(transformedPoints); 0533 0534 KIS_ASSERT_RECOVER_NOOP(numExistingPoints == 4); 0535 return false; 0536 } 0537 }; 0538 0539 struct RegularGridIndexesOp { 0540 0541 RegularGridIndexesOp(const QSize &gridSize) 0542 : m_gridSize(gridSize) 0543 { 0544 } 0545 0546 inline QVector<int> calculateMappedIndexes(int col, int row, 0547 int *numExistingPoints) const { 0548 0549 *numExistingPoints = 4; 0550 QVector<int> cellIndexes = 0551 GridIterationTools::calculateCellIndexes(col, row, m_gridSize); 0552 0553 return cellIndexes; 0554 } 0555 0556 inline int tryGetValidIndex(const QPoint &cellPt) const { 0557 Q_UNUSED(cellPt); 0558 0559 KIS_ASSERT_RECOVER_NOOP(0 && "Not applicable"); 0560 return -1; 0561 } 0562 0563 inline QPointF getSrcPointForce(const QPoint &cellPt) const { 0564 Q_UNUSED(cellPt); 0565 0566 KIS_ASSERT_RECOVER_NOOP(0 && "Not applicable"); 0567 return QPointF(); 0568 } 0569 0570 inline const QPolygonF srcCropPolygon() const { 0571 KIS_ASSERT_RECOVER_NOOP(0 && "Not applicable"); 0572 return QPolygonF(); 0573 } 0574 0575 QSize m_gridSize; 0576 }; 0577 0578 /** 0579 * There is a weird problem in fetching correct bounds of the polygon. 0580 * If the rightmost (bottommost) point of the polygon is integral, then 0581 * QRectF() will end exactly on it, but when converting into QRect the last 0582 * point will not be taken into account. It happens due to the difference 0583 * between center-point/topleft-point point representation. In many cases 0584 * the latter is expected, but we don't work with it in Qt/Krita. 0585 */ 0586 inline void adjustAlignedPolygon(QPolygonF &polygon) 0587 { 0588 static const qreal eps = 1e-5; 0589 static const QPointF p1(eps, 0.0); 0590 static const QPointF p2(eps, eps); 0591 static const QPointF p3(0.0, eps); 0592 0593 polygon[1] += p1; 0594 polygon[2] += p2; 0595 polygon[3] += p3; 0596 } 0597 0598 template <template <class PolygonOp, class IndexesOp> class IncompletePolygonPolicy, 0599 class PolygonOp, 0600 class IndexesOp> 0601 void iterateThroughGrid(PolygonOp &polygonOp, 0602 IndexesOp &indexesOp, 0603 const QSize &gridSize, 0604 const QVector<QPointF> &originalPoints, 0605 const QVector<QPointF> &transformedPoints) 0606 { 0607 QVector<int> polygonPoints(4); 0608 0609 for (int row = 0; row < gridSize.height() - 1; row++) { 0610 for (int col = 0; col < gridSize.width() - 1; col++) { 0611 int numExistingPoints = 0; 0612 0613 polygonPoints = indexesOp.calculateMappedIndexes(col, row, &numExistingPoints); 0614 0615 if (!IncompletePolygonPolicy<PolygonOp, IndexesOp>:: 0616 tryProcessPolygon(col, row, 0617 numExistingPoints, 0618 polygonOp, 0619 indexesOp, 0620 polygonPoints, 0621 originalPoints, 0622 transformedPoints)) { 0623 0624 QPolygonF srcPolygon; 0625 QPolygonF dstPolygon; 0626 0627 for (int i = 0; i < 4; i++) { 0628 const int index = polygonPoints[i]; 0629 srcPolygon << originalPoints[index]; 0630 dstPolygon << transformedPoints[index]; 0631 } 0632 0633 adjustAlignedPolygon(srcPolygon); 0634 adjustAlignedPolygon(dstPolygon); 0635 0636 polygonOp(srcPolygon, dstPolygon); 0637 } 0638 } 0639 } 0640 } 0641 0642 } 0643 0644 #endif /* __KIS_GRID_INTERPOLATION_TOOLS_H */