This is a Canvas programming introductory series:
- Canvas Part 1: Introduction
- Canvas Part 2: Transforms and Transparency
- Canvas Part 3: Shadows and Gradient Fill
- Canvas Part 4: Inserting Images and Text
- Canvas Part 5: Interactive Shapes
- Canvas Part 6: Animation
Table of Contents
- Inserting Images into Canvas
- Cropping, Cutting and Resizing an Image
- Video Frames
- Pasting Text Into the Canvas
Any desired graphic can be drawn perfectly on the Canvas, from a set of lines or simple geometric shapes to a portrait with all the smallest details. But, as the level of graphics complexity increases, so does the level of code complexity. It is highly unlikely that one could write all the code required to create a high-quality image on one’s own.
Luckily, developers have other options besides writing all the code themselves. The possibilities of the drawing context are not limited to drawing simple straight and curved lines, there are also methods for inserting ready-made images, text, patterns, and even frames for displaying videos.
Inserting Images into Canvas
You probably have seen web pages with satellite maps of a particular region of the planet. These maps are created from images of individual areas of the Earth’s surface, which these pages download from a satellite and combine into one image. This is an example of how you can take multiple images and merge them into one in the required way.
Canvas supports regular images through the drawImage()
method of drawing text. To insert an image into the Canvas, the drawImage()
method is passed the image object and the Canvas coordinates at which this image should be inserted as parameters:
canvas = document.getElementById('my-canvas');
context = canvas.getContext('2d');
context.drawImage(img, 10, 10);
But in order to pass an image object, it must first be received. In HTML5, there are three different ways to get an image object. The first approach is to create it yourself pixel by pixel using the createImageData(
method. But this approach is very time-consuming and slow.
The second approach is to use the <img>
element already present in the markup. For example, if we have the following markup:
<img id="photo" src="photo.jpg">
Then the image can be inserted into the Canvas with this code:
let img = document.getElementById('photo');
context.drawImage(img, 10, 10);
I like this method the best because I can preload the images by having them in the DOM with a CSS property of display: none
.
Finally, you can create an image object and load an image from a separate file. The disadvantage of this approach is that the image cannot be used with the drawImage()
method until it has fully loaded. To avoid problems, wait until the image’s onLoad
event fires before attempting to perform any operations on it.
This article is part of the JavaScript Canvas series where I post experiments, JavaScript generative art, visualizations, Canvas animations, code snippets and how-to’s.
To understand this process, consider an example. Let’s say we want to display photo.jpg
on the Canvas. Theoretically, this can be done with the following sequence of operations:
let img = new Image();
img.src = 'photo.jpg';
context.drawImage(img, 10, 10);
The problem here is that setting the src
attribute starts loading the image, but the code continues to execute without waiting for the loading to complete. The correct code would be:
let img = new Image();
img.onload = () => {
context.drawImage(img, 10, 10);
};
img.src = 'photo.jpg';
This approach may seem counter-intuitive, since the order in which operations are specified in the code does not match the order in which they are executed. In this example, the call to the context.drawImage()
method happens last, shortly after setting the img.src
property.
Images have a wide range of uses. They can be used to embellish line art or as an alternative to drawing images yourself. Images can be used for different objects and characters by placing them appropriately on the Canvas. Images can also be used to fill in lines to give them a textured look.
Distorting Drawings in the Canvas Element
If you find that your image is stretched, shrunk, or otherwise distorted for some reason, the most likely cause is setting the Canvas size via CSS. Correctly setting the size of the Canvas is necessary by specifying its height and width in the height
and width
attributes of the <canvas>
element in the page markup. It may seem that these values do not need to be set in the markup using this form of tag:
<canvas id="my-canvas"></canvas>
and set them in the stylesheet rule like this:
canvas {
width: 500px;
height: 300px;
}
But this approach won’t work. The problem is that the CSS width
and height
properties are different from the <canvas>
element’s properties of the same name. If you don’t specify Canvas dimensions in the markup, the default Canvas size is set to 300×150 pixels. The CSS will then stretch or compress the Canvas to fit the dimensions specified in it, resizing the entire contents of the Canvas accordingly. As a result, the images placed on the Canvas will be distorted, which, of course, will not make them attractive.
To avoid this problem, you should always specify the size of the Canvas in your markup using the height
and width
attributes of the <canvas>
element. And if you want to change the size of the Canvas based on some other criteria, the values of these attributes are changed in the markup using JavaScript.
Cropping, Cutting and Resizing an Image
The drawImage()
function accepts several optional parameters that allow you to manipulate the image on the Canvas. For example, an image can be resized by specifying the width
and height
parameters as follows:
context.drawImage(img, 10, 10, 80, 150);
In this case, the method places the image in a frame of 80×150 pixels, the upper left corner of which is located at the Canvas point with coordinates (10, 10). If the original size of the image was 160×300 pixels, this operation reduces the size of the image by half in both directions, resulting in a total size of the final image only a quarter of the size of the original.
If you want to insert only part of the image into the Canvas, you must pass four parameters to the drawImage()
method at the beginning of the parameter list. These options determine the position and size of the part of the image to be cut:
context.drawImage(img, source_x, source_y, source_width, source_height, x, y, width, height);
The last four parameters in this code are the same as in the previous example – they define the position and size of the image on the Canvas. Let’s say we want to paste only the top half of the image into the Canvas, with an original size of 225×300 pixels. To do this, from the upper left corner of the image (point (0, 0)) we cut off a part of the image with a width of 225 and a height of 150 pixels, which we then insert into the Canvas at the starting point with coordinates (50, 25). All this is done in one line of code:
context.drawImage(img, 0, 0, 225, 150, 50, 25, 225, 150);
The capabilities of the drawImage()
method are not enough if the image inserted into the Canvas needs to be rotated or bevelled at a certain angle. But these and other manipulations with the image on the canvas can be done using transformations.
Video Frames
The first parameter of the drawImage()
method is the image to insert into the Canvas. As we just saw, this image can be a generated image object or an <img>
element located somewhere else in the markup.
But that’s not all that can be inserted into the Canvas through this method. Instead of an image, you can specify a <canvas>
element (but not the one on which the actual drawing is performed). In the same way, you can insert a <video>
element in which the video is playing:
let video = document.getElementById('video-player');
context.drawImage(video, 0, 0, video.clientWidth, video.clientWidth);
When this code is executed, one frame of the video playing at the time of code execution is captured, which is then inserted into the Canvas.
This feature opens the door to some interesting effects. For example, you can use a timer to capture frames of the video you’re playing in sequence and paste them onto the Canvas. If you do this fast enough, the sequence of frames inserted into the Canvas will create the effect of a video playing.
But to make sure it’s not just another video player, the captured frames can be modified before being pasted onto the Canvas. For example, the inserted frame can be enlarged or reduced, or a Photoshop-style effect can be applied by modifying it at the pixel level.
Pasting Text Into the Canvas
Another type of data that we don’t want to be made up of separate lines and curves is text.
Before you insert any text into the Canvas, you need to specify the font for it by setting the value of the font
property of the context. This value is specified via a string with the same syntax as for the CSS font generic property. At a minimum, you need to specify the font size in pixels and its name. If you are not sure about the support of a particular font by browsers, you can specify a list of fonts:
context.font = '20px Arial, sans-serif';
You can also set it to bold or italic by specifying the appropriate options at the beginning of the line:
context.font = 'bold 20px Arial, sans-serif';
Most importantly, thanks to CSS, fancy inline fonts can be used. To do this, you just need to first register the name of the font in the style sheet. After the font is set, the text in the Canvas is entered using the fillText()
method. For example, the following code inserts a line of text into the Canvas whose upper left corner is at (10, 10):
context.textBaseline = 'top';
context.font = 'bold 20px Arial';
context.fillStyle = 'black';
context.fillText('This is some example text', 10, 10);
Text can be inserted anywhere on the Canvas, but only one line at a time. To insert multiple lines, you need to make the appropriate number of calls to the fillText()
method.
Instead of the fillText()
method, you can use another method for entering text – strokeText()
. This method introduces the outlines of the letters of the text; the color and thickness of the outlines are determined by the values of the strokeStyle
and lineWidth
properties of the context. The following shows the use of this method:
context.font = 'bold 40px Arial, sans-serif';
context.lineWidth = 1;
context.strokeStyle = 'red';
context.strokeText('This is another example text', 20, 50);
As already noted, the strokeText()
method only enters the outlines of the letters. If you want to create text of one color and its stroke of another, you can use the fillText()
method first, and then the strokeText()
method.
The operation of drawing text to the Canvas is much slower than drawing lines and even images. Speed is not important when creating a static image (such as a chart), but can be a factor when creating an interactive application or animation. You can optimize text input by first storing the desired text in an image file and then displaying it on the Canvas using the drawImage()
method.