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() {
$(".gif-hover").hover(
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.