Photo Pad

J.M. Gustafson

September 2013

(For more resources related to this topic, see here.)

Time for action – creating Photo Pad

In the HTML file, we will add a toolbar with buttons for Load, Save, and Effects.

<body> <div id="app"> <header>Photo Pad </header> <div id="main"> <div id="toolbar"> <div class="dropdown-menu"> <button data-action="menu">Load</button> <ul id="load-menu" data-option="file-picker" class="file-picker menu"> <li data-value="file-picker"> <input type="file" /> </li> </ul> </div> <button data-action="save">Save</button> <div class="dropdown-menu"> <button data-action="menu">Effects</button> <ul data-option="applyEffect" class="menu"> <li data-value="invert">Invert</li> </ul> </div> </div> <canvas width="0" height="0"> Sorry, your browser doesn't support canvas. </canvas> </div> <footer>Click load to choose a file</footer> </div> </body>

The Load toolbar item has a drop-down menu, but instead of menu items it has a file input control in it where the user can select a file to load. The Effects item has a drop-down menu of effects. For now we just have one in there, Invert, but we will add more later.

For our CSS we will copy everything we had in canvasPad.css to photoPad.css, so that we get all of the same styling for the toolbar and menus. We will also use the Toolbar object in toolbar.js.

In our JavaScript file we will change the application object name to PhotoPadApp. We also need a couple of variables in PhotoPadApp. We will set the canvas variable to the <canvas> element, the context variable to the canvas's context, and define an $img variable to hold the image we will be showing. Here we initialize it to a new <img> element using jQuery:

function PhotoPadApp() { var version = "5.2", canvas = $("#main>canvas")[0], context = canvas.getContext("2d"), $img = $("<img>");

The first toolbar action we will implement is the Save button, since we already have that code from Canvas Pad. We check the action in toolbarButtonClicked() to see if it's "save", and if so we get the data URL and open it in a new browser window:

function toolbarButtonClicked(action) { switch (action) { case "save": var url = canvas.toDataURL();, "PhotoPadImage"); break; } }

What just happened?

We created the scaffolding for the Photo Pad application with toolbar items for Load, Save, and Effects. We implemented the save function the same as we did for Canvas Pad.

The next thing we'll implement is the Load drop-down menu since we need an image to manipulate. When the Load toolbar button is clicked, it will show the drop-down menu with a file input control in it that we defined previously. All of that we get for free because it's just another drop-down menu in our toolbar.

But before we can do that we need to learn about the HTML5 File API.

The File API

We may not be able to save files directly to the user's filesystem, but we can access files using HTML5's File API. The File API allows you to get information about, and load the contents of, files that the user selects. The user can select files using an input element with a type of file. The process for loading a file works in the following way:

  1. The user selects one or more files using a <input type="file"> element.
  2. We get the list of files from the input element's files property. The list is a FileList object containing File objects.
  3. You can enumerate over the file list and access the files just like you would an array.

    The File object contains three fields.

    • name: This is the filename. It doesn't include path information.
    • size: This is the size of the file in bytes.
    • type: This is the MIME type, if it can be determined.
  4. Use a FileReader object to read the file's data. The file is loaded asynchronously. After the file has been read, it will call the onload event handler. FileReader has a number of methods for reading files that take a File object and return the file contents.
    • readAsArrayBuffer(): This method reads the file contents into an ArrayBuffer object.
    • readAsBinaryString(): This method reads the file contents into a string as binary data.
    • readAsText(): This method reads the file contents into a string as text.
    • readAsDataURL(): This method reads the file contents into a data URL string. You can use this as the URL for loading an image.

Time for action – loading an image file

Let's add some code to the start() method of our application to check if the File API is available. You can determine if a browser supports the File API by checking if the File and FileReader objects exist:

