Drawing a 1px thick line in canvas creates a 2px thick line

In this jsfiddle there's a line with a lineWidth of 1.

http://jsfiddle.net/mailrox/9bMPD/350/

e.g:

ctx.lineWidth = 1;

However the line is 2px thick when it's drawn on the canvas, how do you create a 1px thick line.

I could draw a rectangle (with 1px height) however I want the line to also work on diagonals. So how do you get this line to be 1px high?

Thanks!


Canvas calculates from the half of a pixel

ctx.moveTo(50,150.5);
ctx.lineTo(150,150.5);

So starting at a half will fix it

Fixed version: http://jsfiddle.net/9bMPD/357/

This answer explains why it works that way.


You can also translate by half a pixel in the X and Y directions and then use whole values for your coordinates (you may need to round them in some cases):

context.translate(0.5, 0.5)

context.moveTo(5,5);
context.lineTo(55,5);

Keep in mind that if you resize your canvas the translate will be reset - so you'll have to translate again.

You can read about the translate function and how to use it here:

https://www.rgraph.net/canvas/reference/translate.html

This answer explains why it works that way.


Or as this answer states, to get a width of 1, you need to start at a half pixel.

ctx.moveTo(50.5,150.5);
ctx.lineTo(150.5,150.5);

http://jsfiddle.net/9bMPD/355/


For me, only a combination of different 'pixel perfect' techniques helped to archive the results:

  1. Get and scale canvas with the pixel ratio:

    pixelRatio = window.devicePixelRatio/ctx.backingStorePixelRatio

  2. Scale the canvas on the resize (avoid canvas default stretch scaling).

  3. multiple the lineWidth with pixelRatio to find proper 'real' pixel line thickness:

    context.lineWidth = thickness * pixelRatio;

  4. Check whether the thickness of the line is odd or even. add half of the pixelRatio to the line position for the odd thickness values.

    x = x + pixelRatio/2;

The odd line will be placed in the middle of the pixel. The line above is used to move it a little bit.

function getPixelRatio(context) {
  dpr = window.devicePixelRatio || 1,
    bsr = context.webkitBackingStorePixelRatio ||
    context.mozBackingStorePixelRatio ||
    context.msBackingStorePixelRatio ||
    context.oBackingStorePixelRatio ||
    context.backingStorePixelRatio || 1;

  return dpr / bsr;
}


var canvas = document.getElementById('canvas');
var context = canvas.getContext("2d");
var pixelRatio = getPixelRatio(context);
var initialWidth = canvas.clientWidth * pixelRatio;
var initialHeight = canvas.clientHeight * pixelRatio;


window.addEventListener('resize', function(args) {
  rescale();
  redraw();
}, false);

function rescale() {
  var width = initialWidth * pixelRatio;
  var height = initialHeight * pixelRatio;
  if (width != context.canvas.width)
    context.canvas.width = width;
  if (height != context.canvas.height)
    context.canvas.height = height;

  context.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
}

function pixelPerfectLine(x) {

  context.save();
  context.beginPath();
  thickness = 1;
  // Multiple your stroke thickness  by a pixel ratio!
  context.lineWidth = thickness * pixelRatio;

  context.strokeStyle = "Black";
  context.moveTo(getSharpPixel(thickness, x), getSharpPixel(thickness, 0));
  context.lineTo(getSharpPixel(thickness, x), getSharpPixel(thickness, 200));
  context.stroke();
  context.restore();
}

function pixelPerfectRectangle(x, y, w, h, thickness, useDash) {
  context.save();
  // Pixel perfect rectange:
  context.beginPath();

  // Multiple your stroke thickness by a pixel ratio!
  context.lineWidth = thickness * pixelRatio;
  context.strokeStyle = "Red";
  if (useDash) {
    context.setLineDash([4]);
  }
  // use sharp x,y and integer w,h!
  context.strokeRect(
    getSharpPixel(thickness, x),
    getSharpPixel(thickness, y),
    Math.floor(w),
    Math.floor(h));
  context.restore();
}

function redraw() {
  context.clearRect(0, 0, canvas.width, canvas.height);
  pixelPerfectLine(50);
  pixelPerfectLine(120);
  pixelPerfectLine(122);
  pixelPerfectLine(130);
  pixelPerfectLine(132);
  pixelPerfectRectangle();
  pixelPerfectRectangle(10, 11, 200.3, 443.2, 1, false);
  pixelPerfectRectangle(41, 42, 150.3, 443.2, 1, true);
  pixelPerfectRectangle(102, 100, 150.3, 243.2, 2, true);
}

function getSharpPixel(thickness, pos) {

  if (thickness % 2 == 0) {
    return pos;
  }
  return pos + pixelRatio / 2;

}

rescale();
redraw();
canvas {
  image-rendering: -moz-crisp-edges;
  image-rendering: -webkit-crisp-edges;
  image-rendering: pixelated;
  image-rendering: crisp-edges;
  width: 100vh;
  height: 100vh;
}
<canvas id="canvas"></canvas>

Resize event is not fired in the snipped so you can try the file on the github


The Canvas can draw clean straight lines with fillRect(). A rectangle with a 1px height or a 1px width does the job. It doesn't need half-pixel value:

var ctx = document.getElementById("myCanvas").getContext("2d");

ctx.drawVerticalLine = function(left, top, width, color){
    this.fillStyle=color;
    this.fillRect(left, top, 1, width);
};

ctx.drawHorizontalLine = function(left, top, width, color){
    this.fillStyle=color;
    this.fillRect(left, top, width, 1);
}

ctx.drawVerticalLine(150, 0, 300, "green");
ctx.drawHorizontalLine(0, 150, 300, "red");

https://jsfiddle.net/ynur1rab/