pheelicks

Introduction to images in Go - part 1

Posted at — Oct 1, 2013

This post is the first in a series. For a listing of all the posts, as well as instructions on running the code, see here. It is meant as an introduction to Go, by way of a bit of simple graphics processing. If you haven’t come across Go before, I’d encourage you to head over to http://golang.org to check it out, in particular going through the excellent online tutorial. At the very least you should be familiar with how to run a simple Go program, see here for details.

Anyway, here’s a preview of the beauty that we will soon bestow upon the world:

Spiral preview

Such artwork is best digested in stages, and as such we’ll be looking at:

Writing an image to a file

Go comes with the image package which makes it easy to write out in a variety of formats. Here’s how to create a blank PNG and write it out to a file:

width, height := 128, 128
m := image.NewRGBA(image.Rect(0, 0, width, height))
out_filename := "blank.png"
out_file, err := os.Create(out_filename)
if err != nil {
  log.Fatal(err)
}
defer out_file.Close()
log.Print("Saving image to: ", out_filename)
png.Encode(out_file, m)

Full blank.go source on github

Adding some color

Now the above is hardly interesting, so let’s actually draw something. The image.RGBA struct we created above is little more than a 2D array of pixels, so to create an image we’ll iterate over them, and set the color depending the position - creating a gradient effect:

func drawGradient(m image.RGBA) {
  size := m.Bounds().Size()
  for x := 0; x < size.X; x++ {
    for y := 0; y < size.Y; y++ {
      color := color.RGBA{
        uint8(255 * x / size.X),
        uint8(255 * y / size.Y),
        55,
        255}
      m.Set(x, y, color)
    }
  }
}

Resulting in:

Gradient preview

Full gradient.go source on github

Creating a Canvas

Wouldn’t it be much nicer if we could just call drawGradient directly on the RGBA instance, rather than passing? Perhaps not, but we’re going to anyway.

Because the RGBA type comes from an external package, we cannot declare methods on it. Instead, we’ll create a new type Canvas, and embed the image.RGBA type inside it. This is done by having the image.RGBA type as an anonymous field when defining the struct, like so:

type Canvas struct {
  image.RGBA
}

What is happening here is pretty neat. By declaring our Canvas type in this way we will be able to access the embedded RGBA instance on a Canvas instance as we’d expect, at canvas.RGBA. However, all the methods that are declared on the RGBA type are now callable on our new Canvas type, and will automatically get passed through to the embedded RGBA type. This is very important, as we are now free to pass the Canvas instance around, as if it were of RGBA type, for example when we pass it to the png.Encode function.

To initialize our new type, we need to first create an instance of it and then initialize the embedded RGBA object.

func NewCanvas(r image.Rectangle) *Canvas {
  canvas := new(Canvas)
  canvas.RGBA = *image.NewRGBA(r)
  return canvas
}

At this point the code should still work with the drawGradient method as long as we change the type of the parameter to Canvas. However, we want to go one step further and make DrawGradient a method on the Canvas type itself:

func (c Canvas)DrawGradient() {
  size := c.Bounds().Size()
  // Rest of function...
}

And we’re done! Now we can call canvas.DrawGradient directly.

Full canvas.go source on github

Be sure to check out Drawing, where we’ll draw lines onto the canvas, as promised…