diff options
Diffstat (limited to 'karbon/tools/vcurvefit.cpp')
-rw-r--r-- | karbon/tools/vcurvefit.cpp | 565 |
1 files changed, 565 insertions, 0 deletions
diff --git a/karbon/tools/vcurvefit.cpp b/karbon/tools/vcurvefit.cpp new file mode 100644 index 00000000..40d7b7e3 --- /dev/null +++ b/karbon/tools/vcurvefit.cpp @@ -0,0 +1,565 @@ +/* This file is part of the KDE project + Copyright (C) 2001, 2002, 2003 The Karbon Developers + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include <karbon_part.h> +#include <karbon_view.h> +#include <core/vcolor.h> +#include <core/vcomposite.h> +#include <core/vfill.h> +#include <core/vstroke.h> +#include <core/vglobal.h> +#include <render/vpainter.h> +#include <render/vpainterfactory.h> +#include <commands/vshapecmd.h> + +/* + An Algorithm for Automatically Fitting Digitized Curves + by Philip J. Schneider + from "Graphics Gems", Academic Press, 1990 + + http://www.acm.org/pubs/tog/GraphicsGems/gems/FitCurves.c + http://www.acm.org/pubs/tog/GraphicsGems/gems/README +*/ + + +#include "vcurvefit.h" + +#define MAXPOINTS 1000 /* The most points you can have */ + + +class FitVector { + public: + FitVector(KoPoint &p){ + m_X=p.x(); + m_Y=p.y(); + } + + FitVector(){ + m_X=0; + m_Y=0; + } + + FitVector(KoPoint &a,KoPoint &b){ + m_X=a.x()-b.x(); + m_Y=a.y()-b.y(); + } + + void normalize(){ + double len=length(); + if(len==0.0f) + return; + m_X/=len; m_Y/=len; + } + + void negate(){ + m_X = -m_X; + m_Y = -m_Y; + } + + void scale(double s){ + double len = length(); + if(len==0.0f) + return; + m_X *= s/len; + m_Y *= s/len; + } + + double dot(FitVector &v){ + return ((m_X*v.m_X)+(m_Y*v.m_Y)); + } + + double length(){ + return (double) sqrt(m_X*m_X+m_Y*m_Y); + } + + KoPoint operator+(KoPoint &p){ + KoPoint b(p.x()+m_X,p.y()+m_Y); + return b; + } + + public: + double m_X,m_Y; +}; + +double distance(KoPoint *p1,KoPoint *p2){ + double dx = (p1->x()-p2->x()); + double dy = (p1->y()-p2->y()); + return sqrt( dx*dx + dy*dy ); +} + + +FitVector ComputeLeftTangent(TQPtrList<KoPoint> &points,int end){ + FitVector tHat1(*points.at(end+1),*points.at(end)); + + tHat1.normalize(); + + return tHat1; +} + +FitVector ComputeRightTangent(TQPtrList<KoPoint> &points,int end){ + FitVector tHat1(*points.at(end-1),*points.at(end)); + + tHat1.normalize(); + + return tHat1; +} + +/* + * ChordLengthParameterize : + * Assign parameter values to digitized points + * using relative distances between points. + */ +static double *ChordLengthParameterize(TQPtrList<KoPoint> points,int first,int last) +{ + int i; + double *u; /* Parameterization */ + + u = new double[(last-first+1)]; + + u[0] = 0.0; + for (i = first+1; i <= last; i++) { + u[i-first] = u[i-first-1] + + distance(points.at(i), points.at(i-1)); + } + + for (i = first + 1; i <= last; i++) { + u[i-first] = u[i-first] / u[last-first]; + } + + return(u); +} + +static FitVector VectorAdd(FitVector a,FitVector b) +{ + FitVector c; + c.m_X = a.m_X + b.m_X; c.m_Y = a.m_Y + b.m_Y; + return (c); +} +static FitVector VectorScale(FitVector v,double s) +{ + FitVector result; + result.m_X = v.m_X * s; result.m_Y = v.m_Y * s; + return (result); +} + +static FitVector VectorSub(FitVector a,FitVector b) +{ + FitVector c; + c.m_X = a.m_X - b.m_X; c.m_Y = a.m_Y - b.m_Y; + return (c); +} + +static FitVector ComputeCenterTangent(TQPtrList<KoPoint> points,int center) +{ + FitVector V1, V2, tHatCenter; + + FitVector cpointb = *points.at(center-1); + FitVector cpoint = *points.at(center); + FitVector cpointa = *points.at(center+1); + + V1 = VectorSub(cpointb,cpoint); + V2 = VectorSub(cpoint,cpointa); + tHatCenter.m_X= ((V1.m_X + V2.m_X)/2.0); + tHatCenter.m_Y= ((V1.m_Y + V2.m_Y)/2.0); + tHatCenter.normalize(); + return tHatCenter; +} + +/* + * B0, B1, B2, B3 : + * Bezier multipliers + */ +static double B0(double u) +{ + double tmp = 1.0 - u; + return (tmp * tmp * tmp); +} + + +static double B1(double u) +{ + double tmp = 1.0 - u; + return (3 * u * (tmp * tmp)); +} + +static double B2(double u) +{ + double tmp = 1.0 - u; + return (3 * u * u * tmp); +} + +static double B3(double u) +{ + return (u * u * u); +} + +/* + * GenerateBezier : + * Use least-squares method to find Bezier control points for region. + * + */ +KoPoint* GenerateBezier(TQPtrList<KoPoint> &points, int first, int last, double *uPrime,FitVector tHat1,FitVector tHat2) +{ + int i; + FitVector A[MAXPOINTS][2]; /* Precomputed rhs for eqn */ + int nPts; /* Number of pts in sub-curve */ + double C[2][2]; /* Matrix C */ + double X[2]; /* Matrix X */ + double det_C0_C1, /* Determinants of matrices */ + det_C0_X, + det_X_C1; + double alpha_l, /* Alpha values, left and right */ + alpha_r; + FitVector tmp; /* Utility variable */ + KoPoint *curve; + + curve = new KoPoint[4]; + nPts = last - first + 1; + + + /* Compute the A's */ + for (i = 0; i < nPts; i++) { + FitVector v1, v2; + v1 = tHat1; + v2 = tHat2; + v1.scale(B1(uPrime[i])); + v2.scale(B2(uPrime[i])); + A[i][0] = v1; + A[i][1] = v2; + } + + /* Create the C and X matrices */ + C[0][0] = 0.0; + C[0][1] = 0.0; + C[1][0] = 0.0; + C[1][1] = 0.0; + X[0] = 0.0; + X[1] = 0.0; + + for (i = 0; i < nPts; i++) { + C[0][0] += (A[i][0]).dot(A[i][0]); + C[0][1] += A[i][0].dot(A[i][1]); + /* C[1][0] += V2Dot(&A[i][0], &A[i][1]);*/ + C[1][0] = C[0][1]; + C[1][1] += A[i][1].dot(A[i][1]); + + FitVector vfirstp1(*points.at(first+i)); + FitVector vfirst(*points.at(first)); + FitVector vlast(*points.at(last)); + + tmp = VectorSub(vfirstp1, + VectorAdd( + VectorScale(vfirst, B0(uPrime[i])), + VectorAdd( + VectorScale(vfirst, B1(uPrime[i])), + VectorAdd( + VectorScale(vlast, B2(uPrime[i])), + VectorScale(vlast, B3(uPrime[i])) )))); + + + X[0] += A[i][0].dot(tmp); + X[1] += A[i][1].dot(tmp); + } + + /* Compute the determinants of C and X */ + det_C0_C1 = C[0][0] * C[1][1] - C[1][0] * C[0][1]; + det_C0_X = C[0][0] * X[1] - C[0][1] * X[0]; + det_X_C1 = X[0] * C[1][1] - X[1] * C[0][1]; + + /* Finally, derive alpha values */ + if (det_C0_C1 == 0.0) { + det_C0_C1 = (C[0][0] * C[1][1]) * 10e-12; + } + alpha_l = det_X_C1 / det_C0_C1; + alpha_r = det_C0_X / det_C0_C1; + + + /* If alpha negative, use the Wu/Barsky heuristic (see text) */ + /* (if alpha is 0, you get coincident control points that lead to + * divide by zero in any subsequent NewtonRaphsonRootFind() call. */ + if (alpha_l < 1.0e-6 || alpha_r < 1.0e-6) { + double dist = distance(points.at(last),points.at(first)) / + 3.0; + + curve[0] = *points.at(first); + curve[3] = *points.at(last); + + tHat1.scale(dist); + tHat2.scale(dist); + + curve[1] = tHat1 + curve[0]; + curve[2] = tHat2 + curve[3]; + return curve; + } + + /* First and last control points of the Bezier curve are */ + /* positioned exactly at the first and last data points */ + /* Control points 1 and 2 are positioned an alpha distance out */ + /* on the tangent vectors, left and right, respectively */ + curve[0] = *points.at(first); + curve[3] = *points.at(last); + + tHat1.scale(alpha_l); + tHat2.scale(alpha_r); + + curve[1] = tHat1 + curve[0]; + curve[2] = tHat2 + curve[3]; + + return (curve); +} + +/* + * Bezier : + * Evaluate a Bezier curve at a particular parameter value + * + */ +static KoPoint BezierII(int degree,KoPoint *V, double t) +{ + int i, j; + KoPoint Q; /* Point on curve at parameter t */ + KoPoint *Vtemp; /* Local copy of control points */ + + Vtemp = new KoPoint[degree+1]; + + for (i = 0; i <= degree; i++) { + Vtemp[i] = V[i]; + } + + /* Triangle computation */ + for (i = 1; i <= degree; i++) { + for (j = 0; j <= degree-i; j++) { + Vtemp[j].setX((1.0 - t) * Vtemp[j].x() + t * Vtemp[j+1].x()); + Vtemp[j].setY((1.0 - t) * Vtemp[j].y() + t * Vtemp[j+1].y()); + } + } + + Q = Vtemp[0]; + delete[] Vtemp; + return Q; +} + +/* + * ComputeMaxError : + * Find the maximum squared distance of digitized points + * to fitted curve. +*/ +static double ComputeMaxError(TQPtrList<KoPoint> points,int first,int last,KoPoint *curve,double *u,int *splitPoint) +{ + int i; + double maxDist; /* Maximum error */ + double dist; /* Current error */ + KoPoint P; /* Point on curve */ + FitVector v; /* Vector from point to curve */ + + *splitPoint = (last - first + 1)/2; + maxDist = 0.0; + for (i = first + 1; i < last; i++) { + P = BezierII(3, curve, u[i-first]); + v = VectorSub(P, *points.at(i)); + dist = v.length(); + if (dist >= maxDist) { + maxDist = dist; + *splitPoint = i; + } + } + return (maxDist); +} + + +/* + * NewtonRaphsonRootFind : + * Use Newton-Raphson iteration to find better root. + */ +static double NewtonRaphsonRootFind(KoPoint *Q,KoPoint P,double u) +{ + double numerator, denominator; + KoPoint Q1[3], Q2[2]; /* Q' and Q'' */ + KoPoint TQ_u, Q1_u, Q2_u; /*u evaluated at Q, Q', & Q'' */ + double uPrime; /* Improved u */ + int i; + + /* Compute Q(u) */ + TQ_u = BezierII(3,Q, u); + + /* Generate control vertices for Q' */ + for (i = 0; i <= 2; i++) { + Q1[i].setX((Q[i+1].x() - Q[i].x()) * 3.0); + Q1[i].setY((Q[i+1].y() - Q[i].y()) * 3.0); + } + + /* Generate control vertices for Q'' */ + for (i = 0; i <= 1; i++) { + Q2[i].setX((Q1[i+1].x() - Q1[i].x()) * 2.0); + Q2[i].setY((Q1[i+1].y() - Q1[i].y()) * 2.0); + } + + /* Compute Q'(u) and Q''(u) */ + Q1_u = BezierII(2, Q1, u); + Q2_u = BezierII(1, Q2, u); + + /* Compute f(u)/f'(u) */ + numerator = (TQ_u.x() - P.x()) * (Q1_u.x()) + (TQ_u.y() - P.y()) * (Q1_u.y()); + denominator = (Q1_u.x()) * (Q1_u.x()) + (Q1_u.y()) * (Q1_u.y()) + + (TQ_u.x() - P.x()) * (Q2_u.x()) + (TQ_u.y() - P.y()) * (Q2_u.y()); + + /* u = u - f(u)/f'(u) */ + uPrime = u - (numerator/denominator); + return (uPrime); +} + +/* + * Reparameterize: + * Given set of points and their parameterization, try to find + * a better parameterization. + * + */ +static double *Reparameterize(TQPtrList<KoPoint> points,int first,int last,double *u,KoPoint *curve) +{ + int nPts = last-first+1; + int i; + double *uPrime; /* New parameter values */ + + uPrime = new double[nPts]; + for (i = first; i <= last; i++) { + uPrime[i-first] = NewtonRaphsonRootFind(curve, *points.at(i), u[i- + first]); + } + return (uPrime); +} + +KoPoint *FitCubic(TQPtrList<KoPoint> &points,int first,int last,FitVector tHat1,FitVector tHat2,float error,int &width){ + double *u; + double *uPrime; + double maxError; + int splitPoint; + int nPts; + double iterationError; + int maxIterations=4; + FitVector tHatCenter; + KoPoint *curve; + int i; + + width=0; + + + iterationError=error*error; + nPts = last-first+1; + + if(nPts == 2){ + double dist = distance(points.at(last), points.at(first)) / 3.0; + + curve = new KoPoint[4]; + + curve[0] = *points.at(first); + curve[3] = *points.at(last); + + tHat1.scale(dist); + tHat2.scale(dist); + + curve[1] = tHat1 + curve[0]; + curve[2] = tHat2 + curve[3]; + + width=4; + return curve; + } + + /* Parameterize points, and attempt to fit curve */ + u = ChordLengthParameterize(points, first, last); + curve = GenerateBezier(points, first, last, u, tHat1, tHat2); + + + /* Find max deviation of points to fitted curve */ + maxError = ComputeMaxError(points, first, last, curve, u, &splitPoint); + if (maxError < error) { + delete[] u; + width=4; + return curve; + } + + + /* If error not too large, try some reparameterization */ + /* and iteration */ + if (maxError < iterationError) { + for (i = 0; i < maxIterations; i++) { + uPrime = Reparameterize(points, first, last, u, curve); + curve = GenerateBezier(points, first, last, uPrime, tHat1, tHat2); + maxError = ComputeMaxError(points, first, last, + curve, uPrime, &splitPoint); + if (maxError < error) { + delete[] u; + width=4; + return curve; + } + delete[] u; + u = uPrime; + } + } + + /* Fitting failed -- split at max error point and fit recursively */ + delete[] u; + delete[] curve; + tHatCenter = ComputeCenterTangent(points, splitPoint); + + int w1,w2; + KoPoint *cu1=NULL, *cu2=NULL; + cu1 = FitCubic(points, first, splitPoint, tHat1, tHatCenter, error,w1); + + tHatCenter.negate(); + cu2 = FitCubic(points, splitPoint, last, tHatCenter, tHat2, error,w2); + + KoPoint *newcurve = new KoPoint[w1+w2]; + for(int i=0;i<w1;i++){ + newcurve[i]=cu1[i]; + } + for(int i=0;i<w2;i++){ + newcurve[i+w1]=cu2[i]; + } + + delete[] cu1; + delete[] cu2; + width=w1+w2; + return newcurve; +} + + +VPath *bezierFit(TQPtrList<KoPoint> &points,float error){ + FitVector tHat1, tHat2; + + tHat1 = ComputeLeftTangent(points,0); + tHat2 = ComputeRightTangent(points,points.count()-1); + + int width=0; + KoPoint *curve; + curve = FitCubic(points,0,points.count()-1,tHat1,tHat2,error,width); + + VPath *path = new VPath(NULL); + + if(width>3){ + path->moveTo(curve[0]); + path->curveTo(curve[1],curve[2],curve[3]); + for(int i=4;i<width;i+=4){ + path->curveTo(curve[i+1],curve[i+2],curve[i+3]); + } + } + + + delete[] curve; + return path; +} + |