Lesson 13 - 2D canvas context in JavaScript

JavaScript Basics 2D canvas context in JavaScript

In the previous lesson, Images and drawing on canvas using JavaScript, we focused on images and learned to work with <canvas>. In today's JavaScript tutorial, we're going to look at the <canvas> in more detail. We'll explore its possibilities step by step.

Clearing rectangles

In the previous lesson, we were drawing rectangles. We introduced the fillRect() and strokeRect() methods. There's also the clearRect() method which erases the contents of a canvas on the given area. The parameters are the same as of the two methods mentioned above. But what's the difference between clearRect() and fillRect() when we set the fill color to white? The canvas is not white by default, but transparent. So if we clear the canvas using fillRect(), we'll see a white area, but we will not see the canvas background and what's under the canvas.

Rectangles in paths

The last way to draw a rectangle is to put it in a path. Although it might seem unnecessary at first glance as we have the fillRect() and strokeRect() methods available, it's an easy way to both fill a rectangle and draw its border at the same time. We'll simply draw the rectangle as a path, draw its contents and then draw the border.

context.translate(30, 0);
context.beginPath();
context.rect(10, 10, 60, 60);
context.closePath();
context.fillStyle = "red";
context.strokeStyle = "blue";
context.stroke();
context.fill();

The result:

Bordered rectangle
localhost

Context transformation

A very important thing when processing an image is transformation. From time to time, it's useful when the zero position is not zero. Perhaps the most basic example is when we want to render a specific 10px wide frame around the canvas which we wouldn't be able to style just in CSS. Of course, we can draw everything to the x + 10 and y + 10 positions, but it'd be much easier to use canvas transformations. This gets more interesting with rotations and sometimes it's the only way to achieve the expected result.

Storing the context

Before performing any manipulation on the context it's useful to store it first. We could return it to the x - 10 and y - 10 position later, but that'd be unnecessary and we'd have to remember the values. Therefore, the context has the save() and restore() methods, neither requires any parameters. The save() method saves the current transformations, and the restore() method restores them.

As an example, we're going to draw a red frame around an image. Although this is easier to implement with CSS, and we'll try it in JavaScript.

As first, we'll store the canvas, its context and the image, you can do this yourself. We'll set the context's fill color to red, for example, and fill the entire canvas with it. Then we'll save the context.

context.fillStyle = "#f00";
context.fillRect(0, 0, 510, 340);

context.save();

Context translation

We move the context by the translate() method which accepts the x and` y` coordinates as its parameters. The new zero point will be at these coordinates. We'll move the canvas by 10px on both the x and y axes.

context.translate(10, 10);

Now, we'll draw the image at the [0;0] position and restore the canvas context.

context.drawImage(image, 0, 0);
context.restore();

When we open the application now, we can notice that although we have drawn the image at [0;0], it's been drawn at [10;10]. That happened thanks to the translation.

Context translation
localhost

Do not forget to return the context to the original state if you ever need to work with it again.

Context scaling

We can zoom the context in and out. There's the scale() method to do so, which accepts multiples of the current scale for the X and Y axes. For zooming out, we enter a decimal number less than 1.

Context scaling
localhost

Rotation

The last operation is rotation. We often need to turn something a bit. E.g. when we need to draw a diamond, there's nothing easier than to rotate the context. Of course, we could recalculate the points, but it'd be unnecessarily complicated. We rotate the context using the rotate() method, which accepts the angle in radians as a parameter.

context.save();
context.translate(100, 100);
context.rotate(45 * Math.PI / 180);
context.strokeRect(0, 0, 50, 50);
context.restore();

The result:

Context rotation
localhost

Shadow

All the operations that draw something on canvas can be done with a shadow. We use the shadowColor property to set the shadow color. We shift the shadow by shadowOffsetX and shadowOffsetY and specify how blurry it should be by shadowBlur.

context.shadowColor = "red";
context.shadowOffsetX = 6;
context.shadowOffsetY = 3;
context.shadowBlur = 10;

The result:

Context rotation
localhost

Color gradients

Occasionally, the image can be "improved" by color gradients. There are 2 types of gradients - linear and radial. Both can be set for both filling and stroking.

