Compositing, Transparency, and Transformations - HTML5 and the Canvas - HTML5, 20 Lessons to Successful Web Development (2015)

HTML5, 20 Lessons to Successful Web Development (2015)

PART II HTML5 and the Canvas

LESSON 14 Compositing, Transparency, and Transformations

Images

To view the accompanying video for this lesson, please visit mhprofessional.com/nixonhtml5/.

In this final lesson on the HTML5 canvas, I show you how to use the remaining advanced graphical features not yet introduced, including compositing, transparency, and transformations, as well as how to save and restore context between operations.

Compositing and Transparency

Compositing is the method used for placing an element on the canvas, and there are 12 different available types, which have the effect of placing new elements in front of or behind existing ones, only on top of an existing element, never over an existing element, and so forth.

This is achieved using a property called globalCompositeOperation, providing it with the required value for the compositing you require.

The globalCompositeOperation Property

This property drastically affects the way new elements are added to the canvas. It supports 12 different values such as source-over, which is the default, and is applied like this:

context.globalCompositeOperation = ’source-over’

Following is a breakdown of all 12 types and the way they work. You should read them in conjunction with looking at Figure 14-1, which displays an example of each type:

source-over The default. The source image is copied over the destination image.

source-in Only parts of the source image that will appear within the destination are shown, and the destination image is removed. Any alpha transparency in the source image causes the destination under it to be removed.

source-out Only parts of the source image that do not appear within the destination are shown, and the destination image is removed. Any alpha transparency in the source image causes the destination under it to be removed.

source-atop The source image is displayed where it overlays the destination. The destination image is displayed where the destination image is opaque and the source image is transparent. Other regions are transparent.

destination-over The source image is drawn under the destination image.

destination-in The destination image displays where the source and destination image overlap, but not in any areas of source image transparency. The source image does not display.

destination-out Only those parts of the destination outside of the source image’s nontransparent sections are shown. The source image does not display.

destination-atop The source image displays where the destination is not displayed. Where the destination and source overlap, the destination image is displayed. Any transparency in the source image prevents that area of the destination image being shown.

lighter The sum of the source and destination is applied such that where they do not overlap they display as normal, but where they overlap, the sum of both images is shown, but lightened.

darker The sum of the source and destination is applied such that where they do not overlap they display as normal, but where they overlap, the sum of both images is shown, but darkened.

copy The source image is copied over the destination. The destination image is ignored.

xor Where the source and destination images do not overlap, they display as normal. Where they overlap their color values are exclusive ored.

Images

FIGURE 14-1 The 12 different compositing types

Images

Compositing can be really tricky to get right, so my advice is to use trial and error, and you may prefer to choose a compositing type based on the examples shown in Figure 14-1, rather than on the descriptions given here.

The globalAlpha Property

When drawing an element to the canvas, you can choose how much transparency to give it by providing a floating-point value to the globalAlpha property of between 0 and 1 inclusive, with 0 signifying no transparency, 1 being totally transparent, and (for example) 0.5 being half transparent, and so on, like this:

context.globalAlpha = 0.5

In Figure 14-2 a value of 0.5 has been applied to the globalAlpha property of the previous compositing example by adding the preceding line of code.

Images

FIGURE 14-2 These elements have been drawn using 50 percent transparency.

Using Transformations

There are four functions available for applying transformations to elements when drawing them to the HTML5 canvas. They are: scale(), rotate(), translate(), and transform(), and with them, if your element is not quite at the right angle, the correct size, or at the right perspective, you can tweak it until you get it just right.

The scale() Function

As you’ve already seen, there are various ways you can scale different objects, such as by specifying the width and height at which to draw them on the canvas. But you can also use the scale() function to apply a global scaling factor to all future elements drawn on the canvas, like the following, which sets the scaling of horizontal dimensions to 1.8 times, and vertical to 1.5 times the original size:

context.scale(1.8, 1.5)

Let’s look at this in practice with the following code, which draws a rectangle, increases the scaling factors, and then redraws the rectangle, as shown in Figure 14-3:

Images

Images

FIGURE 14-3 Two identical strokeRect() calls display differently due to scaling.

To help show the difference between the rectangles, the original (smaller) one is displayed in red, and the second (larger) one in blue. The arguments used in the parameters to the calls for drawing the rectangles are identical to each other, but the scale() call between the two ensures that the second rectangle is 1.8 times wider and 1.5 times deeper.

