练习 3.1: 如果f函数返回的是无限制的float64值,那么SVG文件可能输出无效的多边形元素(虽然许多SVG渲染器会妥善处理这类问题)。修改程序跳过无效的多边形。
package main
import (
"fmt"
"math"
)
const (
width, height = 600, 320 //画布大小
cells = 100 //单元格的个数
xyrange = 30.0 //坐标轴的范围(-xyrnage..+xyrange)
xyscale = width / 2 / xyrange //x或y轴上每个单位长度的像素
zscale = height * 0.4 //z轴上每个单位长度的像素
angle = math.Pi / 6 //x、y轴的角度(=30°)
)
var sin30, cos30 = math.Sin(angle), math.Cos(angle) // sin(30°),cos(30°)
func main() {
fmt.Printf("<svg xmlns='http://www.w3.org/2000/svg' "+
"style='stroke: grey; fill: white; stroke-width:0.7' "+
"width='%d' height='%d'>", width, height)
for i := 0; i < cells; i++ {
for j := 0; j < cells; j ++ {
ax, ay := corner(i+1, j)
bx, by := corner(i, j)
cx, cy := corner(i, j+1)
dx, dy := corner(i+1, j+1)
if math.IsNaN(ax) || math.IsNaN(ay) || math.IsNaN(bx) || math.IsNaN(by) || math.IsNaN(cx) || math.IsNaN(cy) || math.IsNaN(dx) || math.IsNaN(dy) {
fmt.Errorf("corner() 产生非数值")
} else {
fmt.Printf("<polygon points='%g,%g,%g,%g,%g,%g,%g,%g'/>\n", ax, ay, bx, by, cx, cy, dx, dy)
}
}
}
fmt.Println("</svg>")
}
func corner(i, j int) (float64, float64) {
//求出网格单元(i,j)的顶点坐标(x,y)
x := xyrange * (float64(i)/cells - 0.5)
y := xyrange * (float64(j)/cells - 0.5)
//计算曲面高度z
z := f(x, y)
//将(x, y, z)等角投射到二维SVG绘图平面上,坐标是(sx, sy)
sx := width/2 + (x-y)*cos30*xyscale
sy := height/2 + (x+y)*sin30*xyscale - z*zscale
return sx, sy
}
func f(x, y float64) float64 {
r := math.Hypot(x, y) //到(0,0)的距离
return math.Sin(r) / r
}
练习 3.2: 试验math包中其他函数的渲染图形。你是否能输出一个鸡蛋盒状、雪堆状或马鞍状图案?
//改变函数f为对应的即可查看
package main
import (
"fmt"
"math"
)
const (
width, height = 600, 320 //画布大小
cells = 100 //单元格的个数
xyrange = 30.0 //坐标轴的范围(-xyrnage..+xyrange)
xyscale = width / 2 / xyrange //x或y轴上每个单位长度的像素
zscale = height * 0.4 //z轴上每个单位长度的像素
angle = math.Pi / 6 //x、y轴的角度(=30°)
)
var sin30, cos30 = math.Sin(angle), math.Cos(angle) // sin(30°),cos(30°)
func main() {
fmt.Printf("<svg xmlns='http://www.w3.org/2000/svg' "+
"style='stroke: grey; fill: white; stroke-width:0.7' "+
"width='%d' height='%d'>", width, height)
for i := 0; i < cells; i++ {
for j := 0; j < cells; j ++ {
ax, ay := corner(i+1, j)
bx, by := corner(i, j)
cx, cy := corner(i, j+1)
dx, dy := corner(i+1, j+1)
if math.IsNaN(ax) || math.IsNaN(ay) || math.IsNaN(bx) || math.IsNaN(by) || math.IsNaN(cx) || math.IsNaN(cy) || math.IsNaN(dx) || math.IsNaN(dy) {
continue
} else {
fmt.Printf("<polygon points='%g,%g,%g,%g,%g,%g,%g,%g'/>\n", ax, ay, bx, by, cx, cy, dx, dy)
}
}
}
fmt.Println("</svg>")
}
func corner(i, j int) (float64, float64) {
//求出网格单元(i,j)的顶点坐标(x,y)
x := xyrange * (float64(i)/cells - 0.5)
y := xyrange * (float64(j)/cells - 0.5)
//计算曲面高度z
z := f(x, y)
//将(x, y, z)等角投射到二维SVG绘图平面上,坐标是(sx, sy)
sx := width/2 + (x-y)*cos30*xyscale
sy := height/2 + (x+y)*sin30*xyscale - z*zscale
return sx, sy
}
func f(x, y float64) float64 {//雪堆
r := math.Hypot(x, y) //到(0,0)的距离
return math.Sin(r) / r
}
func eggbox(x, y float64) float64 { //鸡蛋盒
r := 0.2 * (math.Cos(x) + math.Cos(y))
return r
}
func saddle(x, y float64) float64 { //马鞍
a := 25.0
b := 17.0
a2 := a * a
b2 := b * b
r := y*y/a2 - x*x/b2
return r
}
练习 3.3: 根据高度给每个多边形上色,那样峰值部将是红色(#ff0000),谷部将是蓝色(#0000ff)。
//改变函数f为对应的即可查看
package main
import (
"fmt"
"math"
)
const (
width, height = 600, 320 //画布大小
cells = 100 //单元格的个数
xyrange = 30.0 //坐标轴的范围(-xyrnage..+xyrange)
xyscale = width / 2 / xyrange //x或y轴上每个单位长度的像素
zscale = height * 0.4 //z轴上每个单位长度的像素
angle = math.Pi / 6 //x、y轴的角度(=30°)
)
var sin30, cos30 = math.Sin(angle), math.Cos(angle) // sin(30°),cos(30°)
func main() {
z_min, z_max := min_max()
fmt.Printf("<svg xmlns='http://www.w3.org/2000/svg' "+
"style='fill: white; stroke-width:0.7' "+
"width='%d' height='%d'>", width, height)
for i := 0; i < cells; i++ {
for j := 0; j < cells; j ++ {
ax, ay := corner(i+1, j)
bx, by := corner(i, j)
cx, cy := corner(i, j+1)
dx, dy := corner(i+1, j+1)
if math.IsNaN(ax) || math.IsNaN(ay) || math.IsNaN(bx) || math.IsNaN(by) || math.IsNaN(cx) || math.IsNaN(cy) || math.IsNaN(dx) || math.IsNaN(dy) {
continue
} else {
fmt.Printf("<polygon style='stroke: %s;' points='%g,%g %g,%g %g,%g %g,%g'/>\n",
color(i, j, z_min, z_max), ax, ay, bx, by, cx, cy, dx, dy)
}
}
}
fmt.Println("</svg>")
}
// minmax返回给定x和y的最小值/最大值并假设为方域的z的最小值和最大值。
func min_max() (min, max float64) {
min = math.NaN()
max = math.NaN()
for i := 0; i < cells; i++ {
for j := 0; j < cells; j++ {
for xoff := 0; xoff <= 1; xoff++ {
for yoff := 0; yoff <= 1; yoff++ {
x := xyrange * (float64(i+xoff)/cells - 0.5)
y := xyrange * (float64(j+yoff)/cells - 0.5)
z := f(x, y)
if math.IsNaN(min) || z < min {
min = z
}
if math.IsNaN(max) || z > max {
max = z
}
}
}
}
}
return min, max
}
func color(i, j int, zmin, zmax float64) string {
min := math.NaN()
max := math.NaN()
for xoff := 0; xoff <= 1; xoff++ {
for yoff := 0; yoff <= 1; yoff++ {
x := xyrange * (float64(i+xoff)/cells - 0.5)
y := xyrange * (float64(j+yoff)/cells - 0.5)
z := f(x, y)
if math.IsNaN(min) || z < min {
min = z
}
if math.IsNaN(max) || z > max {
max = z
}
}
}
color := ""
if math.Abs(max) > math.Abs(min) {
red := math.Exp(math.Abs(max)) / math.Exp(math.Abs(zmax)) * 255
if red > 255 {
red = 255
}
color = fmt.Sprintf("#%02x0000", int(red))
} else {
blue := math.Exp(math.Abs(min)) / math.Exp(math.Abs(zmin)) * 255
if blue > 255 {
blue = 255
}
color = fmt.Sprintf("#0000%02x", int(blue))
}
return color
}
func corner(i, j int) (float64, float64) {
//求出网格单元(i,j)的顶点坐标(x,y)
x := xyrange * (float64(i)/cells - 0.5)
y := xyrange * (float64(j)/cells - 0.5)
//计算曲面高度z
z := f(x, y)
//将(x, y, z)等角投射到二维SVG绘图平面上,坐标是(sx, sy)
sx := width/2 + (x-y)*cos30*xyscale
sy := height/2 + (x+y)*sin30*xyscale - z*zscale
return sx, sy
}
func f(x, y float64) float64 {//雪堆
r := math.Hypot(x, y) //到(0,0)的距离
return math.Sin(r) / r
}
func eggbox(x, y float64) float64 { //鸡蛋盒
r := 0.2 * (math.Cos(x) + math.Cos(y))
return r
}
func saddle(x, y float64) float64 { //马鞍
a := 25.0
b := 17.0
a2 := a * a
b2 := b * b
r := y*y/a2 - x*x/b2
return r
}
练习 3.4: 参考1.7节Lissajous例子的函数,构造一个web服务器,用于计算函数曲面然后返回SVG数据给客户端。
package main
import (
"fmt"
"io"
"log"
"math"
"net/http"
)
const (
width, height = 600, 320 //画布大小
cells = 100 //单元格的个数
xyrange = 30.0 //坐标轴的范围(-xyrnage..+xyrange)
xyscale = width / 2 / xyrange //x或y轴上每个单位长度的像素
zscale = height * 0.5 //z轴上每个单位长度的像素
angle = math.Pi / 6 //x、y轴的角度(=30°)
)
var sin30, cos30 = math.Sin(angle), math.Cos(angle) // sin(30°),cos(30°)
func main() {
handler := func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "image/svg+xml")
surface(w)
}
http.HandleFunc("/", handler)
log.Fatal(http.ListenAndServe("localhost:1234", nil))
}
func surface(w io.Writer) {
z_min, z_max := min_max()
fmt.Fprintf(w, "<svg xmlns='http://www.w3.org/2000/svg' "+
"style='fill: white; stroke-width:0.7' "+
"width='%d' height='%d'>", width, height)
for i := 0; i < cells; i++ {
for j := 0; j < cells; j ++ {
ax, ay := corner(i+1, j)
bx, by := corner(i, j)
cx, cy := corner(i, j+1)
dx, dy := corner(i+1, j+1)
if math.IsNaN(ax) || math.IsNaN(ay) || math.IsNaN(bx) || math.IsNaN(by) || math.IsNaN(cx) || math.IsNaN(cy) || math.IsNaN(dx) || math.IsNaN(dy) {
continue
} else {
fmt.Fprintf(w, "<polygon style='stroke: %s;' points='%g,%g %g,%g %g,%g %g,%g'/>\n",
color(i, j, z_min, z_max), ax, ay, bx, by, cx, cy, dx, dy)
}
}
}
fmt.Fprintln(w, "</svg>")
}
// minmax返回给定x和y的最小值/最大值并假设为方域的z的最小值和最大值。
func min_max() (min, max float64) {
min = math.NaN()
max = math.NaN()
for i := 0; i < cells; i++ {
for j := 0; j < cells; j++ {
for xoff := 0; xoff <= 1; xoff++ {
for yoff := 0; yoff <= 1; yoff++ {
x := xyrange * (float64(i+xoff)/cells - 0.5)
y := xyrange * (float64(j+yoff)/cells - 0.5)
z := f(x, y)
if math.IsNaN(min) || z < min {
min = z
}
if math.IsNaN(max) || z > max {
max = z
}
}
}
}
}
return min, max
}
func color(i, j int, zmin, zmax float64) string {
min := math.NaN()
max := math.NaN()
for xoff := 0; xoff <= 1; xoff++ {
for yoff := 0; yoff <= 1; yoff++ {
x := xyrange * (float64(i+xoff)/cells - 0.5)
y := xyrange * (float64(j+yoff)/cells - 0.5)
z := f(x, y)
if math.IsNaN(min) || z < min {
min = z
}
if math.IsNaN(max) || z > max {
max = z
}
}
}
color := ""
if math.Abs(max) > math.Abs(min) {
red := math.Exp(math.Abs(max)) / math.Exp(math.Abs(zmax)) * 255
if red > 255 {
red = 255
}
color = fmt.Sprintf("#%02x0000", int(red))
} else {
blue := math.Exp(math.Abs(min)) / math.Exp(math.Abs(zmin)) * 255
if blue > 255 {
blue = 255
}
color = fmt.Sprintf("#0000%02x", int(blue))
}
return color
}
func corner(i, j int) (float64, float64) {
//求出网格单元(i,j)的顶点坐标(x,y)
x := xyrange * (float64(i)/cells - 0.5)
y := xyrange * (float64(j)/cells - 0.5)
//计算曲面高度z
z := f(x, y)
//将(x, y, z)等角投射到二维SVG绘图平面上,坐标是(sx, sy)
sx := width/2 + (x-y)*cos30*xyscale
sy := height/2 + (x+y)*sin30*xyscale - z*zscale
return sx, sy
}
func f(x, y float64) float64 {
r := math.Hypot(x, y) //到(0,0)的距离
return math.Sin(r) / r
}
func eggbox(x, y float64) float64 { //鸡蛋盒
r := 0.2 * (math.Cos(x) + math.Cos(y))
return r
}
func saddle(x, y float64) float64 { //马鞍
a := 25.0
b := 17.0
a2 := a * a
b2 := b * b
r := y*y/a2 - x*x/b2
return r
}
加强版
//浏览器url后面添加eggbox/saddle 会出现对应的图案,默认是雪堆状
package main
import (
"fmt"
"io"
"log"
"math"
"net/http"
)
const (
width, height = 600, 320 //画布大小
cells = 100 //单元格的个数
xyrange = 30.0 //坐标轴的范围(-xyrnage..+xyrange)
xyscale = width / 2 / xyrange //x或y轴上每个单位长度的像素
zscale = height * 0.5 //z轴上每个单位长度的像素
angle = math.Pi / 6 //x、y轴的角度(=30°)
)
var sin30, cos30 = math.Sin(angle), math.Cos(angle) // sin(30°),cos(30°)
type zFunc func(x, y float64) float64
func main() {
http.HandleFunc("/", handler)
http.HandleFunc("/eggbox", eggboxs)
http.HandleFunc("/saddle", saddles)
log.Fatal(http.ListenAndServe("localhost:1234", nil))
}
func handler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "image/svg+xml")
surface(w, "f")
}
func eggboxs(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "image/svg+xml")
surface(w, "eggbox")
}
func saddles(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "image/svg+xml")
surface(w, "saddle")
}
func surface(w io.Writer, fName string) {
var fn zFunc
switch fName {
case "saddle":
fn = saddle
case "eggbox":
fn = eggbox
default:
fn = f
}
z_min, z_max := min_max(fn)
fmt.Fprintf(w, "<svg xmlns='http://www.w3.org/2000/svg' "+
"style='fill: white; stroke-width:0.7' "+
"width='%d' height='%d'>", width, height)
for i := 0; i < cells; i++ {
for j := 0; j < cells; j ++ {
ax, ay := corner(i+1, j, fn)
bx, by := corner(i, j, fn)
cx, cy := corner(i, j+1, fn)
dx, dy := corner(i+1, j+1, fn)
if math.IsNaN(ax) || math.IsNaN(ay) || math.IsNaN(bx) || math.IsNaN(by) || math.IsNaN(cx) || math.IsNaN(cy) || math.IsNaN(dx) || math.IsNaN(dy) {
continue
} else {
fmt.Fprintf(w, "<polygon style='stroke: %s;' points='%g,%g %g,%g %g,%g %g,%g'/>\n",
color(i, j, z_min, z_max), ax, ay, bx, by, cx, cy, dx, dy)
}
}
}
fmt.Fprintln(w, "</svg>")
}
// minmax返回给定x和y的最小值/最大值并假设为方域的z的最小值和最大值。
func min_max(f zFunc) (min, max float64) {
min = math.NaN()
max = math.NaN()
for i := 0; i < cells; i++ {
for j := 0; j < cells; j++ {
for xoff := 0; xoff <= 1; xoff++ {
for yoff := 0; yoff <= 1; yoff++ {
x := xyrange * (float64(i+xoff)/cells - 0.5)
y := xyrange * (float64(j+yoff)/cells - 0.5)
z := f(x, y)
if math.IsNaN(min) || z < min {
min = z
}
if math.IsNaN(max) || z > max {
max = z
}
}
}
}
}
return min, max
}
func color(i, j int, zmin, zmax float64) string {
min := math.NaN()
max := math.NaN()
for xoff := 0; xoff <= 1; xoff++ {
for yoff := 0; yoff <= 1; yoff++ {
x := xyrange * (float64(i+xoff)/cells - 0.5)
y := xyrange * (float64(j+yoff)/cells - 0.5)
z := f(x, y)
if math.IsNaN(min) || z < min {
min = z
}
if math.IsNaN(max) || z > max {
max = z
}
}
}
color := ""
if math.Abs(max) > math.Abs(min) {
red := math.Exp(math.Abs(max)) / math.Exp(math.Abs(zmax)) * 255
if red > 255 {
red = 255
}
color = fmt.Sprintf("#%02x0000", int(red))
} else {
blue := math.Exp(math.Abs(min)) / math.Exp(math.Abs(zmin)) * 255
if blue > 255 {
blue = 255
}
color = fmt.Sprintf("#0000%02x", int(blue))
}
return color
}
func corner(i, j int, f zFunc) (float64, float64) {
//求出网格单元(i,j)的顶点坐标(x,y)
x := xyrange * (float64(i)/cells - 0.5)
y := xyrange * (float64(j)/cells - 0.5)
//计算曲面高度z
z := f(x, y)
//将(x, y, z)等角投射到二维SVG绘图平面上,坐标是(sx, sy)
sx := width/2 + (x-y)*cos30*xyscale
sy := height/2 + (x+y)*sin30*xyscale - z*zscale
return sx, sy
}
func f(x, y float64) float64 {
r := math.Hypot(x, y) //到(0,0)的距离
return math.Sin(r) / r
}
func eggbox(x, y float64) float64 { //鸡蛋盒
r := 0.2 * (math.Cos(x) + math.Cos(y))
return r
}
func saddle(x, y float64) float64 { //马鞍
a := 25.0
b := 17.0
a2 := a * a
b2 := b * b
r := y*y/a2 - x*x/b2
return r
}
练习 3.5: 实现一个彩色的Mandelbrot图像,使用image.NewRGBA创建图像,使用color.RGBA或color.YCbCr生成颜色。
package main
import (
"image"
"image/color"
"image/png"
"math/cmplx"
"os"
)
func main() {
const (
xmin, ymin, xmax, ymax = -2, -2, +2, +2
width, height = 1024, 1024
)
img := image.NewRGBA(image.Rect(0, 0, width, height))
for py := 0; py < height; py++ {
y := float64(py)/height*(ymax-ymin) + ymin
for px := 0; px < width; px++ {
x := float64(px)/width*(xmax-xmin) + xmin
z := complex(x, y)
//点(x,y)表示复数值z
img.Set(px, py, mandelbrot(z))
}
}
png.Encode(os.Stdout, img)
}
func mandelbrot(z complex128) color.Color {
const iterations = 200
const contrast = 15
var v complex128
for n := uint8(0); n < iterations; n++ {
v = v*v + z
if cmplx.Abs(v) > 2 {
return getColor(n)
}
}
return color.Black
}
func getColor(n uint8) color.Color {
paletted := [16]color.Color{
color.RGBA{66, 30, 15, 255}, // # brown 3
color.RGBA{25, 7, 26, 255}, // # dark violett
color.RGBA{9, 1, 47, 255}, //# darkest blue
color.RGBA{4, 4, 73, 255}, //# blue 5
color.RGBA{0, 7, 100, 255}, //# blue 4
color.RGBA{12, 44, 138, 255}, //# blue 3
color.RGBA{24, 82, 177, 255}, //# blue 2
color.RGBA{57, 125, 209, 255}, //# blue 1
color.RGBA{134, 181, 229, 255}, // # blue 0
color.RGBA{211, 236, 248, 255}, // # lightest blue
color.RGBA{241, 233, 191, 255}, // # lightest yellow
color.RGBA{248, 201, 95, 255}, // # light yellow
color.RGBA{255, 170, 0, 255}, // # dirty yellow
color.RGBA{204, 128, 0, 255}, // # brown 0
color.RGBA{153, 87, 0, 255}, // # brown 1
color.RGBA{106, 52, 3, 255}, // # brown 2
}
return paletted[n%16]
}
练习 3.6: 升采样技术可以降低每个像素对计算颜色值和平均值的影响。简单的方法是将每个像素分成四个子像素,实现它。
package main
import (
"image"
"image/color"
"image/png"
"math/cmplx"
"os"
)
func main() {
const (
xmin, ymin, xmax, ymax = -2, -2, +2, +2
width, height = 1024, 1024
epsX = (xmax - xmin) / width
epsY = (ymax - ymin) / height
)
offX := []float64{-epsX, epsX}
offY := []float64{-epsY, epsY}
img := image.NewRGBA(image.Rect(0, 0, width, height))
for py := 0; py < height; py++ {
y := float64(py)/height*(ymax-ymin) + ymin
for px := 0; px < width; px++ {
x := float64(px)/width*(xmax-xmin) + xmin
subPixels := make([]color.Color, 0)
for i := 0; i < 2; i++ {
for j := 0; j < 2; j++ {
z := complex(x+offX[i], y+offY[j])
subPixels = append(subPixels, mandelbrot(z))
}
}
img.Set(px, py, avg(subPixels))
}
}
png.Encode(os.Stdout, img)
}
func mandelbrot(z complex128) color.Color {
const iterations = 200
const contrast = 15
var v complex128
for n := uint8(0); n < iterations; n++ {
v = v*v + z
if cmplx.Abs(v) > 2 {
return getColor(n)
}
}
return color.Black
}
func getColor(n uint8) color.Color {
paletted := [16]color.Color{
color.RGBA{66, 30, 15, 255}, // # brown 3
color.RGBA{25, 7, 26, 255}, // # dark violett
color.RGBA{9, 1, 47, 255}, //# darkest blue
color.RGBA{4, 4, 73, 255}, //# blue 5
color.RGBA{0, 7, 100, 255}, //# blue 4
color.RGBA{12, 44, 138, 255}, //# blue 3
color.RGBA{24, 82, 177, 255}, //# blue 2
color.RGBA{57, 125, 209, 255}, //# blue 1
color.RGBA{134, 181, 229, 255}, // # blue 0
color.RGBA{211, 236, 248, 255}, // # lightest blue
color.RGBA{241, 233, 191, 255}, // # lightest yellow
color.RGBA{248, 201, 95, 255}, // # light yellow
color.RGBA{255, 170, 0, 255}, // # dirty yellow
color.RGBA{204, 128, 0, 255}, // # brown 0
color.RGBA{153, 87, 0, 255}, // # brown 1
color.RGBA{106, 52, 3, 255}, // # brown 2
}
return paletted[n%16]
}
func avg(colors []color.Color) color.Color {
var r, g, b, a uint16
n := len(colors)
for _, c := range colors {
r_, g_, b_, a_ := c.RGBA()
r += uint16(r_ / uint32(n))
g += uint16(g_ / uint32(n))
b += uint16(b_ / uint32(n))
a += uint16(a_ / uint32(n))
}
return color.RGBA64{r, g, b, a}
}
练习 3.7: 另一个生成分形图像的方式是使用牛顿法来求解一个复数方程,例如$z^4-1=0$。每个起点到四个根的迭代次数对应阴影的灰度。方程根对应的点用颜色表示。
package main
import (
"image"
"image/color"
"image/png"
"math"
"math/cmplx"
"os"
)
type Func func(complex128) complex128
var colorPool = []color.RGBA{
{170, 57, 57, 255},
{170, 108, 57, 255},
{34, 102, 102, 255},
{45, 136, 45, 255},
}
var chosenColors = map[complex128]color.RGBA{}
func main() {
const (
xmin, ymin, xmax, ymax = -2, -2, +2, +2
width, height = 1024, 1024
)
img := image.NewRGBA(image.Rect(0, 0, width, height))
for py := 0; py < height; py++ {
y := float64(py)/height*(ymax-ymin) + ymin
for px := 0; px < width; px++ {
x := float64(px)/width*(xmax-xmin) + xmin
z := complex(x, y)
//点(x,y)表示复数值z
//img.Set(px, py, mandelbrot(z))
img.Set(px, py, z4(z))
}
}
png.Encode(os.Stdout, img)
}
func z4(z complex128) color.Color {
f := func(z complex128) complex128 {
return z*z*z*z - 1
}
fPrime := func(z complex128) complex128 {
return (z - 1/(z*z*z)) / 4
}
return newton(z, f, fPrime)
}
func newton(z complex128, f Func, fPrime Func) color.Color {
const iterations = 37
const contrast = 7
for i := uint8(0); i < iterations; i++ {
z -= fPrime(z)
if cmplx.Abs(f(z)) < 1e-6 {
root := complex(round(real(z), 4), round(imag(z), 4))
c, ok := chosenColors[root]
if !ok {
if len(colorPool) == 0 {
panic("no colors left")
}
c = colorPool[0]
colorPool = colorPool[1:]
chosenColors[root] = c
}
y, cb, cr := color.RGBToYCbCr(c.R, c.G, c.B)
scale := math.Log(float64(i)) / math.Log(iterations)
y -= uint8(float64(y) * scale)
return color.YCbCr{y, cb, cr}
}
}
return color.Black
}
func round(f float64, digits int) float64 {
if math.Abs(f) < 0.5 {
return 0
}
pow := math.Pow10(digits)
return math.Trunc(f*pow+math.Copysign(0.5, f)) / pow
}
练习 3.8: 通过提高精度来生成更多级别的分形。使用四种不同精度类型的数字实现相同的分形:complex64、complex128、big.Float和big.Rat。(后面两种类型在math/big包声明。Float是有指定限精度的浮点数;Rat是无限精度的有理数。)它们间的性能和内存使用对比如何?当渲染图可见时缩放的级别是多少?
package main
import (
"image"
"image/color"
"image/png"
"math/big"
"math/cmplx"
"os"
)
func main() {
const (
xmin, ymin, xmax, ymax = -2, -2, +2, +2
width, height = 1024, 1024
)
img := image.NewRGBA(image.Rect(0, 0, width, height))
for py := 0; py < height; py++ {
y := float64(py)/height*(ymax-ymin) + ymin
for px := 0; px < width; px++ {
x := float64(px)/width*(xmax-xmin) + xmin
z := complex(x, y)
img.Set(px, py, mandelbrotBigFloat(z))
}
}
png.Encode(os.Stdout, img)
}
func mandelbrot(z complex128) color.Color {
const iterations = 200
const contrast = 15
var v complex128
for n := uint8(0); n < iterations; n++ {
v = v*v + z
if cmplx.Abs(v) > 2 {
return getColor(n)
}
}
return color.Black
}
func mandelbrot64(z complex128) color.Color {
const iterations = 200
const contrast = 15
var v complex64
for n := uint8(0); n < iterations; n++ {
v = v*v + complex64(z)
if cmplx.Abs(complex128(v)) > 2 {
return getColor(n)
}
}
return color.Black
}
func mandelbrotBigFloat(z complex128) color.Color {
const iterations = 200
const contrast = 15
zR := (&big.Float{}).SetFloat64(real(z))
zI := (&big.Float{}).SetFloat64(imag(z))
var vR, vI = &big.Float{}, &big.Float{}
for i := uint8(0); i < iterations; i++ {
vR2, vI2 := &big.Float{}, &big.Float{}
vR2.Mul(vR, vR).Sub(vR2, (&big.Float{}).Mul(vI, vI)).Add(vR2, zR)
vI2.Mul(vR, vI).Mul(vI2, big.NewFloat(2)).Add(vI2, zI)
vR, vI = vR2, vI2
squareSum := &big.Float{}
squareSum.Mul(vR, vR).Add(squareSum, (&big.Float{}).Mul(vI, vI))
if squareSum.Cmp(big.NewFloat(4)) == 1 {
return getColor(i)
}
}
return color.Black
}
func mandelbrotRat(z complex128) color.Color {
const iterations = 200
const contrast = 15
zR := (&big.Rat{}).SetFloat64(real(z))
zI := (&big.Rat{}).SetFloat64(imag(z))
var vR, vI = &big.Rat{}, &big.Rat{}
for i := uint8(0); i < iterations; i++ {
// (r+i)^2 = r^2 + 2ri + i^2
vR2, vI2 := &big.Rat{}, &big.Rat{}
vR2.Mul(vR, vR).Sub(vR2, (&big.Rat{}).Mul(vI, vI)).Add(vR2, zR)
vI2.Mul(vR, vI).Mul(vI2, big.NewRat(2, 1)).Add(vI2, zI)
vR, vI = vR2, vI2
squareSum := &big.Rat{}
squareSum.Mul(vR, vR).Add(squareSum, (&big.Rat{}).Mul(vI, vI))
if squareSum.Cmp(big.NewRat(4, 1)) == 1 {
return getColor(i)
}
}
return color.Black
}
func getColor(n uint8) color.Color {
paletted := [16]color.Color{
color.RGBA{66, 30, 15, 255}, // # brown 3
color.RGBA{25, 7, 26, 255}, // # dark violett
color.RGBA{9, 1, 47, 255}, //# darkest blue
color.RGBA{4, 4, 73, 255}, //# blue 5
color.RGBA{0, 7, 100, 255}, //# blue 4
color.RGBA{12, 44, 138, 255}, //# blue 3
color.RGBA{24, 82, 177, 255}, //# blue 2
color.RGBA{57, 125, 209, 255}, //# blue 1
color.RGBA{134, 181, 229, 255}, // # blue 0
color.RGBA{211, 236, 248, 255}, // # lightest blue
color.RGBA{241, 233, 191, 255}, // # lightest yellow
color.RGBA{248, 201, 95, 255}, // # light yellow
color.RGBA{255, 170, 0, 255}, // # dirty yellow
color.RGBA{204, 128, 0, 255}, // # brown 0
color.RGBA{153, 87, 0, 255}, // # brown 1
color.RGBA{106, 52, 3, 255}, // # brown 2
}
return paletted[n%16]
}
练习 3.9: 编写一个web服务器,用于给客户端生成分形的图像。运行客户端用过HTTP参数参数指定x,y和zoom参数
package main
import (
"fmt"
"image"
"image/color"
"image/png"
"log"
"math/cmplx"
"net/http"
"strconv"
)
func main() {
const (
width, height = 1024, 1024
)
params := map[string]float64{
"xmin": -2,
"xmax": 2,
"ymin": -2,
"ymax": 2,
"zoom": 1,
}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
for name := range params {
s := r.FormValue(name)
if s == "" {
continue
}
f, err := strconv.ParseFloat(s, 64)
if err != nil {
http.Error(w, fmt.Sprintf("query param %s: %s", name, err), http.StatusBadRequest)
return
}
params[name] = f
}
if params["xmax"] <= params["xmin"] || params["ymax"] <= params["ymin"] {
http.Error(w, fmt.Sprintf("min coordinate greater than max"), http.StatusBadRequest)
return
}
xmin := params["xmin"]
xmax := params["xmax"]
ymin := params["ymin"]
ymax := params["ymax"]
zoom := params["zoom"]
lenX := xmax - xmin
midX := xmin + lenX/2
xmin = midX - lenX/2/zoom
xmax = midX + lenX/2/zoom
lenY := ymax - ymin
midY := ymin + lenY/2
ymin = midY - lenY/2/zoom
ymax = midY + lenY/2/zoom
img := image.NewRGBA(image.Rect(0, 0, width, height))
for py := 0; py < height; py++ {
y := float64(py)/height*(ymax-ymin) + ymin
for px := 0; px < width; px++ {
x := float64(px)/width*(xmax-xmin) + xmin
z := complex(x, y)
img.Set(px, py, mandelbrot(z))
}
}
err := png.Encode(w, img)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
})
log.Fatal(http.ListenAndServe(":1234", nil))
}
func mandelbrot(z complex128) color.Color {
const iterations = 200
const contrast = 15
var v complex128
for n := uint8(0); n < iterations; n++ {
v = v*v + z
if cmplx.Abs(v) > 2 {
return getColor(n)
}
}
return color.Black
}
func getColor(n uint8) color.Color {
paletted := [16]color.Color{
color.RGBA{66, 30, 15, 255}, // # brown 3
color.RGBA{25, 7, 26, 255}, // # dark violett
color.RGBA{9, 1, 47, 255}, //# darkest blue
color.RGBA{4, 4, 73, 255}, //# blue 5
color.RGBA{0, 7, 100, 255}, //# blue 4
color.RGBA{12, 44, 138, 255}, //# blue 3
color.RGBA{24, 82, 177, 255}, //# blue 2
color.RGBA{57, 125, 209, 255}, //# blue 1
color.RGBA{134, 181, 229, 255}, // # blue 0
color.RGBA{211, 236, 248, 255}, // # lightest blue
color.RGBA{241, 233, 191, 255}, // # lightest yellow
color.RGBA{248, 201, 95, 255}, // # light yellow
color.RGBA{255, 170, 0, 255}, // # dirty yellow
color.RGBA{204, 128, 0, 255}, // # brown 0
color.RGBA{153, 87, 0, 255}, // # brown 1
color.RGBA{106, 52, 3, 255}, // # brown 2
}
return paletted[n%16]
}
练习 3.10: 编写一个非递归版本的comma函数,使用bytes.Buffer代替字符串链接操作。
func comma(s string) string {
var buffer bytes.Buffer
l := len(s)
for i := 0; i < len(s) ; i++ {
buffer.WriteString(string(s[i]))
if (i+1)%3 == l%3 { //取余3可以得到第一个插入逗号的位置,后面依次+3即可
buffer.WriteString(",")
}
}
s = buffer.String()
return s[:len(s)-1] // 末尾会多一个逗号,去掉
}
练习 3.11: 完善comma函数,以支持浮点数处理和一个可选的正负号的处理。
// 判断是否有正负号
// 判断是否有小数部分
func comma(s string) string {
var buffer bytes.Buffer
// 获取正负号
var symbol byte
if s[0] == '-' || s[0] == '+' {
symbol = s[0]
s = s[1:]
}
// 将符号添加到返回的字符串中
buffer.WriteByte(symbol)
// 分离整数部分与小数部位
arr := strings.Split(s, ".")
s = arr[0]
l := len(s)
// 格式整数部分
for i := 0; i < len(s); i++ {
buffer.WriteString(string(s[i]))
// 取余3可以得到第一个插入逗号的位置,后面依次+3即可,末尾不加","
if (i+1)%3 == l%3 && i != l-1 {
buffer.WriteString(",")
}
}
// 存在小数部分
if len(arr) > 1 {
buffer.WriteString(".")
buffer.WriteString(arr[1])
}
s = buffer.String()
return s
}
练习 3.12: 编写一个函数,判断两个字符串是否是是相互打乱的,也就是说它们有着相同的字符,但是对应不同的顺序。
func isReverse(a, b string) bool {
// 长度不一样直接返回false
if len(a) != len(b) {
return false
}
// 用于记录每个字符串出现的次数
m := make(map[rune]int)
n := make(map[rune]int)
// 以字符串Unicode码作为map的Key
for _, v := range a {
m[v]++
}
for _, v := range b {
n[v]++
}
// 判断相同下标值是否相同
for i, v := range m {
if n[i] != v {
return false
}
}
return true
}
练习 3.13: 编写 KB、MB 的常量声明,然后扩展到 YB。
const (
KB = 1000
MB = KB * KB
GB = MB * KB
TB = GB * KB
PB = TB * KB
EB = PB * KB
ZB = EB * KB
YB = ZB * KB
)
const (
_ = 1 << (10 * iota)
KiB // 1024
MiB // 1048576
GiB // 1073741824
TiB // 1099511627776 (exceeds 1 << 32)
PiB // 1125899906842624
EiB // 1152921504606846976
ZiB // 1180591620717411303424 (exceeds 1 << 64)
YiB // 1208925819614629174706176
)
const (
KB = 1000
MB = MiB - MiB % (KB * KB)
GB = GiB - GiB % (MB * KB)
TB = TiB - TiB % (GB * KB)
PB = PiB - PiB % (TB * KB)
EB = EiB - EiB % (PB * KB)
ZB = ZiB - ZiB % (EB * KB)
YB = YiB - YiB % (ZB * KB)
)
提示:本系列更新完第三章结束,后续章节请前往金戋博客查看