Image Manipulation with HTML5 Canvas - Part II

In my last post I was describing how to add a reflection effect to an image using an HTML5 canvas. As promised in that post, here finally comes part 2, which is dealing with creating a perspective view of a 2D image.

Originally I wanted to deform an image to the form of an trapezium and rotate it afterwards, so it looks kind of 3D. You can create that effect with nearly every image editor like GIMP or Photoshop. My goal however was to achieve this using the HTML5 canvas and JavaScript. The advantage would be, that the transformation can be applied to any image when rendering the webpage and without modifying the image itself.

Deformed and rotated HTML5 logo

As already mentioned in part 1 of this blog post, this idea turned out to me much more complicated than I expected. Whereas it is pretty easy to apply affine transformations to a canvas, like rotation, scaling or translation, it is not supported by default to apply projective transformations. To put this into another context: the canvas element provides methods to apply affine transformations, which makes it possible to rotate, scale or transform an image. This gives you the possibility to deform an image to a parallelogram shape, but unfortunately not into a trapezium shape.

Affine Transformation

Let's have a look at the affine transformation first and start like the example from part 1. We start with a simple image (this time a screenshot from our homepage) within a canvas element. But before drawing the image on the canvas, we use the 'transform' function to deform the canvas.

var context = document.getElementById("canvas1").getContext("2d");
var img = new Image();
img.src = "images/screenshot.png";
img.onload = function() {
	context.transform(1, -0.08, 0, 1, 0, 50);
    context.drawImage(img, 0, 0);

The 'transform' function with six parameters applies an transformation matrix to the canvas. The second parameter defines the rotation angle, whereas the last parameter is used to move the image down a bit. Otherwise the top right corner would get chopped off. To understand this transformation it is helpful to read the wikipedia page about shear transformation.

To get this canvas displayed a canvas element like the following has to be added to the HTML. The result should look like the following image.

<canvas id="canvas1" width="800" height="460"></canvas>
Screenshot with applied shear transformation

Projective Transformation I

So, to apply a shear transformation is pretty easy. But that wasn't the original goal... When I was looking for answers how to achieve a perspective transformation instead, I came across the Yahoo! User Interface Blog and an intersting post about Image Transformations in Canvas with Slicing. To put it all in a nutshell, the idea is to apply scaling and translation operations to slices of an image, to simulate a perspective transformation. If the slices are small enough it creates the illusion of a perspective deformation of the whole image. To draw only slices of an image can be done by using the canvas' 'drawImage' function. I used this function in part 1 of this blog post as well for creating the reflections, which are actually just a cut-out of the original image. See the Canvas / Slicing section at the Mozilla Developers Network for a good explanation of this feature.

The illustration on the right hand side is taken directly from the YUIBlog. I hope, they don't mind...

Slicing Diagram from YUIBlog

To get one step closer to solving my problem, I took the JavaScript code from the Yahoo! blog post and used it for transforming my screenshot. I just added a little modification to be able to turn off the scaling of the overall width. This makes it possible to illustrate the idea behind the transformation by only transforming five slices of the image (see next image). If you apply the transformation slices with the width of 1 pixel, you will get the result of the second image.

Screenshot with applied keystone transformation Screenshot with applied keystone transformation

The source code for the second example would look like this:

var context = document.getElementById("canvas1").getContext("2d");
var img = new Image();
img.src = "images/screenshot.png";
img.onload = function() {
	keystoneAndDisplayImage(context, img, 0, 0, img.width * 0.75, 0.8);

Projective Transformation II

The examples above look pretty close to what I wanted to achieve, but I was still not there completely. The 'keystone' function provided on the Yahoo! page, creates a vertical transformation. Because I needed a horizontal transformation, I had to take their code and modify it for my needs.

function keystoneAndDisplayImageH(ctx, img, scalingFactor, numSlices) {
	var h = img.height, w = img.width,
	// how much should every slice be scaled in width?
	widthScale = (1 - scalingFactor) / numSlices;
	// height of each slice
	sliceHeight = h / numSlices;
	// iterate over all slices		
	for (var n = 0; n < numSlices; n++) {
	    // source - where to take the slices from
		var sx = 0,
			sy = sliceHeight * n,
			sWidth = w,
			sHeight = sliceHeight;
		// destination - where to draw the new slices
		var dx = (w * widthScale * (numSlices - n)) / 2,
			dy = sliceHeight * n,
			dWidth =  w * (1 - (widthScale * (numSlices - n))),
			dHeight = sliceHeight;
		ctx.drawImage(img, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);

A call of the function above like this...

// setting img.height as parameter for numSlices means: 
// perform transformation for 1px thin slices 
keystoneAndDisplayImageH(context, img, 0.8, img.height);

... will result in an image like this:

Screenshot with applied keystone transformation (horizontally)

Surprise, surprise...

Nearly there! Last thing to do is to rotate the image slightly for the desired effect. This can be accomplished easily by using the 'rotate' function of the canvas element.

var context = document.getElementById("canvas1").getContext("2d");
var img = new Image();
img.src = "images/screenshot.png";
img.onload = function() {
	// translate the image so it doesn't get chopped off by the canvas height
	context.translate(0, 120);
	// rotate and keystone image
	keystoneAndDisplayImageH(context, img, 0.8, img.height);

But when I looked at the result I got a nice surprise. The image looks all distorted... I still have no idea why this happened and I couldn't come up with a solution, yet. But maybe some of you have got an idea?!

Screenshot with applied keystone and rotation transformation (horizontally)

A more complex example

During my research on this topic I came across a very impressive example of projective transformation. This example proves that it is possible to apply perspective transformations to images within a canvas in realtime. But if you look behind the scenes of this example, it also show that it takes some more effort and that it is a quite complex topic. Turns out it is not as easy as I thought it would be ;)

Sven Hoffmann

Sven Hoffmann

2011-12-29 • 04:44 PM

Web Technology