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 */