The Ninja Way

a.k.a. Creating animated GIFs with OpenCV, ImageMagick, and Gifsicle

I have these two similar images that I am to use for an undisclosed secret feature, that certainly cannot be derived:

I needed both on the site. However, I feared that people would be confused that they would have to do both, instead of just one or the other.

I'm sure that some UI guru says something like "Don't allow there to be more than one call to action for any given (buzzword that basically means page)".

So I wanted an animation that fades between the two images.

In jQuery, there's a generic $.animate with callbacks. I could do it this way, but I wanted the classic GIF.

The Results

Because I'm impatient too...

Here are a few that I generated:

Nothing too special, really. And I'm not actually going to use them for some other UX reasons. Plus, I'm sure there's some TOS violations in there somewhere.

How I made these is, I hope, however, much more interesting to you, the impatient reader.

Analyzing the Images

If I run ImageMagick's identify program, I find

$ identify tw.png fb.png
tw.png PNG 151x24 151x24+0+0 8-bit DirectClass 2.37KB 0.008u 0:00.000
fb.png[1] PNG 154x22 154x22+0+0 8-bit PseudoClass 242c 1.66KB 0.000u 0:00.007

This of course means that they are different sizes and can't be superimposed on top of each other without resizing one of them. Since they are close, I decided resizing wouldn't be too egregious. I resized the twitter icon to the fb dimensions using ImageMagick's convert.

You need to be careful here

$ convert tw.png -resize 154x22 tw1.png

The above command actually won't work ... it will try to keep dimensions. You need to use the !, and escape it.

$ convert tw.png -resize 154\!x22\! tw1.png

Now we rerun identify

$ identify tw1.png fb.png
tw1.png PNG 154x22 154x22+0+0 8-bit DirectClass 4.32KB 0.008u 0:00.007
fb.png[1] PNG 154x22 154x22+0+0 8-bit PseudoClass 242c 1.66KB 0.008u 0:00.007

Create the Frames

To fade one image to the other, you want to fade out one while the other fades in. There are many models for fading ... linkjacked image follows

The one I'm going to use is linear or "transition". So when one image is at 20% opacity, the other is at 80%. This one is easy and gets the job done.

So let's say that we want to fade in 5 steps. Here are the opacity levels for each image in each step:

Image1Image2
100%0%
80%20%
60%40%
40%60%
20%80%
0%100%

At step 2, for instance, when image1 is at 80% and image2 is at 20%, our composite image, the one that we are generating, would basically be0.8 * image1 + 0.2 * image2 and so on.

It's worth noting, that if you want a loop, then you are reversing the process. It's symmetrical.

The "Pivot" Point

(Trying to use that college degree)

We will call the end of the sequence, when image2 is at 100% and image1 is at 0%, the pivot point. This will also be our sequence length.

If we extend the table above to two rounds we'll get the following:

Image1Image2
100%0%
80%20%
60%40%
40%60%
20%80%
0%100%
20%80%
40%60%
60%40%
80%20%
100%0%
80%20%
60%40%
40%60%
20%80%
0%100%
20%80%
40%60%
60%40%
80%20%

Notice how some step is repeated multiple times. Mathematically, if there are X steps (or here, 5) and you are on step i out of X, then you can expect to see that same image at 2 * X - i - 1The 2 * X is a full round, a fade in and a fade out. The "- i" is because there is a symmetry and the "-1" is because we don't hold the faded-in image for two frames ... this means that the symmetry point is actually in the center of the fully faded in frame with respect to time.

Let me explain this. So at the end of the frame when image2 is fully faded in and the fully faded frame has been on the screen for say, 1 second, you have already started to fade out. Well not really. But if you were to add more frames and wanted to have a smoother transition, say two frames for every one you had before, then you would have started fading out 1 / 2 / 2 times sooner.

WAIT, where did that 1 / 2 / 2 come from? Well let's look at that table again: We're going from

