HTML canvas: unsatisfying results when drawing a border around a filled shape

54 views Asked by At

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 stroke first for the border, then fill for the background.
  • examples 2 and 4 draw the shape twice using fill both 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:

  1. why is example 2 the only one giving a crisp result?
  2. 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.

0

There are 0 answers