2

I'm facing an apparently known problem, in which lines in HTML canvases cannot disable antialiasing (e.g. this question mentions this can only be done for images using context.imageSmoothingEnabled= false, but not for lines).

I wonder if there is any update on this, and if it is possible to draw a crisp (even if "pixelated") line? All I'd need is what e.g. MS Paint is able to achieve when using the plain "pencil" tool (cf. left-side stroke below), instead of a smoother "brush" type (cf. right-side stroke).

crisp pixel line in paint

Currently, the lines in my HTML canvas look more like the "brush" type. blurry canvas lines

It is an issue, because I'm trying to set a mouse-event that gives me the exact color of the line, but with the antialiasing surrounding the actual color, it returns some undesired color(s) before the cursor reaches the center part of the line.

Could someone confirm that there is really no way to force a "pencil"-like stroke in the canvas? If so, is there maybe a simple workaround, to e.g. "cut off" the edges of the stroke?

sc28
  • 1,163
  • 4
  • 26
  • 48

1 Answers1

1

You can draw such a pixelated shape on canvas by using SVG filter which removes alpha values of shape edge. (But SVG filter is a bit heavy.)

const ctx = canvas.getContext("2d");

//normal line(antialiased)
ctx.moveTo(20,20); ctx.lineTo(180,20);
ctx.stroke();

//set "crisp edge" filter
ctx.filter = "url(#crisp)";
//crisp line
ctx.beginPath();ctx.moveTo(20,40); ctx.lineTo(180,40);
ctx.moveTo(20, 50); ctx.lineTo(180, 70);
//crisp circle
ctx.moveTo(150, 130); ctx.arc(100, 130, 50, 0, Math.PI*2);
ctx.stroke();
<canvas id="canvas" width="200" height="200"></canvas>
<svg style="visibility:hidden;width:0;height:0;">
  <defs>
    <!--SVG filter to remove alpha-->
    <filter id="crisp">
      <feComponentTransfer>
        <feFuncA type="discrete" tableValues="0,1"></feFuncA>
      </feComponentTransfer>
    </filter>
  </defs>
</svg>

NOTE:But Chrome raises security error on output image by using SVG Filter.


If you need to implements such filter on your own, ImageData object will help you. This has no security error risk.

But hand made filter is too heavy to apply on every event drawings. You should consider the apply timing carefully.

const ctx = canvas.getContext("2d");

//temporary canvas to removeAlpha
const tmp = document.createElement("canvas");
[tmp.width, tmp.height] = [canvas.width, canvas.height];
const tctx = tmp.getContext("2d");

//normal line(antialiased)
ctx.moveTo(20,20); ctx.lineTo(180,20);
ctx.stroke();

//crisp line
tctx.beginPath(); tctx.moveTo(20,40); tctx.lineTo(180,40);
tctx.moveTo(20, 50); tctx.lineTo(180, 70);
removeAlpha();

//crisp circle
tctx.moveTo(150, 130); tctx.arc(100, 130, 50, 0, Math.PI*2);
tctx.stroke();
removeAlpha();

//remove alpha on tmp canvas and draw it to main canvas.
function removeAlpha(){
  const threshold = 128;//you can change the value
  const id = tctx.getImageData(0, 0, tmp.width, tmp.height);
  const data = id.data;//pixel data array
  //remove alpha values pixel by pixel.
  for(let i = 0, len = canvas.width * canvas.height; i<len; i++){
    const apos = i * 4 + 3;//alpha data position
    const a = data[apos];
    data[apos] = a > threshold ? 255 : 0;
  }
  tctx.putImageData(id, 0, 0);
  ctx.drawImage(tmp, 0, 0);
  tctx.clearRect(0, 0, tmp.width, tmp.height);
}
<canvas id="canvas" width="200" height="200"></canvas>
defghi1977
  • 5,081
  • 1
  • 30
  • 29
  • clever use of svg filters – Kaiido Oct 20 '17 at 02:04
  • Thanks, this is pretty nice! But I still have two issues. 1) This filter causes the following error when using my mousemove event on the filtered canvas: `Uncaught DOMException: Failed to execute 'getImageData' on 'CanvasRenderingContext2D': The canvas has been tainted by cross-origin data.` Is it possible to avoid this cross-origin? maybe by loading the SVG xml differently then using `url(#crisp)`? 2) The line is much crisper, but not completely. Is it possible to parametrize how much alpha is filtered? – sc28 Oct 20 '17 at 09:03
  • I checked Chrome raises security error on output image by using SVG filter. (No problem on FireFox) So, you can remove alpha values by editing pixel values instead of SVG filter. – defghi1977 Oct 20 '17 at 09:27
  • Sorry, but I don't understand: how could I remove alpha values by editing pixels without the SVG filter? Would this solve the security issue also on Chrome? Thanks in advance if you can clarify. – sc28 Oct 20 '17 at 09:33
  • 1
    I filed a bug report about this SVGFilter tainting the canvas. https://bugs.chromium.org/p/chromium/issues/detail?id=777081&can=2&q=SVGFilter%20canvas&colspec=ID%20Pri%20M%20Stars%20ReleaseBlock%20Component%20Status%20Owner%20Summary%20OS%20Modified – Kaiido Oct 21 '17 at 02:34
  • 1
    I finally accepted your answer because it provides an interesting way to obtain crisp lines, though it didn't fully solve my problem. I can share the simple workaround I finally came up with: repeat the `ctx.stroke()` 10 to 50 times, and by doing so the line becomes darker, with crisper edges. Some undesired pixels always remain, but most are removed which is already helpful in my case. – sc28 Oct 23 '17 at 20:12
  • >sc28 Please add your solution as a answer for this question. I think it is simple and useful. – defghi1977 Oct 23 '17 at 23:31