/* calculate best fit from i+.5 to j+.5. Assume i<j (cyclically). Return 0 and set badness and parameters (alpha, beta), if possible. Return 1 if impossible. */ static bool opti_penalty(Path pp, int i, int j, ref opti res, double opttolerance, int[] convc, double[] areac) { int m = pp.Curves.n; int k, k1, k2, conv, i1; double area, alpha, d, d1, d2; dPoint p0, p1, p2, p3, pt; double A, R, A1, A2, A3, A4; double s, t; /* check convexity, corner-freeness, and maximum bend < 179 degrees */ if (i == j) { /* sanity - a full loop can never be an opticurve */ return true; } k = i; i1 = mod(i + 1, m); k1 = mod(k + 1, m); conv = convc[k1]; if (conv == 0) { return true; } d = ddist(pp.Curves.vertex[i], pp.Curves.vertex[i1]); for (k = k1; k != j; k = k1) { k1 = mod(k + 1, m); k2 = mod(k + 2, m); if (convc[k1] != conv) { return true; } if (sign(cprod(pp.Curves.vertex[i], pp.Curves.vertex[i1], pp.Curves.vertex[k1], pp.Curves.vertex[k2])) != conv) { return true; } if (iprod1(pp.Curves.vertex[i], pp.Curves.vertex[i1], pp.Curves.vertex[k1], pp.Curves.vertex[k2]) < d * ddist(pp.Curves.vertex[k1], pp.Curves.vertex[k2]) * COS179) { return true; } } /* the curve we're working in: */ p0 = pp.Curves.ControlPoints[mod(i, m), 2]; p1 = pp.Curves.vertex[mod(i + 1, m)]; p2 = pp.Curves.vertex[mod(j, m)]; p3 = pp.Curves.ControlPoints[mod(j, m), 2]; /* determine its area */ area = areac[j] - areac[i]; area -= dpara(pp.Curves.vertex[0], pp.Curves.ControlPoints[i, 2], pp.Curves.ControlPoints[j, 2]) / 2; if (i >= j) { area += areac[m]; } /* find intersection o of p0p1 and p2p3. Let t,s such that o = interval(t,p0,p1) = interval(s,p3,p2). Let A be the area of the triangle (p0,o,p3). */ A1 = dpara(p0, p1, p2); A2 = dpara(p0, p1, p3); A3 = dpara(p0, p2, p3); /* A4 = dpara(p1, p2, p3); */ A4 = A1 + A3 - A2; if (A2 == A1) { /* this should never happen */ return true; } t = A3 / (A3 - A4); s = A2 / (A2 - A1); A = A2 * t / 2.0; if (A == 0.0) { /* this should never happen */ return true; } R = area / A; /* relative area */ alpha = 2 - Math.Sqrt(4 - R / 0.3); /* overall alpha for p0-o-p3 curve */ res.c = new dPoint[2]; res.c[0] = interval(t * alpha, p0, p1); res.c[1] = interval(s * alpha, p3, p2); res.alpha = alpha; res.t = t; res.s = s; p1 = res.c[0]; p2 = res.c[1]; /* the proposed curve is now (p0,p1,p2,p3) */ res.pen = 0; /* calculate penalty */ /* check tangency with edges */ for (k = mod(i + 1, m); k != j; k = k1) { k1 = mod(k + 1, m); t = tangent(p0, p1, p2, p3, pp.Curves.vertex[k], pp.Curves.vertex[k1]); if (t < -.5) { return true; } pt = bezier(t, p0, p1, p2, p3); d = ddist(pp.Curves.vertex[k], pp.Curves.vertex[k1]); if (d == 0.0) { /* this should never happen */ return true; } d1 = dpara(pp.Curves.vertex[k], pp.Curves.vertex[k1], pt) / d; if (Math.Abs(d1) > opttolerance) { return true; } if (iprod(pp.Curves.vertex[k], pp.Curves.vertex[k1], pt) < 0 || iprod(pp.Curves.vertex[k1], pp.Curves.vertex[k], pt) < 0) { return true; } res.pen += d1 * d1; } /* check corners */ for (k = i; k != j; k = k1) { k1 = mod(k + 1, m); t = tangent(p0, p1, p2, p3, pp.Curves.ControlPoints[k, 2], pp.Curves.ControlPoints[k1, 2]); if (t < -.5) { return true; } pt = bezier(t, p0, p1, p2, p3); d = ddist(pp.Curves.ControlPoints[k, 2], pp.Curves.ControlPoints[k1, 2]); if (d == 0.0) { /* this should never happen */ return true; } d1 = dpara(pp.Curves.ControlPoints[k, 2], pp.Curves.ControlPoints[k1, 2], pt) / d; d2 = dpara(pp.Curves.ControlPoints[k, 2], pp.Curves.ControlPoints[k1, 2], pp.Curves.vertex[k1]) / d; d2 *= 0.75 * pp.Curves.alpha[k1]; if (d2 < 0) { d1 = -d1; d2 = -d2; } if (d1 < d2 - opttolerance) { return true; } if (d1 < d2) { res.pen += (d1 - d2) * (d1 - d2); } } return false; }
/* optimize the path p, replacing sequences of Bezier segments by a single segment when possible. Return 0 on success, 1 with errno set on failure. */ static void opticurve(Path pp, double opttolerance) { int m = pp.Curves.n; int[] pt = new int[m + 1]; /* pt[m+1] */ double[] pen = new double[m + 1]; /* pen[m+1] */ int[] len = new int[m + 1]; /* len[m+1] */ opti[] opt = new opti[m + 1]; /* opt[m+1] */ int[] convc = new int[m]; /* conv[m]: pre-computed convexities */ double[] areac = new double[m + 1]; /* cumarea[m+1]: cache for fast area computation */ int om; int i, j; bool r; opti o = new opti(); dPoint p0; int i1; double area; double alpha; double[] s; double[] t; /* pre-calculate convexity: +1 = right turn, -1 = left turn, 0 = corner */ for (i = 0; i < m; i++) { if (pp.Curves.tag[i] == POTRACE_CURVETO) { convc[i] = sign(dpara(pp.Curves.vertex[mod(i - 1, m)], pp.Curves.vertex[i], pp.Curves.vertex[mod(i + 1, m)])); } else { convc[i] = 0; } } /* pre-calculate areas */ area = 0.0; areac[0] = 0.0; p0 = pp.Curves.vertex[0]; for (i = 0; i < m; i++) { i1 = mod(i + 1, m); if (pp.Curves.tag[i1] == POTRACE_CURVETO) { alpha = pp.Curves.alpha[i1]; area += 0.3 * alpha * (4 - alpha) * dpara(pp.Curves.ControlPoints[i, 2], pp.Curves.vertex[i1], pp.Curves.ControlPoints[i1, 2]) / 2; area += dpara(p0, pp.Curves.ControlPoints[i, 2], pp.Curves.ControlPoints[i1, 2]) / 2; } areac[i + 1] = area; } pt[0] = -1; pen[0] = 0; len[0] = 0; /* Fixme: we always start from a fixed point -- should find the best curve cyclically ### */ for (j = 1; j <= m; j++) { /* calculate best path from 0 to j */ pt[j] = j - 1; pen[j] = pen[j - 1]; len[j] = len[j - 1] + 1; for (i = j - 2; i >= 0; i--) { r = opti_penalty(pp, i, mod(j, m), ref o, opttolerance, convc, areac); if (r) { break; } if (len[j] > len[i] + 1 || (len[j] == len[i] + 1 && pen[j] > pen[i] + o.pen)) { pt[j] = i; pen[j] = pen[i] + o.pen; len[j] = len[i] + 1; opt[j] = o; } } } om = len[m]; pp.OptimizedCurves = new privcurve(om); s = new double[om]; t = new double[om]; j = m; for (i = om - 1; i >= 0; i--) { if (pt[j] == j - 1) { pp.OptimizedCurves.tag[i] = pp.Curves.tag[mod(j, m)]; pp.OptimizedCurves.ControlPoints[i, 0] = pp.Curves.ControlPoints[mod(j, m), 0]; pp.OptimizedCurves.ControlPoints[i, 1] = pp.Curves.ControlPoints[mod(j, m), 1]; pp.OptimizedCurves.ControlPoints[i, 2] = pp.Curves.ControlPoints[mod(j, m), 2]; pp.OptimizedCurves.vertex[i] = pp.Curves.vertex[mod(j, m)]; pp.OptimizedCurves.alpha[i] = pp.Curves.alpha[mod(j, m)]; pp.OptimizedCurves.alpha0[i] = pp.Curves.alpha0[mod(j, m)]; pp.OptimizedCurves.beta[i] = pp.Curves.beta[mod(j, m)]; s[i] = t[i] = 1.0; } else { pp.OptimizedCurves.tag[i] = POTRACE_CURVETO; pp.OptimizedCurves.ControlPoints[i, 0] = opt[j].c[0]; pp.OptimizedCurves.ControlPoints[i, 1] = opt[j].c[1]; pp.OptimizedCurves.ControlPoints[i, 2] = pp.Curves.ControlPoints[mod(j, m), 2]; pp.OptimizedCurves.vertex[i] = interval(opt[j].s, pp.Curves.ControlPoints[mod(j, m), 2], pp.Curves.vertex[mod(j, m)]); pp.OptimizedCurves.alpha[i] = opt[j].alpha; pp.OptimizedCurves.alpha0[i] = opt[j].alpha; s[i] = opt[j].s; t[i] = opt[j].t; } j = pt[j]; } /* calculate beta parameters */ for (i = 0; i < om; i++) { i1 = mod(i + 1, om); pp.OptimizedCurves.beta[i] = s[i] / (s[i] + t[i1]); } }