Jamie Oaks bio photo

Jamie Oaks

Evolutionary biologist.

Email Twitter Github Youtube Interweb CV

The power and flexibility of the ImageMagick suite of software never fails to amaze me. Not only does ImageMagick provide several command-line tools capable of meeting all your image editing/conversion needs, there are also APIs for over a dozen languages, including Python, Ruby, and Perl. I should preface this post by declaring that I am not an expert on the use of ImageMagick, but I have used it successfully to script solutions for many of my image-editing needs. One recent task for which it spared me from having to resort to using a GUI-based image editor was converting a series of PDF slides into an animation for one of my recent posts. I wanted to document how I did this for my own memory, and in case someone else might find it useful.

If you want to follow along, create a directory and download the PDF slides:

mkdir fun-with-im
cd fun-with-im
curl -o slides.pdf http://phyletica.org/downloads/dpp-3-slides.pdf

My first step was to use the convert tool to convert the slides into separate PNG files:

convert -density 600 slides.pdf -strip -resize @1048576 PNG8:slide-%02d.png

The -density option determines the resolution (in dots per inch) at which to rasterize the PDF. The -strip option removes any comments or profiles from the output images; I use this option a lot to reduce the file size of images for the web. The -resize option determines the size of the output PNG. This option is very flexible in the arguments it can handle; here I use the @ symbol to specify the area in pixels (ImageMagick will preserve the aspect ratio). To further reduce file size, I specified 8-bit PNG files using the PNG8: syntax. Lastly, the slide-%02d.png syntax in the output file name specifies that I want the slides to be named slide-00.png, slide-01.png, slide-02.png, etc.

Next, we can convert the PNGs into an animated GIF:

convert -layers OptimizePlus -delay 75 slide-0?.png slide-1[01234].png -delay 300 slide-1[567].png -loop 0 slides.gif

I have no idea how the -layers OptimizePlus option works, but it optimizes the final output to reduce the animated file size. The -delay option specifies the number of ticks (the default rate is 100 ticks per second) to pause each image. The options in the command above specify to spend 3/4 of a second on the first 15 slides, and then 3 seconds on the last three slides. The -loop option specifies the number of times the GIF animation should repeat; setting it to zero will cause the GIF to repeat indefinitely.

We can use the same command (minus the loop option) to create a movie out of the PNG images:

convert -layers OptimizePlus -delay 75 slide-0?.png slide-1[01234].png -delay 300 slide-1[567].png slides.mp4

Here, we specified an MP4, but other output formats are supported. I think ImageMagick uses ffmpeg in the background to make the video, so you might have to install it. You can also use ffmpeg directly:

ffmpeg -f gif -i slides.gif slides.mp4

Either way, you should end up with a video like the following:

NOTE: The video is not displaying for some OS/browser combinations. If it’s not working for you, check out the animated GIF below.

The slides animated as a video.

Pretty slick; with just a few simple command lines, we went from a PDF to an animated GIF and movie. Ok, let’s clean up all those intermediate PNGs:

rm slide-??.png

Next, we can make a faux play-button image, which, when used with a little javascript, will make the GIF appear controllable. To make the “play button,” we’ll start with a simple blue triangle, which you can download:

curl -o blue-triangle.png http://phyletica.org/images/play-blue.png

We can use convert to add a shadow to the triangle to make it stand out a bit:

convert blue-triangle.png \( +clone -background black -shadow 80x3+0+8 \) +swap -background none -layers merge +repage play-button.png

This command was taken, almost verbatim, from the fantastic ImageMagick documentation.

Next, we will parse the GIF frames into individual PNG files in order to overlay the play button onto the first frame.

convert -coalesce slides.gif frame-%02d.png

NOTE: I realize we could have avoided this step by simply using the PNG files we just deleted above, but I often have a GIF as the starting point for this task, and so I wanted to document how to convert the GIF into separate frames.

Next, let’s overlay the shadowed play button:

convert frame-00.png play-button.png -gravity center -composite slides.png

Now, slides.png is the first frame of the GIF and looks like it has a “play” button on it. Notice that I named the output “play-button” as slides.png such that the prefix matches the slides.gif file we made above. That was intentional, because now we can use some javascript sleight-of-hand to make the GIF appear controllable on the web. Here’s the javascript magic that I found on codepen:

$(document).ready(function() {
        function() {
            var src = $(this).attr("src");
            $(this).attr("src", src.replace(/\.png$/i, ".gif"));
        function() {
            var src = $(this).attr("src");
            $(this).attr("src", src.replace(/\.gif$/i, ".png"));

Adding this javascript to our page allows us to use the gif-hover class when embedding the PNG/GIF as an image:

<img class="gif-hover" src="dpp-3-example.png"></a>

The result is the following GIF that will “play” when moused over.


A GIF that will "play" when hovered over.

If we prefer to “control” the GIF with mouse clicks, we can modify the javascript a bit:

$(document).ready(function() {
    function swap_to_gif() {
        var src = $(this).attr("src");
        $(this).attr("src", src.replace(/\.png$/i, ".gif"));
        $(this).one("click", swap_to_png);
    function swap_to_png() {
        var src = $(this).attr("src");
        $(this).attr("src", src.replace(/\.gif$/i, ".png"));
        $(this).one("click", swap_to_gif);
    $(".gif-click").one("click", swap_to_gif);

Now, we can use the gif-click class for the image:

<img class="gif-click" src="dpp-3-example.png">

The result is the following GIF that will start/stop with mouse clicks.

A GIF that will "play/stop" when clicked.

Notice, the javascript functions work by simply changing the path from the “play-button” PNG file to the GIF, and vice versa. For this to work, the files need to be in the same directory and share the same prefix. A little hacky, no doubt, but it works.