Golang 第一章 4.动画 GIF

  • By v2ray节点

  • 2024-06-26 10:02:51

  • 评论

下一个程序演示了 Go 标准图像包的基本用法,我们将使用它来创建一系列位图图像,然后将这些图像编码为 GIF 动画。这些图像称为 Lissajous 图形,是 1960 年代科幻电影中的主要视觉效果。它们是由二维谐波振动产生的参数曲线,例如两个正弦波输入示波器的 x 和 y 输入。图 1.1 显示了一些示例。

图 1.1. 四个李萨如图形。

这段代码中有几个新的构造,包括常量声明、结构体类型和复合字面量。与我们大多数示例不同,这个示例还涉及浮点计算。我们在这里只会简要讨论这些主题,把大多数细节留到后面的章节中,因为目前的主要目标是让你了解 Go 的外观以及使用该语言及其库可以轻松完成的各种事情。

https://gopl.io/ch1/lissajous
// 生成随机 Lissajous 图形的 GIF 动画。
package main

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

var palette = []color.Color{color.White, color.Black}

const (
	whiteIndex = 0 // 调色板中的第一个颜色
	blackIndex = 1 // 调色板中的下一种颜色
)

func main() {
	lissajous(os.Stdout)
}
func lissajous(out io.Writer) {
	const (
		cycles  = 5     // 完整的 x 振荡器旋转次数
		res     = 0.001 // 角分辨率
		size    = 100   // 图片画布封面 [-size..+size]
		nframes = 64    // 动画帧数
		delay   = 8     // 帧间延迟以 10ms 为单位
	)
	freq := rand.Float64() * 3.0 // y 振荡器的相对频率
	anim := gif.GIF{LoopCount: nframes}
	phase := 0.0 // 相位差
	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) // 注意:忽略编码错误
}

在导入路径包含多个组件的包后,例如 image/color,我们使用来自最后一个组件的名称来引用该包。因此,变量 color.White 属于 image/color 包,而 gif.GIF 属于 image/gif 包。

const 声明(§3.6)为常量命名,即在编译时固定的值,例如 cycles、frames 和 delay 的数值参数。与 var 声明类似,const 声明可以出现在包级别(这样名称在整个包中可见)或函数内(这样名称仅在该函数内可见)。常量的值必须是数字、字符串或布尔值。

表达式 []color.Color{...} 和 gif.GIF{...} 是复合字面量(§4.2, §4.4.1),这是一种紧凑的表示法,用于从一系列元素值实例化 Go 的任何复合类型。在这里,第一个是切片,第二个是结构体。

类型 gif.GIF 是一种结构体类型(§4.4)。结构体是一组称为字段的值,通常是不同类型的值,它们被收集在一个可以作为单元处理的对象中。变量 anim 是一个 gif.GIF 类型的结构体。结构体字面量创建了一个结构体值,其 LoopCount 字段被设置为 nframes;所有其他字段都有其类型的零值。结构体的各个字段可以使用点表示法访问,如最后两个赋值中明确更新 anim 的 Delay 和 Image 字段。

函数 lissajous 包含两个嵌套循环。外部循环运行 64 次迭代,每次生成动画的一帧。它创建了一个新的 201x201 大小的图像,带有一个包含两种颜色(白色和黑色)的调色板。所有像素最初都设置为调色板的零值(调色板中的第零种颜色,即白色)。每次内部循环通过将某些像素设置为黑色来生成新的图像。结果使用内置的 append 函数(§4.2.1)追加到 anim 的帧列表中,并指定每帧的延迟为 80 毫秒。最后,帧序列和延迟被编码为 GIF 格式并写入输出流 out。输出流 out 的类型是 io.Writer,它让我们能够将数据写入各种可能的目标,我们很快会展示它们。

内部循环驱动两个振荡器。x 振荡器是正弦函数。y 振荡器也是正弦波,但其频率相对于 x 振荡器是一个在 0 到 3 之间的随机数,其相位相对于 x 振荡器最初为零,但随着动画的每一帧增加。该循环在 x 振荡器完成五个完整周期之前运行。在每一步中,它调用 SetColorIndex 将坐标 (x, y) 对应的像素涂成黑色,即调色板中的第 1 种颜色。

主函数调用 lissajous 函数,并指示它将输出写入标准输出,因此此命令生成的动画 GIF 具有类似于图 1.1 中帧的效果:

$ go build gopl.io/ch1/lissajous
$ ./lissajous >out.gif

练习1.5:
将 Lissajous 程序的颜色调色板改为绿色背景黑色前景,以增加真实感。要创建 web 颜色 #RRGGBB,可以使用 color.RGBA{0xRR, 0xGG, 0xBB, 0xff},其中每对十六进制数字表示像素的红色、绿色或蓝色分量的强度。

练习1.6:
通过向调色板添加更多的值并以一些有趣的方式修改 SetColorIndex 函数的第三个参数,修改 Lissajous 程序以生成多种颜色的图像。

v2ray节点购买