P h o t o b o o t h   D e m y s t i f i e d
  L i b e ro  S p a g n o l i n i
Since my friends introduced me to Apple Photobooth I was instantly hooked: it’s pretty easy to get addicted to its effects because they’re so much funny and witty. It’s such a shame that both Windows and Linux lack of such a productivity-killer so I decided to try to write my own using GLSL (OpenGL Shading Language).
In this little article-tutorial I’m going to show you a tiny fragment shader that takes a picture and tries to deform it in order to mimic some Photobooth effects. For the impatiens I set up a video with the shader running.


The concepts and the tricks presented in the following tutorial are pretty much basic math stuff, and you should be fully able to understand them if you’ve taken some high school math course. I also assume that you have a basic knowledge of OpenGL and GLSL.

PhotoBooth shader overview
Our Photobooth shader will take two input parameters: a floating point number “shader” and a sampler2D “sampler” representing the texture to deform. The parameter shader will be used to choose which effects apply on the texture while the sampler will be used to get the color of a single texel. Before applying our Photobooth-like effects we scale the coordinates system of the texture from the GL_TEXTURE_2D space ([0.0, 1.0] x [0.0, 1.0]) to a more appealing space to deform our texture in ([-1.0, 1.0] x [-1.0, 1.0]).

vec2 texCoord = gl_TexCoord[0].xy; // [0.0 ,1.0] x [0.0, 1.0]
vec2 normCoord = 2.0 * texCoord - 1.0; // [-1.0 ,1.0] x [-1.0, 1.0]

The vector normCoord is the texture cartesian coordinate of a point in a new “normalized” space; note that, in this space, the origin (0.0, 0.0) is located at the texture center.
Now with same simple trig, given a cartesian texture coordinate of a point, we can compute its polar texture coordinate using the handy GLSL functions atan and length:

float r = length(normCoord); // to polar coords
float phi = atan(normCoord.y, normCoord.x); // to polar coords

Ok we’ve got everything we need, let’s define what an “effect” is! An effect is a simple function that transforms a texture coordinate by translating, scaling and rotating it in same way…yeah it’s that easy! An effect can be seen as a stupid little function defined like this:

[normCoordNew] = Effect(normCoord)
[rNew, phiNew] = Effect(r, phi)

Of course you can transform only the radius, or the angle or the normCoord, what matters is that they get tweaked in some cool way. Now that we have this tweaked texture coordinate, we just have to scale back to the GL_TEXTURE_2D texture space to let OpenGL display the picture properly. If we modified the polar coordinate we make sure to get back to cartesian coordinates first and then to scale to GL_TEXTURE_2D texture space. Here’s the GLSL code:

normCoord.x = r * cos(phi);
normCoord.y = r * sin(phi);
texCoord = normCoord / 2.0 + 0.5; // [0.0 ,1.0] x [0.0, 1.0]

The last thing to do is to fetch the texture color at the texture coordinate computed by our effect:

gl_FragColor = texture2D(sampler, texCoord);

Smoothstep is your friend!
As other shading languages, GLSL defines a function called smoothstep(edge0, edge1, x). Here’s smoothstep in action:

As you can see smoothstep delivers a smooth transition between 0.0 and 1.0 when edge0 < X < edge1; if X <= edge0 smoothstep returns 0.0, if X >= edge1 it returns 1.0. For the mathematically inclined, smoothstep is just a Hermite interpolation (a cubic spline).
Who cares about smooth transitions? You’ll find out that your effects will look much better with smooth transitions on instead of sharp ones. Sharp transitions can be very unpleasant and often result in unsightly artifacts so for now just trust me: you’ll want to have smooth transitions in your effects and smoothstep will just get the job done!
Ok now it’s show time! :-)

Squeeze effect
The squeeze effect will tweak the radius of the texture coordinate.
This effect can be achieved by making the radius fast growing when we’re near the center of the texture and then it should grow more gently, almost linearly. Fortunately there’s a little function that goes just like that: it’s called “gamma correction” function and people use it to compensate for the non linearity of CRT displays but what matters is that works very well for our squeeze effect!
Note that, in our “normalized” space, the radius is defined in [0.0, sqrt(2.0)] but the tweaked radius reaches almost 1.0 at sqrt(2.0): this give us a little zoom effect which is quite tasty! The magic number 1.8 tunes how much the curve bends while the magic number 0.8 tunes the amount of zoom of the effect.

if (shader == 1.0) r = pow(r, 1.0/1.8) * 0.8; // squeeze



Twirl effect
The twirl effect will tweak the angle of the texture coordinate.
The angle is defined in radians and the magic number 4.0 tunes the amount of rotation of the twirl.
The trick here is to sum to the angle a decreasing offset as long as we’re moving along the radius; when we are at a texture coordinate whose radius is greater than 0.5 the angle is unaffected and the texture coordinate will be equal to the one of the original texture.
By using smoothstep we make sure that there’s a smooth transition between the twirled region (0.0 < radius < 0.5) and the original texture (when radius >= 0.5).

