cover logo: fit the traced outline to lines and curves, not a decimated polyline
traceToSvg emitted polylines. Marching squares produced a sub-pixel contour and RDP threw points away at eps=0.2 until what was left could be joined with "L", so a curve arrived as facets and a straight edge arrived as a ripple. The ripple is the one that reads at hero size, and the reason is a measurement rather than a matter of taste: on these masks a mathematically straight edge still wanders through the alpha field -- print grain, JPEG ringing, and the shadow un-blend being a two-colour model of a soft gradient. Measured over the acceptance set that wander peaks around 0.15-0.3px across a 40px run on a clean source, so eps=0.2 sits BELOW the noise floor and RDP was faithfully reproducing the grain. Tightening it makes that worse; loosening it far enough to clear the noise starts cutting real curvature. Fitting cubics under a looser tolerance is the obvious answer and it does not work. Schneider pins each segment's endpoints to actual sample points, so every join inherits that one point's share of the noise; measured, the straight edges came out no better than RDP left them (ripple 0.56 -> 0.64px). Raising the tolerance does not help, because the endpoints are pinned regardless. What removes the noise is fitting a LINE to the whole run and throwing the samples away. A total-least-squares line over a few hundred points has an error of noise/sqrt(n) -- hundredths of a pixel, not tenths. Vertices then become the INTERSECTIONS of neighbouring primitives rather than sample points, which has a second payoff: the trace blur rounds every real corner into a small arc, and intersecting the two adjacent lines puts the sharp corner back. So the pass is corners, cut at them, maximal straight runs, cubics for the gaps, then resolve the joints. Prefix moments make a line fit over any index range O(1), which is what keeps growing a run near-linear rather than quadratic on exactly the long straight edges it exists for. Three things a straightness test cannot decide on its own, each found by looking at where the lines actually landed rather than at an aggregate. - A residual test admits CHORDS OF A BOWL. On a circle of radius R a chord of arc length L departs from its own best-fit line by only about 0.3 L^2/(8R), so at any tolerance loose enough to absorb grain a big enough bowl accepts straight pieces -- which is how the counters of Hot Toddy's O's came out as polygons, 73% of that outline drawn straight. The evidence lies OUTSIDE the run: fit a circle to a window extended past both ends, and if that window is a clean arc whose sagitta across the run exceeds arcSagitta, the run is a chord. On a loop with no corners the window has to WRAP, because the seam goes at the tightest bend and clipping there would exempt the one place that most needs the test. - The ends OVERRUN into the bend. RMS is an average, so a run absorbs the first few px of a curve and still passes; the line then chords the start of the bend and the cubic is left too little room, which is the kink at a stem-to-shoulder junction. Each end is trimmed back to where a window-fitted direction still agrees with the run's line. It has to be a WINDOW fit -- a point tangent over a few px of grainy edge carries several degrees of noise by itself and trims at random, which put the ripple back up from 0.19 to 0.48px -- and the threshold has to rise with the run's own measured noise, or it means something different on a clean print than on a rough one. An end sitting on a detected corner is not trimmed: the contour runs straight into a corner by definition, and that direction is what the corner is reconstructed from. - Extrapolating to an intersection is unbounded. As a wedge closes the legitimate apex offset grows without limit, and so does the error in it, because two nearly anti-parallel lines cross at a point a fraction of a degree can move a long way. The inside of Mildlife's M reconstructed 11px past its real apex and cut a white spike into the letterform. The shift is now bounded by what the rounding can explain -- r*(1/sin(theta/2) - 1) for a wedge of interior angle theta -- and CLAMPED rather than discarded, since a corner pulled back to the limit is still sharper than the blurred contour whereas falling back to the contour point throws the reconstruction away. Corner sharpness measures identical anywhere from a 2px to a 12px ceiling, so the ceiling costs nothing and only removes the failure. Acceptance over the eight-artist set, against the polyline it replaces: ripple on edges that are straight beyond argument 0.228 -> 0.015px, with 637 -> 8 interior vertices spent on them; curvature reversals 6.7 -> 1.5 per 100px; corner turn-arc 8.03 -> 5.48px; 1557 -> 276 segments and 21.2 -> 9.5KB of path. Hot Toddy, the mark whose terminals must not soften, goes 3.32 -> 0.25px with 0 of 34 corners softened. Men I Trust improves least (9.7 -> 6.1 reversals) because its print genuinely is crenellated and the path tracks it. Cost is 18-33ms in the app's own engine, against contours() at 90-140ms. The old polyline remains behind traceToSvg's `fit: false` for A/B.
This commit is contained in:
1 parent
4c0c4a1ea5
commit
0cc01328a4
1 file changed
+1047
-11
+1047
-11
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user