this.start = function() { // code not shown... if (window.File && window.FileReader) { $("#load-menu input[type=file]").change(function(e) { onLoadFile($(this)); }); } else { loadImage("images/default.jpg"); } }

First we check if the File and FileReader objects are available in the window object. If so, we hook up a change event handler for the file input control to call the onLoadFile() method passing in the <input> element wrapped in a jQuery object. If the File API is not available we will just load a default image by calling loadImage(), which we will write later.

Let's implement the onLoadFile() event handler method:

function onLoadFile($input) { var file = $input[0].files[0]; if (file.type.match("image.*")) { var reader = new FileReader(); reader.onload = function() { loadImage(reader.result); }; reader.readAsDataURL(file); } else { alert("Not a valid image type: " + file.type); setStatus("Error loading image!"); } }

Here we get the file that was selected by looking at the file input's files array and taking the first one. Next we check the file type, which is a MIME type, to make sure it is an image. We are using the String object's regular expression match() method to check that it starts with "image".

If it is an image, we create a new instance of the FileReader object. Then we set the onload event handler to call the loadImage() method, passing in the FileReader object's result field, which contains the file's contents. Lastly, we call the FileReader object's readAsDataURL() method, passing in the File object to start loading the file asynchronously.

If it isn't an image file, we show an alert dialog box with an error message and show an error message in the footer by calling setStatus().

Once the file has been read, the loadImage() method will be called. Here we will use the data URL we got from the FileReader object's result field to draw the image into the canvas:

function loadImage(url) { setStatus("Loading image"); $img.attr("src", url); $img[0].onload = function() { // Here "this" is the image canvas.width = this.width; canvas.height = this.height; context.drawImage(this, 0, 0); setStatus("Choose an effect"); } $img[0].onerror = function() { setStatus("Error loading image!"); } }

First we set the src attribute for the image element to the data URL we got after the file was loaded. This will cause the image element to load that new image.

Next we define the onload event handler for the image, so that we are notified when the image is loaded. Note that when we are inside the onload event handler, this points to the <image> element. First we change the canvas' width and height to the image's width and height. Then we draw the image on the canvas using the context's drawImage() method. It takes the image to draw and the x and y coordinates of where to draw it. In this case we draw it at the top-left corner of the canvas (0, 0).

Lastly, we set an onerror event handler for the image. If an error occurs loading the image, we show an error message in the footer.

What just happened?

We learned how to use the File API to load an image file from the user's filesystem. After the image was loaded we resized the canvas to the size of the image and drew the image onto the canvas.

Adding effects

Now let's add some effects to the effects menu. The first one we will implement is a color inverter. It will take the image in the canvas and invert the colors so the image looks like an old film negative (remember those?). We can do this by iterating over every pixel in the image and inverting their colors.

You can get the pixels from the canvas using the context's getImageData() method. It gets the pixels for a rectangular area of the canvas. You pass it the position and size of the area:

var data = context.getImageData(0, 0, width, height);

The getImageData() method returns an array of bytes, four for each pixel, that represent each pixel's color. The first byte is the red amount, second is the green amount, third is the blue amount, and fourth is the alpha amount. All values are from 0 to 255. The total number of bytes in the array is 4 * width * height .

After you get the image data, you can access and change any value in the array that you want. Note that this will only change the image in memory. After changing image data, you can write it back to the canvas using the putImageData() method. This method takes parameters for the image data to draw and the position to draw it at.

context.putImageData(data, 0, 0);

Time for action – the imageEffects object

We will now create a new object called imageEffects to encapsulate all of the code for our image effects and put it in a new file, imageEffects.js. The imageEffects object will be a global static object defined using the revealing module pattern.

With the revealing module pattern, you define a set of functions in a private scope and then return an anonymous object that reveals which of those methods you want to be public. This works well for defining static objects.

Let's start by defining the imageEffects object and adding two helper functions which will remain private. They are used to get and set the image data for the entire canvas:

var imageEffects = function() { function getImageData(canvas) { return canvas.getContext("2d").getImageData(0, 0, canvas.width, canvas.height) } function putImageData(canvas, imageData) { canvas.getContext("2d").putImageData(imageData, 0, 0); }

The getImageData() method takes a canvas and returns the image data for the entire canvas. The putImageData() method takes a canvas and image data as parameters and puts the image data back into the canvas.

Let's implement our first effect; inverting the colors of an image. The invert() method takes the canvas as a parameter. Inverting colors is very simple. We just take each color channel for each pixel and subtract its value from the maximum color value of 255:

function invert(canvas) { var imageData = getImageData(canvas); var data =; for (var i = 0; i < data.length; i += 4) { data[i] = 255 - data[i]; //red data[i+1] = 255 - data[i+1]; //green data[i+2] = 255 - data[i+2]; //blue //data[i+3] is alpha } putImageData(canvas, imageData); }

First we get the image data for the canvas and then loop over the bytes, incrementing by four every time because there are four bytes for each pixel. Each color channel value is inverted and set back into the byte. The alpha amount is unchanged. Then we put the image data back onto the canvas.

Now let's finish the imageEffects object off. We need to return an anonymous object that defines all of the methods that we want to be public. The only one we have so far is the invert() method:

return { invert: invert }; }();

Notice that we have open and close parenthesis at the end of the function declaration. That immediately executes the function and assigns the anonymous object returned to the imageEffects variable. So now we have an imageEffects object with an invert() public method.

Now we need to hook up our Effects menu items to the imageEffects object. We can do this in the menuItemClicked() method of PhotoPadApp. Previously we gave our menu element a data-option custom attribute of "applyEffect". So we will check for that:

function menuItemClicked(option, value) { if (option == "applyEffect") { imageEffects[value](canvas); } }

We have given our Invert menu item element a data-value custom attribute set to "invert". We will use this to dynamically access the invert() method in the imageEffects object. We pass in the canvas object as a parameter. For "invert", this is equivalent to calling imageEffects.invert(canvas). We will implement all of our menu items in this way so that they automatically bind to a method in the imageEffects object.

What just happened?

We created an imageEffects object to hold all of our image effects algorithms. We implemented an effect to invert the colors of an image. We hooked up the Effects menu using custom data attributes to bind the menu items to methods in the imageEffects object.

Now let's open up our application in the browser and give it a try. After loading an image, choose Invert from the Effects menu and you should see the inverted image:

Time for action – black and white

Ok, the invert() method was pretty simple. Let's try something a little more challenging, but not much more. We will implement an effect that changes a color image to black and white. Let's implement a toBlackAnWhite() method in the imageEffects object:

function toBlackAndWhite(canvas) { var imageData = getImageData(canvas); var data =; for (var i = 0; i < data.length; i += 4) { var grayscale = (data[i] * 0.3) + (data[i + 1] * .59) + (data[i + 2] * .11); data[i] = grayscale; data[i+1] = grayscale; data[i+2] = grayscale; } putImageData(canvas, imageData); }

For each pixel, we compute the gray scale value by taking a percentage of each color channel and adding them together; 30 percent red, 59 percent green, and 11 percent blue. Then we set each color channel to that gray scale value.

Now let's add a menu item for black and white to the Effects menu. The data-value attribute is set to the method we created previously, toBlackAndWhite:

<li data-value=" toBlackAndWhite ">B&amp;W</li>

What just happened?

We created a filter to change each pixel to its gray scale value and set it back into the image data. Now we can convert a color image to black and white:

Time for action – sepia

Let's implement another simple effect. This time we will convert the image to sepia, which gives it an old-timey picture look. Sepia is very similar to black and white except a little warmer. First let's add the menu item for it and set the data-value attribute to toSepia:

<li data-value="toSpeia">Sepia</li>

Now let's add a toSepia() method to the imageEffects object.:

function toSepia(canvas, depth, intensity) { depth = depth || 20; intensity = intensity || 10; var imageData = getImageData(canvas); var data =; for (var i = 0; i < data.length; i += 4) { var grayscale = (data[i] * 0.3) + (data[i + 1] * .59) + (data[i + 2] * .11); data[i] = Math.min(255, grayscale + (depth * 2)); data[i+1] = Math.min(255, grayscale + depth); data[i+2] = Math.max(0, grayscale - intensity); } putImageData(canvas, imageData); }

Although toSepia() has three parameters, we will only pass in one parameter, the canvas, so we can use our default Effects menu handling code, and set the rest to default values. The first two lines of the method set default values for the depth and intensity parameters. depth is used to adjust the red and green channels and intensity is used to adjust the blue channel to give more fine-tuning over the final result.

To convert a pixel to its sepia tone, we first get the gray scale value the same way as we did for black and white. Then instead of just setting the gray scale for all color channels, we adjust those values based on the channel. Red is boosted the most, which accounts for sepia's reddish tone. Green is also boosted, half as much as red. Blue is reduced by the intensity amount. We use the Math.max() and min() function to make sure we don't set the value out of range.

What just happened?

We created a filter to convert color images to sepia by finding the gray scale and then adjusting the color channels independently by a fixed amount that can be passed in as parameters or defaulted:

Have a go hero

Try using different percentages of red, green, and blue when computing the gray scale value to see what effect it has on the image. Try passing in different values for depth and intensity to see what effect it has on the sepia tone.


Just follows these steps and enjoy the application created.

Resources for Article :

Further resources on this subject:

You've been reading and excerpt of:

HTML5 Web Application Development By Example : Beginner's guide

Explore Title
comments powered by Disqus