As well as modifying the dimensions, this scaling is applied to the origin of the rectangles (their top-left corners), and so the larger one is offset to a location 1.8 times the original rectangle’s horizontal offset of 20 pixels (to a new coordinate of 36), and 1.5 times the original vertical offset of 20 pixels (to a new coordinate of 30). Thus the new origin for the larger square is at location 36,30, rather than 20,20.

Here’s an example of using the scale() function more than once on the same rectangle (but changing the colors so you can see the difference), as shown in Figure 14-4:

Images

Images

Images

FIGURE 14-4 The same rectangle scaled up three times

Images

In this lesson I have selected colors that are reasonably different to each other when viewed in greyscale (as with the print version of this book). You can easily tell which grey represents which color, by comparing the example source code for each figure with the image.

The save() and restore() Functions

After drawing elements using a changed scaling, if you then wish to draw some using their original dimensions, you’ll need to issue the correct scale() call with negative values to get the scaling back down to a ratio of 1:1. But in the preceding example where the scaling ratio was increased upward three times, you would have to reduce the scaling ratio back three times by using values lower than 1 that achieve the same amount of reduction, like this (where 0.625 is the inverse of 1.6):

Images

Obviously, this is somewhat fiddly in that it involves calculating the inverse value, and also repeating a function call unnecessarily. Or, you could calculate the full inverse value for a single call to scale(), which happens to be 0.24414 (arrived at by multiplying 0.625 by itself three times), like this:

context.scale(0.24414, 0.24414)

But there’s a much simpler method for returning the scale to the default (along with many other properties too), and that’s to issue a call to save() before making any changes to properties or calling functions such as scale(), and then calling restore() afterward to reset all the properties to the way they were, like this code, which uses the functions to restore the scaling before drawing a final rectangle in orange on top of the very first red one, as shown in Figure 14-5:

Images

Images

Images

FIGURE 14-5 The scaling is saved, and then restored for a final rectangle.

As well as the scaling ratio, several other properties are saved and restored by these functions, as follows:

• fillStyle

• font

• globalAlpha

• globalCompositeOperation

• lineCap

• lineJoin

• lineWidth

• miterLimit

• shadowBlur

• shadowColor

• shadowOffsetX

• shadowOffsetY

• strokeStyle

• textAlign

• textBaseline

• scale() properties

• rotate() properties

• translate() properties

• transform() properties

This process of using save() and restore() is known as saving and restoring the drawing context. You can call the save() method as often as you like and each time the current context will be saved. For each call to save, you can issue a matching restore() call to return the context to the previous state. This enables you to save the state as you perform more steps and then “unwind” the state backward as each step completes.

The rotate() Function

Before applying an element to a canvas, you can also rotate it to just the right angle with a call to rotate(), like this:

context.rotate(Math.PI / 2)

The value passed to the function is in radians, each of which has a value of 180 ÷ Π (or about 57.3 degrees). There are Δ × 2 radians in a complete circle.

Radians are a sensible unit of measurement because Π ÷2 radians is a quarter circle Π radians is a half circle Π × 3 ÷2 radians is three-quarters of a circle, and Π × 2 radians is a full circle. I leave it to you to calculate other values you might require, but generally you will need to only use fractions and multiples of Π, whose value you can get by using the JavaScript property of Math.PI.

Therefore the previous example line will rotate all future elements placed on the canvas by a quarter turn (or 90 degrees). Here’s an example in action, as seen in Figure 14-6, in which a square has been rotated nine times:

Images

Images

FIGURE 14-6 A square is rotated nine times.

Because the rotate() function is called prior to each call to fillRect(), the rotation factor is increased for each one. The place around which the rotation takes place is the origin of the canvas, at location 0,0.

Working with Degrees

If you prefer working with degrees rather than radians, you can convert degrees to radians using the formula radians = degrees × 0.01745324 (because 0.01745324 is Π ÷180). For example, if you want a quick way to supply a value of 90 degrees to a function that requires radians, just pass the expression 90 * 0.01745324 as the argument. Or you can create a function to do this to JavaScript’s maximum level of accuracy, like this:

Images

Then (for example) just supply the expression degToRad(90) to the function.

The translate() Function

If you prefer to rotate an object around another axis such as its center, you need to also call the translate() function to move the origin of the canvas to a new location, around which the elements can rotate, like this line for example (which sets it to the coordinates 100,100):

context.translate(100, 100)

Let’s use this in a simple example similar to the previous one, but in which the squares will rotate around their centers, like this (as shown in Figure 14-7):

Images

Images

Images

FIGURE 14-7 Five squares are rotated and overlaid on each other.

