Intro to (images in) Go – basics

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 out an image to a file
  • Drawing gradients
  • Creating our own Canvas type which extends image.RGBA
  • Creating a Vector type to draw on the Canvas (Drawing)
  • Some simple line and spiral drawing on the Canvas (Drawing)

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…

  • Pingback: pheelicks.com | Intro to (images in) Go – part 2

  • Pingback: Intro to (images in) Go | Rocketboom

  • Pingback: Intro to (images in) Go | Enjoying The Moment

  • juliusfoitzik

    It seems like the code you have on your repo does not compile. I am no expert to go but go build refuses to build the project because every file contains a main function. Compiling a specific file does not work either, since they require other files to be included.

    • pheelicks

      Yes, that is an unfortunate side effect of having multiple examples in one directory. You can it using “go run”, just be sure to include all the dependancies, e.g. “go run lines.go canvas.go vector.go”

      I plan to fix this in the future

  • Craig Weber

    This seems like it would be a lot more useful if you could draw it to the screen rather than saving it to a file. Personally, I would be a lot more interested in that tutorial, as the applications seem more powerful than writing a script to make some kind of image.

  • Jeff Lu

    @pheelicks:disqus do you have an example using canvas that will take a json input to generate the image or the image file? I’m looking for something similar to
    canvas.loadFromJSON(json, () => {
    // add other objects to canvas, then generate the image file
    });