Complete solution for drawing 1 pixel line on HTML5 canvas

Drawing a 1 pixel line on an HTML5 canvas is always problematic (see http://jsbin.com/voqubexu/1/edit?js,output )

The approach to the vertical / horizontal line is x + 0.5, y + 0.5 (see the behavior of the Canvas line at 0 <lineWidth <1 ). To do this globally, ctx.translate(0.5, 0.5); would be a good idea.

However, when it comes to diagonal lines, this method does not work. It always gives a 2 pixel line. Is there a way to stop this browser behavior? If not, is there a package that can provide a solution to this problem?

+7
source share
2 answers

In the "wider" line, you refer to the smoothing results that are automatically executed by the browser.

Smoothing is used to display a visually less jagged line.

Shrinking from pixel by pixel, there is currently no way to turn off anti-aliasing drawn by the browser.

You can use the Bresenham line algorithm to draw your line by specifying individual pixels. Of course, setting individual pixels leads to less performance.

Here's a sample code and demo: http://jsfiddle.net/m1erickson/3j7hpng0/

enter image description here

 <!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> 
+6
source

For me, only a combination of various β€œpixel perfect” techniques helped archive the results:

  1. Get and scale canvas with pixel ratio:

    pixelRatio = window.devicePixelRatio / ctx.backingStorePixelRatio

  2. Scale the canvas when resizing (avoid stretching the canvas by default).

  3. multiply lineWidth by pixelRatio to find the correct "real" pixel line thickness:

    context.lineWidth = thickness * pixelRatio;

  4. Check if the line thickness is odd or even. add half pixelRatio to the line position for 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.

  1. use image rendering: pixel;

 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> 

The Resize event does not fire, so you can try the file on GitHub

0
source

Source: https://habr.com/ru/post/973742/


All Articles