Draw 1 pixel line on HTML5 canvas is always problematic.(Refer to http://jsbin.com/voqubexu/1/edit?js,output)
The approach to draw a vertical/horizontal line is x+0.5, y
The "wider" line you refer to results from anti-aliasing that's automatically done by the browser.
Anti-aliasing is used to display a visually less jagged line.
Short of drawing pixel-by-pixel, there's currently no way of disabling anti-aliasing drawn by the browser.
You can use Bresenham's line algorithm to draw your line by setting individual pixels. Of course, setting individual pixels results in lesser performance.
Here's example code and a Demo: http://jsfiddle.net/m1erickson/3j7hpng0/
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: ivory; }
canvas{border:1px solid red;}
</style>
<script>
$(function(){
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var imgData=ctx.getImageData(0,0,canvas.width,canvas.height);
var data=imgData.data;
bline(50,50,250,250);
ctx.putImageData(imgData,0,0);
function setPixel(x,y){
var n=(y*canvas.width+x)*4;
data[n]=255;
data[n+1]=0;
data[n+2]=0;
data[n+3]=255;
}
// Refer to: http://rosettacode.org/wiki/Bitmap/Bresenham's_line_algorithm#JavaScript
function bline(x0, y0, x1, y1) {
var dx = Math.abs(x1 - x0), sx = x0 < x1 ? 1 : -1;
var dy = Math.abs(y1 - y0), sy = y0 < y1 ? 1 : -1;
var err = (dx>dy ? dx : -dy)/2;
while (true) {
setPixel(x0,y0);
if (x0 === x1 && y0 === y1) break;
var e2 = err;
if (e2 > -dx) { err -= dy; x0 += sx; }
if (e2 < dy) { err += dx; y0 += sy; }
}
}
}); // end $(function(){});
</script>
</head>
<body>
<canvas id="canvas" width=300 height=300></canvas>
</body>
</html>
For me, only a combination of different 'pixel perfect' techniques helped to archive the results:
Get and scale canvas with a 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(x1, y1, x2, y2) {
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, x1), getSharpPixel(thickness, y1));
context.lineTo(getSharpPixel(thickness, x2), getSharpPixel(thickness, y2));
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,50,250,250);
pixelPerfectLine(120,0,120,250);
pixelPerfectLine(122,0,122,250);
pixelPerfectRectangle(10, 11, 200.3, 43.2, 1, false);
pixelPerfectRectangle(41, 42, 150.3, 43.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