astropanic
astropanic

Reputation: 10939

Canvas / precise line drawing

I'm playing currently with javascript and canvas around.

I want draw a 1 pixel border without blurry lines. I've read about the 0.5 px translation.

var shift = 0.5;
if (x1 === x2) {
  var shiftX = shift;
  var shiftY = 0;
} else {
  var shiftX = 0;
  var shiftY = shift;
}

What is wrong ? JSFiddle

Why is one pixel missing in the right bottom corner ?

Upvotes: 1

Views: 472

Answers (2)

GameAlchemist
GameAlchemist

Reputation: 19294

The center of a screen pixel is it at its (+0.5,+0.5) context2d coordinate.

Truth is you don't have to translate by 0.5 for x OR y, but for both, be it a vertical or horizontal line.

Your whole code is equivalent to this one :

ctx.sline(0    , 0.5,      399,   0.5);
ctx.sline(399.5 ,0,        399.5, 399);
ctx.sline(0     ,399.5,    399,   399.5);
ctx.sline(0.5   ,399,      0.5,   0);
// With 'sline' a straight line function that just use provided coordinates.

It's easy to see that points do not connect one to another. There are 8 points involved here, not 4.
The right-down most point (399.5, 399.5) is never reached, while other corners have at least 2 points drawn.

A simple way to settle the issue once and for all is to translate the canvas by (0.5, 0.5) on context creation, and use rounded coordinates after that.
Obviously -as it should always be done- save/restore the context when using a transform.

http://jsfiddle.net/gamealchemist/xaxK4/6/

ctx.translate(0.5, 0.5);
ctx.lineWidth = 1;
ctx.strokeStyle = "#AAAAAA";

ctx.line = function (x1, y1, x2, y2) {
    x1 = 0 | x1 ;
    y1 = 0 | y1 ;
    x2 = 0 | x2 ;
    y2 = 0 | y2 ;
    ctx.beginPath();
    ctx.moveTo(x1, y1);
    ctx.lineTo(x2, y2);
    ctx.stroke();
};

ctx.line(1, 1, 399, 1);
ctx.line(399, 1, 399, 399);
ctx.line(399, 399, 1, 399);
ctx.line(1, 399, 1, 1);

And do not use css width/height, or maybe set it in the code to ensure you never have canvas.width != canvas.style.width or canvas.height != canvas.style.height, unless you know what you're doing in having different values of course. But if you don't plan on playing with resolution, to support hiDpi devices, or to render at a lower resolution, quit using css sizing.

Upvotes: 0

Gershom Maes
Gershom Maes

Reputation: 8178

This works!!

ctx.line = function (x1, y1, x2, y2) {
    var shift = 0.5;

    if (x1 === x2) {
        var shiftX = x1 < 2 ? shift : -shift;
        var shiftY = 0;
    } else if (y1 === y2) {
        var shiftX = 0;
        var shiftY = y1 < 2 ? shift : -shift;
    } else {
        var shiftX = 0;
        var shiftY = 0;
    }

    ctx.translate(shiftX, shiftY);
    ctx.beginPath();
    ctx.moveTo(x1, y1);
    ctx.lineTo(x2, y2);
    ctx.stroke();
    ctx.translate(-shiftX, -shiftY);
};

The issue with your attempt was that a pixel too close to the edge could be shifted "off" the canvas. This method ensures that if the point is within 2 pixels of an edge, the shifting occurs in a direction AWAY from the edge.

Upvotes: 2

Related Questions