Canvas warping with JavaScript

2013-03-10 15:12

Playing around with HTML's canvas element lately I thought about implementing a well-known warping effect with it.

warp example 1warp example 2

It's quite simple really. Iterate over all output pixels, when you are close enough to the center of the deformation, calculate the coordinates of the pixel according to the set angle and its actual distance to the center, then copy it over from source image.

Since it's an experiment, I did not bother too much with optmizing the code and preferred to keep it readable. I was surprised how fast it actually works when doing such heavy work (albeit it's fast only in Chrome, Firefox is really slow on this and Opera lagging behind Chrome a bit).

You can do cool effects with it, especially when interactive or combined with animations.

Interactive examples

Move your mouse over the canvases below:

In this example the input canvas has a grid drawn on it and we never touch it again. On mousemove event we calculate the distance from center and set that as the deformation radius. The angle from 12 o'clock to the mouse position is set as the deformation's angle. You can see that if you move left or right or pass 6 o'clock (where the angle is reversed).

Here is how it's more or less done (utilizing some helper functions):

var warp = new Warp({
        input_canvas: $('#input_canvas1').get(0),
        viewport_canvas: $('#vp_canvas1').get(0),
        top: 100,
        left: 100,
});

$('#vp_canvas').on('mousemove', warp, function( event ) {
        var warp = event.data;
        var cx = 200, cy = 200;

        // calculate relative coords
        var x = event.pageX - $(this).offset().left + warp.options.left;
        var y = event.pageY - $(this).offset().top + warp.options.top;

        warp.deform({
                center: {x: cx, y: cy},
                radius: distance(cx, cy, x, y),
                angle: Math.atan2(cx - x, cy - y) * 180 / Math.PI,
                copy_input: true
        });
});

Another interactive use:

This is an example where we have a "dynamic" input canvas, and the deformation applied is static (i.e. has all static parameters). On mousemove event on viewport canvas we calculate relative position on input canvas and place the text there. Then the deformation is applied and we get the final result on viewport canvas.

In the code below, draw_text() is a simple helper function which clears the whole input canvas and puts the text on provided coordinates.

var warp = new Warp({
        input_canvas: $('#input_canvas').get(0),
        viewport_canvas: $('#vp_canvas').get(0),
        top: 100,
        left: 100,
});

$('#vp_canvas').on('mousemove', warp, function( event ) {
        var warp = event.data;
        var cx = 200, cy = 200;

        // calculate relative coords
        var x = event.pageX - $(this).offset().left + warp.options.left;
        var y = event.pageY - $(this).offset().top + warp.options.top;

        draw_text(warp.options.input_canvas, x, y);

        warp.deform({
                center: {x: cx, y: cy},
                radius: 150,
                angle: 45,
        });
});

Animation examples

Let's try some animation on the input canvas, simply scrolling some text right to left. Warping is applied after the text is placed on input canvas.


By supplying specific deformation center and radius/angle parameters (e.g. outside the canvas, big radius, etc.) you can achieve many interesting effects.

You can also animate the center of warping, keeping all the rest static:


Picture by Carol Munro

Deformation controlling function

Warping is controlled with a function which calculates the angle of rotation at the specific pixel, according to its distance from the origin and the supplied angle.

The function takes one floating point argument from 0.0 to 1.0, being the distance from origin (0.0 is the center of warping, 1.0 the edge of it) and returns a value between 0.0 and 1.0 representing the relative angle of rotation at this distance (0.0 being no rotation at all, 1.0 being the full angle). That means you probably want a function f which satisfies the following conditions:

f(0) = 1 (meaning at the center of warping make full rotation)

f(1) = 0 (meaning at the edge of warping make no rotation at all)

Here is a dynamically-generated visual representations of various functions you could use:

The red line shows the relative angle of rotation. X-axis is the input, that is the relative distance from origin (left is center of deformation, right the edge of deformation). Y-axis shows the relative angle at this point. The top being full angle, and the bottom no rotation at all.

The gradient shows basically the same, but in a sort of top view where colors represent angles. White color means 'at this spot make full rotation' and black means 'no rotation'. As you can see, the first function is just all white and it would rotate every pixel by full angle. On the other hand, the last function starts slowly from the black and end sharply with white in the center, so it will have smooth edges of deformation.

Check out the live_examples.js file included in this post to see how the demos are made in detail.

Some more info on github and in warp.js source code.

See other posts about: