File indexing completed on 2024-05-12 15:58:11

0001 /*
0002  *  SPDX-FileCopyrightText: 2014 Dmitry Kazakov <dimula73@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "kis_cage_transform_worker.h"
0008 
0009 #include "kis_grid_interpolation_tools.h"
0010 #include "kis_green_coordinates_math.h"
0011 
0012 #include <QPainter>
0013 
0014 #include "KoColor.h"
0015 #include "kis_selection.h"
0016 #include "kis_painter.h"
0017 #include "kis_image.h"
0018 #include "krita_utils.h"
0019 
0020 #include <qnumeric.h>
0021 
0022 struct Q_DECL_HIDDEN KisCageTransformWorker::Private
0023 {
0024     Private(const QVector<QPointF> &_origCage,
0025             KoUpdater *_progress,
0026             int _pixelPrecision)
0027         : origCage(_origCage),
0028           progress(_progress),
0029           pixelPrecision(_pixelPrecision)
0030     {
0031     }
0032 
0033     QRect srcBounds;
0034 
0035     QImage srcImage;
0036     QPointF srcImageOffset;
0037 
0038     QVector<QPointF> origCage;
0039     QVector<QPointF> transfCage;
0040     KoUpdater *progress;
0041     int pixelPrecision;
0042 
0043     QVector<int> allToValidPointsMap;
0044     QVector<QPointF> validPoints;
0045 
0046     /**
0047      * Contains all points fo the grid including non-defined
0048      * points (the ones which are placed outside the cage).
0049      */
0050     QVector<QPointF> allSrcPoints;
0051 
0052     KisGreenCoordinatesMath cage;
0053 
0054     QSize gridSize;
0055 
0056     bool isGridEmpty() const {
0057         return allSrcPoints.isEmpty();
0058     }
0059 
0060 
0061     QVector<QPointF> calculateTransformedPoints();
0062 
0063     inline QVector<int> calculateMappedIndexes(int col, int row,
0064                                                int *numExistingPoints);
0065 
0066     int tryGetValidIndex(const QPoint &cellPt);
0067 
0068     struct MapIndexesOp;
0069 };
0070 
0071 KisCageTransformWorker::KisCageTransformWorker(const QRect &deviceNonDefaultRegion,
0072                                                const QVector<QPointF> &origCage,
0073                                                KoUpdater *progress,
0074                                                int pixelPrecision)
0075     : m_d(new Private(origCage, progress, pixelPrecision))
0076 {
0077     m_d->srcBounds = deviceNonDefaultRegion;
0078 }
0079 
0080 KisCageTransformWorker::KisCageTransformWorker(const QImage &srcImage,
0081                                                const QPointF &srcImageOffset,
0082                                                const QVector<QPointF> &origCage,
0083                                                KoUpdater *progress,
0084                                                int pixelPrecision)
0085     : m_d(new Private(origCage, progress, pixelPrecision))
0086 {
0087     m_d->srcImage = srcImage;
0088     m_d->srcImageOffset = srcImageOffset;
0089     m_d->srcBounds = QRectF(m_d->srcImageOffset, m_d->srcImage.size()).toAlignedRect();
0090 }
0091 
0092 KisCageTransformWorker::~KisCageTransformWorker()
0093 {
0094 }
0095 
0096 void KisCageTransformWorker::setTransformedCage(const QVector<QPointF> &transformedCage)
0097 {
0098     m_d->transfCage = transformedCage;
0099 }
0100 
0101 struct PointsFetcherOp
0102 {
0103     PointsFetcherOp(const QPolygonF &cagePolygon)
0104         : m_cagePolygon(cagePolygon),
0105           m_numValidPoints(0)
0106     {
0107         m_polygonDirection = KisAlgebra2D::polygonDirection(cagePolygon);
0108     }
0109 
0110     inline void processPoint(int col, int row,
0111                              int prevCol, int prevRow,
0112                              int colIndex, int rowIndex) {
0113 
0114         Q_UNUSED(prevCol);
0115         Q_UNUSED(prevRow);
0116         Q_UNUSED(colIndex);
0117         Q_UNUSED(rowIndex);
0118 
0119         QPointF pt(col, row);
0120 
0121         if (m_cagePolygon.containsPoint(pt, Qt::OddEvenFill)) {
0122             KisAlgebra2D::adjustIfOnPolygonBoundary(m_cagePolygon, m_polygonDirection, &pt);
0123 
0124             m_points << pt;
0125             m_pointValid << true;
0126             m_numValidPoints++;
0127         } else {
0128             m_points << pt;
0129             m_pointValid << false;
0130         }
0131     }
0132 
0133     inline void nextLine() {
0134     }
0135 
0136     QVector<bool> m_pointValid;
0137     QVector<QPointF> m_points;
0138     QPolygonF m_cagePolygon;
0139     int m_polygonDirection;
0140     int m_numValidPoints;
0141 };
0142 
0143 void KisCageTransformWorker::prepareTransform()
0144 {
0145     if (m_d->origCage.size() < 3) return;
0146 
0147     const QPolygonF srcPolygon(m_d->origCage);
0148 
0149     QRect srcBounds = m_d->srcBounds;
0150     srcBounds &= srcPolygon.boundingRect().toAlignedRect();
0151 
0152     // no need to process empty devices
0153     if (srcBounds.isEmpty()) return;
0154     m_d->gridSize =
0155         GridIterationTools::calcGridSize(srcBounds, m_d->pixelPrecision);
0156 
0157     PointsFetcherOp pointsOp(srcPolygon);
0158     GridIterationTools::processGrid(pointsOp, srcBounds, m_d->pixelPrecision);
0159 
0160     const int numPoints = pointsOp.m_points.size();
0161     KIS_ASSERT_RECOVER_RETURN(numPoints == m_d->gridSize.width() * m_d->gridSize.height());
0162 
0163     m_d->allSrcPoints = pointsOp.m_points;
0164     m_d->allToValidPointsMap.resize(pointsOp.m_points.size());
0165     m_d->validPoints.resize(pointsOp.m_numValidPoints);
0166 
0167     {
0168         int validIdx = 0;
0169         for (int i = 0; i < numPoints; i++) {
0170             const QPointF &pt = pointsOp.m_points[i];
0171             const bool pointValid = pointsOp.m_pointValid[i];
0172 
0173             if (pointValid) {
0174                 m_d->validPoints[validIdx] = pt;
0175                 m_d->allToValidPointsMap[i] = validIdx;
0176                 validIdx++;
0177             } else {
0178                 m_d->allToValidPointsMap[i] = -1;
0179             }
0180         }
0181         KIS_ASSERT_RECOVER_NOOP(validIdx == m_d->validPoints.size());
0182     }
0183 
0184     m_d->cage.precalculateGreenCoordinates(m_d->origCage, m_d->validPoints);
0185 }
0186 
0187 QVector<QPointF> KisCageTransformWorker::Private::calculateTransformedPoints()
0188 {
0189     cage.generateTransformedCageNormals(transfCage);
0190 
0191     const int numValidPoints = validPoints.size();
0192     QVector<QPointF> transformedPoints(numValidPoints);
0193 
0194     for (int i = 0; i < numValidPoints; i++) {
0195         transformedPoints[i] = cage.transformedPoint(i, transfCage);
0196 
0197         if (qIsNaN(transformedPoints[i].x()) ||
0198             qIsNaN(transformedPoints[i].y())) {
0199             warnKrita << "WARNING: One grid point has been removed from consideration" << validPoints[i];
0200             transformedPoints[i] = validPoints[i];
0201         }
0202 
0203     }
0204 
0205     return transformedPoints;
0206 }
0207 
0208 inline QVector<int> KisCageTransformWorker::Private::
0209 calculateMappedIndexes(int col, int row,
0210                        int *numExistingPoints)
0211 {
0212     *numExistingPoints = 0;
0213     QVector<int> cellIndexes =
0214         GridIterationTools::calculateCellIndexes(col, row, gridSize);
0215 
0216     for (int i = 0; i < 4; i++) {
0217         cellIndexes[i] = allToValidPointsMap[cellIndexes[i]];
0218         *numExistingPoints += cellIndexes[i] >= 0;
0219     }
0220 
0221     return cellIndexes;
0222 }
0223 
0224 
0225 
0226 int KisCageTransformWorker::Private::
0227 tryGetValidIndex(const QPoint &cellPt)
0228 {
0229     int index = -1;
0230     if (cellPt.x() >= 0 &&
0231         cellPt.y() >= 0 &&
0232         cellPt.x() < gridSize.width() - 1 &&
0233         cellPt.y() < gridSize.height() - 1) {
0234 
0235         index = allToValidPointsMap[GridIterationTools::pointToIndex(cellPt, gridSize)];
0236     }
0237 
0238     return index;
0239 }
0240 
0241 
0242 struct KisCageTransformWorker::Private::MapIndexesOp {
0243 
0244     MapIndexesOp(KisCageTransformWorker::Private *d)
0245         : m_d(d),
0246           m_srcCagePolygon(QPolygonF(m_d->origCage))
0247     {
0248     }
0249 
0250     inline QVector<int> calculateMappedIndexes(int col, int row,
0251                                                int *numExistingPoints) const {
0252 
0253         return m_d->calculateMappedIndexes(col, row, numExistingPoints);
0254     }
0255 
0256     inline int tryGetValidIndex(const QPoint &cellPt) const {
0257         return m_d->tryGetValidIndex(cellPt);
0258     }
0259 
0260     inline QPointF getSrcPointForce(const QPoint &cellPt) const {
0261         return m_d->allSrcPoints[GridIterationTools::pointToIndex(cellPt, m_d->gridSize)];
0262     }
0263 
0264     inline const QPolygonF srcCropPolygon() const {
0265         return m_srcCagePolygon;
0266     }
0267 
0268     KisCageTransformWorker::Private *m_d;
0269     QPolygonF m_srcCagePolygon;
0270 };
0271 
0272 QRect KisCageTransformWorker::approxChangeRect(const QRect &rc)
0273 {
0274     const qreal margin = 0.30;
0275 
0276     QVector<QPointF> cageSamplePoints;
0277 
0278     const int minStep = 3;
0279     const int maxSamples = 200;
0280 
0281     const int totalPixels = rc.width() * rc.height();
0282     const int realStep = qMax(minStep, totalPixels / maxSamples);
0283     const QPolygonF cagePolygon(m_d->origCage);
0284 
0285     for (int i = 0; i < totalPixels; i += realStep) {
0286         const int x = rc.x() + i % rc.width();
0287         const int y = rc.y() + i / rc.width();
0288 
0289         const QPointF pt(x, y);
0290         if (cagePolygon.containsPoint(pt, Qt::OddEvenFill)) {
0291             cageSamplePoints << pt;
0292         }
0293     }
0294 
0295     if (cageSamplePoints.isEmpty()) {
0296         return rc;
0297     }
0298 
0299     KisGreenCoordinatesMath cage;
0300     cage.precalculateGreenCoordinates(m_d->origCage, cageSamplePoints);
0301     cage.generateTransformedCageNormals(m_d->transfCage);
0302 
0303     const int numValidPoints = cageSamplePoints.size();
0304     QVector<QPointF> transformedPoints(numValidPoints);
0305 
0306     int failedPoints = 0;
0307 
0308     for (int i = 0; i < numValidPoints; i++) {
0309         transformedPoints[i] = cage.transformedPoint(i, m_d->transfCage);
0310 
0311         if (qIsNaN(transformedPoints[i].x()) ||
0312             qIsNaN(transformedPoints[i].y())) {
0313 
0314             transformedPoints[i] = cageSamplePoints[i];
0315             failedPoints++;
0316         }
0317     }
0318 
0319     QRect resultRect =
0320         KisAlgebra2D::approximateRectFromPoints(transformedPoints).toAlignedRect();
0321 
0322     return KisAlgebra2D::blowRect(resultRect | rc, margin);
0323 }
0324 
0325 QRect KisCageTransformWorker::approxNeedRect(const QRect &rc, const QRect &fullBounds)
0326 {
0327     Q_UNUSED(rc);
0328     return fullBounds;
0329 }
0330 
0331 void KisCageTransformWorker::run(KisPaintDeviceSP srcDevice, KisPaintDeviceSP dstDevice)
0332 {
0333     if (m_d->isGridEmpty()) return;
0334 
0335     KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->origCage.size() >= 3);
0336     KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->origCage.size() == m_d->transfCage.size());
0337     KIS_SAFE_ASSERT_RECOVER_RETURN(*srcDevice->colorSpace() == *dstDevice->colorSpace());
0338 
0339     QVector<QPointF> transformedPoints = m_d->calculateTransformedPoints();
0340 
0341     KisPaintDeviceSP tempDevice = new KisPaintDevice(dstDevice->colorSpace());
0342 
0343     {
0344         KisSelectionSP selection = new KisSelection();
0345 
0346         KisPainter painter(selection->pixelSelection());
0347         painter.setPaintColor(KoColor(Qt::black, selection->pixelSelection()->colorSpace()));
0348         painter.setAntiAliasPolygonFill(true);
0349         painter.setFillStyle(KisPainter::FillStyleForegroundColor);
0350         painter.setStrokeStyle(KisPainter::StrokeStyleNone);
0351 
0352         painter.paintPolygon(m_d->origCage);
0353 
0354         dstDevice->clearSelection(selection);
0355     }
0356 
0357     GridIterationTools::PaintDevicePolygonOp polygonOp(srcDevice, tempDevice);
0358     Private::MapIndexesOp indexesOp(m_d.data());
0359     GridIterationTools::iterateThroughGrid
0360         <GridIterationTools::IncompletePolygonPolicy>(polygonOp, indexesOp,
0361                                                       m_d->gridSize,
0362                                                       m_d->validPoints,
0363                                                       transformedPoints);
0364 
0365     QRect rect = tempDevice->extent();
0366     KisPainter gc(dstDevice);
0367     gc.bitBlt(rect.topLeft(), tempDevice, rect);
0368 }
0369 
0370 QImage KisCageTransformWorker::runOnQImage(QPointF *newOffset)
0371 {
0372     if (m_d->isGridEmpty()) return QImage();
0373 
0374     KIS_ASSERT_RECOVER(m_d->origCage.size() >= 3 &&
0375                        m_d->origCage.size() == m_d->transfCage.size()) {
0376         return QImage();
0377     }
0378 
0379     KIS_ASSERT_RECOVER(!m_d->srcImage.isNull()) {
0380         return QImage();
0381     }
0382 
0383     KIS_ASSERT_RECOVER(m_d->srcImage.format() == QImage::Format_ARGB32) {
0384         return QImage();
0385     }
0386 
0387     QVector<QPointF> transformedPoints = m_d->calculateTransformedPoints();
0388 
0389     QRectF dstBounds;
0390     Q_FOREACH (const QPointF &pt, transformedPoints) {
0391         KisAlgebra2D::accumulateBounds(pt, &dstBounds);
0392     }
0393 
0394     const QRectF srcBounds(m_d->srcImageOffset, m_d->srcImage.size());
0395     dstBounds |= srcBounds;
0396 
0397     QPointF dstQImageOffset = dstBounds.topLeft();
0398     *newOffset = dstQImageOffset;
0399 
0400     QRect dstBoundsI = dstBounds.toAlignedRect();
0401 
0402 
0403     QImage dstImage(dstBoundsI.size(), m_d->srcImage.format());
0404     dstImage.fill(0);
0405 
0406     QImage tempImage(dstImage);
0407 
0408     {
0409         // we shouldn't create too many painters
0410         QPainter gc(&dstImage);
0411         gc.drawImage(-dstQImageOffset + m_d->srcImageOffset, m_d->srcImage);
0412         gc.setBrush(Qt::black);
0413         gc.setPen(Qt::black);
0414         gc.setCompositionMode(QPainter::CompositionMode_Clear);
0415         gc.drawPolygon(QPolygonF(m_d->origCage).translated(-dstQImageOffset));
0416         gc.end();
0417     }
0418 
0419     GridIterationTools::QImagePolygonOp polygonOp(m_d->srcImage, tempImage, m_d->srcImageOffset, dstQImageOffset);
0420     Private::MapIndexesOp indexesOp(m_d.data());
0421     GridIterationTools::iterateThroughGrid
0422         <GridIterationTools::IncompletePolygonPolicy>(polygonOp, indexesOp,
0423                                                       m_d->gridSize,
0424                                                       m_d->validPoints,
0425                                                       transformedPoints);
0426 
0427     {
0428         QPainter gc(&dstImage);
0429         gc.drawImage(QPoint(), tempImage);
0430     }
0431 
0432     return dstImage;
0433 }
0434