Making Gifs from PNGs with ImageMagick

Posted on 2019, June 27 by Marcos.

It all started when I need a rotation animation for a simple image. Since I suck at image manipulation with GIMP and this seems to be the kind of problem where’s there’s no need to fiddle with Javascript (or download 5000 node dependencies).

In the end it was just a matter of using imagemagick and writing a shell script. To give you an idea on how this works, take your original image and rotate it for, like, 15 degrees. Here’s how it’s done in imagemagick:

$ convert img.png -background 'rgba(0,0,0,0)' -rotate 15 img2.png

Since we’re dealing with transparent pngs make sure to use the -background 'rgba(0,0,0,0)' option. Now how do we repeat this? Well, just use a for loop. Here’s a snipped, we’re iterating with 10 degrees for now (just look at the rot variable).

for i in {00..11}; do
    rot=`expr $i \* 10`
    convert img.png -background 'rgba(0,0,0,0)' -rotate $rot img-$i.png
    echo img-$i.png
done

This will create 11 files named “img-{%d}.png” in the current folder. The next step consists of combining our rotating images into an animation, a quick read at the docs on animations gave me something like this:

#!/usr/bin/env bash

for i in {00..11}; do
    rot=`expr $i \* 5`
    convert img.png -background 'rgba(0,0,0,0)' -rotate $rot img-$i.png
    echo "img-$i.png, ROT: $rot"
done

convert -delay 5 -size 400x458^ -dispose Background \
    -page +0+0 img-00.png \
    -page +0+0 img-01.png \
    -page +0+0 img-02.png \
    -page +0+0 img-03.png \
    -page +0+0 img-04.png \
    -page +0+0 img-05.png \
    -page +0+0 img-06.png \
    -page +0+0 img-07.png \
    -page +0+0 img-08.png \
    -page +0+0 img-09.png \
    -page +0+0 img-10.png \
    -page +0+0 img-11.png \
    -loop 0 animation.gif

Notice the -dispose Background option, without it the buffer won’t be redrawn once a image is composed. But there’s two problems, if you run this you’ll probably get no animation at all. Firt, add this before the for loop:

convert -border 50x50 -bordercolor 'rgba(0,0,0,0)' "$IMG" "b_${IMG}"

It just so happens that our image is not square, which means rotating around its center loses some parts of it. Now, whenever we had references the original image dimentions must be changed to the padded one, since we did a (50,50) padding this will add 100px on both width and heigth. Now our script looks like this:

#!/usr/bin/env bash

convert -border 50x50 -bordercolor 'rgba(0,0,0,0)' "img.png" "b_img.png"

for i in {00..11}; do
    rot=`expr $i \* 5`
    convert b_img.png -background 'rgba(0,0,0,0)' -rotate $rot -crop 500x558+0+0 img-$i.png
    echo "img-$i.png"
done

convert -delay 5 -size 500x558^ -dispose Background \
    -page +0+0 img-00.png \
    -page +0+0 img-01.png \
    -page +0+0 img-02.png \
    -page +0+0 img-03.png \
    -page +0+0 img-04.png \
    -page +0+0 img-05.png \
    -page +0+0 img-06.png \
    -page +0+0 img-07.png \
    -page +0+0 img-08.png \
    -page +0+0 img-09.png \
    -page +0+0 img-10.png -loop 0 animation.gif

Now… what if need to this in another image? You’ll have to change your source code again and that’s really boooring. Well, no need to fret, here’s a gif made the old way and the correponding shell script:

Here it is, a gif made in the old ways:

$ ./create-gif.sh -i img.png -f 10 -d 5 -w 5

#!/usr/bin/env bash

usage()
{
  echo "Usage: $0 -i PATH_TO_IMG
        [ -d DEGREES ]
        [ -w ANIMATION_DELAY]
        [ -f NUM_FRAMES]"
  exit 2
}

build_cmd_list()
{
  for i in img-*.png; do
    CMD_LST="$CMD_LST -page +0+0 $i"
  done
}

IMG=""
FRAMES=11
DEG=10
DELAY=10
CMD_LST=""
CMD=""

while getopts ":d:i:f:w:" opt; do
  case $opt in
    d)
      DEG="$OPTARG"
      ;;
    f)
      FRAMES="$OPTARG"
      ;;
    i)
      IMG="$OPTARG"
      ;;
    w)
      DELAY="$OPTARG"
      ;;
    h|\?)
      usage
      ;;
  esac
done

[ -z $IMG ] && usage

# --------------------------------------
# Main Script Starts Here

convert -border 50x50 -bordercolor 'rgba(0,0,0,0)' "$IMG" "b_${IMG}"
echo "Padding original image in b_$IMG"

r_og=$(identify $IMG | cut -d " " -f 3)
rx_og=$(echo $r_og | cut -d "x" -f 1)
ry_og=$(echo $r_og | cut -d "x" -f 2)

res=$(identify "b_${IMG}" | cut -d " " -f 3)
rx=$(echo $res | cut -d "x" -f 1)
ry=$(echo $res | cut -d "x" -f 2)

echo "Image Selected: $IMG - ($rx_og, $ry_og)"
echo "Padded Version: b_$IMG - ($rx, $ry)"
echo "Rotations: $DEG, Frames: $FRAMES"

for i in `seq -w 0 $FRAMES`; do
  rot=`expr $i \* $DEG`
  convert "b_$IMG" -background 'rgba(0,0,0,0)' -rotate $rot -crop ${rx}x${ry}+0+0 img-$i.png
  echo "img-$i.png created"
done

build_cmd_list
CMD="convert -delay $DELAY -size ${rx}x${ry}^ -dispose Background $CMD_LST -loop 0 animation.gif"
echo $CMD

eval $CMD

echo "Removing tmp images"
rm img-*.png "b_${IMG}"