How to fix blurry edges on JS canvas shapes

Hi,

I am working on a canvas and I want it to be responsive (look same on different screen sizes), resizable (look same when browser window resizes and resize properly), size-adjustable (user can enter canvas output size) and look sharp (no blurry edges of shapes).

The code I have below satisfies all other criteria but it shows edges of shapes blurry. For example, the top and left edges of the rectangle in the drawing below are blurry, whereas right and bottom edges are sharp (on Firefox, and vice versa on Chrome).

My code is below but you can also test it live here on my site: https://www.yenren.art/cnv/

Please either right click and view the canvas image or save it to see the blurry edges better.

test.html:

<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>Test</title>
    <style>body{overflow:hidden;background:#222;margin:0}#wrp{margin:0 auto}canvas{margin:auto;width:100%;height:100%}
</style>
</head>
<body>
    <div id="wrp"><canvas id="canvas"></canvas></div>
    <script src="./draw.js"></script>
</body>
</html>

draw.js:

var cnv = document.getElementById("canvas"), ctx = cnv.getContext("2d");
var wrp = document.getElementById("wrp");

// ar (aspect ratio), size (user can enter different values - the canvas size to save as output)
var ar = 0.8, size = 2000;
var width, height;

var scale = size / ((innerWidth < innerHeight * ar) ? innerWidth : innerHeight * ar);

function draw() {
    setup();
    
    ctx.fillStyle = "#D4CAA3"; ctx.fillRect(0, 0, width, height); // background
    ctx.fillStyle = "#A15837"; ctx.fillRect(200, 200, 200, 200); // position can change and a scene may have multiple rectangles
}

function setup() {
    const PR = window.devicePixelRatio || 1;
    
    let w = innerWidth, h = innerHeight;
    
    // accounts for portrait and landscape orientations
    width = (w < h * ar) ? w : h * ar;
    height = (w < h * ar) ? w / ar : h;
    
    wrp.style.width = width + "px";
    wrp.style.height = height + "px";

    cnv.width = Math.ceil(size * PR);
    cnv.height = Math.ceil(size / ar * PR);
    
    ctx.scale(scale, scale);
}

window.addEventListener("resize", draw, false);
draw();

The above code is a result of endless search on various resources, but I still couldn’t get rid of the blurry edges. Any ideas how to fix that?

P.S. Adding 0.5px to coordinates or width/height do not always work, so, I am looking for other approaches if possible.

My last resort is to draw each shape 3-4 times, which makes them appear sharp but this is not ideal code and performance wise, and will avoid if I can find another solution.

Thanks!

Have you considered using <svg> instead of <canvas>?

1 Like

To be honest, I never thought of that. Thank you for the suggestion :+1:

I’m guessing your implication is that with SVG I’ll not have this sort of headaches?

I’m a generative artist drawing mostly abstract artworks on canvas and now I shall do a research to figure out if I can replace SVG with canvas for the long term -will it be better/easier to use… But for the time being, I’ll be happy to find an approach to get rid of this blurring on the canvas if possible.

On your website, I am seeing no more than one pixel width of “blur”, as much-enlarged here:

canvas1

Are you seeing more blur?

I have not examined your code in any detail but I am fairly sure you will eliminate that if you ensure all four values of fillRect are integers.

The main advantage of using SVG is that SVG graphics will remain appearing sharp when the element is resized by a browser. You would not need any of the code you have written apart from drawing the rectangles. However, browsers generally will use anti-aliasing to make graphics appear sharp and smooth to the eye by adjusting the ‘intensity’ of pixels at the edges.

You may be interested in trying out Google Drawing which is within Google Drive. You need to log into Google to use it. It gives the option of exporting to an SVG file which can then be enlarged to any size, even to be printed on a large banner.

2 Likes

Thank you for your insights and suggestions. I tried integer values in many cases but it was not a “perfect” remedy, as it caused issues in certain drawings which involved polygons.

I learned SVG to an extent, but realized it gets too slow/laggy compared to canvas when there are a lot of shapes drawn (in the range of hundreds of thousands), which my drawings usually have.

I also tried p5.js to see if it fares any better, but it has the same blurring and background bleeding issues.

In the end, I decided to

  1. use a larger canvas (ex: screen height 1080px, canvas height 3600px) and resize it to fit with JS-CSS - need to check how things look on 1440p and 2160p.

  2. draw multiple of the same shape on top of each other.

It still does not work perfectly in all scenarios, but it’s the best I have. After all, the blur/background bleeding only matter in very clean concepts with geometric adjacent shapes. On a screen with textures, grain, or a nature scene, it wouldn’t really matter at all.

I am fairly certain that the “blur” you are seeing is anti-aliasing. Browsers use anti-aliasing to make shapes, diagonal lines and curves appear smoother and sharper. It avoids “jaggies” appearing.

This is how a browser may use anti-aliasing on a small circle and on a diagonal line (much enlarged):

anti-alias

Yea, you’re right. And this makes drawing perfectly aligned complex shapes kind of impossible on Canvas.

Anti-aliasing significantly improves the appearance of shapes and lines. That’s why browsers use it when drawing onto a <canvas> element and when rendering a SVG image. You are limited by the size of pixels, not by the anti-aliasing.