TimeImage1Image2
0.0020%80%
1.000%100%
2.0020%80%
to this:
TimeImage1Image2
0.0020%80%
0.5010%90%
1.000%100%
1.5010%90%
2.0020%80%
We could do it at a higher precision
TimeImage1Image2
0.0020%80%
0.2515%85%
0.5010%90%
0.755%95%
1.000%100%
1.255%95%
1.5010%90%
1.7515%85%
2.0020%80%

See, it's pretty basic calculus. The pivot point is the intangible center point of time of the fully faded in frame.

Writing the Code

So my program takes the following arguments

fade [sequence count] [image1] [image2]

And then emits transition[number].png as the sequence. We are using OpenCV and C. Let me go over the code before I provide the links:

#include <cv.h>
#include <cvaux.h>
#include <highgui.h>
#include <stdio.h>

I create a basic r, g, b structure:

typedef struct {
 unsigned char r, g, b;
} rgb;

int main(int argc, char** argv) {
 int iy = 0,
     ix = 0,
     step = 0,
     phases; 

 rgb *temp1 = 0, 
     *temp2 = 0, 
     *tempOut = 0;

 char *pStart1, 
      *pStart2, 
      *pOut, 
      name[100];

 IplImage *image1 = 0, 
          *image2 = 0, 
          *imageOut = 0;

The number of phases, and the two files that we will be fading:

 argv++;
 phases = atoi(*argv++);
 image1 = cvLoadImage(*argv++, CV_LOAD_IMAGE_COLOR);
 image2 = cvLoadImage(*argv++, CV_LOAD_IMAGE_COLOR);

The output frame

 imageOut = cvCreateImage(cvGetSize( image1 ), 8, 3);

The number of phases

 for(step = 0; step < phases; step++) {

Get the row (that means the horizontal part) pointer for each image so that we can compute the composite pixel.

  for(ix = 0; ix < image1->height; ix++) {
   pStart1 = image1->imageData + ix * image1->widthStep;
   pStart2 = image2->imageData + ix * image2->widthStep;
   pOut = imageOut->imageData + ix * imageOut->widthStep;

   for(iy = 0; iy < image1->width; iy++) {

Create references to the three pixels, one in each image

    temp1 = (rgb*)(pStart1 + (iy * 3));
    temp2 = (rgb*)(pStart2 + (iy * 3));
    tempOut = (rgb*)(pOut + (iy * 3));

Create the Composite Pixel

(validating all this trouble)

We take the channel value for this pixel and multiply it by our step / phase. That's the first part of the channel value. The other part is computed by taking the channel value of the other image's pixel multiplied by (phase - step) / phase which would be the complementary percentage. Repeat this for the other two channels.

    tempOut->r = (temp1->r * step) / phases +
                 (temp2->r * (phases - step)) / phases;

    tempOut->g = (temp1->g * step) / phases +
                 (temp2->g * (phases - step)) / phases;

    tempOut->b = (temp1->b * step) / phases +
                 (temp2->b * (phases - step)) / phases;

The above code is where you can have a lot of fun. You could do

etc... You have much more control here then you would with some pre-packaged program.

And after all, proposterous levels of customization is the biggest benefit of rolling your own solution.

After all the fun is over, we write the image, and its phases * 2 - step - 1 counterpart

   }
  }
  sprintf(name, "transition%03d.png", step);
  cvSaveImage(name, imageOut);

  sprintf(name, "transition%03d.png", phases * 2 - step - 1);
  cvSaveImage(name, imageOut);
 }

And then we clean up:

 cvReleaseImage(&image1);
 cvReleaseImage(&image2);
 cvReleaseImage(&imageOut);

 return 0;
}

The whole code is here: http://9ol.es/fade.c.txt and the makefile is here: http://9ol.es/Makefile.fade.txt

Creating the Animation

After I compile it, I run

./fade 10 fb.png tw1.png

If I do an ls, I'll see these new files:

$ ls trans*png
transition000.png
transition001.png
transition002.png
transition003.png
transition004.png
transition005.png
transition006.png
transition007.png
transition008.png
transition009.png
transition010.png
transition011.png
transition012.png
transition013.png
transition014.png
transition015.png
transition016.png
transition017.png
transition018.png
transition019.png

