I recently discovered the HTML5 Canvas, it's lovely, like the good old days of using $A000:0000!

However, when drawing an image to the canvas it kept coming out all blurry. As it turns out, there are actually quite a few possible reasons for this to happen... here's a short checklist of what I discovered on my quest to fix it.

Setting Canvas dimensions



Most articles I came across mentioned that you should always explicitly specify the dimensions of a canvas, either when creating the element in bland HTML e.g.,
<canvas id="drawable" width="100px" height="100px"></canvas>

or via JavaScript e.g,
canvas        = document.getElementById("drawable");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;


Setting Canvas style dimensions



The above is actually not enough though, you also need to set the CSS dimensions, changing the above JavaScript to SOMETHING like:
canvas              = document.getElementById("drawable");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
canvas.style.width = canvas.width.toString() + "px";
canvas.style.height = canvas.height.toString() + "px";


Catering for different pixel ratios



Testing my code out on my retina iPad, I discovered that window.innerWidth and window.innerHeight were returning half of what they should have.

Turns out there's a variable called window.devicePixelRatio that is present on retina devices, and contains the scale you should use when dealing with dimensions...

...sort of. See the "Drawing Pixels is hard" link below for a much clearer explanation.

CSS image rendering settings



Turns out there's ALSO a CSS variable you can change to affect how things are handled internally, it's called image-rendering

You can use it something like this:
/* applies to GIF and PNG images; avoids blurry edges */

img[src$=".gif"], img[src$=".png"] {
image-rendering: -moz-crisp-edges; /* Firefox */
image-rendering: -o-crisp-edges; /* Opera */
image-rendering: -webkit-optimize-contrast;/* Webkit (non-standard naming) */
image-rendering: crisp-edges;
-ms-interpolation-mode: nearest-neighbor; /* IE (non-standard property) */
}


Pixel offsets



Finally getting to what actually fixed it for me, a silly mistake to make.

I wanted to draw my image centered on the canvas, so ended up doing something like: x = canvas.width / 2 - image.naturalWidth / 2, doing the same thing for y.

This leads to floating point co-ordinates, perhaps starting in the middle of a pixel. Being a JavaScript dunce I simply used parseInt(x) to draw the image, and that seemed to cure it for me.

Oddly enough though, I've seen many mentions of always drawing to x+0.5,y+0.5, but I have no idea why you'd do that... unless you always happened to start at 0.5 in the first place.

References



Drawing pixels is hard

About the image-rendering setting


permalink print article