Here are 4 examples on how to draw (in an HTML canvas) a filled shape with a border. Only one of them gives a clean result and I wish to understand why:
let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d');
ctx.save();
let border = 2;
// integer coordinates
usingStroke(ctx, 50, 50, border, 1);
usingFill(ctx, 150, 50, border, 2);
// half-pixel shift
usingStroke(ctx, 50.5, 150.5, border, 3);
usingFill(ctx, 150.5, 150.5, border, 4);
ctx.restore();
function usingStroke(ctx, x, y, border, text) {
ctx.beginPath();
ctx.arc(x, y, 25, 0, 2 * Math.PI);
ctx.rect(x+10, y, 50, 30);
ctx.lineWidth = border * 2; //half of it will be behind the fill
ctx.strokeStyle = 'darkred';
ctx.stroke();
ctx.fillStyle = 'salmon';
ctx.fill();
addText(ctx, text, x, y);
}
function usingFill(ctx, x, y, border, text) {
ctx.beginPath();
ctx.arc(x, y, 25 + border, 0, 2 * Math.PI);
ctx.rect(x+10-border, y-border, 50+border*2, 30+border*2);
ctx.fillStyle = 'darkred';
ctx.fill();
ctx.beginPath();
ctx.arc(x, y, 25, 0, 2 * Math.PI);
ctx.rect(x+10, y, 50, 30);
ctx.fillStyle = 'salmon';
ctx.fill();
addText(ctx, text, x, y);
}
function addText(ctx, text, x, y) {
ctx.font = "bold 20px sans";
ctx.fillStyle = 'black';
ctx.fillStyle = 'black';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(text, x, y);
}
<canvas id="canvas" width="600" height="400"></canvas>
It uses two different methods:
- examples 1 and 3 use
strokefirst for the border, thenfillfor the background. - examples 2 and 4 draw the shape twice using
fillboth times: a bit larger the first time, with the border color, and at the right size the second time.
I also know that lines (and borders) of odd width tend to be blurry, and need to be drawn a half-pixel further to get a crisp result (hence the exemples 3 and 4). More info on MDN.
What I don't understand is:
- why is example 2 the only one giving a crisp result?
- why do example 1 and 3 (using
stroke) give a non-homogenous border? The border appears thicker at the top left thant at the bottom right of the shape(s). At first I thought that it was just an artefact of the low resolution and that adding a half-pixel would solve it, but no...
In conclusion, I feel frustrated with the stroke approach and wonder if there is a better way to use it. I know it works well with thicker borders, but I only work with borders of 1 to 3 pixels.