Reputation: 5800
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!
Upvotes: 63
Views: 48178
Reputation: 2029
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.
var can = document.getElementById('canvas1');
var ctx = can.getContext('2d');
ctx.lineWidth = 1;
// linear gradient from start to end of line
var grad = ctx.createLinearGradient(50, 150, 150, 150);
grad.addColorStop(0, "red");
grad.addColorStop(1, "green");
ctx.strokeStyle = grad;
ctx.beginPath();
ctx.moveTo(50, 150.5);
ctx.lineTo(150, 150.5);
ctx.stroke();
<canvas id="canvas1" width="500" height="500"></canvas>http://jsfiddle.net/9bMPD/http://jsfiddle.net/9bMPD/
This answer explains why it works that way.
Upvotes: 119
Reputation: 5101
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.
Upvotes: 41
Reputation: 4443
For me, only a combination of different 'pixel perfect' techniques helped to archive the results:
Get and scale canvas with the pixel ratio:
pixelRatio = window.devicePixelRatio/ctx.backingStorePixelRatio
Scale the canvas on the resize (avoid canvas default stretch scaling).
multiple the lineWidth with pixelRatio to find proper 'real' pixel line thickness:
context.lineWidth = thickness * pixelRatio;
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
Upvotes: 6
Reputation: 2535
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)
Upvotes: 1
Reputation: 21
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.
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.
Upvotes: 1
Reputation: 705
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/
Upvotes: 4
Reputation: 4247
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/
Upvotes: 9
Reputation: 8821
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...
Upvotes: 3