Belén Albeza

Retro, crisp pixel art in HTML 5 games

One thing that I've been doing in my games for Ludum Dare has been drawing the graphics with a pixel art style.

What I'd do was to draw the graphic in a very small size, and then scale it up to 4 times its size to have those big fat pixels.

Pixelated character

The scaling algorithm used is nearest-neighbor, which doesn't introduce any kind of anti-aliasing –which would ruin pixel art.

However, this approach it's not efficient: the images are heavier (in file size) than they could be! And depending on our game, an image with bigger dimensions can be more expensive to process (for instance, if we need to change its pixels to a different color).

Introducing image-rendering

Wouldn't it be cool if we could render the images in their original, small size and then use CSS to scale the canvas?

Let's remember that the width and height properties of a <canvas> element refer to its pixel dimensions, but the element itself can be rendered with a different size via CSS.

However, until recently, this approach made images blurry.

The world before image-rendering

Let's try some JavaScript code that renders an image in a canvas. The idea would be to render a 128x128 image in a canvas that we would scale 2x or 4x.

Original cat.png image:

cat.png

HTML:

<canvas id="game"  
 width="128" height="128"
 style="width:256px;height:256px">
</canvas>  

JavaScript:

// get canvas context
var ctx = document.getElementById('game').getContext('2d');

// load image
var image = new Image();  
image.onload = function () {  
    // draw the image into the canvas
    ctx.drawImage(image, 0, 0);
}
image.src = 'cat.png';  

This would render a blurry picture:

Blurry cat

image-rendering to the rescue

Fortunately, browsers have started to implement the image-rendering property. This CSS property establishes how images are scaled up or down when rendered. According to the spec, it admits three values:

Note that the spec doesn't force any particular algorithm for crisp-edges, though in practice nearest-neighbor is used.

Bad news is that not all browsers have implemented this property yet, and not all support the same values! You can check browser support for image-rendering at caniuse, but as today (Feb 2016) the situation is the following:

For our purposes, since we are only scaling up, either pixelated and crisp-edgescould work. So the following CSS would work in all those browsers except for Edge:

canvas {  
  image-rendering: -moz-crisp-edges;
  image-rendering: -webkit-crisp-edges;
  image-rendering: pixelated;
}

TA-DAH!

Pixelated cat

Note that image-rendering works with both Canvas2D and WebGL contexts! Yay!

The caveat of this technique is that you need to use integer values for x and y when rendering images. For instance, this would blur the image regardless of image-rendering:

ctx.drawImage(image, 0.5, 0.5);  

Source and demo

You can see the online demo and get the code from the Github repository.

HTML:

<canvas id="game" width="128" height="128"></canvas>  

CSS:

/* 4x scale for canvas */
canvas {  
    width: 512px;
    height: 512px;
    image-rendering: -moz-crisp-edges;
    image-rendering: -webkit-crisp-edges;
    image-rendering: pixelated;
}

JavaScript:

// get canvas context
var ctx = document.getElementById('game').getContext('2d');

// load image
var image = new Image();  
image.onload = function () {  
    // draw the image into the canvas
    ctx.drawImage(image, 0, 0);
}
image.src = 'cat.png';