File indexing completed on 2024-05-12 15:57:01
0001 /* 0002 * SPDX-FileCopyrightText: 2020 Dmitry Kazakov <dimula73@gmail.com> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #ifndef KISBEZIERUTILS_H 0008 #define KISBEZIERUTILS_H 0009 0010 #include "kritaglobal_export.h" 0011 0012 #include <kis_algebra_2d.h> 0013 0014 0015 namespace KisBezierUtils 0016 { 0017 using KisAlgebra2D::lerp; 0018 0019 0020 0021 0022 inline QPointF bezierCurveDeriv(const QPointF p0, 0023 const QPointF p1, 0024 const QPointF p2, 0025 const QPointF p3, 0026 qreal t) 0027 { 0028 const qreal t_2 = pow2(t); 0029 const qreal t_inv = 1.0 - t; 0030 const qreal t_inv_2 = pow2(t_inv); 0031 0032 return 0033 3 * t_inv_2 * (p1 - p0) + 0034 6 * t_inv * t * (p2 - p1) + 0035 3 * t_2 * (p3 - p2); 0036 } 0037 0038 inline QPointF bezierCurveDeriv2(const QPointF p0, 0039 const QPointF p1, 0040 const QPointF p2, 0041 const QPointF p3, 0042 qreal t) 0043 { 0044 const qreal t_inv = 1.0 - t; 0045 0046 return 0047 6 * t_inv * (p2 - 2 * p1 + p0) + 0048 6 * t * (p3 - 2 * p2 + p1); 0049 } 0050 0051 0052 inline void deCasteljau(const QPointF &q0, 0053 const QPointF &q1, 0054 const QPointF &q2, 0055 const QPointF &q3, 0056 qreal t, 0057 QPointF *p0, 0058 QPointF *p1, 0059 QPointF *p2, 0060 QPointF *p3, 0061 QPointF *p4) 0062 { 0063 QPointF q[4]; 0064 0065 q[0] = q0; 0066 q[1] = q1; 0067 q[2] = q2; 0068 q[3] = q3; 0069 0070 // points of the new segment after the split point 0071 QPointF p[3]; 0072 0073 // the De Casteljau algorithm 0074 for (unsigned short j = 1; j <= 3; ++j) { 0075 for (unsigned short i = 0; i <= 3 - j; ++i) { 0076 q[i] = (1.0 - t) * q[i] + t * q[i + 1]; 0077 } 0078 p[j - 1] = q[0]; 0079 } 0080 0081 *p0 = p[0]; 0082 *p1 = p[1]; 0083 *p2 = p[2]; 0084 *p3 = q[1]; 0085 *p4 = q[2]; 0086 } 0087 0088 inline QPointF bezierCurve(const QPointF p0, 0089 const QPointF p1, 0090 const QPointF p2, 0091 const QPointF p3, 0092 qreal t) 0093 { 0094 #if 0 0095 const qreal t_2 = pow2(t); 0096 const qreal t_3 = t_2 * t; 0097 const qreal t_inv = 1.0 - t; 0098 const qreal t_inv_2 = pow2(t_inv); 0099 const qreal t_inv_3 = t_inv_2 * t_inv; 0100 0101 return 0102 t_inv_3 * p0 + 0103 3 * t_inv_2 * t * p1 + 0104 3 * t_inv * t_2 * p2 + 0105 t_3 * p3; 0106 #else 0107 QPointF q0, q1, q2, q3, q4; 0108 deCasteljau(p0, p1, p2, p3, t, &q0, &q1, &q2, &q3, &q4); 0109 return q2; 0110 #endif 0111 } 0112 0113 inline QPointF bezierCurve(const QList<QPointF> &points, 0114 qreal t) 0115 { 0116 QPointF result; 0117 0118 if (points.size() == 2) { 0119 result = lerp(points.first(), points.last(), t); 0120 } else if (points.size() == 3) { 0121 result = bezierCurve(points[0], points[1], points[1], points[2], t); 0122 } else if (points.size() == 4) { 0123 result = bezierCurve(points[0], points[1], points[2], points[3], t); 0124 } else { 0125 KIS_SAFE_ASSERT_RECOVER_NOOP(0 && "Unsupported number of bezier control points"); 0126 } 0127 0128 return result; 0129 } 0130 0131 inline bool isLinearSegmentByDerivatives(const QPointF &p0, const QPointF &d0, 0132 const QPointF &p1, const QPointF &d1, 0133 const qreal eps = 1e-4) 0134 { 0135 const QPointF diff = p1 - p0; 0136 const qreal dist = KisAlgebra2D::norm(diff); 0137 0138 const qreal normCoeff = 1.0 / 3.0 / dist; 0139 0140 const qreal offset1 = 0141 normCoeff * qAbs(KisAlgebra2D::crossProduct(diff, d0)); 0142 if (offset1 > eps) return false; 0143 0144 const qreal offset2 = 0145 normCoeff * qAbs(KisAlgebra2D::crossProduct(diff, d1)); 0146 if (offset2 > eps) return false; 0147 0148 return true; 0149 } 0150 0151 inline bool isLinearSegmentByControlPoints(const QPointF &p0, const QPointF &p1, 0152 const QPointF &p2, const QPointF &p3, 0153 const qreal eps = 1e-4) 0154 { 0155 return isLinearSegmentByDerivatives(p0, (p1 - p0) * 3.0, p3, (p3 - p2) * 3.0, eps); 0156 } 0157 0158 inline int bezierDegree(const QPointF p0, 0159 const QPointF p1, 0160 const QPointF p2, 0161 const QPointF p3) 0162 { 0163 const qreal eps = 1e-4; 0164 0165 int degree = 3; 0166 0167 if (isLinearSegmentByControlPoints(p0, p1, p2, p3, eps)) { 0168 degree = 1; 0169 } else if (KisAlgebra2D::fuzzyPointCompare(p1, p2, eps)) { 0170 degree = 2; 0171 } 0172 0173 return degree; 0174 } 0175 0176 KRITAGLOBAL_EXPORT 0177 QVector<qreal> linearizeCurve(const QPointF p0, 0178 const QPointF p1, 0179 const QPointF p2, 0180 const QPointF p3, 0181 const qreal eps); 0182 KRITAGLOBAL_EXPORT 0183 QVector<qreal> mergeLinearizationSteps(const QVector<qreal> &a, const QVector<qreal> &b); 0184 0185 KRITAGLOBAL_EXPORT 0186 qreal nearestPoint(const QList<QPointF> controlPoints, const QPointF &point, qreal *resultDistance = 0, QPointF *resultPoint = 0); 0187 0188 KRITAGLOBAL_EXPORT 0189 int controlPolygonZeros(const QList<QPointF> &controlPoints); 0190 0191 /** 0192 * @brief calculates local (u,v) coordinates of the patch corresponding to \p globalPoint 0193 * 0194 * The function uses Krita's own level-based patch interpolation algorithm 0195 * 0196 * @param points control points as the layouted in KisBezierPatch 0197 * @param globalPoint point in global coordinates 0198 * @return point in local coordinates 0199 */ 0200 KRITAGLOBAL_EXPORT 0201 QPointF calculateLocalPos(const std::array<QPointF, 12> &points, 0202 const QPointF &globalPoint); 0203 0204 /** 0205 * @brief calculates global coordinate corresponding to the patch coordinate (u, v) 0206 * 0207 * The function uses Krita's own level-based patch interpolation algorithm 0208 * 0209 * @param points control points as the layouted in KisBezierPatch 0210 * @param localPoint point in local coordinates 0211 * @return point in global coordinates 0212 */ 0213 KRITAGLOBAL_EXPORT 0214 QPointF calculateGlobalPos(const std::array<QPointF, 12> &points, const QPointF &localPoint); 0215 0216 /** 0217 * @brief calculates local (u,v) coordinates of the patch corresponding to \p globalPoint 0218 * 0219 * The function uses SVG2 toon patches algorithm 0220 * 0221 * @param points control points as the layouted in KisBezierPatch 0222 * @param globalPoint point in global coordinates 0223 * @return point in local coordinates 0224 */ 0225 KRITAGLOBAL_EXPORT 0226 QPointF calculateLocalPosSVG2(const std::array<QPointF, 12> &points, 0227 const QPointF &globalPoint); 0228 0229 /** 0230 * @brief calculates global coordinate corresponding to the patch coordinate (u, v) 0231 * 0232 * The function uses SVG2 toon patches algorithm 0233 * 0234 * @param points control points as the layouted in KisBezierPatch 0235 * @param localPoint point in local coordinates 0236 * @return point in global coordinates 0237 */ 0238 KRITAGLOBAL_EXPORT 0239 QPointF calculateGlobalPosSVG2(const std::array<QPointF, 12> &points, const QPointF &localPoint); 0240 0241 0242 /** 0243 * @brief Interpolates quadric curve passing through given points 0244 * 0245 * Interpolates quadric curve passing through \p p0, \p pt and \p p2 0246 * with ensuring that \p pt placed at position \p t 0247 * @return interpolated value for control point p1 0248 */ 0249 KRITAGLOBAL_EXPORT 0250 QPointF interpolateQuadric(const QPointF &p0, const QPointF &p2, const QPointF &pt, qreal t); 0251 0252 /** 0253 * @brief moves point \p t of the curve by offset \p offset 0254 * @return proposed offsets for points p1 and p2 of the curve 0255 */ 0256 KRITAGLOBAL_EXPORT 0257 std::pair<QPointF, QPointF> offsetSegment(qreal t, const QPointF &offset); 0258 0259 KRITAGLOBAL_EXPORT 0260 qreal curveLength(const QPointF p0, 0261 const QPointF p1, 0262 const QPointF p2, 0263 const QPointF p3, 0264 const qreal error); 0265 0266 KRITAGLOBAL_EXPORT 0267 qreal curveLengthAtPoint(const QPointF p0, 0268 const QPointF p1, 0269 const QPointF p2, 0270 const QPointF p3, 0271 qreal t, 0272 const qreal error); 0273 0274 KRITAGLOBAL_EXPORT 0275 qreal curveParamByProportion(const QPointF p0, 0276 const QPointF p1, 0277 const QPointF p2, 0278 const QPointF p3, 0279 qreal proportion, 0280 const qreal error); 0281 0282 KRITAGLOBAL_EXPORT 0283 qreal curveProportionByParam(const QPointF p0, const QPointF p1, const QPointF p2, const QPointF p3, qreal t, const qreal error); 0284 0285 /** 0286 * @brief Adjusts position for the bezier control points 0287 * after removing a node. 0288 * 0289 * First source curve P: \p p0, \p p1, \p p2, \p p3 0290 * Second source curve Q: \p p3, \p q1, \p q2, \p q3 0291 * 0292 * Node to remove: \p p3 and its control points \p p2 and \p q1 0293 * 0294 * @return a pair of new positions for \p p1 and \p q2 0295 */ 0296 KRITAGLOBAL_EXPORT 0297 std::pair<QPointF, QPointF> removeBezierNode(const QPointF &p0, 0298 const QPointF &p1, 0299 const QPointF &p2, 0300 const QPointF &p3, 0301 const QPointF &q1, 0302 const QPointF &q2, 0303 const QPointF &q3); 0304 0305 /** 0306 * Find intersection points (their parameter values) between a cubic 0307 * Bezier curve and a line. 0308 * 0309 * Curve: \p p0, \p p1, \p p2, \p p3 0310 * Line: \p line 0311 * 0312 * For cubic Bezier curves there can be at most three intersection points. 0313 */ 0314 KRITAGLOBAL_EXPORT 0315 QVector<qreal> intersectWithLine(const QPointF &p0, 0316 const QPointF &p1, 0317 const QPointF &p2, 0318 const QPointF &p3, 0319 const QLineF &line, 0320 qreal eps); 0321 0322 /** 0323 * Find new nearest intersection point between a cubic Bezier curve and a line. 0324 * The resulting point will be the nearest to \p nearestAnchor. 0325 */ 0326 KRITAGLOBAL_EXPORT 0327 boost::optional<qreal> intersectWithLineNearest(const QPointF &p0, 0328 const QPointF &p1, 0329 const QPointF &p2, 0330 const QPointF &p3, 0331 const QLineF &line, 0332 const QPointF &nearestAnchor, 0333 qreal eps); 0334 0335 } 0336 0337 #endif // KISBEZIERUTILS_H