代码:
package main
import (
"image"
"image/color"
"image/gif"
"io"
"math"
"math/rand"
"os"
)
//!-main
// Packages not needed by version in book.
import (
"log"
"net/http"
"time"
)
//!+main
var palette = []color.Color{color.White, color.Black}
const (
whiteIndex = 0 // first color in palette
blackIndex = 1 // next color in palette
)
func main() {
//!-main
// The sequence of images is deterministic unless we seed
// the pseudo-random number generator using the current time.
// Thanks to Randall McPherson for pointing out the omission.
rand.Seed(time.Now().UTC().UnixNano())
if len(os.Args) > 1 && os.Args[1] == "web" {
//!+http
handler := func(w http.ResponseWriter, r *http.Request) {
lissajous(w)
}
http.HandleFunc("/", handler)
//!-http
log.Fatal(http.ListenAndServe("localhost:8000", nil))
return
}
//!+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
}
运行结果:
代码解析:
var palette = []color.Color{color.White, color.Black}
这句代码首先声明了一个变量"palette",它是一个颜色(Color)类型的切片(slice)。切片是一种动态数组,可以存储多个元素。在这个例子中,切片中存储了两个颜色,分别是color.White和color.Black。color.White代表白色,color.Black代表黑色。所以这段代码的意思是创建了一个包含白色和黑色两种颜色的调色板。
const (
whiteIndex = 0 // first color in palette
blackIndex = 1 // next color in palette
)
这段代码定义了两个常量,分别是whiteIndex
和blackIndex
。常量是在程序运行期间不可更改的值。whiteIndex
的值为0,它表示调色板中的第一个颜色。blackIndex
的值为1,它表示调色板中的下一个颜色。这些常量的目的是为了提供对调色板中颜色索引的易读性和可维护性。通过使用这些常量,可以在代码中使用whiteIndex
和blackIndex
来表示调色板中的特定颜色,而不必直接使用数字0和1。这样可以使代码更具可读性,并且如果需要更改调色板中的颜色顺序,只需修改常量的值即可,而不必在整个代码中查找和替换数字。其中 const
是 Go 语言中的关键字,用于声明常量。声明语句为:
const identifier [type] = value
rand.Seed(time.Now().UTC().UnixNano())
这段代码是使用Go语言中的rand
包来设置随机数生成器的种子(seed)。在生成随机数时,需要一个种子来初始化随机数生成器,以确保每次运行程序时生成的随机数序列是不同的。time.Now().UTC().UnixNano()
是一个时间相关的表达式,它获取当前时间的UTC时间戳,并以纳秒为单位返回一个整数值。这个整数值作为种子传递给rand.Seed()
函数。通过使用当前时间的纳秒级时间戳作为种子,可以使每次运行程序时生成的随机数序列都是不同的。这样可以增加随机性,使得每次运行程序时得到的随机数更加随机和不可预测。
if len(os.Args) > 1 && os.Args[1] == "web" {
//!+http
handler := func(w http.ResponseWriter, r *http.Request) {
lissajous(w)
}
http.HandleFunc("/", handler)
//!-http
log.Fatal(http.ListenAndServe("localhost:8000", nil))
return
}
这段代码是一个条件语句,用于判断命令行参数是否包含"web"。如果命令行参数中包含"web",则执行相应的代码块。它创建了一个处理函数handler
,该函数接收一个http.ResponseWriter
和一个http.Request
作为参数。w
是一个http.ResponseWriter
类型的变量,它用于向客户端发送HTTP响应。r
是一个指向http.Request
类型的指针变量,它用于获取客户端发送的HTTP请求的信息。通过将http.ResponseWriter
赋值给变量w
,我们可以在处理函数中使用w
来发送HTTP响应。通过将http.Request
赋值给*r
,我们可以通过*r
来访问和操作HTTP请求的信息。他的下一句代码就用到了它:在这个处理函数中,调用了lissajous
函数,并将http.ResponseWriter(w)
作为参数传递给它。http.HandleFunc("/", handler)
是将处理函数handler
注册到默认的HTTP路由器中的根路径("/")上。这意味着当有HTTP请求发送到服务器的根路径时,将调用handler
函数来处理该请求。
log.Fatal(http.ListenAndServe("localhost:8000", nil))
return
这段代码使用http.ListenAndServe
函数来启动一个HTTP服务器,并监听本地主机的8000端口。它将服务器绑定到localhost:8000
地址上,以便接收来自客户端的HTTP请求。http.ListenAndServe
函数是一个阻塞调用,它会一直运行,直到发生错误或服务器被关闭。如果发生错误,它会返回一个非空的错误值。为了处理这种情况,代码使用log.Fatal
函数来打印错误信息并终止程序的执行。在这段代码中,nil
作为第二个参数传递给http.ListenAndServe
函数。这表示我们没有指定一个自定义的路由器,而是使用默认的路由器。默认的路由器会根据请求的URL路径来匹配注册的处理函数,并调用相应的处理函数来处理请求。最后,代码中的return
语句用于显式地结束函数的执行。在这种情况下,它可以用来确保在服务器启动失败时,程序不会继续执行后续的代码。综上所述,这段代码的作用是启动一个HTTP服务器,监听本地主机的8000端口,并使用默认的路由器来处理接收到的HTTP请求。如果启动失败,程序将打印错误信息并终止执行。
lissajous(os.Stdout)
这是在args[1]不为web的情况直接使用输入的值执行函数并将结果输出到终端上。
综上所述,这段main代码的作用是根据命令行参数的不同,以不同的方式展示生成的Lissajous曲线的动画或图像。如果命令行参数中包含"web",则通过HTTP服务器模式在浏览器中展示;否则,在终端上直接输出。
func lissajous(out io.Writer)
这段代码是一个函数声明,函数名为lissajous,它接受一个参数out,类型为io.Writer。函数的作用是生成Lissajous图形,并将结果写入到out中。
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
)
cycles
:表示x轴震荡器完成的完整周期数。res
:表示角度分辨率,即每个点之间的角度差。size
:表示图像画布的大小,范围为[-size, +size]。nframes
:表示动画帧的数量。delay
:表示帧之间的延迟,以10毫秒为单位。
freq := rand.Float64() * 3.0 // relative frequency of y oscillator
anim := gif.GIF{LoopCount: nframes}
phase := 0.0 // phase difference
-
freq := rand.Float64() * 3.0
:这行代码使用rand.Float64()
函数生成一个0到1之间的随机浮点数,并将其乘以3.0,得到一个相对频率(relative frequency)的值。这个相对频率将用于控制y轴震荡器的频率。 -
anim := gif.GIF{LoopCount: nframes}
:这行代码创建了一个gif.GIF
类型的变量anim
,并设置了它的LoopCount
属性为nframes
,即动画的循环次数。 -
phase := 0.0
:这行代码初始化了一个名为phase
的变量,用于表示相位差(phase difference)。
for i := 0; i < nframes; i++
这行代码表示一个循环,从0到nframes-1
,用于生成指定数量的动画帧。
rect := image.Rect(0, 0, 2*size+1, 2*size+1)
这行代码创建了一个image.Rect
类型的变量rect
,表示一个矩形区域,其左上角坐标为(0, 0),右下角坐标为(2size+1, 2size+1)。
img := image.NewPaletted(rect, palette)
这行代码创建了一个新的image.Paletted
类型的变量img
,使用之前定义的rect
作为图像的尺寸,并使用palette
作为调色板。
for t := 0.0; t < cycles*2*math.Pi; t += res
这行代码表示一个循环,从0.0到cycles*2*math.Pi(周长)
,以res
为步长,用于生成Lissajous图形中的每个点的坐标。
x := math.Sin(t)
y := math.Sin(t*freq + phase)
这行代码计算了x坐标,使用了正弦函数math.Sin()
。计算了y坐标,使用了正弦函数,并乘以相对频率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)
这行代码更新相位差phase
的值,每次循环增加0.1。将延迟值delay
添加到动画的延迟序列中。将生成的图像帧img
添加到动画的图像序列中。
综上所述:这段代码的循环过程用于生成Lissajous图形的动画帧。在每个循环中,它创建一个新的图像对象,然后根据循环变量t
计算每个点的坐标,并将对应的像素颜色设置为黑色。然后,它更新相位差phase
的值,将延迟值delay
添加到动画的延迟序列中,并将生成的图像帧添加到动画的图像序列中。最终,循环结束后,就生成了指定数量的动画帧,可以用于播放Lissajous图形的动画。
gif.EncodeAll(out, &anim) // NOTE: ignoring encoding errors
这段代码用于将生成的Lissajous图形动画编码为GIF格式,并将结果写入到out
中。gif.EncodeAll(out, &anim)
:这行代码调用了gif
包中的EncodeAll
函数,将动画anim
编码为GIF格式,并将结果写入到out
中。out
是一个io.Writer
类型的参数,用于指定输出的目标位置