All formats

image/gif

LanguageGo (standard library)
LicenseBSD-3-Clause
Repositorygo.googlesource.com/go
Documentationpkg.go.dev/image/gif
Depends onnothing
Read / writeyes / yes; animation, loop count, disposal methods, transparency, local palettes

image/gif decodes and encodes GIF in pure Go. gif.Decode returns the first frame; gif.DecodeAll returns a *gif.GIF with every frame as an *image.Paletted plus per-frame Delay (1/100 s) and Disposal, the LoopCount and the canvas size in Config. gif.EncodeAll writes a *gif.GIF back.

Frames are returned as stored: each *image.Paletted has its own bounds, which can be a sub-rectangle of the canvas. To display frame N you draw frames 0..N onto a canvas yourself. When encoding a non-paletted image, gif.Encode uses Options.Quantizer (default: the fixed Plan 9 palette) and Options.Drawer (default: Floyd-Steinberg dithering).

Installation#

Part of the standard library:

Go
import "image/gif"

Basic usage: decode all frames of an animated GIF#

Go
package main

import (
	"fmt"
	"image/gif"
	"log"
	"os"
)

func main() {
	f, err := os.Open("anim.gif")
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()

	g, err := gif.DecodeAll(f)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("canvas %d x %d, %d frames, loop count %d\n",
		g.Config.Width, g.Config.Height, len(g.Image), g.LoopCount)
	for i, frame := range g.Image {
		fmt.Printf("frame %d: bounds %v, %d colors, delay %d0 ms, disposal %d\n",
			i, frame.Bounds(), len(frame.Palette), g.Delay[i], g.Disposal[i])
	}
}

LoopCount is 0 for “loop forever”, -1 for “play once” (no Netscape block), and N for N extra repetitions.

How to composite GIF frames into full images#

Go
import (
	"image"
	"image/draw"
	"image/gif"
)

// frames returns one full-canvas RGBA image per animation frame.
func frames(g *gif.GIF) []*image.RGBA {
	bounds := image.Rect(0, 0, g.Config.Width, g.Config.Height)
	canvas := image.NewRGBA(bounds)
	var out []*image.RGBA
	for i, frame := range g.Image {
		var saved *image.RGBA
		if g.Disposal[i] == gif.DisposalPrevious {
			saved = image.NewRGBA(bounds)
			draw.Draw(saved, bounds, canvas, image.Point{}, draw.Src)
		}
		draw.Draw(canvas, frame.Bounds(), frame, frame.Bounds().Min, draw.Over)

		snapshot := image.NewRGBA(bounds)
		draw.Draw(snapshot, bounds, canvas, image.Point{}, draw.Src)
		out = append(out, snapshot)

		switch g.Disposal[i] {
		case gif.DisposalBackground:
			draw.Draw(canvas, frame.Bounds(), image.Transparent, image.Point{}, draw.Src)
		case gif.DisposalPrevious:
			canvas = saved
		}
	}
	return out
}

How to create an animated GIF#

Go
package main

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

func main() {
	palette := color.Palette{color.White, color.Black, color.RGBA{255, 0, 0, 255}}
	anim := &gif.GIF{LoopCount: 0} // loop forever

	for i := 0; i < 20; i++ {
		img := image.NewPaletted(image.Rect(0, 0, 100, 100), palette)
		for x := 0; x < 100; x++ {
			img.SetColorIndex(x, i*5, 2) // a red line moving down
		}
		anim.Image = append(anim.Image, img)
		anim.Delay = append(anim.Delay, 5) // 50 ms
		anim.Disposal = append(anim.Disposal, gif.DisposalNone)
	}

	f, err := os.Create("anim.gif")
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()
	if err := gif.EncodeAll(f, anim); err != nil {
		log.Fatal(err)
	}
}

How to convert a PNG or JPEG to GIF#

gif.Encode quantizes and dithers non-paletted images. Pass a quantizer for an adaptive palette:

Go
import (
	"image"
	"image/draw"
	"image/gif"
	_ "image/jpeg"
	_ "image/png"
	"os"
)

func toGIF(src image.Image, path string) error {
	f, err := os.Create(path)
	if err != nil {
		return err
	}
	defer f.Close()
	return gif.Encode(f, src, &gif.Options{
		NumColors: 256,
		Drawer:    draw.FloydSteinberg, // or draw.Src for no dithering
		// Quantizer: quantize.MedianCutQuantizer{}, // github.com/ericpauley/go-quantize
	})
}

Tips#