File indexing completed on 2024-05-12 15:58:48
0001 /* 0002 * kis_warptransform_worker.cc -- part of Krita 0003 * 0004 * SPDX-FileCopyrightText: 2010 Marc Pegon <pe.marc@free.fr> 0005 * 0006 * SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "kis_warptransform_worker.h" 0010 #include "kis_random_sub_accessor.h" 0011 #include "kis_iterator_ng.h" 0012 #include "kis_datamanager.h" 0013 0014 #include <QTransform> 0015 #include <QVector2D> 0016 #include <QPainter> 0017 #include <QVarLengthArray> 0018 0019 #include <KoColorSpace.h> 0020 #include <KoColor.h> 0021 0022 #include <math.h> 0023 0024 #include "kis_grid_interpolation_tools.h" 0025 0026 QPointF KisWarpTransformWorker::affineTransformMath(QPointF v, QVector<QPointF> p, QVector<QPointF> q, qreal alpha) 0027 { 0028 int nbPoints = p.size(); 0029 QVarLengthArray<qreal> w(nbPoints); 0030 qreal sumWi = 0; 0031 QPointF pStar(0, 0), qStar(0, 0); 0032 QVarLengthArray<QPointF> pHat(nbPoints), qHat(nbPoints); 0033 0034 for (int i = 0; i < nbPoints; ++i) { 0035 if (v == p[i]) 0036 return q[i]; 0037 0038 QVector2D tmp(p[i] - v); 0039 w[i] = 1. / pow(tmp.lengthSquared(), alpha); 0040 pStar += w[i] * p[i]; 0041 qStar += w[i] * q[i]; 0042 sumWi += w[i]; 0043 } 0044 pStar /= sumWi; 0045 qStar /= sumWi; 0046 0047 qreal A_tmp[4] = {0, 0, 0, 0}; 0048 for (int i = 0; i < nbPoints; ++i) { 0049 pHat[i] = p[i] - pStar; 0050 qHat[i] = q[i] - qStar; 0051 0052 A_tmp[0] += w[i] * pow(pHat[i].x(), 2); 0053 A_tmp[3] += w[i] * pow(pHat[i].y(), 2); 0054 A_tmp[1] += w[i] * pHat[i].x() * pHat[i].y(); 0055 } 0056 A_tmp[2] = A_tmp[1]; 0057 qreal det_A_tmp = A_tmp[0] * A_tmp[3] - A_tmp[1] * A_tmp[2]; 0058 0059 qreal A_tmp_inv[4]; 0060 0061 if (det_A_tmp == 0) 0062 return v; 0063 0064 A_tmp_inv[0] = A_tmp[3] / det_A_tmp; 0065 A_tmp_inv[1] = - A_tmp[1] / det_A_tmp; 0066 A_tmp_inv[2] = A_tmp_inv[1]; 0067 A_tmp_inv[3] = A_tmp[0] / det_A_tmp; 0068 0069 QPointF t = v - pStar; 0070 QPointF A_precalc(t.x() * A_tmp_inv[0] + t.y() * A_tmp_inv[1], t.x() * A_tmp_inv[2] + t.y() * A_tmp_inv[3]); 0071 qreal A_j; 0072 0073 QPointF res = qStar; 0074 for (int j = 0; j < nbPoints; ++j) { 0075 A_j = A_precalc.x() * pHat[j].x() + A_precalc.y() * pHat[j].y(); 0076 0077 res += w[j] * A_j * qHat[j]; 0078 } 0079 0080 return res; 0081 } 0082 0083 QPointF KisWarpTransformWorker::similitudeTransformMath(QPointF v, QVector<QPointF> p, QVector<QPointF> q, qreal alpha) 0084 { 0085 int nbPoints = p.size(); 0086 QVarLengthArray<qreal> w(nbPoints); 0087 qreal sumWi = 0; 0088 QPointF pStar(0, 0), qStar(0, 0); 0089 QVarLengthArray<QPointF> pHat(nbPoints), qHat(nbPoints); 0090 0091 for (int i = 0; i < nbPoints; ++i) { 0092 if (v == p[i]) 0093 return q[i]; 0094 0095 QVector2D tmp(p[i] - v); 0096 w[i] = 1. / pow(tmp.lengthSquared(), alpha); 0097 pStar += w[i] * p[i]; 0098 qStar += w[i] * q[i]; 0099 sumWi += w[i]; 0100 } 0101 pStar /= sumWi; 0102 qStar /= sumWi; 0103 0104 qreal mu_s = 0; 0105 QPointF res_tmp(0, 0); 0106 qreal qx, qy, px, py; 0107 for (int i = 0; i < nbPoints; ++i) { 0108 pHat[i] = p[i] - pStar; 0109 qHat[i] = q[i] - qStar; 0110 0111 QVector2D tmp(pHat[i]); 0112 mu_s += w[i] * tmp.lengthSquared(); 0113 0114 qx = w[i] * qHat[i].x(); 0115 qy = w[i] * qHat[i].y(); 0116 px = pHat[i].x(); 0117 py = pHat[i].y(); 0118 0119 res_tmp += QPointF(qx * px + qy * py, qx * py - qy * px); 0120 } 0121 0122 res_tmp /= mu_s; 0123 QPointF v_m_pStar(v - pStar); 0124 QPointF res(res_tmp.x() * v_m_pStar.x() + res_tmp.y() * v_m_pStar.y(), res_tmp.x() * v_m_pStar.y() - res_tmp.y() * v_m_pStar.x()); 0125 res += qStar; 0126 0127 return res; 0128 } 0129 0130 QPointF KisWarpTransformWorker::rigidTransformMath(QPointF v, QVector<QPointF> p, QVector<QPointF> q, qreal alpha) 0131 { 0132 int nbPoints = p.size(); 0133 QVarLengthArray<qreal> w(nbPoints); 0134 qreal sumWi = 0; 0135 QPointF pStar(0, 0), qStar(0, 0); 0136 QVarLengthArray<QPointF> pHat(nbPoints), qHat(nbPoints); 0137 0138 for (int i = 0; i < nbPoints; ++i) { 0139 if (v == p[i]) 0140 return q[i]; 0141 0142 QVector2D tmp(p[i] - v); 0143 w[i] = 1. / pow(tmp.lengthSquared(), alpha); 0144 pStar += w[i] * p[i]; 0145 qStar += w[i] * q[i]; 0146 sumWi += w[i]; 0147 } 0148 pStar /= sumWi; 0149 qStar /= sumWi; 0150 0151 QVector2D res_tmp(0, 0); 0152 qreal qx, qy, px, py; 0153 for (int i = 0; i < nbPoints; ++i) { 0154 pHat[i] = p[i] - pStar; 0155 qHat[i] = q[i] - qStar; 0156 0157 qx = w[i] * qHat[i].x(); 0158 qy = w[i] * qHat[i].y(); 0159 px = pHat[i].x(); 0160 py = pHat[i].y(); 0161 0162 res_tmp += QVector2D(qx * px + qy * py, qx * py - qy * px); 0163 } 0164 0165 QPointF f_arrow(res_tmp.normalized().toPointF()); 0166 QVector2D v_m_pStar(v - pStar); 0167 QPointF res(f_arrow.x() * v_m_pStar.x() + f_arrow.y() * v_m_pStar.y(), f_arrow.x() * v_m_pStar.y() - f_arrow.y() * v_m_pStar.x()); 0168 res += qStar; 0169 0170 return res; 0171 } 0172 0173 KisWarpTransformWorker::KisWarpTransformWorker(WarpType warpType, QVector<QPointF> origPoint, QVector<QPointF> transfPoint, qreal alpha, KoUpdater *progress) 0174 : m_progress(progress) 0175 { 0176 m_origPoint = origPoint; 0177 m_transfPoint = transfPoint; 0178 m_alpha = alpha; 0179 0180 switch(warpType) { 0181 case AFFINE_TRANSFORM: 0182 m_warpMathFunction = &affineTransformMath; 0183 break; 0184 case SIMILITUDE_TRANSFORM: 0185 m_warpMathFunction = &similitudeTransformMath; 0186 break; 0187 case RIGID_TRANSFORM: 0188 m_warpMathFunction = &rigidTransformMath; 0189 break; 0190 default: 0191 m_warpMathFunction = 0; 0192 break; 0193 } 0194 } 0195 0196 KisWarpTransformWorker::~KisWarpTransformWorker() 0197 { 0198 } 0199 0200 struct KisWarpTransformWorker::FunctionTransformOp 0201 { 0202 FunctionTransformOp(KisWarpTransformWorker::WarpMathFunction function, 0203 const QVector<QPointF> &p, 0204 const QVector<QPointF> &q, 0205 qreal alpha) 0206 : m_function(function), 0207 m_p(p), 0208 m_q(q), 0209 m_alpha(alpha) 0210 { 0211 } 0212 0213 QPointF operator() (const QPointF &pt) const { 0214 return m_function(pt, m_p, m_q, m_alpha); 0215 } 0216 0217 KisWarpTransformWorker::WarpMathFunction m_function; 0218 const QVector<QPointF> &m_p; 0219 const QVector<QPointF> &m_q; 0220 qreal m_alpha; 0221 }; 0222 0223 void KisWarpTransformWorker::run(KisPaintDeviceSP srcDev, KisPaintDeviceSP dstDev) 0224 { 0225 KIS_SAFE_ASSERT_RECOVER_RETURN(*srcDev->colorSpace() == *dstDev->colorSpace()); 0226 0227 if (!m_warpMathFunction || 0228 m_origPoint.isEmpty() || 0229 m_origPoint.size() != m_transfPoint.size()) { 0230 0231 return; 0232 } 0233 0234 if (m_origPoint.size() == 1) { 0235 dstDev->makeCloneFromRough(srcDev, srcDev->extent()); 0236 QPointF translate(QPointF(srcDev->x(), srcDev->y()) + m_transfPoint[0] - m_origPoint[0]); 0237 dstDev->moveTo(translate.toPoint()); 0238 return; 0239 } 0240 0241 const QRect srcBounds = srcDev->region().boundingRect(); 0242 0243 dstDev->clear(); 0244 0245 const int pixelPrecision = 8; 0246 0247 FunctionTransformOp functionOp(m_warpMathFunction, m_origPoint, m_transfPoint, m_alpha); 0248 GridIterationTools::PaintDevicePolygonOp polygonOp(srcDev, dstDev); 0249 GridIterationTools::processGrid(polygonOp, functionOp, 0250 srcBounds, pixelPrecision); 0251 } 0252 0253 #include "krita_utils.h" 0254 0255 QRect KisWarpTransformWorker::approxChangeRect(const QRect &rc) 0256 { 0257 const qreal margin = 0.05; 0258 0259 FunctionTransformOp functionOp(m_warpMathFunction, m_origPoint, m_transfPoint, m_alpha); 0260 QRect resultRect = KisAlgebra2D::approximateRectWithPointTransform(rc, functionOp); 0261 0262 return KisAlgebra2D::blowRect(resultRect, margin); 0263 } 0264 0265 QRect KisWarpTransformWorker::approxNeedRect(const QRect &rc, const QRect &fullBounds) 0266 { 0267 Q_UNUSED(rc); 0268 return fullBounds; 0269 } 0270 0271 QImage KisWarpTransformWorker::transformQImage(WarpType warpType, 0272 const QVector<QPointF> &origPoint, 0273 const QVector<QPointF> &transfPoint, 0274 qreal alpha, 0275 const QImage& srcImage, 0276 const QPointF &srcQImageOffset, 0277 QPointF *newOffset) 0278 { 0279 KIS_ASSERT_RECOVER(srcImage.format() == QImage::Format_ARGB32) { 0280 return QImage(); 0281 } 0282 0283 WarpMathFunction warpMathFunction = &rigidTransformMath; 0284 0285 switch (warpType) { 0286 case AFFINE_TRANSFORM: 0287 warpMathFunction = &affineTransformMath; 0288 break; 0289 case SIMILITUDE_TRANSFORM: 0290 warpMathFunction = &similitudeTransformMath; 0291 break; 0292 case RIGID_TRANSFORM: 0293 warpMathFunction = &rigidTransformMath; 0294 break; 0295 default: 0296 KIS_ASSERT_RECOVER(0 && "Unknown warp mode") { return QImage(); } 0297 } 0298 0299 if (!warpMathFunction || 0300 origPoint.isEmpty() || 0301 origPoint.size() != transfPoint.size()) { 0302 0303 return srcImage; 0304 } 0305 0306 if (origPoint.size() == 1) { 0307 *newOffset = srcQImageOffset + (transfPoint[0] - origPoint[0]).toPoint(); 0308 return srcImage; 0309 } 0310 0311 FunctionTransformOp functionOp(warpMathFunction, origPoint, transfPoint, alpha); 0312 0313 const QRectF srcBounds = QRectF(srcQImageOffset, srcImage.size()); 0314 QRectF dstBounds; 0315 0316 { 0317 QPolygonF testPoints; 0318 testPoints << srcBounds.topLeft(); 0319 testPoints << srcBounds.topRight(); 0320 testPoints << srcBounds.bottomRight(); 0321 testPoints << srcBounds.bottomLeft(); 0322 testPoints << srcBounds.topLeft(); 0323 0324 QPolygonF::iterator it = testPoints.begin() + 1; 0325 0326 while (it != testPoints.end()) { 0327 it = testPoints.insert(it, 0.5 * (*it + *(it - 1))); 0328 it += 2; 0329 } 0330 0331 it = testPoints.begin(); 0332 0333 while (it != testPoints.end()) { 0334 *it = functionOp(*it); 0335 ++it; 0336 } 0337 0338 dstBounds = testPoints.boundingRect(); 0339 } 0340 0341 QPointF dstQImageOffset = dstBounds.topLeft(); 0342 *newOffset = dstQImageOffset; 0343 0344 QRect dstBoundsI = dstBounds.toAlignedRect(); 0345 QImage dstImage(dstBoundsI.size(), srcImage.format()); 0346 dstImage.fill(0); 0347 0348 const int pixelPrecision = 32; 0349 GridIterationTools::QImagePolygonOp polygonOp(srcImage, dstImage, srcQImageOffset, dstQImageOffset); 0350 GridIterationTools::processGrid(polygonOp, functionOp, srcBounds.toAlignedRect(), pixelPrecision); 0351 0352 return dstImage; 0353 }