lissajous 曲线随着参数的不同,曲线的形状也会随之改变。如果每次修改一下参数再生成一张图片,未免也太麻烦了。这一次,我们希望生成一个动态图(GIF,Graphics Interchange Format).

前面我已经学会了将 in-memory 图像编码成某种格式(png, jpeg)的方法,如果要将 in-memory 编码成 gif,那就使用 gif 编码器就好了。同样的,gif 也会有相应的 ​​Encode​​​ 方法。不过,动态图的一个特点是它是由很多很多的帧表示,每一帧都是一帧图像。gif 也提供了一个 ​​EncodeAll​​ 图像,用于将多个 in-memory 编码成 gif.

1. gif 编码

gif 包的 ​​EncodeAll​​ 定义如下:

func EncodeAll(w io.Writer, g *GIF) error

其中 GIF 定义是这样的:

type GIF struct {
Image []*image.Paletted // in-memory 的 slice
Delay []int // 当前帧延时,单位是 10ms
LoopCount int // 循环次数
// ..

可以看到 GIF 这个『结构体』(暂且这么称呼它)里有一个成员是 Image 『数组』。这就好办了,只要我们生成所有不同参数下的 in-memory 图像再塞到这个『数组』就 ok。(注意,这里我尽量避免使用 slice 这个类型,说数组你会更容易理解。实际上在 go 里数组和 slice 是两种不同的东西)

2. 程序

这个程序的路径是 ​​gopl/tutorial/image/lissajous/lissajous2.go​

  • 代码
// lissajous2.go
package main

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

const (
size = 128
nframes = 100
)

func main() {
p := 1.0
q := 2.0
phi := 0.0

palett := []color.Color{color.RGBA{0xee, 0xee, 0xee, 0xff}, color.RGBA{0xff, 0, 0, 0xff}}
rec := image.Rect(0, 0, 2*size, 2*size)

s := float64(size - 10)

step := 0.01
// 生成 100 帧
anim := gif.GIF{LoopCount: 1}
for frames := 0; frames < nframes; frames++ {
img := image.NewPaletted(rec, palett)
for t := -2 * math.Pi; t <= 2*math.Pi; t += 0.0001 {
x := math.Sin(p * t)
y := math.Sin(q*t + phi)
img.SetColorIndex(size+int(s*x+0.5), size+int(s*y+0.5), 1)
}
anim.Image = append(anim.Image, img)
anim.Delay = append(anim.Delay, 10)
// 每一帧 q 递增 0.01,这里相当于 q/p 以 0.01 步长递增
p += step
}

gif.EncodeAll(os.Stdout, &anim)
}
  • 运行
$ go run lissajous2.go


010-lissajous(二)_lissajous


图1

pq=[0.01:0.01:1] p q = [ 0.01 : 0.01 : 1 ]

图 1 所示的动态图,初始值 p=0.01,q=1 p = 0.01 , q = 1 ,每一帧的 p 值增幅是 0.01,也就是说 pq p q

  • append 函数

append 函数是 go 的内建函数,用于给 slice 追加元素。定义如下:

func append(slice []Type, elems ...Type) []Type

append 第一个参数传入 slice 对象,第二个参数类似 C 语言里的变长参数。append 返回一个新的 slice. 如果传入的 slice 容量不足,append 会自动对其扩容。

3. 总结

  • 掌握动态图生成方法
  • 掌握 append 函数

练习:

  1. 尝试不同的相位(​​phi​​ 变量)