Retro, crisp pixel art in HTML 5 games
| Tags: front-end game development
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.
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:
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:
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:
auto
: default value, this introduces anti-aliasing.crisp-edges
: uses an intepolation algorithm that preserves hard edges and doesn't introduce anti-aliasing.pixelated
: uses the nearest-neighbor algorithm when scaling up, andauto
when scaling down.
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:
- Edge: LOLNO
- Firefox:
crisp-edges
only (with-moz
prefix) - Chrome / Chrome for Android:
pixelated
only - Safari / iOS Safari:
crisp-edges
only (with-webkit
prefix) - Android browser:
pixelated
only
For our purposes, since we are only scaling up, either pixelated
and crisp-edges
could 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!
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';