Manipulating Images Using the Canvas API

How to crop and resize images using HTML’s canvas

Lucas Andión
Trabe
6 min readMay 18, 2020

--

Photo by Igor Miske on Unsplash

At Trabe we had to batch process images from one of our client’s API and send them transformed to another one.

We needed to apply some default transformations to each image, preview all of them in the browser and let the user modify them and add new ones before submitting the results.

To perform this transformations in-browser we used the Canvas API, using its “2d” context and drawImage.

Then, the transformed images were sent to an external service as base64, using the canvas toDataUrl method.

The Canvas API

This API lets us draw graphics using the HTML <canvas> element and JavaScript. It can be used for animations, games, data visualization, photo manipulation, and even video processing.

A canvas is a single DOM element that encapsulates a picture, providing a programming interface for drawing on it. It has width and height properties, determining its size in pixels.

<canvas width=”200" height=”200" />

It supports two drawing styles or contexts “2d” for two-dimensional graphics and “webgl” for 3D. The context we’ll be using is CanvasRenderingContext2D.

To select one context for a canvas you just have to use getContext on the selected canvas element like this:

const canvas = document.getElementById("myCanvas");
const ctx = canvas.getContext("2d");

Once you get a specific context for a canvas, every call to getContext using the same context type will return the same drawing context instance.

canvas.getContext("2d") === canvas.getContext("2d"); // true

You can manipulate your canvas and draw on it in many ways, here is a list of everything that’s possible. There are also many libraries that leverage using this API.

In our case we’ll be just “pasting” our image on the canvas using drawImage and adjusting its width and height when necessary to get our image transformations.

Using drawImage

The CanvasRenderingContext2D.drawImage() method of the Canvas 2D API provides different ways to draw an image onto the canvas.

It uses three different signatures, and their meaning may be confusing until you check the image below.

void ctx.drawImage(image, dx, dy);
void ctx.drawImage(image, dx, dy, dWidth, dHeight);
void ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
Graphical explanation of drawImage’s params. Original image by Fabian Rensch.

Using sx, sy, sWidth and sHeight we can control the portion of the image we “copy” from the original source, this way we can get only one portion of it and crop the image.

Using dx, dy, sWidth and sHeight we control where it will be “pasted” on the canvas. This way we could resize the portion we just copied.

A combination of this six parameters is all we need for our transformation needs.

The transformations

We will illustrate resizing, cropping and changing the aspect ratio, without configuration, using only drawImage.

Two sample transformations. Original image by Fabian Rensch.

Resizing an image

The most basic transformation is resizing an image without maintaining its aspect ratio.

First, we’ll need the canvas itself. We will use this HTML structure as a base in all our examples.

Barebones canvas example

Resizing the image is just a matter of changing the canvas’ width and height and using drawImage to draw our image on it.

If you want to maintain the aspect ratio you could just play with drawImage parameters. We can also extract the code creating those parameters too.

Cropping an image

Cropping an image follows the same principle, but you just have to play with drawImage’s parameters, changing them to the area of the image you want to select.

Selecting the area we are going to crop. Original image by Fabian Rensch.

And then setting the destination image accordingly, starting from 0,0 all the way to the size of the cropped part.

The destination parameters must cover the full canvas.

Don’t forget to set your canvas to the same width and height and start from 0,0. If you don’t do that you could end with an image that looks correct on a bigger canvas, and the exported image won’t look right.

In the next example we crop an area 50% the size of an image at its center.

Changing the aspect ratio

Changing an image aspect ratio uses just the same basic principles. We have to decide which area of the original image to crop and where to paste it on the canvas.

The aspect ratio of an image is usually represented as two numbers separated with semicolons (W:H) as in 3:4 or 16:9. Programmatically is usually more useful to have it as a the calculation of the image’s width divided by its height so we will use 0,75 and 1,77 instead.

Depending on the original image aspect and the one to transform to we can have one of this three situations:

  • Both are the same.
  • The original ratio is greater than the one we want (16:9 to 3:4).
  • The original ratio is smaller than the one we want (3:4 to 16:9).

When the aspect ratios are different we should do one of these two things:

  • Fill the rest of the canvas with some color. This way we have the full image but the resulting format is the one we want.
  • Crop the image to be the format we need, as we have seen before.
    In this case we just select the center of the image.
Changing aspect ratios. Filling with color vs cropping the greyed areas. Original image by Fabian Rensch.

We create a method that returns all the parameters we need to use in drawImage and the size of the resulting canvas.

We need the image original size and the 2d context of the canvas as usual. In this case we will be transforming our image to a 3:4 format.

Then we can just use it to change aspect ratios filling or cropping it. Here’s the code that would crop the image.

In the fill version, we will have to fill the totality of the resulting canvas with a color using fillRect. Then we can just paste the cropped image on top of it.

And that’s about it. You just need to extract this functions to a more reusable version and you will have your images transformed to any format.

Sending the images

After transforming the images we send them as base64 using toDataUrl.

In many cases the function will work just fine for valid images. We also wanted to keep the original file format and specify image quality when sending jpg images.

You can do it like this:

const base64DataFor = (fileName, canvas) => {
const extension = imageExtension(fileName);
switch (extension) {
case "jpg":
case "jpeg":
return canvas.toDataURL("image/jpeg", 1.0);
default:
return canvas.toDataURL();
}
};

Maximum image size

The canvas maximum size was not a limitation for us, since it’s very large. We handled successfully images up to 20Mb.

This max size depends on the browser though, you can check it here.

Beware of CORS

We got the original images from an external source and pasted them on our canvas. When you do this the canvas becomes tainted.

A tainted canvas is no longer considered secure, and any attempts to retrieve image data back from it will cause an exception to be thrown. This includes toDataUrl, getImageData and toBlob.

To be able to use toDataUrl you have to:

  1. Serve source images from a host with Access-Control-Allow-Origin header configured to permit cross-origin access to image files.
  2. Set crossOrigin="Anonymous" on the HTMLImageElement into which the image will be loaded.
<img src="http://external-image.png" crossOrigin="Anonymous" />// Or programmatically const image = new Image;
image.crossOrigin = "Anonymous";
image.src = "http://external-image.png";

Wrapping up

As you have seen, using drawImage is an easy way of transforming and manipulating images inside the browser.

You can use all this in vanilla JavaScript as we did here or in your framework of choice. We used them in React components so they are more convenient and reusable.

--

--

Lucas Andión
Trabe
Writer for

Galician. Software developer @trabe. Bike lover, beer enthusiast, mad traveler, beagle friend, surfer wannabe — https://andion.github.io