In my endeavours to build a GIF decoder for SerenityOS I soon realised the need for a suite of test GIFs to have reference targets to aim for in my implementation. Initially this involved me browsing r/gifs and the occasional Google Doodle, however it’s quite difficult to find contemporary GIFs that exhibit some of the more obscure GIF functionality. It wasn’t long before a few edge cases and bugs cropped up as people discovered GIFs with as-yet unimplemented features, and as I fixed one, another would break. I needed a better way.
Enter ImageMagick, an extremely powerful open-source platform for creating, converting, manipulating, inspecting, and visualising over 200 image file formats. It turns out that we can use ImageMagick’s command-line interface to conjure animated GIFs out of thin air. Using this we can programmatically and systematically generate a suite of GIFs that exhibit all different kinds of GIF features: perfect for testing a decoder.
You can find the GIF test suite on GitHub1. The rest of this post will give an overview of the ImageMagick command-line interface, driven by examples of GIF generation from the test suite. Each example can be run and modified interactively in the browser using a WebAssembly build of ImageMagick.
Note: the command-line examples use ImageMagick 7 which consolidates all functionality into a single magick
command. All of the examples should also work with ImageMagick 6, but you will likely need to use convert
instead of magick
.
ImageMagick command format
An ImageMagick command is a pipeline of settings and operations that are successively applied to input images to generate an output image. The ordering of command-line parameters is therefore important as they specify this pipeline. A command consists of the following types of parameter:
- At least one input image. This could be an image loaded from file, a frame of an existing GIF, or an image that is “built in” to ImageMagick.
- Optional image operators. These are one-off operations that build up or transform an image. They include various drawing commands and image transforms.
- Optional image settings. These apply to subsequent processing operations unless reset. For GIFs, this includes settings such as the animation frame delay and frame disposal mode.
- Optional image stack commands. These manipulate the set of images that operators apply to.
- Optional output image filename.
Let’s explore these via some examples.
Static GIF
The following simple command draws a pale green circle on a dark green background, and saves the output as a static GIF:
magick -size 100x100 xc:DarkSeaGreen \
-fill PaleGreen -draw "circle 50,50 15,25" \
"static_nontransparent.gif"
Output
Breaking it down:
- [Setting]:
-size 100x100
– set the size of the image to 100x100 pixels. - [Input image]:
xc:DarkSeaGreen
– built-in image2 of solid DarkSeaGreen colour. - [Setting]:
-fill PaleGreen
– sets the fill colour for subsequent drawing operations to PaleGreen. - [Operator]:
-draw "circle 50,50 15,25"
– draws a circle centred at (50,50) whose circumference intersects point (15,25). This is filled to PaleGreen colour as per the previous setting. - [Output image filename]:
static_nontransparent.gif
– write the output to the specified file.
Basic animated GIF
To create an animated GIF, new frames can be added using the -page
operator:
magick -delay 100 \
-size 100x100 xc:transparent \
-fill DarkSeaGreen -draw "circle 50,50 15,25" \
-dispose previous \
-page +10+10 -size 30x30 xc:LightSalmon \
-page +30+30 -size 40x40 xc:SkyBlue \
-page +60+60 -size 30x30 xc:Khaki \
-loop 0 \
"animated_transparent_restoreprev_loop.gif"
Output
Breaking down the differences from the previous example:
- [Setting]:
-delay 100
– sets the delay between showing one frame and the next to 100 ticks. By default a tick is 1/100th of a second, therefore this will result in a 1Hz frame rate. - [Setting]:
-dispose previous
– specifies that the background upon which a frame is drawn should be redrawn when the frame is disposed (i.e. when the next frame is shown). - [Setting]:
-page +10+10
– specifies a new subimage be placed at offset (10,10) pixels from the top-left corner of the image. - [Setting]:
-size 30x30
– sets the size of this subimage to 30x30 pixels. - [Input image]:
xc:LightSalmon
– built-in image2 of LightSalmon colour. - [Setting]:
-loop 0
– specifies that the GIF should loop indefinitely.
Image stacks
If you play around with the above example you may notice that performing any drawing commands on the subimages will actually draw to both the first frame, but also to any subimages that have already been specified. This is because ImageMagick applies operators to all images in its current image stack. We can open and close image stacks using parentheses in order to control which set of images to apply drawing commands to:
Note: in Bash, the parentheses must be escaped with a backslash. This may not be necessary in other shells.
magick -dispose none -delay 100 \
-size 100x100 xc:transparent \
-fill DarkSeaGreen -draw "circle 50,50 15,25" \
-page +10+10 \( -size 30x30 xc:transparent -fill LightSalmon -draw "circle 15,15 5,5" \) \
-page +20+20 \( -size 40x40 xc:transparent -fill SkyBlue -draw "circle 20,20 10,5" \) \
-page +30+30 \( -size 30x30 xc:transparent -fill Khaki -draw "circle 15,15 5,5" \) \
-loop 0 \
"animated_transparent_frame_restoreprev_loop.gif"
Output
Breaking down the differences from the animated GIF example:
- [Setting]:
-dispose none
– overlay subsequent frames on top of each other. - [Setting]:
-page +10+10
– as before, specifies a new subimage be placed at offset (10,10) pixels from the top-left corner of the image. - [Image stack]:
\(
– opens a new stack of images. Subsequent drawing commands will only affect subimages defined within this new stack. - [Setting]:
-size 30x30
– as before, sets the size of this subimage to 30x30 pixels. - [Operator]:
-draw "circle 15,15 5,5"
– draws a circle centred at (15,15) whose circumference intersects point (5,5). - [Image stack]:
\)
– closes the stack. Subsequent drawing commands will no longer affect the subimages defined within the stack that has just closed.
ImageMagick settings applicable to GIFs
Some of the settings that are of particular interest for a GIF test suite include:
-dispose
: this specifies what happens to a GIF frame after it is disposed (i.e. the frame that follows it is displayed). Options here includenone
,background
,previous
. Different disposal modes can be applied to different frames.-interlace
: creates an interlaced GIF3.-loop
: specifies how many times the GIF should loop (0 means indefinitely).-delay
: sets the delay between showing one frame and the next to 100 ticks. By default a tick is 1/100th of a second.
Other useful ImageMagick commands
magick identify --verbose <file>
will output all sorts of details about an image file. For GIFs this will include all of the settings for each frame, whether it’s interlaced, etc.magick +adjoin <input.gif> <output.png>
will output a PNG for each frame of the input GIF.magick +adjoin -coalesce <input.gif> <output.png>
will output a PNG for each frame of the input GIF. Each is the cumulative result of the GIF animation up to that frame (i.e. what you would see as you watched the GIF play).
Try it yourself
Step through an ImageMagick command in your browser using the command-runner below.
Note: the initial magick
command is not required, nor should you specify an output file.
References
The ImageMagick documentation is very thorough and highly recommended if you want to dive into this subject in more detail. Particularly relevant pages include:
- Command-line processing: provides a more detailed anatomy of the ImageMagick command-line.
- Animation basics: particularly useful for understanding GIF disposal modes.