Design, CG Graphics & Inspiration
Pixel Distortions with Bilinear Filtration in HTML5 Canvas

Pixel Distortions with Bilinear Filtration in HTML5 Canvas

In this post I want to describe simple methodology of pixel image distortion on “clean” javascript in 2D – Canvas without the use of the special libraries and shaders, by the direct access to the pixels of the image. I hope, it will be interesting and useful both for general development and for the decision of some tasks.

Canvas and pixels

I will not describe the object of Canvas fully, there is a certain documentation for this purpose. We will stop on the issues that are interesting to us. Firstly, it’s the receipt of 2D context:

var context = canvas.getContext('2d');

This context is able to do great things with two-dimensional graphic, including the ability to get the direct access to the pixels in the set area:

var pixels = context.getImageData(x, y, width, height);
context.putImageData(pixels, x, y);

And we are going to change these pixels. We will examine 32-bit images only. Every pixel of such image is four bytes, a byte for a channel(R, G, B, A). Pixels are a unidimensional array of these bytes. One can access them through the data field (x, y are coordinates, c is a channel, b is a value):

pixels.data[(x+y*height)*4+c] = b;

Function of distortion

Distortions of the image that we examine is a function the parameters of which are coordinates of the image we get (further we will call them pixels), and the result of which are coordinates of the initial image (further we will call them texels, because actually an initial image is a texture, and coordinates are numbers with floating point). Thus, a function for the increase of the image has approximately next look:

var zoom = function(px, py) {
    return {
        'x': (px+width/2)*0.5,
        'y': (py+height/2)*0.5
    }
}

Zoom in Pixel Distortions with Bilinear Filtration in HTML5 Canvas

We will make a few functions for other distortions. I don’t see any point to describe every algorithm, mathematics is simple enough and talks for itself.

var twirl = function(px, py) {
    var x = px-width/2;
    var y = py-height/2;
    var r = Math.sqrt(x*x+y*y);
    var maxr = width/2;
    if (r>maxr) return {
        'x':px,
        'y':py
    }
    var a = Math.atan2(y,x);
    a += 1-r/maxr;
    var dx = Math.cos(a)*r;
    var dy = Math.sin(a)*r;
    return {
        'x': dx+width/2,
        'y': dy+height/2
    }
}

Twirl in Pixel Distortions with Bilinear Filtration in HTML5 Canvas

var reflect = function(px, py) {
    if (py<height/2) return {
        'x': px,
        'y': py
    }
    var dx = (py-height/2)*(-px+width/2)/width;
    return {
        'x': px+dx,
        'y': height-py
    }
}

Reflect in Pixel Distortions with Bilinear Filtration in HTML5 Canvas

var spherize = function(px,py) {
    var x = px-width/2;
    var y = py-height/2;
    var r = Math.sqrt(x*x+y*y);
    var maxr = width/2;
    if (r>maxr) return {
        'x':px,
        'y':py
    }
    var a = Math.atan2(y,x);
    var k = (r/maxr)*(r/maxr)*0.5+0.5;
    var dx = Math.cos(a)*r*k;
    var dy = Math.sin(a)*r*k;
    return {
        'x': dx+width/2,
        'y': dy+height/2
    }
}

Spherize in Pixel Distortions with Bilinear Filtration in HTML5 Canvas

Hash-table

So, we got the possibility to know, what texels to take for every pixel. We shouldn’t calculate the coordinates each time, should we? It will be a hard way. For this purpose a hash-table comes for help. Thus, we calculate all the transformations for every size of image singly, and in the future we’ll use it for every transformation:

// A parameter is a translator function. If it is a line, a function is set from the available functions in an object.
var setTranslate = function(translator) {
    if (typeof translator === 'string') translator = this[translator];
    for (var y=0; y<height; y++) {
        for (var x=0; x<width; x++) {
            var t = translator(x, y);
            map[(x+y*height)*2+0] = Math.max(Math.min(t.x, width-1), 0);
            map[(x+y*height)*2+1] = Math.max(Math.min(t.y, height-1), 0);
        }
    }
}

Bilinear filtering

In order not to spoil our mood with sharp borders, we will apply the classic algorithm of bilinear filtration. More detailed information you can find on Wikipedia. The essence of the algorithm consists in finding a color of pixel depending on the four nearest texels. In our case, the algorithm will look like this:

var colorat = function(x, y, channel) {
    return texture.data[(x+y*height)*4+channel];
}
for (var j=0; j<height; j++) {
    for (var i=0; i<width; i++) {
        var u = map[(i+j*height)*2];
        var v = map[(i+j*height)*2+1];
        var x = Math.floor(u);
        var y = Math.floor(v);
        var kx = u-x;
        var ky = v-y;
        for (var c=0; c<4; c++) {
            bitmap.data[(i+j*height)*4+c] =
                (colorat(x, y  , c)*(1-kx) + colorat(x+1, y  , c)*kx) * (1-ky) +
                (colorat(x, y+1, c)*(1-kx) + colorat(x+1, y+1, c)*kx) * (ky);
        }
    }
}

Conclusion

That’s actually all. The only thing remain is turning it in a separate object, adding it to the code and see what you get.

You can try it in real time on JSFiddle. Works in Chrome and Firefox.

Thank you for your attention.

p.s. You can read this article on Russian.

  • Stdit,
  • February 22, 2012

SHARE THIS POST

Subscribe for the hottest posts

Subscribe to our email newsletter for useful tips and freebies.

  • Awaks

    Hi,

    Your post is very useful and well explained! However, I face to some difficulties to use it correctly. I’m focused on the twirl feature. Instead of twirl, i would like to have a semi circle or circle (the side of the circle have to depends on the mouse direction) ex: when i onmousedown and go up the top of that semi circle is at top :) . Last thing: the pixel have to be distorted in contact with the exterior part of that circle, not inside…

    Can you give me some advice, or show me some code that would be very pleasant :)

    Thanks a lot, have a good day!

  • ken

    still very confused on how to implement this
    what is x, what is y, how do i call the function spherize and what do i put in it?

  • Awaks

    Hi,

    Your post is very useful and well explained! However, I face to some difficulties to use it correctly. I’m focused on the twirl feature. Instead of twirl, i would like to have a semi circle or circle (the side of the circle have to depends on the mouse direction) ex: when i onmousedown and go up the top of that semi circle is at top :) . Last thing: the pixel have to be distorted in contact with the exterior part of that circle, not inside…

    Can you give me some advice, or show me some code that would be very pleasant :)

    Thanks a lot, have a good day!

  • ken

    still very confused on how to implement this
    what is x, what is y, how do i call the function spherize and what do i put in it?