HTML5, 20 Lessons to Successful Web Development (2015)
PART II HTML5 and the Canvas
LESSON 13 Manipulating Images, Shadows, and Pixels
To view the accompanying video for this lesson, please visit mhprofessional.com/nixonhtml5/.
In this lesson I’ll begin to look into the more advanced aspects of the HTML5 canvas, including drawing using images, adding shadows, and even directly manipulating the pixels (individual dots) of the canvas by their constituent primary colors of red, green, and blue, and their transparency too.
On top of all the other drawing functions available to you for manipulating the HTML5 canvas, you have also seen how you can import an image to use as a fill pattern. In fact, you can also use images to draw directly on the canvas.
The drawImage() Function
Using the drawImage() function, you can load in an image such as a jpg, gif, or png, and draw it directly on the canvas, like this code, which draws it with its top-left corner at location 10,10, as displayed in Figure 13-1:
FIGURE 13-1 An image is loaded in and placed on the canvas.
As you will recall from previous lessons, the onload event of the image object is attached to a function, whose code is executed only when the image has been fully loaded. If this were not the case, the image might not display.
In this instance only the first three parameters the function accepts have been used. These are the image to use and the horizontal and vertical location at which it should be displayed, so the image is displayed full size. However, it is also possible to resize the image before it is placed on the canvas by passing additional arguments, as with these two examples (with the resizing values highlighted in bold), the result of which is shown in Figure 13-2:
FIGURE 13-2 Two copies of the reduced image have been added.
Here the image has been reduced to just under a quarter of its original size by more than halving its dimensions, and then two copies have been placed alongside the original, one above the other. This version of the function call takes arguments in this order: the image to display, the horizontal and vertical location to display it, and the width and height to use for displaying it.
But, as they say in the infomercials, there’s more. Not only can you resize an image down, you can resize it up too. What’s more, you can also choose which area of the image to use when doing so, and you are therefore not restricted to using the entire image.
For example, the original html5.png image used in these examples has dimensions of 132 by 150 pixels. Using the following line of code, a rectangular subsection of this image has been selected, enlarged, and placed to the right of the two smaller images, as shown in Figure 13-3:
context.drawImage(image, 23, 26, 86, 98, 224, 10, 118, 150)
FIGURE 13-3 The image has been cropped and enlarged before use.
You must be wary when using this version of the function call because, rather than adding parameters to the existing ones, four new arguments are inserted between the image argument and the ones in the previous examples. For example, in this instance the first four numeric arguments of 23, 26, 86, and 98 are two pairs, the first of which is the location of the top-left corner of the part of the image to crop, and the second pair are the width and height for the crop.
These values result in clipping out the number 5 from the image. Then the remaining four values are the same as in the previous examples. They are the horizontal and vertical location at which to place the cropped image, and the width and height to use for displaying it.
Using the Canvas as a Source Image
You are not restricted to using only external images within a canvas because you can copy sections of the canvas itself and write them back to it, even after cropping and/or reducing or enlarging them.
For example, in the following code the left half of the canvas is captured, reduced in size, and copied to the top right of the canvas, as shown in Figure 13-4:
context.drawImage(canvas, 10, 10, 205, 150, 352, 10, 48, 35)
FIGURE 13-4 A portion of the image has been grabbed and reused.
This code works by referring to the canvas object in the first argument, rather than one for an external image. As you can see, the reduced images are looking quite jagged now, so it’s probably worth doing your own resizing in an editor for major changes like this, although, as I previously mentioned, as part of an animation or transition effect, this function works just great.
The HTML canvas supports the addition of a shadow to any element that you draw on it with the use of a group of four properties you can set to specify a vertical and horizontal offset, the shadow blur, and its color, as follows:
• shadowOffsetX The horizontal offset in pixels that the shadow should be shifted to the right by (or to the left if the value is negative).
• shadowOffsetY The vertical offset in pixels that the shadow should be shifted down by (or up if the value is negative).
• shadowBlur The number of pixels over which to blur the shadow’s outline.
• shadowColor The base color to use for the shadow. If a blur is in use, this color will blend with the background in the blurred area.
For example, in the following (somewhat longer than usual) example code, four elements are drawn on the canvas using slightly different shadow properties, as shown in Figure 13-5.
FIGURE 13-5 A variety of elements using different shadow properties
I used a variety of elements in this example as they illustrate a number of different points. So let’s look at them in turn, starting with the HTML5 logo.
The first thing you may notice with this image is that, unlike the other external images, this one doesn’t have a colored background but, instead, features a transparent one. Note how the shadow properties make use of this and draw the shadow only around the nontransparent areas. This image also has the largest shadow set, with vertical and horizontal offsets and a blur area of eight pixels, and the background color used for the shadow is black (#000).
The following three external images all have white backgrounds and so the shadow forms around the outside edges of each image. In turn these images use shadow offsets and blur areas of six, four, and two pixels respectively. At the same time the background color for the shadow uses increasingly lighter shades of gray (#333, #666, and #999).
The word “Hello” is drawn in blue, 38-point Arial text, has a vertical and horizontal shadow offset of three pixels, and a blur area of five pixels. See how the shadow lifts it from the background.
Finally, the rectangle uses a line width of three pixels and is drawn in red. The shadow offsets are both 0 but the blur area is six pixels, and black (#000) has been used for the shadow background color. This makes the shadow appear both inside and outside the rectangle.
The rectangle at the bottom right of Figure 13-5 illustrates how you can create inner shadows, which aren’t directly supported by the HTML5 canvas. Simply draw an object and specify a shadow that will appear inside that shape. Then draw another shape to cover over the outside shadows in the places where you don’t want them. Alternatively, it may be easier to simply create a clipped area using the clip() function, to prevent any drawing such as the shadows from being made outside of this area. If you do so, remember to reset the clipped area back to the entire canvas when you’re done.
We’ve now covered just about every conceivable drawing tool you could want for getting creative with the HTML5 canvas, but there’s one more trick remaining in the magician’s hat, and that’s direct pixel editing.
Using the getImageData(), putImageData(), and createImageData() functions, in conjunction with the data array, you can directly manipulate the canvas at pixel level, even down to the red, green, blue, and transparency constituents of a pixel.
The getImageData() Function
Let’s start by creating an image on a canvas (as shown in Figure 13-6) and then grabbing a portion of it with getImageData(), like this:
FIGURE 13-6 An image has been loaded, placed on the canvas, and copied.
This code uses the usual technique of loading in an image and then attaching a function to its onload event so that the code in the function is called only when the image is fully loaded. Within the function the image is then drawn on the canvas so that it takes up its left half. The final line then creates an object called imagedata by grabbing information from the canvas starting at location 0,0 and with a width of 205 and height of 170 pixels.
The data Array
The canvas element supports millions of colors (as well as transparency) for each pixel, and these are managed by allocating four locations per pixel for its red, green, blue, and alpha (transparency) components, each accepting a value of between 0 and 255. These locations are stored contiguously in the data array so that the pixel at the top left of the canvas (at location 0,0) can be accessed as follows:
Therefore the pixel one to the right of this at location 1,0 can be accessed like this:
Once the end of the first row of pixels is reached, the array continues with the next line. So, for a 205-pixel-wide section (such as the one grabbed in this example), there are 4 × 205 locations (or 820) on each row. Therefore the pixels at location 204,0 are accessed like this:
And the pixels at location 0,1 are accessed as follows:
The putImageData() Function
So let’s use the previous information to convert the image data grabbed from the left half of the canvas into grayscale, by averaging all the color values and setting them to the same value in each pixel. For example, if a pixel displays as yellow, which is a combination of red and green (color string #FFFF00), then we add up the FF, the FF, and the 00 to get a value of 1FE in hexadecimal (or 510 decimal, since FF hexadecimal is 255 in decimal, and twice that is 510).
Next, that value is divided by 3 (the number of different component colors) to return the value AA (or 170 in decimal), which is then assigned to all components of the pixel as the hexadecimal color #AAAAAA. Therefore the average brightness value of the color yellow (#FFFF00 in hexadecimal) is a gray tone with the value #AAAAAA, in which each color component has a value of AA in hexadecimal, or 170 in decimal.
Let’s look at some code to do this for the pixel at location 0,0:
The variable average now contains the average value of the red, green, and blue components of the pixel’s color. In this instance the fourth constituent of the pixel (which is the transparency) is being ignored.
Assuming that all the pixels in the imagedata object’s data array have been averaged in this manner, the updated image data can now be written back to the canvas like this, as shown in Figure 13-7 (although you won’t see much difference when viewing this page in monochrome):
context.putImageData(imagedata, 205, 0)
FIGURE 13-7 The left half has been copied, grayed, and pasted back to the right.
By simply adding an extra line after the three lines of code that calculate the average, you can change the transformation to create a negative grayscale image, by subtracting the value in average from the hexadecimal value FF (255 in decimal), like this:
average = 0xFF - average
What this does is change the value 1 to 254, 17 to 238, 255 to 0, and so forth, inverting the image, as shown in Figure 13-8.
FIGURE 13-8 The image data is averaged, inverted, and pasted back.
You can do other things too, such as changing the overall brightness of the image instead of inverting it, for example, by using this line instead, which adds hexadecimal 50 (80 in decimal) to each color component, as displayed as Figure 13-9.
average + = 0x50
FIGURE 13-9 The grayscaled image has been lightened.
As you can see, once you have access to this data in the data array of the imagedata object, you can perform all manner of transformations on it. For example, you can mirror or flip the image, use matrixes (an advanced programming concept) to sharpen or blur the image, and so on. In fact, with a little ingenuity, you can do many of the things a professional graphic editing program like Photoshop can do.
For security reasons, some browsers will not allow examples such as the preceding to directly modify data this way unless they are served from a web server. That means the examples may not work on some browsers if you simply load them in from a file system for testing. Instead ensure you load them using a server domain name, or http://localhost for a local server.
The createImageData() Function
You don’t have to create an imagedata object directly from a canvas. You can simply create a new one with blank data by calling the createImageData() function, like the following, which creates an object with a width of 320 and height of 240 pixels:
imagedata = createImageData(320, 240)
Alternatively, you can create a new object from an existing object, like this:
newimagedataobject = createImageData(imagedata)
It’s then up to you what you do with these objects to add pixel data to them, and how you paste them on the canvas or create other objects from them.
You now have all the basic HTML5 canvas skills in your toolkit, but there are a few more advanced features yet to come, all of which are covered in the final lesson of this part of the book. In it you’ll learn about compositing, transparency, and transformations, which you can use to create just the professional touch you need in your canvases.
Test how much you have learned in this lesson with these questions. If you don’t know an answer, go back and reread the relevant section until your knowledge is complete. You can find the answers in the appendix.
1. Which function is used to draw an image to the canvas?
2. How can you resize an image when it is drawn?
3. How can you ensure that an image is ready for use before drawing?
4. How can you easily copy one portion of a canvas to another?
5. Which four properties are used to add and modify shadows underneath drawn objects?
6. How can you grab all the pixel data from an image into a form that is editable?
7. Once image data has been grabbed from a canvas and placed in an object, what sub-object of that object contains the actual data?
8. What are the four components of each pixel?
9. Which function is used to write image data to the canvas?
10. How can you create a new object containing blank image data?