As many people knew, HTML5 Canvas lineTo() is going to give you a very jaggy line at each corner. At this point, a more preferable solution would be to implement quadraticCurveTo(), which is a very great way to generate smooth drawing. However, I desire to create a smooth, yet accurate, draw on canvas HTML5. Quadratic curve approach works well in smoothing out the draw, but it does not go through all the sample points. In other word, when I try to draw a quick curve using quadratic curve, sometime the curve appears to be "corrected" by the application. Instead of following my drawing path, some of the segment is curved out of its original path to follow a quadratic curve.
My application is intended for a professional drawing on HTML5 canvas, so it is very crucial for the drawing to be both smooth and precise. I am not sure if I am asking for the impossible by trying to put HTML5 canvas on the same level as photoshop or any other painter applications (SAI, painterX, etc.)
Thanks
What you want is a Cardinal spline as cardinal splines goes through the actual points you draw.
Note: to get a professional result you will also need to implement moving average for short thresholds while using cardinal splines for larger thresholds and using knee values to break the lines at sharp corner so you don't smooth the entire line. I won't be addressing the moving average or knee here (nor taper) as these are outside the scope, but show a way to use cardinal spline.
A side note as well - the effect that the app seem to modify the line is in-avoidable as the smoothing happens post. There exists algorithms that smooth while you draw but they do not preserve knee values and the lines seem to "wobble" while you draw. It's a matter of preference I guess.
Here is an fiddle to demonstrate the following:
ONLINE DEMO
First some prerequisites (I am using my easyCanvas library to setup the environment in the demo as it saves me a lot of work, but this is not a requirement for this solution to work):
When you have the points in an array order by X / Y (ie [x1, y1, x2, y2, ... xn, yn]) then you can use this function to smooth it:
The tension value (ts, default 0.5) is what smooths the curve. The higher number the more round the curve becomes. You can go outside the normal interval [0, 1] to make curls.
The segment (nos, or number-of-segments) is the resolution between each point. In most cases you will probably not need higher than 9-10. But on slower computers or where you draw fast higher values is needed.
The function (optimized):
/// cardinal spline by Ken Fyrstenberg, CC-attribute
function smoothCurve(pts, ts, nos) {
// use input value if provided, or use a default value
ts = (typeof ts === 'undefined') ? 0.5 : ts;
nos = (typeof nos === 'undefined') ? 16 : nos;
var _pts = [], res = [], // clone array
x, y, // our x,y coords
t1x, t2x, t1y, t2y, // tension vectors
c1, c2, c3, c4, // cardinal points
st, st2, st3, st23, st32, // steps
t, i, r = 0,
len = pts.length,
pt1, pt2, pt3, pt4;
_pts.push(pts[0]); //copy 1. point and insert at beginning
_pts.push(pts[1]);
_pts = _pts.concat(pts);
_pts.push(pts[len - 2]); //copy last point and append
_pts.push(pts[len - 1]);
for (i = 2; i < len; i+=2) {
pt1 = _pts[i];
pt2 = _pts[i+1];
pt3 = _pts[i+2];
pt4 = _pts[i+3];
t1x = (pt3 - _pts[i-2]) * ts;
t2x = (_pts[i+4] - pt1) * ts;
t1y = (pt4 - _pts[i-1]) * ts;
t2y = (_pts[i+5] - pt2) * ts;
for (t = 0; t <= nos; t++) {
// pre-calc steps
st = t / nos;
st2 = st * st;
st3 = st2 * st;
st23 = st3 * 2;
st32 = st2 * 3;
// calc cardinals
c1 = st23 - st32 + 1;
c2 = st32 - st23;
c3 = st3 - 2 * st2 + st;
c4 = st3 - st2;
res.push(c1 * pt1 + c2 * pt3 + c3 * t1x + c4 * t2x);
res.push(c1 * pt2 + c2 * pt4 + c3 * t1y + c4 * t2y);
} //for t
} //for i
return res;
}
Then simply call it from the mouseup event after the points has been stored:
stroke = smoothCurve(stroke, 0.5, 16);
strokes.push(stroke);
Short comments on knee values:
A knee value in this context is where the angle between points (as part of a line segment) in the line is greater than a certain threshold (typically between 45 - 60 degrees). When a knee occur the lines is broken into a new line so that only the line consisting of points with a lesser angle than threshold between them are used (you see the small curls in the demo as a result of not using knees).
Short comment on moving average:
Moving average is typically used for statistical purposes, but is very useful for drawing applications as well. When you have a cluster of many points with a short distance between them splines doesn't work very well. So here you can use MA to smooth the points.
There is also point reduction algorithms that can be used such as the Ramer/Douglas/Peucker one, but it has more use for storage purposes to reduce amount of data.
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