diff options
Diffstat (limited to 'src/livarot/PathSimplify.cpp')
-rw-r--r-- | src/livarot/PathSimplify.cpp | 1404 |
1 files changed, 1404 insertions, 0 deletions
diff --git a/src/livarot/PathSimplify.cpp b/src/livarot/PathSimplify.cpp new file mode 100644 index 0000000..bf3e200 --- /dev/null +++ b/src/livarot/PathSimplify.cpp @@ -0,0 +1,1404 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * TODO: insert short description here + *//* + * Authors: + * see git history + * Fred + * + * Copyright (C) 2018 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <memory> +#include <glib.h> +#include <2geom/affine.h> +#include "livarot/Path.h" +#include "livarot/path-description.h" + +/* + * Reassembling polyline segments into cubic bezier patches + * thes functions do not need the back data. but they are slower than recomposing + * path descriptions when you have said back data (it's always easier with a model) + * there's a bezier fitter in bezier-utils.cpp too. the main difference is the way bezier patch are split + * here: walk on the polyline, trying to extend the portion you can fit by respecting the treshhold, split when + * treshhold is exceeded. when encountering a "forced" point, lower the treshhold to favor splitting at that point + * in bezier-utils: fit the whole polyline, get the position with the higher deviation to the fitted curve, split + * there and recurse + */ + + +// algo d'origine: http://www.cs.mtu.edu/~shene/COURSES/cs3621/NOTES/INT-APP/CURVE-APP-global.html + +// need the b-spline basis for cubic splines +// pas oublier que c'est une b-spline clampee +// et que ca correspond a une courbe de bezier normale +#define N03(t) ((1.0-t)*(1.0-t)*(1.0-t)) +#define N13(t) (3*t*(1.0-t)*(1.0-t)) +#define N23(t) (3*t*t*(1.0-t)) +#define N33(t) (t*t*t) +// quadratic b-splines (jsut in case) +#define N02(t) ((1.0-t)*(1.0-t)) +#define N12(t) (2*t*(1.0-t)) +#define N22(t) (t*t) +// linear interpolation b-splines +#define N01(t) ((1.0-t)) +#define N11(t) (t) + + + +void Path::Simplify(double treshhold) +{ + if (pts.size() <= 1) { + return; + } + + Reset(); + + int lastM = 0; + while (lastM < int(pts.size())) { + int lastP = lastM + 1; + while (lastP < int(pts.size()) + && (pts[lastP].isMoveTo == polyline_lineto + || pts[lastP].isMoveTo == polyline_forced)) + { + lastP++; + } + + DoSimplify(lastM, lastP - lastM, treshhold); + + lastM = lastP; + } +} + + +#if 0 +// dichomtomic method to get distance to curve approximation +// a real polynomial solver would get the minimum more efficiently, but since the polynom +// would likely be of degree >= 5, that would imply using some generic solver, liek using the sturm method +static double RecDistanceToCubic(Geom::Point const &iS, Geom::Point const &isD, + Geom::Point const &iE, Geom::Point const &ieD, + Geom::Point &pt, double current, int lev, double st, double et) +{ + if ( lev <= 0 ) { + return current; + } + + Geom::Point const m = 0.5 * (iS + iE) + 0.125 * (isD - ieD); + Geom::Point const md = 0.75 * (iE - iS) - 0.125 * (isD + ieD); + double const mt = (st + et) / 2; + + Geom::Point const hisD = 0.5 * isD; + Geom::Point const hieD = 0.5 * ieD; + + Geom::Point const mp = pt - m; + double nle = Geom::dot(mp, mp); + + if ( nle < current ) { + + current = nle; + nle = RecDistanceToCubic(iS, hisD, m, md, pt, current, lev - 1, st, mt); + if ( nle < current ) { + current = nle; + } + nle = RecDistanceToCubic(m, md, iE, hieD, pt, current, lev - 1, mt, et); + if ( nle < current ) { + current = nle; + } + + } else if ( nle < 2 * current ) { + + nle = RecDistanceToCubic(iS, hisD, m, md, pt, current, lev - 1, st, mt); + if ( nle < current ) { + current = nle; + } + nle = RecDistanceToCubic(m, md, iE, hieD, pt, current, lev - 1, mt, et); + if ( nle < current ) { + current = nle; + } + } + + return current; +} +#endif + +static double DistanceToCubic(Geom::Point const &start, PathDescrCubicTo res, Geom::Point &pt) +{ + Geom::Point const sp = pt - start; + Geom::Point const ep = pt - res.p; + double nle = Geom::dot(sp, sp); + double nnle = Geom::dot(ep, ep); + if ( nnle < nle ) { + nle = nnle; + } + + Geom::Point seg = res.p - start; + nnle = Geom::cross(sp, seg); + nnle *= nnle; + nnle /= Geom::dot(seg, seg); + if ( nnle < nle ) { + if ( Geom::dot(sp,seg) >= 0 ) { + seg = start - res.p; + if ( Geom::dot(ep,seg) >= 0 ) { + nle = nnle; + } + } + } + + return nle; +} + + +/** + * Simplification on a subpath. + */ + +void Path::DoSimplify(int off, int N, double treshhold) +{ + // non-dichotomic method: grow an interval of points approximated by a curve, until you reach the treshhold, and repeat + if (N <= 1) { + return; + } + + int curP = 0; + + fitting_tables data; + data.Xk = data.Yk = data.Qk = nullptr; + data.tk = data.lk = nullptr; + data.fk = nullptr; + data.totLen = 0; + data.nbPt = data.maxPt = data.inPt = 0; + + Geom::Point const moveToPt = pts[off].p; + MoveTo(moveToPt); + Geom::Point endToPt = moveToPt; + + while (curP < N - 1) { + + int lastP = curP + 1; + int M = 2; + + // remettre a zero + data.inPt = data.nbPt = 0; + + PathDescrCubicTo res(Geom::Point(0, 0), Geom::Point(0, 0), Geom::Point(0, 0)); + bool contains_forced = false; + int step = 64; + + while ( step > 0 ) { + int forced_pt = -1; + int worstP = -1; + + do { + if (pts[off + lastP].isMoveTo == polyline_forced) { + contains_forced = true; + } + forced_pt = lastP; + lastP += step; + M += step; + } while (lastP < N && ExtendFit(off + curP, M, data, + (contains_forced) ? 0.05 * treshhold : treshhold, + res, worstP) ); + if (lastP >= N) { + + lastP -= step; + M -= step; + + } else { + // le dernier a echoue + lastP -= step; + M -= step; + + if ( contains_forced ) { + lastP = forced_pt; + M = lastP - curP + 1; + } + + AttemptSimplify(off + curP, M, treshhold, res, worstP); // ca passe forcement + } + step /= 2; + } + + endToPt = pts[off + lastP].p; + if (M <= 2) { + LineTo(endToPt); + } else { + CubicTo(endToPt, res.start, res.end); + } + + curP = lastP; + } + + if (Geom::LInfty(endToPt - moveToPt) < 0.00001) { + Close(); + } + + g_free(data.Xk); + g_free(data.Yk); + g_free(data.Qk); + g_free(data.tk); + g_free(data.lk); + g_free(data.fk); +} + + +// warning: slow +// idea behind this feature: splotches appear when trying to fit a small number of points: you can +// get a cubic bezier that fits the points very well but doesn't fit the polyline itself +// so we add a bit of the error at the middle of each segment of the polyline +// also we restrict this to <=20 points, to avoid unnecessary computations +#define with_splotch_killer + +// primitive= calc the cubic bezier patche that fits Xk and Yk best +// Qk est deja alloue +// retourne false si probleme (matrice non-inversible) +bool Path::FitCubic(Geom::Point const &start, PathDescrCubicTo &res, + double *Xk, double *Yk, double *Qk, double *tk, int nbPt) +{ + Geom::Point const end = res.p; + + // la matrice tNN + Geom::Affine M(0, 0, 0, 0, 0, 0); + for (int i = 1; i < nbPt - 1; i++) { + M[0] += N13(tk[i]) * N13(tk[i]); + M[1] += N23(tk[i]) * N13(tk[i]); + M[2] += N13(tk[i]) * N23(tk[i]); + M[3] += N23(tk[i]) * N23(tk[i]); + } + + double const det = M.det(); + if (fabs(det) < 0.000001) { + res.start[0]=res.start[1]=0.0; + res.end[0]=res.end[1]=0.0; + return false; + } + + Geom::Affine const iM = M.inverse(); + M = iM; + + // phase 1: abcisses + // calcul des Qk + Xk[0] = start[0]; + Yk[0] = start[1]; + Xk[nbPt - 1] = end[0]; + Yk[nbPt - 1] = end[1]; + + for (int i = 1; i < nbPt - 1; i++) { + Qk[i] = Xk[i] - N03 (tk[i]) * Xk[0] - N33 (tk[i]) * Xk[nbPt - 1]; + } + + // le vecteur Q + Geom::Point Q(0, 0); + for (int i = 1; i < nbPt - 1; i++) { + Q[0] += N13 (tk[i]) * Qk[i]; + Q[1] += N23 (tk[i]) * Qk[i]; + } + + Geom::Point P = Q * M; + Geom::Point cp1; + Geom::Point cp2; + cp1[Geom::X] = P[Geom::X]; + cp2[Geom::X] = P[Geom::Y]; + + // phase 2: les ordonnees + for (int i = 1; i < nbPt - 1; i++) { + Qk[i] = Yk[i] - N03 (tk[i]) * Yk[0] - N33 (tk[i]) * Yk[nbPt - 1]; + } + + // le vecteur Q + Q = Geom::Point(0, 0); + for (int i = 1; i < nbPt - 1; i++) { + Q[0] += N13 (tk[i]) * Qk[i]; + Q[1] += N23 (tk[i]) * Qk[i]; + } + + P = Q * M; + cp1[Geom::Y] = P[Geom::X]; + cp2[Geom::Y] = P[Geom::Y]; + + res.start = 3.0 * (cp1 - start); + res.end = 3.0 * (end - cp2 ); + + return true; +} + + +bool Path::ExtendFit(int off, int N, fitting_tables &data, double treshhold, PathDescrCubicTo &res, int &worstP) +{ + if ( N >= data.maxPt ) { + data.maxPt = 2 * N + 1; + data.Xk = (double *) g_realloc(data.Xk, data.maxPt * sizeof(double)); + data.Yk = (double *) g_realloc(data.Yk, data.maxPt * sizeof(double)); + data.Qk = (double *) g_realloc(data.Qk, data.maxPt * sizeof(double)); + data.tk = (double *) g_realloc(data.tk, data.maxPt * sizeof(double)); + data.lk = (double *) g_realloc(data.lk, data.maxPt * sizeof(double)); + data.fk = (char *) g_realloc(data.fk, data.maxPt * sizeof(char)); + } + + if ( N > data.inPt ) { + for (int i = data.inPt; i < N; i++) { + data.Xk[i] = pts[off + i].p[Geom::X]; + data.Yk[i] = pts[off + i].p[Geom::Y]; + data.fk[i] = ( pts[off + i].isMoveTo == polyline_forced ) ? 0x01 : 0x00; + } + data.lk[0] = 0; + data.tk[0] = 0; + + double prevLen = 0; + for (int i = 0; i < data.inPt; i++) { + prevLen += data.lk[i]; + } + data.totLen = prevLen; + + for (int i = ( (data.inPt > 0) ? data.inPt : 1); i < N; i++) { + Geom::Point diff; + diff[Geom::X] = data.Xk[i] - data.Xk[i - 1]; + diff[Geom::Y] = data.Yk[i] - data.Yk[i - 1]; + data.lk[i] = Geom::L2(diff); + data.totLen += data.lk[i]; + data.tk[i] = data.totLen; + } + + for (int i = 0; i < data.inPt; i++) { + data.tk[i] *= prevLen; + data.tk[i] /= data.totLen; + } + + for (int i = data.inPt; i < N; i++) { + data.tk[i] /= data.totLen; + } + data.inPt = N; + } + + if ( N < data.nbPt ) { + // We've gone too far; we'll have to recalulate the .tk. + data.totLen = 0; + data.tk[0] = 0; + data.lk[0] = 0; + for (int i = 1; i < N; i++) { + data.totLen += data.lk[i]; + data.tk[i] = data.totLen; + } + + for (int i = 1; i < N; i++) { + data.tk[i] /= data.totLen; + } + } + + data.nbPt = N; + + if ( data.nbPt <= 0 ) { + return false; + } + + res.p[0] = data.Xk[data.nbPt - 1]; + res.p[1] = data.Yk[data.nbPt - 1]; + res.start[0] = res.start[1] = 0; + res.end[0] = res.end[1] = 0; + worstP = 1; + if ( N <= 2 ) { + return true; + } + + if ( data.totLen < 0.0001 ) { + double worstD = 0; + Geom::Point start; + worstP = -1; + start[0] = data.Xk[0]; + start[1] = data.Yk[0]; + for (int i = 1; i < N; i++) { + Geom::Point nPt; + bool isForced = data.fk[i]; + nPt[0] = data.Xk[i]; + nPt[1] = data.Yk[i]; + + double nle = DistanceToCubic(start, res, nPt); + if ( isForced ) { + // forced points are favored for splitting the recursion; we do this by increasing their distance + if ( worstP < 0 || 2*nle > worstD ) { + worstP = i; + worstD = 2*nle; + } + } else { + if ( worstP < 0 || nle > worstD ) { + worstP = i; + worstD = nle; + } + } + } + + return true; + } + + return AttemptSimplify(data, treshhold, res, worstP); +} + + +// fit a polyline to a bezier patch, return true is treshhold not exceeded (ie: you can continue) +// version that uses tables from the previous iteration, to minimize amount of work done +bool Path::AttemptSimplify (fitting_tables &data,double treshhold, PathDescrCubicTo & res,int &worstP) +{ + Geom::Point start,end; + // pour une coordonnee + Geom::Point cp1, cp2; + + worstP = 1; + if (pts.size() == 2) { + return true; + } + + start[0] = data.Xk[0]; + start[1] = data.Yk[0]; + cp1[0] = data.Xk[1]; + cp1[1] = data.Yk[1]; + end[0] = data.Xk[data.nbPt - 1]; + end[1] = data.Yk[data.nbPt - 1]; + cp2 = cp1; + + if (pts.size() == 3) { + // start -> cp1 -> end + res.start = cp1 - start; + res.end = end - cp1; + worstP = 1; + return true; + } + + if ( FitCubic(start, res, data.Xk, data.Yk, data.Qk, data.tk, data.nbPt) ) { + cp1 = start + res.start / 3; + cp2 = end - res.end / 3; + } else { + // aie, non-inversible + double worstD = 0; + worstP = -1; + for (int i = 1; i < data.nbPt; i++) { + Geom::Point nPt; + nPt[Geom::X] = data.Xk[i]; + nPt[Geom::Y] = data.Yk[i]; + double nle = DistanceToCubic(start, res, nPt); + if ( data.fk[i] ) { + // forced points are favored for splitting the recursion; we do this by increasing their distance + if ( worstP < 0 || 2 * nle > worstD ) { + worstP = i; + worstD = 2 * nle; + } + } else { + if ( worstP < 0 || nle > worstD ) { + worstP = i; + worstD = nle; + } + } + } + return false; + } + + // calcul du delta= pondere par les longueurs des segments + double delta = 0; + { + double worstD = 0; + worstP = -1; + Geom::Point prevAppP; + Geom::Point prevP; + double prevDist; + prevP[Geom::X] = data.Xk[0]; + prevP[Geom::Y] = data.Yk[0]; + prevAppP = prevP; // le premier seulement + prevDist = 0; +#ifdef with_splotch_killer + if ( data.nbPt <= 20 ) { + for (int i = 1; i < data.nbPt - 1; i++) { + Geom::Point curAppP; + Geom::Point curP; + double curDist; + Geom::Point midAppP; + Geom::Point midP; + double midDist; + + curAppP[Geom::X] = N13(data.tk[i]) * cp1[Geom::X] + + N23(data.tk[i]) * cp2[Geom::X] + + N03(data.tk[i]) * data.Xk[0] + + N33(data.tk[i]) * data.Xk[data.nbPt - 1]; + + curAppP[Geom::Y] = N13(data.tk[i]) * cp1[Geom::Y] + + N23(data.tk[i]) * cp2[Geom::Y] + + N03(data.tk[i]) * data.Yk[0] + + N33(data.tk[i]) * data.Yk[data.nbPt - 1]; + + curP[Geom::X] = data.Xk[i]; + curP[Geom::Y] = data.Yk[i]; + double mtk = 0.5 * (data.tk[i] + data.tk[i - 1]); + + midAppP[Geom::X] = N13(mtk) * cp1[Geom::X] + + N23(mtk) * cp2[Geom::X] + + N03(mtk) * data.Xk[0] + + N33(mtk) * data.Xk[data.nbPt - 1]; + + midAppP[Geom::Y] = N13(mtk) * cp1[Geom::Y] + + N23(mtk) * cp2[Geom::Y] + + N03(mtk) * data.Yk[0] + + N33(mtk) * data.Yk[data.nbPt - 1]; + + midP = 0.5 * (curP + prevP); + + Geom::Point diff = curAppP - curP; + curDist = dot(diff, diff); + diff = midAppP - midP; + midDist = dot(diff, diff); + + delta += 0.3333 * (curDist + prevDist + midDist) * data.lk[i]; + if ( curDist > worstD ) { + worstD = curDist; + worstP = i; + } else if ( data.fk[i] && 2 * curDist > worstD ) { + worstD = 2*curDist; + worstP = i; + } + prevP = curP; + prevAppP = curAppP; + prevDist = curDist; + } + delta /= data.totLen; + + } else { +#endif + for (int i = 1; i < data.nbPt - 1; i++) { + Geom::Point curAppP; + Geom::Point curP; + double curDist; + + curAppP[Geom::X] = N13(data.tk[i]) * cp1[Geom::X] + + N23(data.tk[i]) * cp2[Geom::X] + + N03(data.tk[i]) * data.Xk[0] + + N33(data.tk[i]) * data.Xk[data.nbPt - 1]; + + curAppP[Geom::Y] = N13(data.tk[i]) * cp1[Geom::Y] + + N23(data.tk[i]) * cp2[Geom::Y] + + N03(data.tk[i]) * data.Yk[0] + + N33(data.tk[i]) * data.Yk[data.nbPt - 1]; + + curP[Geom::X] = data.Xk[i]; + curP[Geom::Y] = data.Yk[i]; + + Geom::Point diff = curAppP-curP; + curDist = dot(diff, diff); + delta += curDist; + + if ( curDist > worstD ) { + worstD = curDist; + worstP = i; + } else if ( data.fk[i] && 2 * curDist > worstD ) { + worstD = 2*curDist; + worstP = i; + } + prevP = curP; + prevAppP = curAppP; + prevDist = curDist; + } +#ifdef with_splotch_killer + } +#endif + } + + if (delta < treshhold * treshhold) { + // premier jet + + // Refine a little. + for (int i = 1; i < data.nbPt - 1; i++) { + Geom::Point pt(data.Xk[i], data.Yk[i]); + data.tk[i] = RaffineTk(pt, start, cp1, cp2, end, data.tk[i]); + if (data.tk[i] < data.tk[i - 1]) { + // Force tk to be monotonic non-decreasing. + data.tk[i] = data.tk[i - 1]; + } + } + + if ( FitCubic(start, res, data.Xk, data.Yk, data.Qk, data.tk, data.nbPt) == false) { + // ca devrait jamais arriver, mais bon + res.start = 3.0 * (cp1 - start); + res.end = 3.0 * (end - cp2 ); + return true; + } + + double ndelta = 0; + { + double worstD = 0; + worstP = -1; + Geom::Point prevAppP; + Geom::Point prevP(data.Xk[0], data.Yk[0]); + double prevDist = 0; + prevAppP = prevP; // le premier seulement +#ifdef with_splotch_killer + if ( data.nbPt <= 20 ) { + for (int i = 1; i < data.nbPt - 1; i++) { + Geom::Point curAppP; + Geom::Point curP; + double curDist; + Geom::Point midAppP; + Geom::Point midP; + double midDist; + + curAppP[Geom::X] = N13(data.tk[i]) * cp1[Geom::X] + + N23(data.tk[i]) * cp2[Geom::X] + + N03(data.tk[i]) * data.Xk[0] + + N33(data.tk[i]) * data.Xk[data.nbPt - 1]; + + curAppP[Geom::Y] = N13(data.tk[i]) * cp1[Geom::Y] + + N23(data.tk[i]) * cp2[Geom::Y] + + N03(data.tk[i]) * data.Yk[0] + + N33(data.tk[i]) * data.Yk[data.nbPt - 1]; + + curP[Geom::X] = data.Xk[i]; + curP[Geom::Y] = data.Yk[i]; + double mtk = 0.5 * (data.tk[i] + data.tk[i - 1]); + + midAppP[Geom::X] = N13(mtk) * cp1[Geom::X] + + N23(mtk) * cp2[Geom::X] + + N03(mtk) * data.Xk[0] + + N33(mtk) * data.Xk[data.nbPt - 1]; + + midAppP[Geom::Y] = N13(mtk) * cp1[Geom::Y] + + N23(mtk) * cp2[Geom::Y] + + N03(mtk) * data.Yk[0] + + N33(mtk) * data.Yk[data.nbPt - 1]; + + midP = 0.5 * (curP + prevP); + + Geom::Point diff = curAppP - curP; + curDist = dot(diff, diff); + + diff = midAppP - midP; + midDist = dot(diff, diff); + + ndelta += 0.3333 * (curDist + prevDist + midDist) * data.lk[i]; + + if ( curDist > worstD ) { + worstD = curDist; + worstP = i; + } else if ( data.fk[i] && 2 * curDist > worstD ) { + worstD = 2*curDist; + worstP = i; + } + + prevP = curP; + prevAppP = curAppP; + prevDist = curDist; + } + ndelta /= data.totLen; + } else { +#endif + for (int i = 1; i < data.nbPt - 1; i++) { + Geom::Point curAppP; + Geom::Point curP; + double curDist; + + curAppP[Geom::X] = N13(data.tk[i]) * cp1[Geom::X] + + N23(data.tk[i]) * cp2[Geom::X] + + N03(data.tk[i]) * data.Xk[0] + + N33(data.tk[i]) * data.Xk[data.nbPt - 1]; + + curAppP[Geom::Y] = N13(data.tk[i]) * cp1[Geom::Y] + + N23(data.tk[i]) * cp2[1] + + N03(data.tk[i]) * data.Yk[0] + + N33(data.tk[i]) * data.Yk[data.nbPt - 1]; + + curP[Geom::X] = data.Xk[i]; + curP[Geom::Y] = data.Yk[i]; + + Geom::Point diff = curAppP - curP; + curDist = dot(diff, diff); + + ndelta += curDist; + + if ( curDist > worstD ) { + worstD = curDist; + worstP = i; + } else if ( data.fk[i] && 2 * curDist > worstD ) { + worstD = 2 * curDist; + worstP = i; + } + prevP = curP; + prevAppP = curAppP; + prevDist = curDist; + } +#ifdef with_splotch_killer + } +#endif + } + + if (ndelta < delta + 0.00001) { + return true; + } else { + // nothing better to do + res.start = 3.0 * (cp1 - start); + res.end = 3.0 * (end - cp2 ); + } + + return true; + } + + return false; +} + + +bool Path::AttemptSimplify(int off, int N, double treshhold, PathDescrCubicTo &res,int &worstP) +{ + Geom::Point start; + Geom::Point end; + + // pour une coordonnee + double *Xk; // la coordonnee traitee (x puis y) + double *Yk; // la coordonnee traitee (x puis y) + double *lk; // les longueurs de chaque segment + double *tk; // les tk + double *Qk; // les Qk + char *fk; // si point force + + Geom::Point cp1; + Geom::Point cp2; + + if (N == 2) { + worstP = 1; + return true; + } + + start = pts[off].p; + cp1 = pts[off + 1].p; + end = pts[off + N - 1].p; + + res.p = end; + res.start[0] = res.start[1] = 0; + res.end[0] = res.end[1] = 0; + if (N == 3) { + // start -> cp1 -> end + res.start = cp1 - start; + res.end = end - cp1; + worstP = 1; + return true; + } + + // Totally inefficient, allocates & deallocates all the time. + tk = (double *) g_malloc(N * sizeof(double)); + Qk = (double *) g_malloc(N * sizeof(double)); + Xk = (double *) g_malloc(N * sizeof(double)); + Yk = (double *) g_malloc(N * sizeof(double)); + lk = (double *) g_malloc(N * sizeof(double)); + fk = (char *) g_malloc(N * sizeof(char)); + + // chord length method + tk[0] = 0.0; + lk[0] = 0.0; + { + Geom::Point prevP = start; + for (int i = 1; i < N; i++) { + Xk[i] = pts[off + i].p[Geom::X]; + Yk[i] = pts[off + i].p[Geom::Y]; + + if ( pts[off + i].isMoveTo == polyline_forced ) { + fk[i] = 0x01; + } else { + fk[i] = 0; + } + + Geom::Point diff(Xk[i] - prevP[Geom::X], Yk[i] - prevP[1]); + prevP[0] = Xk[i]; + prevP[1] = Yk[i]; + lk[i] = Geom::L2(diff); + tk[i] = tk[i - 1] + lk[i]; + } + } + + if (tk[N - 1] < 0.00001) { + // longueur nulle + res.start[0] = res.start[1] = 0; + res.end[0] = res.end[1] = 0; + double worstD = 0; + worstP = -1; + for (int i = 1; i < N; i++) { + Geom::Point nPt; + bool isForced = fk[i]; + nPt[0] = Xk[i]; + nPt[1] = Yk[i]; + + double nle = DistanceToCubic(start, res, nPt); + if ( isForced ) { + // forced points are favored for splitting the recursion; we do this by increasing their distance + if ( worstP < 0 || 2 * nle > worstD ) { + worstP = i; + worstD = 2 * nle; + } + } else { + if ( worstP < 0 || nle > worstD ) { + worstP = i; + worstD = nle; + } + } + } + + g_free(tk); + g_free(Qk); + g_free(Xk); + g_free(Yk); + g_free(fk); + g_free(lk); + + return false; + } + + double totLen = tk[N - 1]; + for (int i = 1; i < N - 1; i++) { + tk[i] /= totLen; + } + + res.p = end; + if ( FitCubic(start, res, Xk, Yk, Qk, tk, N) ) { + cp1 = start + res.start / 3; + cp2 = end + res.end / 3; + } else { + // aie, non-inversible + res.start[0] = res.start[1] = 0; + res.end[0] = res.end[1] = 0; + double worstD = 0; + worstP = -1; + for (int i = 1; i < N; i++) { + Geom::Point nPt(Xk[i], Yk[i]); + bool isForced = fk[i]; + double nle = DistanceToCubic(start, res, nPt); + if ( isForced ) { + // forced points are favored for splitting the recursion; we do this by increasing their distance + if ( worstP < 0 || 2 * nle > worstD ) { + worstP = i; + worstD = 2 * nle; + } + } else { + if ( worstP < 0 || nle > worstD ) { + worstP = i; + worstD = nle; + } + } + } + + g_free(tk); + g_free(Qk); + g_free(Xk); + g_free(Yk); + g_free(fk); + g_free(lk); + return false; + } + + // calcul du delta= pondere par les longueurs des segments + double delta = 0; + { + double worstD = 0; + worstP = -1; + Geom::Point prevAppP; + Geom::Point prevP; + double prevDist; + prevP[0] = Xk[0]; + prevP[1] = Yk[0]; + prevAppP = prevP; // le premier seulement + prevDist = 0; +#ifdef with_splotch_killer + if ( N <= 20 ) { + for (int i = 1; i < N - 1; i++) + { + Geom::Point curAppP; + Geom::Point curP; + double curDist; + Geom::Point midAppP; + Geom::Point midP; + double midDist; + + curAppP[0] = N13 (tk[i]) * cp1[0] + N23 (tk[i]) * cp2[0] + N03 (tk[i]) * Xk[0] + N33 (tk[i]) * Xk[N - 1]; + curAppP[1] = N13 (tk[i]) * cp1[1] + N23 (tk[i]) * cp2[1] + N03 (tk[i]) * Yk[0] + N33 (tk[i]) * Yk[N - 1]; + curP[0] = Xk[i]; + curP[1] = Yk[i]; + midAppP[0] = N13 (0.5*(tk[i]+tk[i-1])) * cp1[0] + N23 (0.5*(tk[i]+tk[i-1])) * cp2[0] + N03 (0.5*(tk[i]+tk[i-1])) * Xk[0] + N33 (0.5*(tk[i]+tk[i-1])) * Xk[N - 1]; + midAppP[1] = N13 (0.5*(tk[i]+tk[i-1])) * cp1[1] + N23 (0.5*(tk[i]+tk[i-1])) * cp2[1] + N03 (0.5*(tk[i]+tk[i-1])) * Yk[0] + N33 (0.5*(tk[i]+tk[i-1])) * Yk[N - 1]; + midP=0.5*(curP+prevP); + + Geom::Point diff; + diff = curAppP-curP; + curDist = dot(diff,diff); + + diff = midAppP-midP; + midDist = dot(diff,diff); + + delta+=0.3333*(curDist+prevDist+midDist)/**lk[i]*/; + + if ( curDist > worstD ) { + worstD = curDist; + worstP = i; + } else if ( fk[i] && 2*curDist > worstD ) { + worstD = 2*curDist; + worstP = i; + } + prevP = curP; + prevAppP = curAppP; + prevDist = curDist; + } + delta/=totLen; + } else { +#endif + for (int i = 1; i < N - 1; i++) + { + Geom::Point curAppP; + Geom::Point curP; + double curDist; + + curAppP[0] = N13 (tk[i]) * cp1[0] + N23 (tk[i]) * cp2[0] + N03 (tk[i]) * Xk[0] + N33 (tk[i]) * Xk[N - 1]; + curAppP[1] = N13 (tk[i]) * cp1[1] + N23 (tk[i]) * cp2[1] + N03 (tk[i]) * Yk[0] + N33 (tk[i]) * Yk[N - 1]; + curP[0] = Xk[i]; + curP[1] = Yk[i]; + + Geom::Point diff; + diff = curAppP-curP; + curDist = dot(diff,diff); + delta += curDist; + if ( curDist > worstD ) { + worstD = curDist; + worstP = i; + } else if ( fk[i] && 2*curDist > worstD ) { + worstD = 2*curDist; + worstP = i; + } + prevP = curP; + prevAppP = curAppP; + prevDist = curDist; + } +#ifdef with_splotch_killer + } +#endif + } + + if (delta < treshhold * treshhold) + { + // premier jet + res.start = 3.0 * (cp1 - start); + res.end = -3.0 * (cp2 - end); + res.p = end; + + // Refine a little. + for (int i = 1; i < N - 1; i++) + { + Geom::Point + pt; + pt[0] = Xk[i]; + pt[1] = Yk[i]; + tk[i] = RaffineTk (pt, start, cp1, cp2, end, tk[i]); + if (tk[i] < tk[i - 1]) + { + // Force tk to be monotonic non-decreasing. + tk[i] = tk[i - 1]; + } + } + + if ( FitCubic(start,res,Xk,Yk,Qk,tk,N) ) { + } else { + // ca devrait jamais arriver, mais bon + res.start = 3.0 * (cp1 - start); + res.end = -3.0 * (cp2 - end); + g_free(tk); + g_free(Qk); + g_free(Xk); + g_free(Yk); + g_free(fk); + g_free(lk); + return true; + } + double ndelta = 0; + { + double worstD = 0; + worstP = -1; + Geom::Point prevAppP; + Geom::Point prevP; + double prevDist; + prevP[0] = Xk[0]; + prevP[1] = Yk[0]; + prevAppP = prevP; // le premier seulement + prevDist = 0; +#ifdef with_splotch_killer + if ( N <= 20 ) { + for (int i = 1; i < N - 1; i++) + { + Geom::Point curAppP; + Geom::Point curP; + double curDist; + Geom::Point midAppP; + Geom::Point midP; + double midDist; + + curAppP[0] = N13 (tk[i]) * cp1[0] + N23 (tk[i]) * cp2[0] + N03 (tk[i]) * Xk[0] + N33 (tk[i]) * Xk[N - 1]; + curAppP[1] = N13 (tk[i]) * cp1[1] + N23 (tk[i]) * cp2[1] + N03 (tk[i]) * Yk[0] + N33 (tk[i]) * Yk[N - 1]; + curP[0] = Xk[i]; + curP[1] = Yk[i]; + midAppP[0] = N13 (0.5*(tk[i]+tk[i-1])) * cp1[0] + N23 (0.5*(tk[i]+tk[i-1])) * cp2[0] + N03 (0.5*(tk[i]+tk[i-1])) * Xk[0] + N33 (0.5*(tk[i]+tk[i-1])) * Xk[N - 1]; + midAppP[1] = N13 (0.5*(tk[i]+tk[i-1])) * cp1[1] + N23 (0.5*(tk[i]+tk[i-1])) * cp2[1] + N03 (0.5*(tk[i]+tk[i-1])) * Yk[0] + N33 (0.5*(tk[i]+tk[i-1])) * Yk[N - 1]; + midP = 0.5*(curP+prevP); + + Geom::Point diff; + diff = curAppP-curP; + curDist = dot(diff,diff); + diff = midAppP-midP; + midDist = dot(diff,diff); + + ndelta+=0.3333*(curDist+prevDist+midDist)/**lk[i]*/; + + if ( curDist > worstD ) { + worstD = curDist; + worstP = i; + } else if ( fk[i] && 2*curDist > worstD ) { + worstD = 2*curDist; + worstP = i; + } + prevP = curP; + prevAppP = curAppP; + prevDist = curDist; + } + ndelta /= totLen; + } else { +#endif + for (int i = 1; i < N - 1; i++) + { + Geom::Point curAppP; + Geom::Point curP; + double curDist; + + curAppP[0] = N13 (tk[i]) * cp1[0] + N23 (tk[i]) * cp2[0] + N03 (tk[i]) * Xk[0] + N33 (tk[i]) * Xk[N - 1]; + curAppP[1] = N13 (tk[i]) * cp1[1] + N23 (tk[i]) * cp2[1] + N03 (tk[i]) * Yk[0] + N33 (tk[i]) * Yk[N - 1]; + curP[0]=Xk[i]; + curP[1]=Yk[i]; + + Geom::Point diff; + diff=curAppP-curP; + curDist=dot(diff,diff); + ndelta+=curDist; + + if ( curDist > worstD ) { + worstD=curDist; + worstP=i; + } else if ( fk[i] && 2*curDist > worstD ) { + worstD=2*curDist; + worstP=i; + } + prevP=curP; + prevAppP=curAppP; + prevDist=curDist; + } +#ifdef with_splotch_killer + } +#endif + } + + g_free(tk); + g_free(Qk); + g_free(Xk); + g_free(Yk); + g_free(fk); + g_free(lk); + + if (ndelta < delta + 0.00001) + { + return true; + } else { + // nothing better to do + res.start = 3.0 * (cp1 - start); + res.end = -3.0 * (cp2 - end); + } + return true; + } else { + // nothing better to do + } + + g_free(tk); + g_free(Qk); + g_free(Xk); + g_free(Yk); + g_free(fk); + g_free(lk); + return false; +} + +double Path::RaffineTk (Geom::Point pt, Geom::Point p0, Geom::Point p1, Geom::Point p2, Geom::Point p3, double it) +{ + // Refinement of the tk values. + // Just one iteration of Newtow Raphson, given that we're approaching the curve anyway. + // [fr: vu que de toute facon la courbe est approchC)e] + double const Ax = pt[Geom::X] - + p0[Geom::X] * N03(it) - + p1[Geom::X] * N13(it) - + p2[Geom::X] * N23(it) - + p3[Geom::X] * N33(it); + + double const Bx = (p1[Geom::X] - p0[Geom::X]) * N02(it) + + (p2[Geom::X] - p1[Geom::X]) * N12(it) + + (p3[Geom::X] - p2[Geom::X]) * N22(it); + + double const Cx = (p0[Geom::X] - 2 * p1[Geom::X] + p2[Geom::X]) * N01(it) + + (p3[Geom::X] - 2 * p2[Geom::X] + p1[Geom::X]) * N11(it); + + double const Ay = pt[Geom::Y] - + p0[Geom::Y] * N03(it) - + p1[Geom::Y] * N13(it) - + p2[Geom::Y] * N23(it) - + p3[Geom::Y] * N33(it); + + double const By = (p1[Geom::Y] - p0[Geom::Y]) * N02(it) + + (p2[Geom::Y] - p1[Geom::Y]) * N12(it) + + (p3[Geom::Y] - p2[Geom::Y]) * N22(it); + + double const Cy = (p0[Geom::Y] - 2 * p1[Geom::Y] + p2[Geom::Y]) * N01(it) + + (p3[Geom::Y] - 2 * p2[Geom::Y] + p1[Geom::Y]) * N11(it); + + double const dF = -6 * (Ax * Bx + Ay * By); + double const ddF = 18 * (Bx * Bx + By * By) - 12 * (Ax * Cx + Ay * Cy); + if (fabs (ddF) > 0.0000001) { + return it - dF / ddF; + } + + return it; +} + +// Variation on the fitting theme: try to merge path commands into cubic bezier patches. +// The goal is to reduce the number of path commands, especially when operations on path produce +// lots of small path elements; ideally you could get rid of very small segments at reduced visual cost. +void Path::Coalesce(double tresh) +{ + if ( descr_flags & descr_adding_bezier ) { + CancelBezier(); + } + + if ( descr_flags & descr_doing_subpath ) { + CloseSubpath(); + } + + if (descr_cmd.size() <= 2) { + return; + } + + SetBackData(false); + Path* tempDest = new Path(); + tempDest->SetBackData(false); + + ConvertEvenLines(0.25*tresh); + + int lastP = 0; + int lastAP = -1; + // As the elements are stored in a separate array, it's no longer worth optimizing + // the rewriting in the same array. + // [[comme les elements sont stockes dans un tableau a part, plus la peine d'optimiser + // la réécriture dans la meme tableau]] + + int lastA = descr_cmd[0]->associated; + int prevA = lastA; + Geom::Point firstP; + + /* FIXME: the use of this variable probably causes a leak or two. + ** It's a hack anyway, and probably only needs to be a type rather than + ** a full PathDescr. + */ + std::unique_ptr<PathDescr> lastAddition(new PathDescrMoveTo(Geom::Point(0, 0))); + bool containsForced = false; + PathDescrCubicTo pending_cubic(Geom::Point(0, 0), Geom::Point(0, 0), Geom::Point(0, 0)); + + for (int curP = 0; curP < int(descr_cmd.size()); curP++) { + int typ = descr_cmd[curP]->getType(); + int nextA = lastA; + + if (typ == descr_moveto) { + + if (lastAddition->flags != descr_moveto) { + FlushPendingAddition(tempDest,lastAddition.get(),pending_cubic,lastAP); + } + lastAddition.reset(descr_cmd[curP]->clone()); + lastAP = curP; + FlushPendingAddition(tempDest, lastAddition.get(), pending_cubic, lastAP); + // Added automatically (too bad about multiple moveto's). + // [fr: (tant pis pour les moveto multiples)] + containsForced = false; + + PathDescrMoveTo *nData = dynamic_cast<PathDescrMoveTo *>(descr_cmd[curP]); + firstP = nData->p; + lastA = descr_cmd[curP]->associated; + prevA = lastA; + lastP = curP; + + } else if (typ == descr_close) { + nextA = descr_cmd[curP]->associated; + if (lastAddition->flags != descr_moveto) { + + PathDescrCubicTo res(Geom::Point(0, 0), Geom::Point(0, 0), Geom::Point(0, 0)); + int worstP = -1; + if (AttemptSimplify(lastA, nextA - lastA + 1, (containsForced) ? 0.05 * tresh : tresh, res, worstP)) { + lastAddition.reset(new PathDescrCubicTo(Geom::Point(0, 0), + Geom::Point(0, 0), + Geom::Point(0, 0))); + pending_cubic = res; + lastAP = -1; + } + + FlushPendingAddition(tempDest, lastAddition.get(), pending_cubic, lastAP); + FlushPendingAddition(tempDest, descr_cmd[curP], pending_cubic, curP); + + } else { + FlushPendingAddition(tempDest,descr_cmd[curP],pending_cubic,curP); + } + + containsForced = false; + lastAddition.reset(new PathDescrMoveTo(Geom::Point(0, 0))); + prevA = lastA = nextA; + lastP = curP; + lastAP = curP; + + } else if (typ == descr_forced) { + + nextA = descr_cmd[curP]->associated; + if (lastAddition->flags != descr_moveto) { + + PathDescrCubicTo res(Geom::Point(0, 0), Geom::Point(0, 0), Geom::Point(0, 0)); + int worstP = -1; + if (AttemptSimplify(lastA, nextA - lastA + 1, 0.05 * tresh, res, worstP)) { + // plus sensible parce que point force + // ca passe + /* (Possible translation: More sensitive because contains a forced point.) */ + containsForced = true; + } else { + // Force the addition. + FlushPendingAddition(tempDest, lastAddition.get(), pending_cubic, lastAP); + lastAddition.reset(new PathDescrMoveTo(Geom::Point(0, 0))); + prevA = lastA = nextA; + lastP = curP; + lastAP = curP; + containsForced = false; + } + } + + } else if (typ == descr_lineto || typ == descr_cubicto || typ == descr_arcto) { + + nextA = descr_cmd[curP]->associated; + if (lastAddition->flags != descr_moveto) { + + PathDescrCubicTo res(Geom::Point(0, 0), Geom::Point(0, 0), Geom::Point(0, 0)); + int worstP = -1; + if (AttemptSimplify(lastA, nextA - lastA + 1, tresh, res, worstP)) { + lastAddition.reset(new PathDescrCubicTo(Geom::Point(0, 0), + Geom::Point(0, 0), + Geom::Point(0, 0))); + pending_cubic = res; + lastAddition->associated = lastA; + lastP = curP; + lastAP = -1; + } else { + lastA = descr_cmd[lastP]->associated; // pourrait etre surecrit par la ligne suivante + /* (possible translation: Could be overwritten by the next line.) */ + FlushPendingAddition(tempDest, lastAddition.get(), pending_cubic, lastAP); + lastAddition.reset(descr_cmd[curP]->clone()); + if ( typ == descr_cubicto ) { + pending_cubic = *(dynamic_cast<PathDescrCubicTo*>(descr_cmd[curP])); + } + lastAP = curP; + containsForced = false; + } + + } else { + lastA = prevA /*descr_cmd[curP-1]->associated */ ; + lastAddition.reset(descr_cmd[curP]->clone()); + if ( typ == descr_cubicto ) { + pending_cubic = *(dynamic_cast<PathDescrCubicTo*>(descr_cmd[curP])); + } + lastAP = curP; + containsForced = false; + } + prevA = nextA; + + } else if (typ == descr_bezierto) { + + if (lastAddition->flags != descr_moveto) { + FlushPendingAddition(tempDest, lastAddition.get(), pending_cubic, lastAP); + lastAddition.reset(new PathDescrMoveTo(Geom::Point(0, 0))); + } + lastAP = -1; + lastA = descr_cmd[curP]->associated; + lastP = curP; + PathDescrBezierTo *nBData = dynamic_cast<PathDescrBezierTo*>(descr_cmd[curP]); + for (int i = 1; i <= nBData->nb; i++) { + FlushPendingAddition(tempDest, descr_cmd[curP + i], pending_cubic, curP + i); + } + curP += nBData->nb; + prevA = nextA; + + } else if (typ == descr_interm_bezier) { + continue; + } else { + continue; + } + } + + if (lastAddition->flags != descr_moveto) { + FlushPendingAddition(tempDest, lastAddition.get(), pending_cubic, lastAP); + } + + Copy(tempDest); + delete tempDest; +} + + +void Path::FlushPendingAddition(Path *dest, PathDescr *lastAddition, + PathDescrCubicTo &lastCubic, int lastAP) +{ + switch (lastAddition->getType()) { + + case descr_moveto: + if ( lastAP >= 0 ) { + PathDescrMoveTo* nData = dynamic_cast<PathDescrMoveTo *>(descr_cmd[lastAP]); + dest->MoveTo(nData->p); + } + break; + + case descr_close: + dest->Close(); + break; + + case descr_cubicto: + dest->CubicTo(lastCubic.p, lastCubic.start, lastCubic.end); + break; + + case descr_lineto: + if ( lastAP >= 0 ) { + PathDescrLineTo *nData = dynamic_cast<PathDescrLineTo *>(descr_cmd[lastAP]); + dest->LineTo(nData->p); + } + break; + + case descr_arcto: + if ( lastAP >= 0 ) { + PathDescrArcTo *nData = dynamic_cast<PathDescrArcTo *>(descr_cmd[lastAP]); + dest->ArcTo(nData->p, nData->rx, nData->ry, nData->angle, nData->large, nData->clockwise); + } + break; + + case descr_bezierto: + if ( lastAP >= 0 ) { + PathDescrBezierTo *nData = dynamic_cast<PathDescrBezierTo *>(descr_cmd[lastAP]); + dest->BezierTo(nData->p); + } + break; + + case descr_interm_bezier: + if ( lastAP >= 0 ) { + PathDescrIntermBezierTo *nData = dynamic_cast<PathDescrIntermBezierTo*>(descr_cmd[lastAP]); + dest->IntermBezierTo(nData->p); + } + break; + } +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : |