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

JavascriptHtmlHtml5 CanvasLine

Javascript Problem Overview


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!

Javascript Solutions


Solution 1 - Javascript

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.

Solution 2 - Javascript

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.

Solution 3 - Javascript

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/

Solution 4 - Javascript

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

  1. Scale the canvas on the resize (avoid canvas default stretch scaling).
  2. multiple the lineWidth with pixelRatio to find proper 'real' pixel line thickness:

context.lineWidth = thickness * pixelRatio;

  1. 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

Solution 5 - Javascript

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/

Solution 6 - Javascript

Did you see the first hit on google? (search for canvas line width 1px). Though I have to admit this isn't exactly "clean" or "lean". Ferry Kobus' solution is much better. Then again: it sucks you need to use "half pixels" in the first place...

Solution 7 - Javascript

If none of these answers worked for you, check your browser zoom. Mine was somehow at 125% so every fourth 1px line was drawn 2px wide.

I spent hours trying to figure out why every fiddle on the internet worked and mine didn't (the zoom was only set for my dev tab)

Solution 8 - Javascript

The fillRect() method can be used to draw thin horizontal or vertical lines in canvas (without having to apply the +0.5 shift on coordinates):

this.fillRect(left, top, 1, height);
this.fillRect(left, top, width, 1);

And you can actually make the lines even thinner by just replacing this code by something like:

this.fillRect(left, top, 0.7, height);
this.fillRect(left, top, width, 0.7);

Lines will be thinner (tending to reach 1 pixel wide) but their color a bit attenuated.

-> working example

To be noted that if we set ctx.lineWidth=0.7 (for the classical beginPath/moveTo/lineTo/stroke sequence), it does not work on Chrome (0.7 and 1 are interpreted the same way). Thus an interest for this fillRect() method.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMintDepartureView Question on Stackoverflow
Solution 1 - JavascriptFerry KobusView Answer on Stackoverflow
Solution 2 - JavascriptRichardView Answer on Stackoverflow
Solution 3 - JavascripttonycouplandView Answer on Stackoverflow
Solution 4 - JavascriptIevgenView Answer on Stackoverflow
Solution 5 - JavascriptTom AhView Answer on Stackoverflow
Solution 6 - JavascriptRobIIIView Answer on Stackoverflow
Solution 7 - JavascriptCurtisView Answer on Stackoverflow
Solution 8 - JavascriptGhislainView Answer on Stackoverflow