if (shader == 2.0) phi = phi + (1.0 - smoothstep(-0.5, 0.5, r)) * 4.0; // twirl



Light-tunnel effect
The light-tunnel effect will tweak the radius of the texture coordinate.
This is one of the most straightforward effects: when the radius is greater than 0.5 it gets capped to 0.5.
This results in a highly pleasant light-tunnel effect that is very good when you want to play God or something like that!

if (shader == 3.0) if (r > 0.5) r = 0.5; // light tunnel



Bulge effect
The bulge effect will tweak the radius of the texture coordinate.
What we’d like to simulate here is some sort of protruding area located around the center of the texture; again, smoothstep turned out to be exactly what we were looking for: to make a good “bulge” we simply scale the radius by a smoothstep function.
As you can see from the picture the radius is slowly growing at the beginning and then, when it’s greater than 0.5, it grows linearly preserving the original texture coordinate.

if (shader == 4.0) r = r * smoothstep(-0.1, 0.5, r); // bulge



Dent effect
The dent effect will tweak the radius of the texture coordinate.
In this effect we’d like to create a “zoom out” effect around the center of the texture in order to give a diminishing view; we’d also like to make a smooth transition to join the diminished view region to the original texture. Smooth transition? Yeah that means smoothstep!
The key of this effect is to make growing the radius with a steep slope for small values and then smooth it down until it grows linearly with a slope of 1.0. Note how the radius is unaffected when it’s greater than 0.7 in order to preserve the original texture coordinate.
As you can see, the first linear ramp delivers the zoom out effect because of its slope 2.0; the smoothstep function is used to smoothly join the ramp to another ramp of slope 1.0 which is the original texture coordinate.

if (shader == 5.0) r = 2.0 * r - r * smoothstep(0.0, 0.7, r); // dent



Fish-eye effect
The fish-eye effect will tweak the radius of the texture coordinate.
This effect should turn our window in a wide-angle lens that takes in an extremely wide, hemispherical image.
A simple parabola will just do the trick. Note that the value is normalize to the length of the maximum radius which is sqrt(2.0);

if (shader == 6.0) r = r * r / sqrt(2.0); // fish eye



Stretch effect
Until now we tweaked only the polar texture coordinate of a point. Now it’s time to tweak the cartesian one: the vector normCoord.
The idea behind this effect is pretty much the same as the one behind the dent effect: the difference is that here we’re zooming in and not out. Moreover the zoom actually takes place on the cartesian coordinate normCoord instead than on the radius.
Because the function we’re looking for is odd we can save some computation by flipping the values when normCoord is in the range [-1.0, 0.0].

if (shader == 7.0) // stretch
{
  vec2 s = sign(normCoord);
  normCoord = abs(normCoord);
  normCoord = 0.5 * normCoord + 0.5 * smoothstep(0.25, 0.5, normCoord) * normCoord;
  normCoord = s * normCoord;
}




Mirror effect
The last effect we’re going to cover is the simplest one! We’re going to reproduce a mirror that reflects, around a vertical line at the center of the image, the half-right part of the picture; this effect will simply flip the cartesian texture coordinate normCoord.x when its range is [-1.0, 0.0].
Although this is a very simple and straightforward shader, the result looks pretty funny!

if (shader == 8.0) normCoord.x = normCoord.x * sign(normCoord.x); // mirror



About the code sample provided
The demo provided shows a simple window with a menu that lets you switch between different effects. The purpose of this demo is not to write a full blown Photobooth but to provide a simple app for you to try the effects out.
The demo should also be pretty portable because it uses Glut to fire up a window, FreeImage to load a picture and Glew to manage GLSL extensions.
The source code was tested on Windows using Nvidia and Ati cards. I also tested it on Linux using Nvidia driver and worked without any problems at all.

Final thoughts
In this tutorial I showed how it’s possible to write a little Photobooth-like app.
These kinds of effects are pretty easy to implement on most current shading languages and the really cool thing about them is that they’re computed exclusively on the graphics card: this means that they’re very suitable for real time video processing and stuff like that.
I hope that you enjoyed this tutorial and I encourage you to experiment a bit so that you can crack out some wonderful distortion effects!
I'd like to thanks my friends at NerdOcracy for hosting this page up until I manage to build my own web page. (I'm afraid this will take a looong time!)

About me
My name is Libero Spagnolini, I am studying Computer Engineering at the Politecnico di Torino, Torino, Italy.
I've always been interested in computer graphics programming since I was a little kid: I'm a Pixar's movies fanatic like everyone else who likes computer graphics I guess!
I’m currently finishing my master thesis where I investigated how to do general purpose computations on current GPUs.
For any suggestions, comments, ideas, you can drop me a line at libero.spagnolini AT gmail.com.