📜 ⬆️ ⬇️

HTML5 canvas bilinear pixel distortion


In this post, I want to describe a simple method of pixel distortion of the image on the "pure" javascript in 2D-Canvas without using special libraries and shaders, by direct access to the image pixels. I hope it will be interesting and useful both for common development and for solving some problems.


Canvas and pixels


I will not fully describe the Canvas object, there is documentation for this. Let's stop on what we need. First, it is getting a 2D context:
var context = canvas.getContext('2d'); 

This context is able to do a lot with two-dimensional graphics, including getting direct access to pieksel in a given area:
 var pixels = context.getImageData(x, y, width, height); context.putImageData(pixels, x, y); 

These are the pixels we are to change. We will only consider 32-bit images. Each pixel of such an image is four bytes, one byte per channel (R, G, B, A). Pixels are a one-dimensional array of these bytes. They are accessed through the data field (x, y - coordinates, c - channel, b - value):
 pixels.data[(x+y*height)*4+c] = b; 

Distortion function


Image distortion, which we are considering, is a function whose parameters are the coordinates of the resulting image (hereafter we call them pixels ), and the result is the coordinates of the original image (hereinafter we call them texels , because in fact the original image is a texture, and the coordinates are these are floating point numbers). Thus, the function for enlarging an image is approximately as follows:
 var zoom = function(px, py) { return { 'x': (px+width/2)*0.5, 'y': (py+height/2)*0.5 } } 

We compose a few more functions for other distortions. I see no point in describing each algorithm; math is quite simple and speaks 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 } } 

 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 } } 

 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 } } 

Hash table


So we got the opportunity to find out which texels to take for each pixel. But do not count the same coordinates every time? It will be too stressful. For this, a hash table comes to the rescue. Thus, we calculate the entire transformation map once for each image size, and later we use it for each transformation:
 //    .   ,      . 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(tx, width-1), 0); map[(x+y*height)*2+1] = Math.max(Math.min(ty, height-1), 0); } } } 

Bilinear filtering


So that in case of distortions, the sharp boundaries do not spoil our mood, let's apply the classic bilinear filtering algorithm. It is described in detail in Wikipedia . The essence of the algorithm is to find the color of the 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 = ux; var ky = vy; 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 all. It remains to wrap this in a separate object, add it to the code and see what happens.
Play in real time on JSFiddle .
Works in Chrome and Firefox. In others I can not yet check if it does not work, write in a personal.
Thanks for attention.

')

Source: https://habr.com/ru/post/138668/


All Articles