GIF image creation in Go tutorial

I would like to share with you a straightforward and elegant way of creating GIF images in Go programming language. Many developers are afraid of images creation or manipulation. However, as you will see, generation of GIF images using out-of-the-box Go libraries is just fun.

Basic GIF image

In order to create a GIF image we need packages such as image , image/color and image/gif . The following function creates a GIF image with a given width and height, the white background and a black pixel in the middle of the image. Note that for simplicity of the example I am not handling any error related to GIF encoding.

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

// CreateBasicGif creates a GIF image with the given width and height.
// It uses white background and a black pixel in the middle of the image.
func CreateBasicGif(out io.Writer, width, height int) {

  palette := []color.Color{color.White, color.Black}
  rect := image.Rect(0, 0, width, height)
  img := image.NewPaletted(rect, palette)

  img.SetColorIndex(width/2, height/2, 1)

  anim := gif.GIF{Delay: []int{0}, Image: []*image.Paletted{img}}

  gif.EncodeAll(out, &anim)
}

Saving generated image

So a GIF image creation was pretty easy. A few lines of code and we have a GIF image ready. Now we need to save it. And the fascinating thing is that you can call that function with multiple io.Writer to achieve different saving methods, e.g. save the image directly to a file, to the output stream or a web request response.

Saving an image to a file

This example presents a method of saving an image directly to a file.

import "os"

func main() {
  f, err := os.Create("my-image.gif")
  if err != nil {
    panic(err)
  }
  defer f.Close()

  CreateBasicGif(f, 50, 50)
}

Saving an image to the output stream

This example sends an image to the output stream. You can save a generated image by streaming output of your program to a file e.g. app > image.gif.

import "os"

func main() {
  CreateBasicGif(os.Stdout, 50, 50)
}

More interesting GIF image

Image with a random pattern

In this example, I create several images with grey color shades palette and randomly set pixels. This example is pretty trivial and its primary purpose is to give you an idea of an image creation. I would encourage you to experiment with different algorithms resulting in exciting designs.

Note that I am using math/rand.Seed function to initialize random number generator with the current time in the main function.

package main

import (
  "image"
  "image/color"
  "image/gif"
  "io"
  "math/rand"
  "os"
  "time"
)

func main() {
  rand.Seed(time.Now().UnixNano())
  CreateGif(os.Stdout, 300, 100, 2)
}

// CreateGif creates a GIF image with the given width and height
// and a number of colors of randomly set pixels.
// The image is written to the given out writer.
func CreateGif(out io.Writer, width, height, colors int) {
	palette := GeneratePalette(colors)
	img := GenerateRandomImage(width, height, colors, palette)

	anim := gif.GIF{Delay: []int{0}, Image: []*image.Paletted{img}}

	gif.EncodeAll(out, &anim)
}

// GenerateRandomImage generates a single frame of a gif image
func GenerateRandomImage(width, height, colors int, palette []color.Color) *image.Paletted {
	rect := image.Rect(0, 0, width, height)
	img := image.NewPaletted(rect, palette)

	for y := 0; y < height; y++ {
		for x := 0; x < width; x++ {
			c := rand.Intn(colors)
			img.SetColorIndex(x, y, uint8(c))
		}
	}
	return img
}

// GeneratePalette generates palette with the given number of colors
func GeneratePalette(colors int) (palette []color.Color) {
	// Make sure there are at least two colors: white and black
	if colors < 2 {
		panic("There must be at least two colors")
	}
	// Grey color can have only 256 values (including 0)
	if colors > 256 {
		panic("There are only 256 avilable colors")
	}

	palette = []color.Color{color.Gray{0}}

	// Reduce number of colors as white is always set
	colors--

	for i, delta := 1, 255/colors; i < colors; i++ {
		palette = append(palette, color.Gray{uint8(delta * i)})
	}

	palette = append(palette, color.Gray{255})

	return
}
100 x 100 pixel images with 2 (black and white)
100 x 100 pixel images with 2 (black and white)

Fractal images based on Mandelbrot set

In this example I create several images presenting Mandelbrot fractals. For that purpose I use go-mandelbrot package. I generate and save a GIF image using the above-mentioned approach.

Note that in this example lowest and highest color index use black color. I would encourage you to experiment with different colors to achieve very interesting results.

package main

import (
  "image"
  "image/color"
  "image/gif"
  "io"
  "os"

  mandelbrot "github.com/t-pwk/go-mandelbrot"
)

const (
  width  = 600
  height = 600
)

func main() {

  m := mandelbrot.Mandelbrot(-2+1.25i, 0.5-1.25i, height, width, 255)
  createImage(os.Stdout, height, width, m, 255)
}

func createImage(w io.Writer, heigh, width int, values [][]uint) {
  palette := make([]color.Color, 256)

  for i := 0; i < 256; i++ {
    palette[i] = color.Gray{255 - uint8(i)}
  }

  rect := image.Rect(0, 0, width, height)
  img := image.NewPaletted(rect, palette)

  for y := 0; y < int(height); y++ {
    for x := 0; x < int(width); x++ {
      img.SetColorIndex(x, y, uint8(values[y][x]))
    }
  }

  anim := gif.GIF{Delay: []int{0}, Image: []*image.Paletted{img}}
  gif.EncodeAll(w, &anim)
}

Mandelbrot set, points between -2+1.25i and 0.5-1.25i with 255 iterations
Mandelbrot set, points between -2+1.25i and 0.5-1.25i with 255 iterations
Mandelbrot - Elephant Valley, points between 0.25+0.05i and 0.35-0.05i with 255 iterations
Mandelbrot - Elephant Valley, points between 0.25+0.05i and 0.35-0.05i with 255 iterations

Conclusion

As you can see from the above examples, GIF image creation is not that scary after all. When you look at the Mandelbrot fractal example, you can see that with a minimal amount of code you can generate really breathtaking images.

I would encourage you to visit Go documentation and review different methods related to GIF as well as other image formats.

Happy coding!