If you look closely at the code in this example, you’ll see some negative values. This is because the origin of the canvas is no longer at location 0,0—it has been translated to the location 280,85. Therefore all function calls that address the canvas must now bear this new origin in mind and, since the squares are 120 pixels wide and deep, to place their centers over the new origin position, they must be located at a point relative to the origin of -60,-60.

Images

After translating the origin of the canvas, you may wish to restore it to 0,0 for future access of the canvas, or you can use the save() and restore() functions in appropriate places to automatically restore the context.

The transform() Function

Finally, in this part of the book on the HTML5 canvas, comes the most complicated and possibly the most powerful feature of all, the transform() function, with which you can stretch and transform elements in many different ways using matrixes.

Interestingly, the previous functions that manipulate elements all actually use matrixes to achieve their effects, and you can do the same yourself using the transform() method to either emulate or improve the built-in functions, or create your own new transforms. For example, you can emulate the scale() function by issuing the following command:

context.transform(1.6, 0, 0, 1.6, 0, 0)

This is equivalent to the following call because the first and fourth parameters represent the horizontal and vertical scaling respectively:

context.scale(1.6, 1.6)

Here’s some code using the function that first draws a 50×50-pixel square in green, then applies a scaling factor of 2 in the horizontal direction and 1.5 vertically and redraws the same square, as shown in Figure 14-8.

Images

Images

FIGURE 14-8 The original square is redrawn with scaling of 2 and 1.5.

This is the same as using the following command:

context.scale(2, 1.5)

The second and third parameters to the transform() function control shearing of the element. For example, to shear the original square downward (and not use any scaling), you could issue a command such as this:

context.transform(1, 0.7, 0, 1, 0, 0)

To shear to the right, you might use a command like this:

context.transform(1, 0, 0.7, 1, 0, 0)

And to shear in both directions, you might use a command such as this:

context.transform(1, 0.7, 0.7, 1, 0, 0)

In fact, here’s some example code that illustrates all three of these shears at once, as shown in Figure 14-9:

Images

Images

FIGURE 14-9 Three different shears have been applied to the square.

The first section of code creates the initial square. Then save() and restore() are used for the following sections to ensure the context is returned for reuse after each. In the first transform() call, the bottom-left shape is created. The top-right one is created next, and then the bottom-right shape is created out of a combined horizontal and vertical shear.

Images

You may use different values than 0.7 in these functions, and that includes negative values to shear in the other direction.

The final two arguments to the function are the horizontal and vertical offset to apply to the element when it is drawn on the canvas. These may be negative as well as positive values. Therefore the following three lines of code modify the transform() calls in the previous example to move the bottom-left shape to the left by 30 and down by 20 pixels, the top-right shape to the right by 20 and up by 30 pixels, and the bottom-right shape both down and to the right by 25 pixels, as shown in Figure 14-10:

Images

Images

FIGURE 14-10 The sheared shapes have been offset away from the original.

The setTransform() Function

As well as using the save() and restore() functions, you can reset the transform matrix at any time by issuing this call:

context.transform(1, 0, 0, 1, 0 0)

Then you are ready to issue a new transform of your choosing, like this, for example:

context.transform(1, 1.2, -1.2, 1, -20, 20)

However, rather than issuing two separate calls, you can make just one call to the setTransform() function instead. This has the effect of resetting the transform matrix and then applying the requested new transform. So, in place of the two preceding calls, for example, you can simply make the following call:

context.setTransform(1, 1.2, -1.2, 1, -20, 20)

Images

For more information about transformation matrixes, there’s a comprehensive article at: wikipedia.org/wiki/Transformation_matrix.

Summary

This concludes your introduction to the world of the HTML5 canvas. I hope you have found it enlightening and will use the functions it provides to create some weird and wonderful and compelling web pages. In the next lesson we’ll move on to seeing how a browser can identify your location, and what you can use this information for.

Self-Test Questions

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. With which property can you change the type of compositing used to draw to the canvas?

2. How can you set the transparency of future drawing operations?

3. Which function lets you change the scale for future drawing operations?

4. How can you easily resume previous settings after changing the scaling one or more times?

5. Which function lets you rotate the angle of future drawing operations?

6. How many radians are there in 360 degrees?

7. How can you move the origin of future drawing operations from its default location at 0,0?

8. What is the procedure to rotate an object around its center before drawing it to the canvas?

9. With which function can you scale, rotate, and skew, all at the same time?

10. Which function can you use to create absolute transformations (as opposed to relative ones from the current transform settings)?