79

How to turn on the anti-aliasing on an canvas.

The following code doesn't draw a smooth line:

var context = mainCanv.getContext("2d");
if (context) {
   context.moveTo(0,0);
   context.lineTo(100,75);

   context.strokeStyle = "#df4b26";
   context.lineWidth = 3;
   context.stroke();
}
KRouane
  • 917
  • 1
  • 8
  • 9
  • 3
    According to [this stackoverflow question](http://stackoverflow.com/questions/195262/can-i-turn-off-antialiasing-on-an-html-canvas-element) it would seem that the canvas *is* anti-aliased by default. What OS/browser/version are you using? – Phrogz Nov 23 '10 at 21:26
  • 2
    When you say it's not smooth, what do you mean? Are you seeing jagged edges / pixels, or is it just blurry? – Nathan Ostgard Mar 08 '11 at 10:18
  • It happens on Firefox Mobile on Android, there is an ugly gray border around the red line. – Thomas Decaux Mar 24 '14 at 10:06
  • 1
    http://phoboslab.org/log/2012/09/drawing-pixels-is-hard – Ian Hern Apr 03 '16 at 04:47
  • 1
    Does this answer your question? [Can I turn off antialiasing on an HTML element?](https://stackoverflow.com/questions/195262/can-i-turn-off-antialiasing-on-an-html-canvas-element) – gman Apr 12 '20 at 05:23

9 Answers9

50

You may translate canvas by half-pixel distance.

ctx.translate(0.5, 0.5);

Initially the canvas positioning point between the physical pixels.

Bacher
  • 1,686
  • 1
  • 12
  • 9
  • 2
    Note that if you clear your canvas using the method of setting the width to itself: canvas.width = canvas.width this will reset the transzlation matrix and you'll need to translate by half a pixel again. You can avoid this by using clearRect() instead. – Richard Jun 02 '14 at 11:42
  • This is the only answer that actually solves OPs issue – Tim Hettler Jun 18 '14 at 00:05
  • 8
    It clearly helps with straight lines, but diagonal lines still look pretty terrible. – Bjorn Nov 09 '14 at 18:47
  • 4
    It just moves where the aliasing occurs. – Octopus May 24 '17 at 00:00
  • Note that floating point pixel values greatly reduce performance, so if you are drawing frequently this may not be a good idea – rococo Aug 24 '17 at 05:49
  • This solution worked perfectly for me, and for all angles. Edge and Chrome. – Kit Jun 05 '20 at 07:56
38

Anti-aliasing cannot be turned on or off, and is controlled by the browser.

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

Community
  • 1
  • 1
Gaurav
  • 12,662
  • 2
  • 36
  • 34
  • 14
    That's not true. It can be turned on and off with ctx.imageSmoothingEnabled. – zachdyer May 20 '14 at 16:35
  • 22
    imageSmoothingEnabled applies to pattern fills and drawImage, it does not affect general anti-aliasing. http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#image-smoothing – Gaurav May 20 '14 at 17:52
  • 1
    Oh in that case maybe it's the video card that would handle that. – zachdyer May 20 '14 at 21:21
  • FWIW the HTML5 canvas is not GPU accelerated – i336_ Feb 08 '21 at 10:34
29

It's now 2018, and we finally have cheap ways to do something around it...

Indeed, since the 2d context API now has a filter property, and that this filter property can accept SVGFilters, we can build an SVGFilter that will keep only fully opaque pixels from our drawings, and thus eliminate the default anti-aliasing.

So it won't deactivate antialiasing per se, but provides a cheap way both in term of implementation and of performances to remove all semi-transparent pixels while drawing.

I am not really a specialist of SVGFilters, so there might be a better way of doing it, but for the example, I'll use a <feComponentTransfer> node to grab only fully opaque pixels.

var ctx = canvas.getContext('2d');
ctx.fillStyle = '#ABEDBE';
ctx.fillRect(0,0,canvas.width,canvas.height);
ctx.fillStyle = 'black';
ctx.font = '14px sans-serif';
ctx.textAlign = 'center';

// first without filter
ctx.fillText('no filter', 60, 20);
drawArc();
drawTriangle();
// then with filter
ctx.setTransform(1, 0, 0, 1, 120, 0);
ctx.filter = 'url(#remove-alpha)';
// and do the same ops
ctx.fillText('no alpha', 60, 20);
drawArc();
drawTriangle();

// to remove the filter
ctx.filter = 'none';


function drawArc() {
  ctx.beginPath();
  ctx.arc(60, 80, 50, 0, Math.PI * 2);
  ctx.stroke();
}

function drawTriangle() {
  ctx.beginPath();
  ctx.moveTo(60, 150);
  ctx.lineTo(110, 230);
  ctx.lineTo(10, 230);
  ctx.closePath();
  ctx.stroke();
}
// unrelated
// simply to show a zoomed-in version
var zCtx = zoomed.getContext('2d');
zCtx.imageSmoothingEnabled = false;
canvas.onmousemove = function drawToZoommed(e) {
  var x = e.pageX - this.offsetLeft,
    y = e.pageY - this.offsetTop,
    w = this.width,
    h = this.height;
    
  zCtx.clearRect(0,0,w,h);
  zCtx.drawImage(this, x-w/6,y-h/6,w, h, 0,0,w*3, h*3);
}
<svg width="0" height="0" style="position:absolute;z-index:-1;">
  <defs>
    <filter id="remove-alpha" x="0" y="0" width="100%" height="100%">
      <feComponentTransfer>
        <feFuncA type="discrete" tableValues="0 1"></feFuncA>
      </feComponentTransfer>
      </filter>
  </defs>
</svg>

<canvas id="canvas" width="250" height="250" ></canvas>
<canvas id="zoomed" width="250" height="250" ></canvas>

And for the ones that don't like to append an <svg> element in their DOM, you can also save it as an external svg file and set the filter property to path/to/svg_file.svg#remove-alpha.

Kaiido
  • 123,334
  • 13
  • 219
  • 285
  • 1
    Looking to do something similar - map translucent to opaque pixels... https://stackoverflow.com/questions/53755451/svg-filter-to-make-translucent-pixels-opaque – jedierikb Dec 13 '18 at 05:37
  • 5
    Terrific answer - you even included a zoom to emphasize the difference! – goat May 25 '19 at 21:40
  • 1
    "It's now 2018", not for Internet Explorer. And it's actually 2020 by the time of this comment, and still not for Internet Explorer. Unfortunately, this dinosaur is STILL used by many people and some projects STILL require to support it... – Jeremy Thille Jun 10 '20 at 13:42
  • 1
    @JeremyThille so? Should we deprive the rest of the world from modern techs because some companies are still using IE? I also have to support it for some clients, we just made clear to them that they won't have the "optimal experience". There are other solutions in this page, pick one if it's really a must for your project. – Kaiido Jun 10 '20 at 14:35
  • Oh, absolutely. I am fully for dropping IE, this is my everyday battle. Your solution is good, but IE drags us all :( – Jeremy Thille Jun 10 '20 at 14:41
  • @JeremyThille Yes but latest IE was released in 2013, so having my answer saying at the beginning "it's now 2018" shouldn't deceive the readers. Your comment felt like it did deceive you. I may misread it though. – Kaiido Jun 10 '20 at 14:44
  • My point is the same as yours. It's now 2018, and now 2020, we should be able to use modern methods and APIs, but... unfortunately it's not 2018 yet for many people who still use IE for some reason :( Many people still live in 2013. Thought of the day. – Jeremy Thille Jun 10 '20 at 15:18
  • Just for the record/out of curiosity, what version of IE was this talking about? 11? ...10? – i336_ Feb 08 '21 at 10:37
  • @i336_ all versions. The thread is from 2020 and it's been said in it that the last release of IE was made in 2013. – Kaiido Feb 08 '21 at 11:13
  • Ah, I see. That logical structure there sailed right over my head, thanks :) - and wow, I just realized that means everything's in a time-warp to (as of right now) 8 years ago (and then with a bunch of inconsistencies on top). That's quite the legacy entrenchment... :( – i336_ Feb 08 '21 at 11:53
28

I haven't needed to turn on anti-alias because it's on by default but I have needed to turn it off. And if it can be turned off it can also be turned on.

ctx.imageSmoothingEnabled = true;

I usually shut it off when I'm working on my canvas rpg so when I zoom in the images don't look blurry.

zachdyer
  • 611
  • 1
  • 9
  • 17
  • 13
    imageSmoothingEnabled applies to pattern fills and drawImage, it does not affect general anti-aliasing. http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#image-smoothing – Gaurav Dec 25 '13 at 07:58
8

Here's a workaround that requires you to draw lines pixel by pixel, but will prevent anti aliasing.

// some helper functions
// finds the distance between points
function DBP(x1,y1,x2,y2) {
    return Math.sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1));
}
// finds the angle of (x,y) on a plane from the origin
function getAngle(x,y) { return Math.atan(y/(x==0?0.01:x))+(x<0?Math.PI:0); }
// the function
function drawLineNoAliasing(ctx, sx, sy, tx, ty) {
    var dist = DBP(sx,sy,tx,ty); // length of line
    var ang = getAngle(tx-sx,ty-sy); // angle of line
    for(var i=0;i<dist;i++) {
        // for each point along the line
        ctx.fillRect(Math.round(sx + Math.cos(ang)*i), // round for perfect pixels
                     Math.round(sy + Math.sin(ang)*i), // thus no aliasing
                     1,1); // fill in one pixel, 1x1
    }
}

Basically, you find the length of the line, and step by step traverse that line, rounding each position, and filling in a pixel.

Call it with

var context = cv.getContext("2d");
drawLineNoAliasing(context, 20,30,20,50); // line from (20,30) to (20,50)
Overcode
  • 4,074
  • 1
  • 21
  • 24
  • 1
    I don't think getAngle is necessary for drawing lines. All you need to do is divide the difference between the two points by the 'dist' and multiply that by 'i'. Am I wrong? – Perry Monschau Aug 06 '13 at 09:18
  • yes you're correct. I just used angles for clarity..? I guess – Overcode Aug 23 '13 at 20:51
  • 1
    holy toledo you just saved me a ton of time! about a year ago i started work on a pixel based painting web app, and got frustrated with the inability to disable anti-aliasing. i think the only solution id found at the time ran so slow that it was hopeless, but yours runs amazingly fast and does exactly what i need it to! thank you!!! – steve Oct 25 '15 at 01:05
  • 7
    Drawing lines pixel by pixel shouldn't need calls to sin and cos. Use Bresenham's algorithm, it's a piece of graphics history: https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm – kristianp May 16 '17 at 22:15
3

If you need pixel level control over canvas you can do using createImageData and putImageData.

HTML:

<canvas id="qrCode" width="200", height="200">
  QR Code
</canvas>

And JavaScript:

function setPixel(imageData, pixelData) {
  var index = (pixelData.x + pixelData.y * imageData.width) * 4;
    imageData.data[index+0] = pixelData.r;
    imageData.data[index+1] = pixelData.g;
    imageData.data[index+2] = pixelData.b;
    imageData.data[index+3] = pixelData.a;
}

element = document.getElementById("qrCode");
c = element.getContext("2d");

pixcelSize = 4;
width = element.width;
height = element.height;


imageData = c.createImageData(width, height);

for (i = 0; i < 1000; i++) {
  x = Math.random() * width / pixcelSize | 0; // |0 to Int32
  y = Math.random() * height / pixcelSize| 0;

  for(j=0;j < pixcelSize; j++){
    for(k=0;k < pixcelSize; k++){
     setPixel( imageData, {
         x: x * pixcelSize + j,  
         y: y * pixcelSize + k,
         r: 0 | 0,
         g: 0 | 0,
         b: 0 * 256 | 0,
         a: 255 // 255 opaque
       });
      }
  }
}

c.putImageData(imageData, 0, 0);

Working sample here

Jordan
  • 187
  • 1
  • 5
1

so I am assuming this is kinda out of use now but one way to do it is actually using document.body.style.zoom=2.0; but if you do this then all of your canvas measurements will have to be divided by the zoom. Also, set the zoom higher for more aliasing. This is helpful because it is adjustable. Also if using this method, I suggest that you make functions to do the same as ctx.fillRect() etc. but with the zoom taken into account. E.g.

function fillRect(x, y, width, height) {
    var zoom = document.body.style.zoom;
    ctx.fillRect(x/zoom, y/zoom, width/zoom, height/zoom);
}

Hope this helps!

Also, a sidenote: this can be used to sharpen circle edges as well so that they don't look as blurred. Just use a zoom such as 0.5!

Neuron
  • 5,141
  • 5
  • 38
  • 59
TOGD
  • 21
  • 5
1

I had the same issue and was able to get smoother lines by using a small shadow along with the line context, such as:

ctx.shadowOffsetX = 0
ctx.shadowOffsetY = 0
ctx.shadowBlur = 2
ctx.shadowColor = 'black'
Dharman
  • 30,962
  • 25
  • 85
  • 135
Tim S.
  • 385
  • 1
  • 2
  • 6
0

I needed a combination of the 0.5 translation and pixel data manipulation:

    context.strokeStyle = "rgb(255, 255, 255)";
    for (let [x1, y1, x2, y2] of linePoints) {
        context.beginPath();
        context.moveTo(x1 + 0.5, y1 + 0.5);
        context.lineTo(x2 + 0.5, y2 + 0.5);
        context.stroke();
    }

    // remove anti-aliasing
    const id = context.getImageData(0, 0, width, height);
    for (let y = 0; y < id.height; ++y) {
        for (let x = 0; x < id.width; ++x) {
            const index = (y * id.width + x) * 4;
            id.data[index] = id.data[index] < 128 ? 0 : 255;
            id.data[index+1] = id.data[index+1] < 128 ? 0 : 255;
            id.data[index+2] = id.data[index+2] < 128 ? 0 : 255;
            id.data[index+3] = id.data[index+3] < 128 ? 0 : 255;
        }
    }
    context.putImageData(id, 0, 0);

I added 0.5 to the coordinates. That could probably be done with a translate. But that 0.5 is important. Without it, the left edge pixels and the top edge pixels get pushed off of the image. It also fixed some inconsistent rounding. What should have been a straight line had some dips in it.

Then just round all the pixel values to 0 or 255. If you need different colors, you can round to those values instead of 0 or 255.

beauxq
  • 1,258
  • 1
  • 13
  • 22