File indexing completed on 2024-05-12 15:56:39

0001 /* This file is part of the KDE project
0002    SPDX-FileCopyrightText: 2001-2003 Rob Buis <buis@kde.org>
0003    SPDX-FileCopyrightText: 2007, 2009 Jan Hambrecht <jaham@gmx.net>
0004 
0005    SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "KoCurveFit.h"
0009 #include <KoPathShape.h>
0010 #include <QVector>
0011 #include <math.h>
0012 
0013 /// our equivalent to zero
0014 const qreal Zero = 10e-12;
0015 
0016 /*
0017     An Algorithm for Automatically Fitting Digitized Curves
0018     by Philip J. Schneider
0019     from "Graphics Gems", Academic Press, 1990
0020 
0021     http://tog.acm.org/resources/GraphicsGems/gems/FitCurves.c
0022     http://tog.acm.org/resources/GraphicsGems/gems/README
0023 */
0024 
0025 class FitVector {
0026 public:
0027     FitVector(const QPointF &p)
0028     : m_X(p.x())
0029     , m_Y(p.y())
0030     {
0031     }
0032 
0033     FitVector()
0034     : m_X(0)
0035     , m_Y(0)
0036     {
0037     }
0038 
0039     FitVector(const QPointF &a, const QPointF &b)
0040     : m_X(a.x() - b.x())
0041     , m_Y(a.y() - b.y())
0042     {
0043     }
0044 
0045     void normalize() {
0046         qreal len = length();
0047         if (qFuzzyCompare(len, qreal(0.0))) {
0048             return;
0049         }
0050         m_X /= len; m_Y /= len;
0051     }
0052 
0053     void negate() {
0054         m_X = -m_X;
0055         m_Y = -m_Y;
0056     }
0057 
0058     void scale(qreal s) {
0059         qreal len = length();
0060         if (qFuzzyCompare(len, qreal(0.0))) {
0061             return;
0062         }
0063         m_X *= s / len;
0064         m_Y *= s / len;
0065     }
0066 
0067     qreal dot(const FitVector &v) const {
0068         return ((m_X*v.m_X) + (m_Y*v.m_Y));
0069     }
0070 
0071     qreal length() const {
0072         return (qreal) sqrt(m_X*m_X + m_Y*m_Y);
0073     }
0074 
0075     QPointF operator+(const QPointF &p) {
0076         QPointF b(p.x() + m_X, p.y() + m_Y);
0077         return b;
0078     }
0079 
0080 public:
0081     qreal m_X, m_Y;
0082 };
0083 
0084 qreal distance(const QPointF &p1, const QPointF &p2)
0085 {
0086     qreal dx = (p1.x() - p2.x());
0087     qreal dy = (p1.y() - p2.y());
0088     return sqrt(dx*dx + dy*dy);
0089 }
0090 
0091 
0092 FitVector ComputeLeftTangent(const QList<QPointF> &points, int end)
0093 {
0094     FitVector tHat1(points.at(end + 1), points.at(end));
0095 
0096     tHat1.normalize();
0097 
0098     return tHat1;
0099 }
0100 
0101 FitVector ComputeRightTangent(const QList<QPointF> &points, int end)
0102 {
0103     FitVector tHat1(points.at(end - 1), points.at(end));
0104 
0105     tHat1.normalize();
0106 
0107     return tHat1;
0108 }
0109 
0110 /*
0111  *  ChordLengthParameterize :
0112  *  Assign parameter values to digitized points
0113  *  using relative distances between points.
0114  */
0115 static qreal *ChordLengthParameterize(const QList<QPointF> &points, int first, int last)
0116 {
0117     int     i;
0118     qreal   *u;         /*  Parameterization        */
0119 
0120     u = new qreal[(last-first+1)];
0121 
0122     u[0] = 0.0;
0123     for (i = first + 1; i <= last; ++i) {
0124         u[i-first] = u[i-first-1] +
0125                      distance(points.at(i), points.at(i - 1));
0126     }
0127 
0128     qreal denominator = u[last-first];
0129     if (qFuzzyCompare(denominator, qreal(0.0))) {
0130         denominator = Zero;
0131     }
0132 
0133     for (i = first + 1; i <= last; ++i) {
0134         u[i-first] = u[i-first] / denominator;
0135     }
0136 
0137     return(u);
0138 }
0139 
0140 static FitVector VectorAdd(FitVector a, FitVector b)
0141 {
0142     FitVector   c;
0143     c.m_X = a.m_X + b.m_X;  c.m_Y = a.m_Y + b.m_Y;
0144     return (c);
0145 }
0146 static FitVector VectorScale(FitVector v, qreal s)
0147 {
0148     FitVector result;
0149     result.m_X = v.m_X * s; result.m_Y = v.m_Y * s;
0150     return (result);
0151 }
0152 
0153 static FitVector VectorSub(FitVector a, FitVector b)
0154 {
0155     FitVector   c;
0156     c.m_X = a.m_X - b.m_X; c.m_Y = a.m_Y - b.m_Y;
0157     return (c);
0158 }
0159 
0160 static FitVector ComputeCenterTangent(const QList<QPointF> &points, int center)
0161 {
0162     FitVector V1, V2, tHatCenter;
0163 
0164     FitVector cpointb(points.at(center - 1));
0165     FitVector cpoint(points.at(center));
0166     FitVector cpointa(points.at(center + 1));
0167 
0168     V1 = VectorSub(cpointb, cpoint);
0169     V2 = VectorSub(cpoint, cpointa);
0170     tHatCenter.m_X = ((V1.m_X + V2.m_X) / 2.0);
0171     tHatCenter.m_Y = ((V1.m_Y + V2.m_Y) / 2.0);
0172     tHatCenter.normalize();
0173     return tHatCenter;
0174 }
0175 
0176 /*
0177  *  B0, B1, B2, B3 :
0178  *  Bezier multipliers
0179  */
0180 static qreal B0(qreal u)
0181 {
0182     qreal tmp = 1.0 - u;
0183     return (tmp * tmp * tmp);
0184 }
0185 
0186 
0187 static qreal B1(qreal u)
0188 {
0189     qreal tmp = 1.0 - u;
0190     return (3 * u *(tmp * tmp));
0191 }
0192 
0193 static qreal B2(qreal u)
0194 {
0195     qreal tmp = 1.0 - u;
0196     return (3 * u * u * tmp);
0197 }
0198 
0199 static qreal B3(qreal u)
0200 {
0201     return (u * u * u);
0202 }
0203 
0204 /*
0205  *  GenerateBezier :
0206  *  Use least-squares method to find Bezier control points for region.
0207  *
0208  */
0209 QPointF* GenerateBezier(const QList<QPointF> &points, int first, int last, qreal *uPrime, FitVector tHat1, FitVector tHat2)
0210 {
0211     int     i;
0212     int     nPts;           /* Number of pts in sub-curve */
0213     qreal   C[2][2];            /* Matrix C     */
0214     qreal   X[2];           /* Matrix X         */
0215     qreal   det_C0_C1,      /* Determinants of matrices */
0216     det_C0_X,
0217     det_X_C1;
0218     qreal   alpha_l,        /* Alpha values, left and right */
0219     alpha_r;
0220     FitVector   tmp;            /* Utility variable     */
0221     QPointF *curve;
0222 
0223     curve = new QPointF[4];
0224     nPts = last - first + 1;
0225 
0226     /* Precomputed rhs for eqn      */
0227     // FitVector A[nPts][2]
0228     QVector< QVector<FitVector> > A(nPts, QVector<FitVector>(2));
0229 
0230     /* Compute the A's  */
0231     for (i = 0; i < nPts; ++i) {
0232         FitVector v1, v2;
0233         v1 = tHat1;
0234         v2 = tHat2;
0235         v1.scale(B1(uPrime[i]));
0236         v2.scale(B2(uPrime[i]));
0237         A[i][0] = v1;
0238         A[i][1] = v2;
0239     }
0240 
0241     /* Create the C and X matrices  */
0242     C[0][0] = 0.0;
0243     C[0][1] = 0.0;
0244     C[1][0] = 0.0;
0245     C[1][1] = 0.0;
0246     X[0]    = 0.0;
0247     X[1]    = 0.0;
0248 
0249     for (i = 0; i < nPts; ++i) {
0250         C[0][0] += (A[i][0]).dot(A[i][0]);
0251         C[0][1] += A[i][0].dot(A[i][1]);
0252         /* C[1][0] += V2Dot(&A[i][0], &A[i][1]);*/
0253         C[1][0] = C[0][1];
0254         C[1][1] += A[i][1].dot(A[i][1]);
0255 
0256         FitVector vfirstp1(points.at(first + i));
0257         FitVector vfirst(points.at(first));
0258         FitVector vlast(points.at(last));
0259 
0260         tmp = VectorSub(vfirstp1,
0261                         VectorAdd(
0262                             VectorScale(vfirst, B0(uPrime[i])),
0263                             VectorAdd(
0264                                 VectorScale(vfirst, B1(uPrime[i])),
0265                                 VectorAdd(
0266                                     VectorScale(vlast, B2(uPrime[i])),
0267                                     VectorScale(vlast, B3(uPrime[i]))))));
0268 
0269 
0270         X[0] += A[i][0].dot(tmp);
0271         X[1] += A[i][1].dot(tmp);
0272     }
0273 
0274     /* Compute the determinants of C and X  */
0275     det_C0_C1 = C[0][0] * C[1][1] - C[1][0] * C[0][1];
0276     det_C0_X  = C[0][0] * X[1]    - C[0][1] * X[0];
0277     det_X_C1  = X[0]    * C[1][1] - X[1]    * C[0][1];
0278 
0279     /* Finally, derive alpha values */
0280     if (qFuzzyCompare(det_C0_C1, qreal(0.0))) {
0281         det_C0_C1 = (C[0][0] * C[1][1]) * 10e-12;
0282         if (qFuzzyCompare(det_C0_C1, qreal(0.0))) {
0283             det_C0_C1 = Zero;
0284         }
0285     }
0286     alpha_l = det_X_C1 / det_C0_C1;
0287     alpha_r = det_C0_X / det_C0_C1;
0288 
0289 
0290     /*  If alpha negative, use the Wu/Barsky heuristic (see text) */
0291     /* (if alpha is 0, you get coincident control points that lead to
0292      * divide by zero in any subsequent NewtonRaphsonRootFind() call. */
0293     if (alpha_l < 1.0e-6 || alpha_r < 1.0e-6) {
0294         qreal dist = distance(points.at(last), points.at(first)) / 3.0;
0295 
0296         curve[0] = points.at(first);
0297         curve[3] = points.at(last);
0298 
0299         tHat1.scale(dist);
0300         tHat2.scale(dist);
0301 
0302         curve[1] = tHat1 + curve[0];
0303         curve[2] = tHat2 + curve[3];
0304         return curve;
0305     }
0306 
0307     /*  First and last control points of the Bezier curve are */
0308     /*  positioned exactly at the first and last data points */
0309     /*  Control points 1 and 2 are positioned an alpha distance out */
0310     /*  on the tangent vectors, left and right, respectively */
0311     curve[0] = points.at(first);
0312     curve[3] = points.at(last);
0313 
0314     tHat1.scale(alpha_l);
0315     tHat2.scale(alpha_r);
0316 
0317     curve[1] = tHat1 + curve[0];
0318     curve[2] = tHat2 + curve[3];
0319 
0320     return (curve);
0321 }
0322 
0323 /*
0324  *  Bezier :
0325  *      Evaluate a Bezier curve at a particular parameter value
0326  *
0327  */
0328 static QPointF BezierII(int degree, QPointF *V, qreal t)
0329 {
0330     int     i, j;
0331     QPointF     Q;          /* Point on curve at parameter t    */
0332     QPointF     *Vtemp;     /* Local copy of control points     */
0333 
0334     Vtemp = new QPointF[degree+1];
0335 
0336     for (i = 0; i <= degree; ++i) {
0337         Vtemp[i] = V[i];
0338     }
0339 
0340     /* Triangle computation */
0341     for (i = 1; i <= degree; ++i) {
0342         for (j = 0; j <= degree - i; ++j) {
0343             Vtemp[j].setX((1.0 - t) * Vtemp[j].x() + t * Vtemp[j+1].x());
0344             Vtemp[j].setY((1.0 - t) * Vtemp[j].y() + t * Vtemp[j+1].y());
0345         }
0346     }
0347 
0348     Q = Vtemp[0];
0349     delete[] Vtemp;
0350     return Q;
0351 }
0352 
0353 /*
0354  *  ComputeMaxError :
0355  *  Find the maximum squared distance of digitized points
0356  *  to fitted curve.
0357 */
0358 static qreal ComputeMaxError(const QList<QPointF> &points, int first, int last, QPointF *curve, qreal *u, int *splitPoint)
0359 {
0360     int     i;
0361     qreal   maxDist;        /*  Maximum error       */
0362     qreal   dist;       /*  Current error       */
0363     QPointF P;          /*  Point on curve      */
0364     FitVector   v;          /*  Vector from point to curve  */
0365 
0366     *splitPoint = (last - first + 1) / 2;
0367     maxDist = 0.0;
0368     for (i = first + 1; i < last; ++i) {
0369         P = BezierII(3, curve, u[i-first]);
0370         v = VectorSub(P, points.at(i));
0371         dist = v.length();
0372         if (dist >= maxDist) {
0373             maxDist = dist;
0374             *splitPoint = i;
0375         }
0376     }
0377     return (maxDist);
0378 }
0379 
0380 
0381 /*
0382  *  NewtonRaphsonRootFind :
0383  *  Use Newton-Raphson iteration to find better root.
0384  */
0385 static qreal NewtonRaphsonRootFind(QPointF *Q, QPointF P, qreal u)
0386 {
0387     qreal       numerator, denominator;
0388     QPointF         Q1[3], Q2[2];   /*  Q' and Q''          */
0389     QPointF     Q_u, Q1_u, Q2_u; /*u evaluated at Q, Q', & Q''  */
0390     qreal       uPrime;     /*  Improved u          */
0391     int         i;
0392 
0393     /* Compute Q(u) */
0394     Q_u = BezierII(3, Q, u);
0395 
0396     /* Generate control vertices for Q' */
0397     for (i = 0; i <= 2; ++i) {
0398         Q1[i].setX((Q[i+1].x() - Q[i].x()) * 3.0);
0399         Q1[i].setY((Q[i+1].y() - Q[i].y()) * 3.0);
0400     }
0401 
0402     /* Generate control vertices for Q'' */
0403     for (i = 0; i <= 1; ++i) {
0404         Q2[i].setX((Q1[i+1].x() - Q1[i].x()) * 2.0);
0405         Q2[i].setY((Q1[i+1].y() - Q1[i].y()) * 2.0);
0406     }
0407 
0408     /* Compute Q'(u) and Q''(u) */
0409     Q1_u = BezierII(2, Q1, u);
0410     Q2_u = BezierII(1, Q2, u);
0411 
0412     /* Compute f(u)/f'(u) */
0413     numerator = (Q_u.x() - P.x()) * (Q1_u.x()) + (Q_u.y() - P.y()) * (Q1_u.y());
0414     denominator = (Q1_u.x()) * (Q1_u.x()) + (Q1_u.y()) * (Q1_u.y()) +
0415                   (Q_u.x() - P.x()) * (Q2_u.x()) + (Q_u.y() - P.y()) * (Q2_u.y());
0416 
0417     if (qFuzzyCompare(denominator, qreal(0.0))) {
0418         denominator = Zero;
0419     }
0420 
0421     /* u = u - f(u)/f'(u) */
0422     uPrime = u - (numerator / denominator);
0423     return (uPrime);
0424 }
0425 
0426 /*
0427  *  Reparameterize:
0428  *  Given set of points and their parameterization, try to find
0429  *   a better parameterization.
0430  *
0431  */
0432 static qreal *Reparameterize(const QList<QPointF> &points, int first, int last, qreal *u, QPointF *curve)
0433 {
0434     int     nPts = last - first + 1;
0435     int     i;
0436     qreal   *uPrime;        /*  New parameter values    */
0437 
0438     uPrime = new qreal[nPts];
0439     for (i = first; i <= last; ++i) {
0440         uPrime[i-first] = NewtonRaphsonRootFind(curve, points.at(i), u[i-first]);
0441     }
0442     return (uPrime);
0443 }
0444 
0445 QPointF *FitCubic(const QList<QPointF> &points, int first, int last, FitVector tHat1, FitVector tHat2, float error, int &width)
0446 {
0447     qreal *u;
0448     qreal *uPrime;
0449     qreal maxError;
0450     int splitPoint;
0451     int nPts;
0452     qreal iterationError;
0453     int maxIterations = 4;
0454     FitVector tHatCenter;
0455     QPointF *curve;
0456     int i;
0457 
0458     width = 0;
0459 
0460 
0461     iterationError = error * error;
0462     nPts = last - first + 1;
0463 
0464     if (nPts == 2) {
0465         qreal dist = distance(points.at(last), points.at(first)) / 3.0;
0466 
0467         curve = new QPointF[4];
0468 
0469         curve[0] = points.at(first);
0470         curve[3] = points.at(last);
0471 
0472         tHat1.scale(dist);
0473         tHat2.scale(dist);
0474 
0475         curve[1] = tHat1 + curve[0];
0476         curve[2] = tHat2 + curve[3];
0477 
0478         width = 4;
0479         return curve;
0480     }
0481 
0482     /*  Parameterize points, and attempt to fit curve */
0483     u = ChordLengthParameterize(points, first, last);
0484     curve = GenerateBezier(points, first, last, u, tHat1, tHat2);
0485 
0486 
0487     /*  Find max deviation of points to fitted curve */
0488     maxError = ComputeMaxError(points, first, last, curve, u, &splitPoint);
0489     if (maxError < error) {
0490         delete[] u;
0491         width = 4;
0492         return curve;
0493     }
0494 
0495 
0496     /*  If error not too large, try some reparameterization  */
0497     /*  and iteration */
0498     if (maxError < iterationError) {
0499         for (i = 0; i < maxIterations; ++i) {
0500             uPrime = Reparameterize(points, first, last, u, curve);
0501             delete[] curve;
0502             curve = GenerateBezier(points, first, last, uPrime, tHat1, tHat2);
0503             maxError = ComputeMaxError(points, first, last,
0504                                        curve, uPrime, &splitPoint);
0505             if (maxError < error) {
0506                 delete[] u;
0507                 delete[] uPrime;
0508                 width = 4;
0509                 return curve;
0510             }
0511             delete[] u;
0512             u = uPrime;
0513         }
0514     }
0515 
0516     /* Fitting failed -- split at max error point and fit recursively */
0517     delete[] u;
0518     delete[] curve;
0519     tHatCenter = ComputeCenterTangent(points, splitPoint);
0520 
0521     int w1, w2;
0522     QPointF *cu1 = 0, *cu2 = 0;
0523     cu1 = FitCubic(points, first, splitPoint, tHat1, tHatCenter, error, w1);
0524 
0525     tHatCenter.negate();
0526     cu2 = FitCubic(points, splitPoint, last, tHatCenter, tHat2, error, w2);
0527 
0528     QPointF *newcurve = new QPointF[w1+w2];
0529     for (int i = 0; i < w1; ++i) {
0530         newcurve[i] = cu1[i];
0531     }
0532     for (int i = 0; i < w2; ++i) {
0533         newcurve[i+w1] = cu2[i];
0534     }
0535 
0536     delete[] cu1;
0537     delete[] cu2;
0538     width = w1 + w2;
0539     return newcurve;
0540 }
0541 
0542 
0543 KoPathShape * bezierFit(const QList<QPointF> &points, float error)
0544 {
0545     FitVector tHat1, tHat2;
0546 
0547     tHat1 = ComputeLeftTangent(points, 0);
0548     tHat2 = ComputeRightTangent(points, points.count() - 1);
0549 
0550     int width = 0;
0551     QPointF *curve;
0552     curve = FitCubic(points, 0, points.count() - 1, tHat1, tHat2, error, width);
0553 
0554     KoPathShape * path = new KoPathShape();
0555 
0556     if (width > 3) {
0557         path->moveTo(curve[0]);
0558         path->curveTo(curve[1], curve[2], curve[3]);
0559         for (int i = 4; i < width; i += 4) {
0560             path->curveTo(curve[i+1], curve[i+2], curve[i+3]);
0561         }
0562     }
0563 
0564     delete[] curve;
0565     return path;
0566 }
0567