The next program demonstrates basic usage of Go’s standard image packages, which we’ll use to create a sequence of bit-mapped images and then encode the sequence as a GIF animation. The images, called Lissajous figures, were a staple visual effect in sci-fi films of the 1960s. They are the parametric curves produced by harmonic oscillation in two dimensions, such as two sine waves fed into the x and y inputs of an oscilloscope. Figure 1.1 shows some examples.
Figure 1.1. Four Lissajous figures.
There are several new constructs in this code, including
const
declarations, struct types, and composite literals.
Unlike most of our examples, this one also involves floating-point computations.
We’ll discuss these topics only briefly here,
pushing most details off to later chapters, since the primary goal
right now is to give you an idea of what Go looks like and the kinds of
things that can be done easily with the language and its libraries.
// Lissajous generates GIF animations of random Lissajous figures. package main import ( "image" "image/color" "image/gif" "io" "math" "math/rand" "os" ) var palette = []color.Color{color.White, color.Black} const ( whiteIndex = 0 // first color in palette blackIndex = 1 // next color in palette ) func main() { lissajous(os.Stdout) } func lissajous(out io.Writer) { const ( cycles = 5 // number of complete x oscillator revolutions res = 0.001 // angular resolution size = 100 // image canvas covers [-size..+size] nframes = 64 // number of animation frames delay = 8 // delay between frames in 10ms units ) freq := rand.Float64() * 3.0 // relative frequency of y oscillator anim := gif.GIF{LoopCount: nframes} phase := 0.0 // phase difference for i := 0; i < nframes; i++ { rect := image.Rect(0, 0, 2*size+1, 2*size+1) img := image.NewPaletted(rect, palette) for t := 0.0; t < cycles*2*math.Pi; t += res { x := math.Sin(t) y := math.Sin(t*freq + phase) img.SetColorIndex(size+int(x*size+0.5), size+int(y*size+0.5), blackIndex) } phase += 0.1 anim.Delay = append(anim.Delay, delay) anim.Image = append(anim.Image, img) } gif.EncodeAll(out, &anim) // NOTE: ignoring encoding errors }
After importing a package whose path has multiple components,
like image/color
,
we refer to the package with a name that
comes from the last component.
Thus the variable color.White
belongs to
the image/color
package and gif.GIF
belongs to
image/gif
.
A const
declaration (§3.6) gives names to constants,
that is, values that are fixed at
compile time, such as the numerical parameters for cycles, frames, and delay.
Like var
declarations, const
declarations may
appear at package level (so the names are visible throughout the
package) or within a function (so the names are visible only within
that function). The value of a constant must be a number, string, or
boolean.
The expressions []color.Color{...}
and gif.GIF{...}
are
composite literals (§4.2, §4.4.1),
a compact notation for instantiating any of
Go’s composite types from a sequence of element values.
Here, the first one is a slice and the second one is a struct.
The type gif.GIF
is a struct type (§4.4).
A struct is a group of values called fields, often of different types,
that are collected together in a single object that can be treated as a
unit.
The variable anim
is a struct of type gif.GIF
.
The struct literal creates a struct value whose LoopCount
field
is set to nframes
; all other fields have the zero value for their
type.
The individual fields of a struct can be accessed using dot notation,
as in the final two assignments which explicitly update the
Delay
and Image
fields of anim
.
The lissajous
function has two nested loops.
The outer loop runs for 64 iterations, each producing a single frame
of the animation. It creates a new 201×201 image with a palette
of two colors, white and black.
All pixels are initially set to the palette’s zero value
(the zeroth color in the palette), which we set to white.
Each pass through the inner loop generates a new
image by setting some pixels to black. The result is appended, using the
built-in append
function (§4.2.1),
to a list of frames in anim
,
along with a specified delay of 80ms. Finally the sequence of frames
and delays is encoded into GIF format and written to the output stream
out
.
The type of out
is io.Writer
,
which lets us write to a wide range of
possible destinations, as we’ll show soon.
The inner loop runs the two oscillators.
The x oscillator is just the sine function. The y
oscillator is also a sinusoid, but its frequency relative to
the x oscillator is a random number between 0 and 3, and its
phase relative to the x oscillator is initially zero but
increases with each frame of the animation.
The loop runs until the x oscillator has completed five full
cycles.
At each step, it calls SetColorIndex
to color the pixel
corresponding to (x, y) black, which is at position
1 in the palette.
The main
function calls the lissajous
function,
directing it to write to the standard output,
so this command produces an animated
GIF with frames like those in Figure 1.1:
$ go build gopl.io/ch1/lissajous $ ./lissajous >out.gif
Exercise 1.5:
Change the Lissajous program’s color palette to green on black, for
added authenticity.
To create the web color #
RRGGBB
, use
color.RGBA{0x
RR
, 0x
GG
, 0x
BB
, 0xff}
,
where each pair of hexadecimal digits
represents the intensity of the red, green, or blue component of the
pixel.
Exercise 1.6:
Modify the Lissajous program to produce images in multiple colors
by adding more values to palette
and then displaying them
by changing the third argument of SetColorIndex
in some
interesting way.