2013/04/03

Hello swirl! (A swirl effect tutorial in javascript)

 

Introduction

 
Nowadays, JavaScript power and the HTML 5 set of cool features let us do image manipulation effects directly in the browser without resorting to any external plug-ins like the Java Plug-in.
This entry is about the Swirl effect, an image effect that basically swirls the pixels around the center of an image to the left and/or to the right like some kind of vortex (if you've ever used Windows 98/ME, you may recall the swirl screensaver that was shipped with it). Watch the following animated gif to see it in action:
 
 
The effect consist in the individual rotation of the image pixels by an angle that is proportional to it's distance from the center, it could sound complex, but it's not. At the end of this post, you should be able to fully understand and implement the effect by yourself using JavaScript and HTML 5 canvas, also it will serve you as a basis for others JavaScript image manipulation effects to come.
 

Loading the image and getting the pixels

 
The first step to do image manipulation, is to load an image, open up your favorite text/programming editor and create a basic HTML 5 document test sheet, add a canvas into it and the proper <script></script> tags: I'll be using the following image for testing from the Geek Office Dog comic, of course you can use any other if you want:
 
 
Now you have a HTML 5 document with a canvas and a test image, the next step is to grab the image pixels into a JavaScript array, for that, create an image on the fly (using an Image object), load the image, add the onload function to draw the loaded image in the canvas and then ask the canvas to create an array with its content that will be passed to another callback that will (alongside with the canvasId) do the pixel manipulation and draw the result. The grabImageData function below summarizes the process:
 
The function creates an ImageData object, wich contains the array of pixels alongside the image dimensions. Then it passes the object to the callback function for further processing:
 
IMPORTANT: The above code will generate the following error message in Google Chromiun (version 20.0+) if you just opened the html file directly into a browser tab by double clicking on it from your file explorer:
 
"Unable to get image data from canvas because the canvas has been tainted by cross-origin data"
 
This is due to some local file loading policy issue when the code tries to read the pixels by means of the getImageData function and will also throw the following error message:
 
"Uncaught Error: SecurityError: DOM Exception 18"
 
I've tried to solve this problem with no luck. To do proper testing in Chrome, we will need a personal html server installed locally. Fortunately, you could use Firefox (in my case, version 17.0) without resorting to a local html server and test there.
 
You have now the pixels array, which is a lineal arrangement of values, so you have to use the following formula to access each element at (x, y): every cell of the returned array contains a pixel component (the red, green, blue or alpha value of the pixel), the pixel format is RGBA (if you want to know more about the image pixel layout check this good tutorial), so if you want to access a pixel at coordinates (x, y) you have to use the above formula to convert the coordinates to the array position and then multiply this value by 4: We've covered the pixel loading/accessing routines, now into the transformation stuff.
 

Image rotation

 
For this tutorial, the pixels that you're going to transform lie within a square about 1/2 of the image in the center region:
 
 
The following code will iterate over that area: Converting the pixel coordinates to polar coordinates would be of help here. Polar Coordinates will let you express the pixel position in function of a radius r and an angle α (in radians), which, in turn, will aid you in the individual rotation of every pixel.
 
The next step it's to covert the pixel Cartesian (x, y) coordinates to their polar equivalent, and check if the pixel is inside a circle with radius 1/4 of the image (a circle inside the interest region), to do that, given a radius r, use the Pythagorean theorem to test if it lies inside the circle area:
 
 
If you shift the pixel's angle by a constant delta, you'll end up rotating that portion of the image, let's first create an image rotation effect to test this. The following functions will help you in the process: Now the rotation: Let's see the results:
 
sorry, no canvas, please, upgrade your browser
 
Check the weird artifacts that arise from the transformation! Can you explain those misleading pixels?
 
The problem is that you're moving pixels from a source image to a destination image, but due to floating point errors (or the fact that the pixels may share very close positions at the end of transformation), not every pixel from the source image will have an unique position on the destination image and some of them fall in already occupied cells, thus leaving empty spaces (or holes) in the destination image. This is what you can recall from math classes as injectivity (or the lack of) in functions, although we're not dealing with functions here but a higher level concept, transformations.
 
The following image will illustrate the concept above:
 
 
As you can see from the picture, close pixels have chance to share the same pixel position after being transformed, resulting in several of those empty cells with a question mark (those are the image artifacts).
 
An universal solution to this problem is to apply the transformation backwards, i.e, instead of calculating the destination coordinates from the source coordinates, iterate the destination coordinates and find which source coordinates will end up in the destination position, see the following image for a visual description:
 
See the rotation fixed: sorry, no canvas, please, upgrade your browser
 
Much better, right?
 

Swirling stuff

 
So far, you can rotate all the pixels in a small circle area by a constant shift, what would happen if, instead of a constant value, you rotate the pixels by an angle that depends on the radius r? Well, that's the swirl effect all about, degrees is not longer shifted by a constant but a function that takes r as parameter instead, this function can be as creative as you want, you can multiply a constant value by r, or divide the constant by r, or just add it, the variation over the distance from the center will make the pixel rotates a little more than it's predecessor pixels, thus achieving the desired swirling effect. In this case I replace the degrees += 30.0 sentence with degrees += 4.0 * r (only that sentence), here you have the result: sorry, no canvas, please, upgrade your browser
 

Animating the swirl

 
Yeah, why not? Let's animate the whole thing, you've made this far, you deserve an eyecandy reward.
 
To do the animation, you need to change the swirl formula to take an extra parameter, we'll call this parameter 'step' an it'll be an indicator of the amount of twist you have to apply in the swirl along time, the swirl sentence changes to degrees += r * 10 * step;.
 
Maybe you think that the step parameter could be fit in a for loop that encloses the pixel iteration loops, and after every transformation you could call drawPixels to refresh the image... But, it won't work... The only thing that you'd see is the final image after the final step is processed, all the intermediate frames will be lost.
 
To work this out, create another function that takes the step parameter, do the image manipulation and then calls the drawPixels routine, this function can put itself in the invocation queue using the setTimeout function, so it will be invoked later with a modified step parameter and it will (after a short amount of time) repeat the process and render the next animation frame. Avoid loading the image every time you enter in the animation function declaring this inside the swirl function, so the (let's call it animate) function will have access to the loaded pixels and it won't need to load the image again.
 
To pass the function with the step parameter modified in the setTimeout call, pass another function as the parameter and inside this new function, invoke the animate function with the incremented/decremented step, like this: You can toy with these parameters, you could add a direction parameter to the animate function to indicate if the animation should go left or right, you can even do a little pause when the step is close to 0, so the user will watch the original image for a brief moment before the animation stars over again, and the list goes on.
 
This is the final code for the swirl function effect with the animation part: Here is the result: sorry, no canvas, please, upgrade your browser
 

Bonus, Bilinear Interpolation

 
To finish, let's make things a little smoother, so far you should have noticed how sharp the animation looks like when revolving the pixels from one side to another, some pixels seems to be moved from the position they should really be, generating some visual artifacts that looks like noise:
 
 
These artifacts arise due to the fact that we're discarding some information after the inverse transformation step, in fact, we're discarding part of the pixel destination coordinates after taking the floor function in the following lines: Yes, we're discarding the fractional part of the newX and newY variables, but why shouldn't we do that? After all the image buffer is a discrete set of pixels with integer coordinates, and there is no place for the fractional part of a pixel, right?... Well, results that there is place for that fractional part, look the following image:
 
 
In the above image you can see that a pixel (let's call it P) with coordinates (x + 0.7, y + 0.55) (where x, y are integers) is drawn over the grid formed by the pixels (x, y), (x + 1, y), (x, y + 1) and (x + 1, y + 1), all of these have a position in the image buffer. The P pixel occupies part of the four pixels, so, the solution is to place P in (x, y) but taking into account the contribution that these other 4 sorrounding pixels do in it.
 
We're going to interpolate the pixels components at (x, y) and (x + 1, y), then interpolate the components of the pixels that lie inmediatelly below of (x, y), that is (x, y + 1) and (x + 1, y + 1), this generates two interpolated components, we interpolate again between these two new generated components creating a pixel component that carries information of it sorrounding area, see the following image for a visual explanation:
 
 
That is called Bilinear Interpolation. Change the following lines in the swirl code to accomplish the visual upgrade: An here, the final result: sorry, no canvas, please, upgrade your browser
 

Conclusion

 
In this post, you have learned to load images dynamically using javascript, to obtain it's pixels and manipulate then, to render back the modified pixels within an animation and how to get rip of some unpleasant artifacts using bilinear interpolation. Try to tweak some of the parameters to experiment with different results. Send a message if you need help to henry.iguaro at gmail dot com.

12 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. Hi. Great article. I'm also creating a similar effect "pinch" and I'm getting these "artifacts". Is there anyway I can contact you? I could use some pointers in applying backwards transformation.

    ReplyDelete
  3. I found your blog while searching for the updates, I am happy to be here. Very useful content and also easily understandable providing.. Believe me I did wrote an post about tutorials for beginners with reference of your blog. 
    python Course in Pune
    python Course institute in Chennai
    python Training institute in Bangalore

    ReplyDelete
  4. Thanks For Sharing The Information The information shared Is Very Valuable Please Keep Updating Us Time just went On reading The article Python Online Training Aws Online Training Hadoop Online Training Data Science Online Training

    ReplyDelete
  5. Attend The Python training in bangalore From ExcelR. Practical Python training in bangalore Sessions With Assured Placement Support From Experienced Faculty. ExcelR Offers The Python training in bangalore.
    python training in bangalore

    ReplyDelete
  6. Thanks for a most informative tutorial. You explain the process of processing pixels in a javascript array so clearly.
    Your code works perfectly until I change the line ‘ degrees += 30.0;’ to 'degrees += 4.0 * r’
    The image stays the same - no swirl effect!
    I am using Safari as a browser and MacOSMojave on an iMac.

    I added the swirlAnimated function and this did not work either.

    BTW your function rotateFixed is missing the canvasId parameter and I think you should include the total code for the function as this may confuse some readers.

    ReplyDelete
  7. Stupid me - I was modifying the wrong file. It all works fine now!

    ReplyDelete
  8. Thanks for the post. Do you think it's possible to apply this swirl effect to entire html page? Not only images.

    ReplyDelete
  9. This post is so interactive and informative.keep update more information...
    RPA Training in Chennai
    RPA Training in Chennai

    ReplyDelete
  10. The emperor casino - Shootercasino
    The emperor casino 바카라 제왕카지노 The febcasino Empire Casino is the best online casino in Canada for Canadian players that want the best value and competitive gaming experience for Canadian

    ReplyDelete