Zooming images with canvas API

One of the great advantages of this API is that it lets you play with the pixels of the canvas. This can be quite powerful when you want to create custom drawings or animations.

The Canvas API provides a means for drawing graphics via JavaScript and the HTML<canvas> element. Among other things, it can be used for animation, game graphics, data visualization, photo manipulation, and real-time video processing.

MDN web docs

This example will cover how to take the pixel data from an image, apply some changes to those pixels and put them back into the canvas.

The canvas data is represented by an array of pixels, each pixel is represented by an array of four numbers (red, green, blue, alpha) with values between 0 and 255.

More information about this topic, here.

Let’s get into our example.

We’ll start by loading a image from an URL and will draw it into our canvas element.

class ImageZoom {
  constructor(canvas) {
    this.image = new Image();
    this.image.crossOrigin = 'Anonymous';
    this.image.onload = () => this.onImageLoad();

    this.canvas = canvas;
    this.ctx = canvas.getContext('2d');

    this.image.src = '...';
  }

  onImageLoad() {
    this.canvas.width = this.image.width;
    this.canvas.height = this.image.height;
    this.hWidth = Math.floor(this.canvas.width / 2);
    this.hHeight = Math.floor(this.canvas.height / 2);

    this.ctx.drawImage(this.image, 0, 0);
    this.initStrokeRect();
  }
}

const canvas = document.getElementById('canvas');
new ImageZoom(canvas);

In order to visualize better what area will be zoomed, we’ll have a box around the mouse cursor.

Let’s create our zooming box.

The stroke will be red so each pixel will have the following rgba structure: [255, 0, 0, 255].

initStrokeRect() {
  this.rectData = [
    new ImageData(this.getLine((this.hWidth + 2) * 4), this.hWidth + 2, 1),
    new ImageData(this.getLine(this.hHeight * 4), 1, this.hHeight)
  ];
}

getLine(size) {
  let arr = new Uint8ClampedArray(size);

  let index = 0;
  while (index < size) {
    arr[index] = 255;
    arr[index + 3] = 255;
    index += 4;
  }

  return arr;
}

Now, we’ll create a mouse move listener that will help us determine the position of the box based on the current coordinates of the cursor.

this.canvas.onmousemove = event => this.onCanvasHover(
      event.offsetX, event.offsetY);

...

onCanvasHover(cx, cy) {
  this.dx = Math.floor(Math.min(this.canvas.width - this.hWidth,
    Math.max(cx - this.hWidth / 2, 0)));
  this.dy = Math.floor(Math.min(this.canvas.height - this.hHeight,
    Math.max(cy - this.hHeight / 2, 0)));

  if (this.strokeData)
    this.cleanStroke();
  this.saveStrokeData(this.dx - 1, this.dy - 1, this.hWidth + 2, this.hHeight);

  this.strokeRect(this.dx - 1, this.dy - 1, this.hWidth + 2, this.hHeight)
}

Each time the mouse move event is fired, we need to clean the current frame and draw the box at the new position. 

cleanStroke() {
  for (let line of this.strokeData)
    this.ctx.putImageData(line.data, line.x, line.y);
}

saveStrokeData(x, y, width, height) {
  this.strokeData = [
    {
      x: x,
      y: y,
      data: this.ctx.getImageData(x, y, width, 1)
    },
    {
      x: x + width - 1,
      y: y + 1,
      data: this.ctx.getImageData(x + width - 1, y + 1, 1, height)
    },
    {
      x: x,
      y: y + height + 1,
      data: this.ctx.getImageData(x, y + height + 1, width, 1)
    },
    {
      x: x,
      y: y + 1,
      data: this.ctx.getImageData(x, y + 1, 1, height)
    }
  ];
}

strokeRect(x, y, width, height) {
  this.ctx.putImageData(this.rectData[0], x, y);
  this.ctx.putImageData(this.rectData[1], x + width - 1, y + 1);
  this.ctx.putImageData(this.rectData[0], x, y + height + 1);
  this.ctx.putImageData(this.rectData[1], x, y + 1);
}

Finally, when the mouse click event is fired, we’ll take the pixel data that’s inside the box and pass it to drawImage() function that will do the zoom for us.

this.canvas.onclick = () => this.onZoom();

...

onZoom() {
  if (this.strokeData) {
    this.cleanStroke();
    this.strokeData = null;
  }

  this.ctx.drawImage(this.canvas,
    this.dx, this.dy, this.hWidth, this.hHeight,
    0, 0, this.canvas.width, this.canvas.height);
}

You can check out the full code here and the demo here.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Create a website or blog at WordPress.com

Up ↑