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