Now we need to combine these images and emit our GIF. The obvious tool that comes to mind is FFmpeg.

FFmpeg

To create an animation that is a composite of these images, I could just use FFmpeg like so:

ffmpeg -i transition%03d.png -r 5 -o out.gif

But a few problems:

So this means you will get a small, but rather ugly image. On a reddit thread, I used FFmpeg to generate this 22MB 298x168 9min 2059 frame sequence.

It does the job, it's rather small for 2000 frames ... but the horrible dithering is also pretty classically bad.

Palettes

FFmpeg uses the web safe GIF palette, which is 216 colors. There are 6 shades for red, 6 for green, and you guessed it, 6 for blue. The palette is reproduced below via javascript:

Since some colors, especially close ones don't map well to the palette above, I need software that is capable of finding optimal palettes and having generally better control over palettes. When you do this, then you can choose your 256 color palette from a full 24 bit one, with 8 bits for each channel i nthe RGB color space.

Gifsicle

With FFmpeg out of the picture, I decided to use gifsicle. It's great at taking GIFs and making an animation out of them. Only GIFs though, not PNGs. :-(

So, using tcsh and ImageMagick's convert tool again, I converted the PNGs to GIFs:

foreach n(transi*.png)
        echo "Converting $n"
        convert $n $n:r.gif
end

You can do this type of iteration in bash of course, but bash's syntax when dealing with extension swapping is more cumbersome, in my opinion.

Running this emits:

% foreach n(transi*.png)
foreach?         echo "Converting $n"
foreach?         convert $n $n:r.gif
foreach? end
Converting transition000.png
Converting transition001.png
Converting transition002.png
Converting transition003.png
Converting transition004.png
Converting transition005.png
Converting transition006.png
Converting transition007.png
Converting transition008.png
Converting transition009.png
Converting transition010.png
Converting transition011.png
Converting transition012.png
Converting transition013.png
Converting transition014.png
Converting transition015.png
Converting transition016.png
Converting transition017.png
Converting transition018.png
Converting transition019.png

Our file listing now looks like this:

% ls -1 trans*
transition000.gif
transition000.png
transition001.gif
transition001.png
transition002.gif
transition002.png
transition003.gif
transition003.png
transition004.gif
transition004.png
transition005.gif
transition005.png
transition006.gif
transition006.png
transition007.gif
transition007.png
transition008.gif
transition008.png
transition009.gif
transition009.png
transition010.gif
transition010.png
transition011.gif
transition011.png
transition012.gif
transition012.png
transition013.gif
transition013.png
transition014.gif
transition014.png
transition015.gif
transition015.png
transition016.gif
transition016.png
transition017.gif
transition017.png
transition018.gif
transition018.png
transition019.gif
transition019.png

We are almost ready to go. I have my PNGs as fully-dithered custom-paletted GIFs. Now I need to use gifsicle and create an animation.

About Style

(lack thereof?)

Gifsicle is so custom palette friendly, that it will create a custom palette for each frame if you let it go unchecked. This can add some file size overhead and will make the animation look fantastic. [60.6K]

Unfortunately, the output isn't very GIF-like any more. Part of GIF's serendipity is its charmingly bad color matching. But only if it's subtle and characteristic. Not when it's atrocious and bludgeoning.

Well, we're in luck because the --colors 64 option satisfies these requirements! Gifsicle will find an optimized palette and then just do nearest color matching (no dithering). [30.5K]

$ gifsicle -O9 --colors 64 -d 20 -l1000 transi*.gif > style.gif

Since we are using blues here, we shouldn't go much lower lest we see ugly stuff. [15.9K]

$ gifsicle -O9 --colors 16 -d 20 -l1000 transi*.gif > uglystuff.gif

If all three of these images look almost identical to you, then it's ok. <gloat>Not everyone is in tune with the finer nuances of skillfull craftsmanship</gloat>.

The above shell script can be found here: http://9ol.es/batch.sh.txt