Generate an animated GIF with Golang

This article shows how to generate an animated GIF with Golang in 3 ways.

  • Generate from static images
  • Generate from video
  • Generate programatically

Generate from static images

Let's make an animated GIF from static images.

Supose that we have following static GIF images:

We got:

This animated GIF is generated by following code:

package main

import "image"
import "image/gif"
import "os"

func main() {
    files := []string{"g1.gif", "g2.gif","g3.gif", "g2.gif"}

    // load static image and construct outGif
    outGif := &gif.GIF{}
    for _, name := range files {
        f, _ := os.Open(name)
        inGif, _ := gif.Decode(f)
        f.Close()

        outGif.Image = append(outGif.Image, inGif.(*image.Paletted))
        outGif.Delay = append(outGif.Delay, 0)
    }

    // save to out.gif
    f, _ := os.OpenFile("out.gif", os.O_WRONLY|os.O_CREATE, 0600)
    defer f.Close()
    gif.EncodeAll(f, outGif)
}

Note:

  • Read each frame GIF image with gif.Decode. If we read JPEG images, we have to convert them programatically (goanigiffy does this by calling gif.Encode and gif.Decode).
  • Call gif.EncodeAll to save animated GIF.

Convert video to Animated GIF

Use MPlayer to extract frames, and create animated GIF with goanigiffy (see GoAniGiffy for details).

Programatically

Let's generate animated GIF programatically.

Generate []*image.Paletted programatically and pass it to gif.EncodeAll.

    var images []*image.Paletted
    var delays []int

    // create 20 images and draw circles
    for step := 0; step < 20; step++ {
        img := image.NewPaletted(image.Rect(0, 0, w, h), palette)
        images = append(images, img)
        delays = append(delays, 0)

        // drawing code (too long)
    }

    // save to rgb.gif
    f, _ := os.OpenFile("rgb.gif", os.O_WRONLY|os.O_CREATE, 0600)
    defer f.Close()
    gif.EncodeAll(f, &gif.GIF{
        Image: images,
        Delay: delays,
    })

The entire code is as follows:

package main

import (
    "image"
    "image/color"
    "image/gif"
    "math"
    "os"
)

type Circle struct {
    X, Y, R float64
}

func (c *Circle) Brightness(x, y float64) uint8 {
    var dx, dy float64 = c.X - x, c.Y - y
    d := math.Sqrt(dx*dx+dy*dy) / c.R
    if d > 1 {
        return 0
    } else {
        return 255
    }
}

func main() {
    var w, h int = 240, 240
    var hw, hh float64 = float64(w / 2), float64(h / 2)
    circles := []*Circle{&Circle{}, &Circle{}, &Circle{}}

    var palette = []color.Color{
        color.RGBA{0x00, 0x00, 0x00, 0xff},
        color.RGBA{0x00, 0x00, 0xff, 0xff},
        color.RGBA{0x00, 0xff, 0x00, 0xff},
        color.RGBA{0x00, 0xff, 0xff, 0xff},
        color.RGBA{0xff, 0x00, 0x00, 0xff},
        color.RGBA{0xff, 0x00, 0xff, 0xff},
        color.RGBA{0xff, 0xff, 0x00, 0xff},
        color.RGBA{0xff, 0xff, 0xff, 0xff},
    }

    var images []*image.Paletted
    var delays []int
    steps := 20
    for step := 0; step < steps; step++ {
        img := image.NewPaletted(image.Rect(0, 0, w, h), palette)
        images = append(images, img)
        delays = append(delays, 0)

        θ := 2.0 * math.Pi / float64(steps) * float64(step)
        for i, circle := range circles {
            θ0 := 2 * math.Pi / 3 * float64(i)
            circle.X = hw - 40*math.Sin(θ0) - 20*math.Sin(θ0+θ)
            circle.Y = hh - 40*math.Cos(θ0) - 20*math.Cos(θ0+θ)
            circle.R = 50
        }

        for x := 0; x < w; x++ {
            for y := 0; y < h; y++ {
                img.Set(x, y, color.RGBA{
                    circles[0].Brightness(float64(x), float64(y)),
                    circles[1].Brightness(float64(x), float64(y)),
                    circles[2].Brightness(float64(x), float64(y)),
                    255,
                })
            }
        }
    }

    f, _ := os.OpenFile("rgb.gif", os.O_WRONLY|os.O_CREATE, 0600)
    defer f.Close()
    gif.EncodeAll(f, &gif.GIF{
        Image: images,
        Delay: delays,
    })
}

Thank you for reading.