I'm working on an application that records strokes, which you paint with a pointing device.
In the image above, I've drawn a single stroke, which contains 453 data points. My goal would be to drastically reduce the amount of data points while still maintaining the shape of the original stroke.
For those interested, the coordinates for the stroke pictured above are available as a gist on GitHub.
As a matter of fact, Adobe Illustrator has a great implementation of what I'm trying to achieve. If I draw a similar stroke (with a calligraphic brush) in Illustrator, the resulting shape will be simplified to what we see below. While drawing the stroke, it will look very similar to the one in my application. Once I release the mouse button, the curve will be simplified to what we see here:
As we can see, the stroke only has 14 data points. Though there are additional control points which define the inclination of the bezier spline (or whatever spline it is they're using). Here we can see a few of those control points:
I have looked at algorithms like Ramer–Douglas–Peucker algorithm, but those seem to only remove points from the input set. If I'm not mistaken, the approach I'm looking for would also have to introduce new points into the set to achieve the desired curve.
I have come across questions like iPhone smooth sketch drawing algorithm which seem related. But those seem to focus on creating a smooth curve from a small set of input points. I feel like I have the opposite case.
Using the Simplify command - Illustrator Tutorial And what it does is it allows you to reduce the number of anchor points inside your artwork.
An easy way to clean up your artwork is to choose Object > Path > Clean Up, and select what to clean up (see Figure 10). Another way to clean up your document is to remove unused swatches, brushes, etc.
I came across the question Smoothing a hand-drawn curve (which this question might actually be a dupe of), which has an answer that proposes using Ramer-Douglas-Peucker and then applying curve fitting according to Philip J. Schneiders approach.
A quick adaptation of the provided sample code to my drawing methods results in the following curve:
The input data from the question has been reduced to 28 points (which are being drawn using Bezier splines).
I'm not sure which approach exactly Adobe is using, but this one serves me extremely well so far.
So, the code provided by Kris is written for WPF and makes some assumptions in that regard. To work for my case (and because I didn't want to adjust his code), I wrote the following snippet:
private List<Point> OptimizeCurve( List<Point> curve ) {
const float tolerance = 1.5f;
const double error = 100.0;
// Remember the first point in the series.
Point startPoint = curve.First();
// Simplify the input curve.
List<Point> simplified = Douglas.DouglasPeuckerReduction( curve, tolerance ).ToList();
// Create a new curve from the simplified one.
List<System.Windows.Point> fitted = FitCurves.FitCurve( simplified.Select( p => new System.Windows.Point( p.X, p.Y ) ).ToArray(), error );
// Convert the points back to our desired type.
List<Point> fittedPoints = fitted.Select( p => new Point( (int)p.X, (int)p.Y ) ).ToList();
// Add back our first point.
fittedPoints.Insert( 0, startPoint );
return fittedPoints;
}
The resulting list will be in the format Start Point, Control Point 1, Control Point 2, End Point.
I've played with bezier simplification extensively in attempt to replicate Illustrator's path > simplify. What works best and most like Illustrator is Philip J. Schneider's Graphic's Gems simplification but with an additional step. That step being excluding sharp/angled points on the path.
With a bezier path:
Split the path at each sharp bezier segment. So any segment where its bezier handles are not smooth/collinear or where it creates a 'sharp point' in relation to the segment's two adjacent curves. You can set a threshold of your own defining when a segment is considered 'sharp'. 180 degrees being smooth, and anything from 179.99 or 170 degrees or less being the threshold of what is considered a sharp segment.
With each of these paths that have been split from the original at its sharp segments, apply the curve fitting algorithm to each of the paths, then rejoin them.
This retains sharp edges but smooths out redundant segments, fitting the curve along the rest of the path.
My implementation is in paper.js but the same technique could be leveraged using the fitcurve algorithm:
C: https://github.com/erich666/GraphicsGems/blob/2bab77250b8d45b4dfcb9cf58cf68f19f8268e56/gems/FitCurves.c
JS: https://github.com/soswow/fit-curve
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With