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