Today we look into implementing a real classic effect which gained popularity by it’s usage in Nintendo’s famous Super Mario World on the SNES.
It’s called pixelation – and is surely more known by it’s colloquial term mosaic. As the name implies it transitions an image from a mosaic-like bunch of huge pixels which decrease in size until the source image is revealed.
Here’s an impression as an animated gif:
The programming language for today will be JavaScript. You will be surprised – though that surely wasn’t the original intention – the canvas object itself offers a method to fake this effect.
We can abuse the .drawImage() method to draw a shrinked version of the target image at the target image’s dimensions!
So let’s warm up! Grab the following image and save it on your harddrive as marioStill.png.
Now use this html template and save it as mario.html.
1 2 3 4 5 6 7 |
<html> <body> </body> <script type="text/javascript"> // here goes our code </script> </html> |
First we need to load the actual image file. This is done by creating a new Image element.
1 2 3 4 |
var percentage = 0.05; var image=new Image(); image.onload=pixelate; image.src="marioStill.png"; |
After loading finished it will invoke the pixelate function we’ll work on next.
1 2 3 4 |
function pixelate(e) { } |
The parameter e of this function is a reference to the element which fired the load event – our image. To get it’s content we need to call e.target
Let’s draw the image to a canvas element the size of the image.
1 2 3 4 5 6 7 |
var canvas = document.createElement("canvas"); canvas.id = "canvas"; canvas.width=e.target.width; canvas.height=e.target.height; var context = canvas.getContext("2d"); context.drawImage(e.target, 0, 0); document.body.appendChild(canvas); |
Nothing too fancy happened yet. We created a canvas element, made it the size of the image, draw the image onto and finally appended the canvas to the DOM.
If you run this inside the browser it’s just like we simply loaded the image directly.
We need to draw a scaled down version of the image to an invisible buffer canvas and ultimately draw this onto the ‘real’ canas at the original dimensions.
For now remove the line
1 |
context.drawImage(e.target, 0, 0); |
add the following lines right after document.body.appendChild(canvas);
1 2 3 4 5 |
var buffer = document.createElement("canvas"); buffer.id = "buffer"; buffer.style.display = "none"; document.body.appendChild(buffer); draw(e.target); |
and insert the following new function:
1 2 3 4 5 6 7 8 |
function draw(canv) { var buffer = document.getElementById("buffer"); var canvas = document.getElementById("canvas"); buffer.width = canv.width * percentage; buffer.height = canv.height * percentage; buffer.getContext("2d").drawImage(canv, 0, 0, buffer.width, buffer.height); canvas.getContext("2d").drawImage(buffer, 0, 0, canvas.width, canvas.height); } |
Preview this in your browser and you’ll see something like this:
Not really looking pixelated in any way, huh? In fact that looks more like a blurry mess! Well this is caused by the browser trying to smooth the image.
We need to disable these filters!
Right below var context = canvas.getContext(“2d”); add these lines
1 2 3 4 |
context.webkitImageSmoothingEnabled = false; context.mozImageSmoothingEnabled = false; context.msImageSmoothingEnabled = false; context.imageSmoothingEnabled = false; |
and preview it in the browser again:
Muuuch better! We got some decent blocks now but what’s actually determining the size of those? Yeah – it’s: var percentage = 0.05; we’ve initialized at the beginning. This makes the little invisible buffer representation of the image a percentage of the original image.
Now imagine – if we dynamically change this number from 0.05 to 1 in small steps and call the draw function periodically using setTimeout() we’re done!
1 2 3 4 5 6 7 8 9 10 11 |
if (percentage + 0.02 <= 1) { percentage += 0.02; setTimeout(draw, 40, canv); } else { percentage = 0.05; buffer.width = canv.width; buffer.height = canv.height; buffer.getContext("2d").drawImage(canv, 0, 0, buffer.width, buffer.height); canvas.getContext("2d").drawImage(buffer, 0, 0, canvas.width, canvas.height); setTimeout(draw, 1000, canv); } |
Here’s the final output: