Last year I was in between jobs, and decided to learn HTML5 since it was gaining popularity within the developer community due its cross-platform capability and the rise of mobile apps. I am a business applications specialist by profession, and a game programmer hobbyist at heart, so I decided to have some fun and make a Zombies game in HTML5 to learn the ropes of this new technology. Recently I turned my learning experience into a lecture series how to make a Zombies game.

I’m terrible at graphics so I set out on a public domain hunt for Zombie images suitable for a game. Instead of finding a nice sprite sheet with all the animation sequences laid out by direction in one image I found 5 animation sequences of a “red zombie” as individual PNG images. Each animation sequence, eg. walking, attacking etc. had a number of frames broken into individual images. All together there were 448 images!

When I opened an image in a Paint program I noticed that its transparency was not set, neither was the shadow. While I’m sure there are applications that can process these images in bulk mode, I decided that this would be a good opportunity to learn about the HTML5 Canvas and how to manipulate pixels of an image.

The HTML5 Canvas element is used to draw graphics on the fly, on a web page. First I needed to construct a simple web page with a canvas so that I can draw an image on it.

<html> <head> <title>HTML5 Test</title> </head> <body> <div>HTML5 Test!</div> <canvas id="mainScreen" width="1024" height="640"> Canvas not supported! </canvas> </body> </html>

The code above includes a canvas tag, you can test it out, and if you see “Canvas not supported!” that means your browser does not support the HTML5 canvas, otherwise you’re fine.

Now lets write some code to display the Zombie image as is. This involves newing up a new Image object and setting its src attribute to the URL where the image is located. I have uploaded a Zombie image for you to test on amazon here.

<html> <head> <title>HTML5 Test</title> <script type="text/javascript"> var g_mainScreen = null; window.onload = onReady; function onReady() { g_mainScreen = document.getElementById("mainScreen").getContext("2d"); var img = new Image(); img.onload = function() { g_mainScreen.drawImage(this, 0,0); }; img.src = "https://s3.amazonaws.com/ken-z/attack.png"; } </script> </head> <body> <div>HTML5 Test!</div> <canvas id="mainScreen" width="1024" height="640"> Canvas not supported! </canvas> </body> </html>

Line 8 registers a handler for onload so that onReady is invoked once the document loads. We get a handle to our canvas (line 12) and grab its 2d context so we can use its drawImage function. We new up an image dynamically (line 14), and register an anonymous function to handle it once it’s loaded (line 16). We set the image’s src property to the location of the Zombie image (line 21), this kicks off the loading process. Once the image is loaded we simply draw it on the canvas (line 18) at location x = 0, and y = 0, which is the top left corner of the canvas.

If you run this code in your browser, it will display the Zombie image in the top left corner. The problem is the background color is not transparent, and the shadow should really be displayed at 50% opacity.

The Zombie image is made of thousands of pixels, and each pixel is composed of Red, Green, Blue setting in addition to Alpha. The Alpha is what controls the opacity, we need to some how set it to zero for the background color so that it is completely transparent, and to 128 (50%) for the shadow.

To do this we need to scan all the pixels in the image and if we notice a background pixel we change its Alpha to zero, and we do the same for the shadow setting its Alpha to 128 which is 50% of 256 (given the range of 0 to 255).

The plan is to use a work canvas which is off screen, where we can draw the image, examine and manipulate its pixels, and then make a new image out of it, so we can draw it on our main screen canvas.

<html> <head> <title>HTML5 Test</title> <script type="text/javascript"> var g_mainScreen = null; window.onload = onReady; function onReady() { g_mainScreen = document.getElementById("mainScreen").getContext("2d"); var img = new Image(); img.onload = function() { processImage(this); }; img.src = "https://s3.amazonaws.com/ken-z/attack.png"; } function processImage(original) { var workCanvas = document.createElement("canvas"); workCanvas.width = original.width; workCanvas.height = original.height; var wctx = workCanvas.getContext("2d"); wctx.drawImage(original, 0, 0); var imageData = wctx.getImageData(0, 0, original.width, original.height); var length = imageData.data.length; for (var i = 3; i < length; i += 4) { // if transaparent color then set alpha to 0 if (imageData.data[i - 3] == 106 && imageData.data[i - 2] == 76 && imageData.data[i - 1] == 48) { imageData.data[i] = 0; } // if shadow color then set alpha to 128 (50%) else if (imageData.data[i - 3] == 39 && imageData.data[i - 2] == 27 && imageData.data[i - 1] == 17) { imageData.data[i] = 128; } } wctx.putImageData(imageData, 0, 0); img = new Image(); img.onload = function() { g_mainScreen.drawImage(this, 0, 0); }; img.src = workCanvas.toDataURL(); } </script> </head> <body> <div>HTML5 Test!</div> <canvas id="mainScreen" width="1024" height="640"> Canvas not supported! </canvas> </body> </html>

If you run this html page in your browser you’ll probably get a cross-origin violation error, this is because the html page is saved to your local file system, and your browser considers that as the origin of the page. When the code starts to inspect the image data via line 33 the browser’s security mechanism kicks in and notices that you’re trying to manipulate the image data which came from a different origin. So to solve this issue you can either put the image and html page on your own server, or try out the html page I uploaded to amazon here.

Instead of displaying the original image once it’s loaded, we now call processImage (line 18). In this function we get the image’s data imageData (line 33) by drawing it on a temporary work canvas (line 30). Now that we have the imageData we simply loop through all the pixels (each pixel is 4 bytes, red + green + blue + alpha = 4) and compare the color to the background (106, 76, 48) or shadow (39, 27, 17) and we set the alpha respectively to 0 or 128. This is done through the loop from line 36 to 53.

The second step is to put it back to our work canvas using putImageData (line 55), then we new up a new Image (line 57) and load it by setting its src to a URL using workCanvas.toDataURL, which will convert our manipulated image to a URL (line 64).

Once the manipulated image is loaded, we simply draw it on our mainScreen canvas (line 61).

With this technique you can process images however you like, by inspecting its pixels as raw data composed of 4 bytes per pixel.

If you like to learn more about image processing, take a look at lecture 5 and 8 of the Zombies game course.