Can I turn off antialiasing on an HTML <canvas> element?

I'm playing around with the <canvas> element, drawing lines and such.

I've noticed that my diagonal lines are antialiased. I'd prefer the jaggy look for what I'm doing - is there any way of turning this feature off?

Answers:

Answer

For images there's now context.imageSmoothingEnabled= false.

However, there's nothing that explicitly controls line drawing. You may need to draw your own lines (the hard way) using getImageData and putImageData.

Answer

Draw your 1-pixel lines on coordinates like ctx.lineTo(10.5, 10.5). Drawing a one-pixel line over the point (10, 10) means, that this 1 pixel at that position reaches from 9.5 to 10.5 which results in two lines that get drawn on the canvas.

A nice trick to not always need to add the 0.5 to the actual coordinate you want to draw over if you've got a lot of one-pixel lines, is to ctx.translate(0.5, 0.5) your whole canvas at the beginning.

Answer

It can be done in Mozilla Firefox. Add this to your code:

contextXYZ.mozImageSmoothingEnabled = false;

In Opera it's currently a feature request, but hopefully it will be added soon.

Answer

It must antialias vector graphics

Antialiasing is required for correct plotting of vector graphics that involves non-integer coordinates (0.4, 0.4), which all but very few clients will do.

When given non-integer coordinates, the canvas has two options:

  • Antialias - paint the pixels around the coordinate with respect to how far the integer coordinate is from non-integer one (the rounding error).
  • Round - apply some rounding function to the non-integer coordinate (so 1.4 will become 1, for example).

The later strategy will work for static graphics, although for small graphics (a circle with radius of 2) curves will show clear steps rather than smooth curve.

The real problem is when the graphics is translated (moved) - the jumps between one pixel and another (1.6 => 2, 1.4 => 1), mean that the origin of the shape may jump with relation to the parent container (constantly shifting 1 pixel up/down and left/right).

Some tips

Tip #1: You can soften (or harden) antialiasing by scaling the canvas (say by x) then apply the reciprocal scale (1/x) to the geometries yourself (not using the canvas).

Compare (no scaling):

A few rectangles

with (canvas scale: 0.75; manual scale: 1.33):

Same rectangles with softer edges

and (canvas scale: 1.33; manual scale: 0.75):

Same rectangles with darker edges

Tip #2: If a jaggy look is really what you're after, try to draw each shape a few times (without erasing). With each draw, the antialiasing pixels get darker.

Compare. After drawing once:

A few paths

After drawing thrice:

Same paths but darker and no visible antialiasing.

Answer

I would draw everything using a custom line algorithm such as Bresenham's line algorithm. Check out this javascript implementation: http://members.chello.at/easyfilter/canvas.html

I think this will definitely solve your problems.

Answer

I want to add that I had trouble when downsizing an image and drawing on canvas, it was still using smoothing, even though it wasn't using when upscaling.

I solved using this:

function setpixelated(context){
    context['imageSmoothingEnabled'] = false;       /* standard */
    context['mozImageSmoothingEnabled'] = false;    /* Firefox */
    context['oImageSmoothingEnabled'] = false;      /* Opera */
    context['webkitImageSmoothingEnabled'] = false; /* Safari */
    context['msImageSmoothingEnabled'] = false;     /* IE */
}

You can use this function like this:

var canvas = document.getElementById('mycanvas')
setpixelated(canvas.getContext('2d'))

Maybe this is useful for someone.

Answer
ctx.translate(0.5, 0.5);
ctx.lineWidth = .5;

With this combo I can draw nice 1px thin lines.

Answer

Notice a very limited trick. If you want to create a 2 colors image, you may draw any shape you want with color #010101 on a background with color #000000. Once this is done, you may test each pixel in the imageData.data[] and set to 0xFF whatever value is not 0x00 :

imageData = context2d.getImageData (0, 0, g.width, g.height);
for (i = 0; i != imageData.data.length; i ++) {
    if (imageData.data[i] != 0x00)
        imageData.data[i] = 0xFF;
}
context2d.putImageData (imageData, 0, 0);

The result will be a non-antialiased black & white picture. This will not be perfect, since some antialiasing will take place, but this antialiasing will be very limited, the color of the shape being very much like the color of the background.

Answer

Just two notes on StashOfCode's answer:

  1. It only works for a grayscale, opaque canvas (fillRect with white then draw with black, or viceversa)
  2. It may fail when lines are thin (~1px line width)

It's better to do this instead:

Stroke and fill with #FFFFFF, then do this:

imageData.data[i] = (imageData.data[i] >> 7) * 0xFF

That solves it for lines with 1px width.

Other than that, StashOfCode's solution is perfect because it doesn't require to write your own rasterization functions (think not only lines but beziers, circular arcs, filled polygons with holes, etc...)

Answer

For those who still looking for answers. here is my solution.

Assumming image is 1 channel gray. I just thresholded after ctx.stroke().

ctx.beginPath();
ctx.moveTo(some_x, some_y);
ctx.lineTo(some_x, some_y);
...
ctx.closePath();
ctx.fill();
ctx.stroke();

let image = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height)
for(let x=0; x < ctx.canvas.width; x++) {
  for(let y=0; y < ctx.canvas.height; y++) {
    if(image.data[x*image.height + y] < 128) {
      image.data[x*image.height + y] = 0;
    } else {
      image.data[x*image.height + y] = 255;
    }
  }
}

if your image channel is 3 or 4. you need to modify the array index like

x*image.height*number_channel + y*number_channel + channel
Answer

Here is a basic implementation of Bresenham's algorithm in JavaScript. It's based on the integer-arithmetic version described in this wikipedia article: https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm

    function range(f=0, l) {
        var list = [];
        const lower = Math.min(f, l);
        const higher = Math.max(f, l);
        for (var i = lower; i <= higher; i++) {
            list.push(i);
        }
        return list;
    }

    //Don't ask me.
    //https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm
    function bresenhamLinePoints(start, end) {

        let points = [];

        if(start.x === end.x) {
            return range(f=start.y, l=end.y)
                        .map(yIdx => {
                            return {x: start.x, y: yIdx};
                        });
        } else if (start.y === end.y) {
            return range(f=start.x, l=end.x)
                        .map(xIdx => {
                            return {x: xIdx, y: start.y};
                        });
        }

        let dx = Math.abs(end.x - start.x);
        let sx = start.x < end.x ? 1 : -1;
        let dy = -1*Math.abs(end.y - start.y);
        let sy = start.y < end.y ? 1 : - 1;
        let err = dx + dy;

        let currX = start.x;
        let currY = start.y;

        while(true) {
            points.push({x: currX, y: currY});
            if(currX === end.x && currY === end.y) break;
            let e2 = 2*err;
            if (e2 >= dy) {
                err += dy;
                currX += sx;
            }
            if(e2 <= dx) {
                err += dx;
                currY += sy;
            }
        }

        return points;

    }

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us Javascript

©2020 All rights reserved.