Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Complete Solution for Drawing 1 Pixel Line on HTML5 Canvas

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+0.5 ( Refer to Canvas line behaviour when 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 give 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?

like image 975
AndyX Avatar asked Aug 13 '14 02:08

AndyX


Video Answer


2 Answers

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/

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>
like image 57
markE Avatar answered Oct 29 '22 23:10

markE


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

  1. Get and scale canvas with a 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.

  1. use image-rendering: pixelated;

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

like image 27
Ievgen Avatar answered Oct 29 '22 21:10

Ievgen