I trying to do an anti-aliasing drawing function in canvas. Thanks to all the super answers on this site about canvas and aliasing.
here's the demo: https://jsfiddle.net/garciaVvV/eu34c8sy/12/
here's the line js:
function lineXY(mouseX, mouseY, mouseXX, mouseYY){
var x0= mouseX;
var y0= mouseY;
var x1= mouseXX;
var y1= mouseYY;
var coordinatesArray = [];
// Translate coordinates
// Define differences and error check
var dx = Math.abs(x1 - x0);
var dy = Math.abs(y1 - y0);
var sx = (x0 < x1) ? 1 : -1;
var sy = (y0 < y1) ? 1 : -1;
var err = dx - dy;
// Set first coordinates
coordinatesArray.push([x0,y0]);
// Main loop
while (!((x0 == x1) && (y0 == y1))) {
var e2 = err << 1;
if (e2 > -dy) {
err -= dy;
x0 += sx;
}
if (e2 < dx) {
err += dx;
y0 += sy;
}
// Set coordinates
coordinatesArray.push([x0,y0]);
// Return the result
}
for(var i=0;i<coordinatesArray.length;i++) {
aliasedCircle(ctx, coordinatesArray[i][0], coordinatesArray[i][1], 100);
}
}
What make it jerky while drawing fast with a large pen ? And how to make it sweet?
Thanks
The main reason is of course that quite a large number of paths are generated, first with the circle and then with the line which reproduces the circle paths x length per pixel.
There are a couple of things we can do to improve this:
We can cache the circle as an image and use it as a bitmap brush. This eliminates the need to regenerate all the lines in the circle for each point in the line. The brush only needs to be updated when size or color changes.
We don't have to draw each point of the line, we can find a way to calculate how many pixels we can skip before we need to draw, but an better option is:
We can "cheat" by drawing a thick line between the first and last point instead of drawing a circle each point.
And finally, we can register mouse on each frame instead of each event to reduce the load.
The first point is simple enough: simply create an offscreen canvas the size of the brush (diameter) and draw in. To change color either regenerate brush (or use composite mode and draw over it):
Now that we have an image/bitmap brush we can look at how to draw the line. We can use two approaches. Since you want it aliased we have to compromise somehow.
Using a Bresenham to draw and fill a line can be very slow in the context we're working. Drawing the circle multiple times is slow as well.
An third option is to use the context's own line and "hack" the edges (of course, if all this is to improve filling with bucket fill, ref. previous question, I would probably spend the energy on improving the bucket fill algorithm instead :) ).
So lets try the third option. We need both the internal line mechanism as well as the Bresenham. The challenge is to make the Bresenham cover the edge exactly.
Lets add
Bresenham, actually, lets use a faster line algorithm: EFLA and try to match the edges - now, this may not be perfect in all cases and the offset (or rather line width of the native draw op.) may have to be adjusted.We also needs to calculate 90° offset to the angle for both side. Instead of adding and subtracting 90° we can switch cos/sin instead.
And finally, if we merge the components and refactor a little we get a neat aliased line drawing mechanism that utilizes these approaches:
Some final notes: Just be aware of that it may not be perfect, alias-wise, in particular in almost 0/90° lines. This is because due to number of samples there can sit many points making a fine gradual line the EFLA line cannot cover with its single pixel point.
One alternative is to make a polygon fill (like scanline) implementation. It's a little more math and steps involved but doable with acceptable performance.