7 min read

(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(); window.open(url, "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.

LEAVE A REPLY

Please enter your comment!
Please enter your name here