Linear gradients

Linear gradients are simple, the colors are changing gradually as we specify it. We create it using the createLinearGradient() method on the context object and we pass 4 parameters to it. These are the x and y coordinates for both the beginning and the end of the gradient. The newly created object has the addStopColor() method, which adds the gradient's color. This method accepts the "stop" position in the range of 0 - 1 as the first parameter and the given color as the second one.

let gradient = context.createLinearGradient(0, 0, 100, 0);
gradient.addColorStop(0, "yellow");
gradient.addColorStop(0.2, "orange");
gradient.addColorStop(0.4, "pink");
gradient.addColorStop(0.6, "red");
gradient.addColorStop(0.8, "green");
gradient.addColorStop(1, "blue");

context.fillStyle = gradient;
context.fillRect(0, 0, 100, 100);

context.font = "19px Calibri"
context.fillText("ICT.social", 0, 115);

The result:

Linear gradient
localhost

Radial gradients

The radial or circular gradients are used in exactly the same way, they only create a different effect. The method that creates it is createRadialGradient() and it accepts 6 parameters. The x and y of the starting point, the radius and all this again for the end point. The values are quite confusing. The first x and` y` position actually defines the center, the first radius is the radius at which the gradient begins. If it's zero, the gradient will start straight from the center. If there's any other value, the first color will be in the center and it'll start to change not before the specified radius. If you want to draw a regular circle, the fourth and fifth parameters will copy the first and second ones. The sixth parameter specifies at which radius the gradient stops, the rest will be only of the last color. We set the colors in the same way.

Radial gradient
localhost

Pattern fill

One of the other fill options (available for stroking as well) is the pattern fill. We create it by creating an image pattern and setting it to the fillStyle (or strokeStyle). We create the pattern using the createPattern() method, where we pass the image as the first parameter and the way to repeat as the second one. To repeat the pattern on both the X and` Y` axes, we can leave an empty string.

let pattern = context.createPattern(image, "");
context.fillStyle = pattern;

The result:

Pattern fill
localhost

We can also use another <canvas> element as a pattern.

Working with pixels

Until now, pixels have been managed by JavaScript, but sometimes, it's useful for us to work with individual pixels.

Image data

The 2D canvas context provides the ImageData object. We get it using the getImageData() method, to which we pass the coordinates of the top left corner and the size of the area as parameters. This object will have the data array in which all the RGBA pixel values of the selected area are available. The pixels are sorted sequentially, row by row, left to right.

Accessing canvas pixels in JavaScript

Let's create an application that inverts colors of an image. We'll store our canvas, context, and the image as we always do. On the canvas, we'll draw the image and get the image data from it.

context.drawImage(img, 0, 0);
let imageData = context.getImageData(0, 0, canvas.width, canvas.height);

Now we'll iterate through all the colors. Each pixel in the array is represented by 4 values, since there's a value for each of its components (R, G, B, A). Therefore, the loop won't have the standard ++ increment, but we'll increase the control variable by 4.

for (let i = 0; i < imageData.data.length; i += 4) {

In the loop, we'll set a new color to the i (R), i + 1 (G), and i + 2 (B) values. In our case, we'll subtract the color value from 255, which is the maximum value. We invert the values by it and achieve the negative effect. We'll not alter the alpha channel.

imageData.data[i + 0] = 255 - imageData.data[i + 0];
imageData.data[i + 1] = 255 - imageData.data[i + 1];
imageData.data[i + 2] = 255 - imageData.data[i + 2];

Finally, we'll put the modified ImageData into the image at the [0;0] position using the putImageData() method.

context.putImageData(imageData, 0, 0);

The result:

Image data
localhost

Now you should be able to control <canvas> via JavaScript. I recommend trying out the exercises. In the next lesson, Timers and animations in JavaScript, we'll look at timers and animations.


 

Download

Downloaded 0x (231.72 kB)
Application includes source codes in language JavaScript

 

 

Article has been written for you by Michal Zurek
Avatar
Do you like this article?
1 votes
Activities (3)

 

 

Comments

To maintain the quality of discussion, we only allow registered members to comment. Sign in. If you're new, Sign up, it's free.

No one has commented yet - be the first!