GIF 动画

这篇展示 Go 标准库的图像包的使用。创建一系列的位图图像,然后将位图序列编码为 GIF 动画。示例要创建的图像叫做利萨如图形(Lissajous-Figure),是20世纪60年代科幻片中的纤维状视觉效果。利萨如图形是参数化的二维谐振曲线,如示波器x轴和y轴馈电输入的两个正弦波。

示例代码

先放上完整的示例:

package mainimport ("image""image/color""image/gif""io""log""math""math/rand""net/http""os""time"
)var palette = []color.Color{color.White, color.Black}const (whiteIndex = 0 // 画板中的第一种颜色blackIndex = 1 // 画板中的下一种颜色
)func main() {rand.Seed(time.Now().UTC().UnixNano())if len(os.Args) > 1 && os.Args[1] == "web" {handler := func(w http.ResponseWriter, r *http.Request) {lissajous(w)}http.HandleFunc("/", handler)log.Fatal(http.ListenAndServe("localhost:8000", nil))return}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 // phase differencsfor 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.1anim.Delay = append(anim.Delay, delay)anim.Image = append(anim.Image, img)}gif.EncodeAll(out, &anim)  // 注意:忽略编码错误
}

lissajous函数

函数有两个嵌套的循环。外层有64个迭代,每个迭代产生一帧。创建一个201×201大小的画板,使用黑白两种颜色。所有的像素值默认设置为0,就是默认的颜色,这里就是白色。每一个内存循环通过设置一些像素为黑色产生一个新的图像。结果用append追加到anim的帧列表中,并且指定80ms的延迟。最后,帧和延迟的序列被编码成GIF格式,然后写入输出流out。
内层循环运行两个振荡器。x方向的振荡器是正弦函数,y方法也是正弦化的。但是它的频率频率相对于x的震动周期是0~3之间的一个随机数。它的相位相对于x的初始值为0,然后随着每个动画帧增加。该循环在x振荡器完成5个完整周期后停止。每一步它都调用SetColorIndex将对应画板上画的(x,y)位置设置为黑色,即值为1。

运行

main函数调用 lissajous 函数,直接写到标准输出,然后用输出重定向指向一个文件名,就生成gif文件了:

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

不过windows貌似不支持gif了。加上web参数调用程序,直接打开浏览器就能查看,每次刷新都是一张新的图形。

浮点数生成 SVG

该篇举了一个浮点绘图运算的例子。根据传入两个参数的函数 z=f(x,y),绘出三维的网线状曲面,绘制过程中运用了可缩放矢量图形(Scalable Vector Graphics, SVG),绘制线条的一种标准XML格式。

示例代码

先放上完整的示例:

// 根据一个三维曲面函数计算并生成SVG,并输出到Web页面
package mainimport ("fmt""io""log""math""net/http"
)const (width, height = 600, 320            // 以像素表示的画布大小cells         = 100                 // 网格单元的个数xyrange       = 30.0                // 坐标轴的范围,-xyrange ~ xyrangexyscale       = width / 2 / xyrange // x 或 y 轴上每个单位长度的像素zscale        = height * 0.4        // z轴上每个单位长度的像素angle         = math.Pi / 6         // x、y轴的角度,30度color         = "grey"              // 线条的颜色
)var sin30, cos30 = math.Sin(angle), math.Cos(angle)func svg(w io.Writer) {fmt.Fprintf(w, "<svg xmlns='http://www.w3.org/2000/svg' "+"style='stroke: %s; fill: white; stroke-width: 0.7' "+"width='%d' height='%d'>", color, 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)fmt.Fprintf(w, "<polygon points='%g,%g %g,%g %g,%g %g,%g'/>\n",ax, ay, bx, by, cx, cy, dx, dy)}}fmt.Fprintln(w, "</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)// 计算曲面高度zz := f(x, y)// 将(x,y,z)等角投射到二维SVG绘图平面上,坐标是(sx,sy)sx := width/2 + (x-y)*cos30*xyscalesy := height/2 + (x+y)*sin30*xyscale - z*zscalereturn sx, sy
}func f(x, y float64) float64 {r := math.Hypot(x, y) // 到(0,0)的距离return math.Sin(r) / r
}func main() {handler := func(w http.ResponseWriter, r *http.Request) {w.Header().Set("Content-Type", "image/svg+xml")svg(w)}http.HandleFunc("/", handler)log.Fatal(http.ListenAndServe("localhost:8000", nil))return
}

说明

corner函数返回两个值,构成网格单元其中一个格子的坐标。
理解这段程序需要一些几何知识。这段程序本质上是三套不同坐标系的相互映射,见下图。首先是一个包含 100×100 个单元的二维网络,每个网络单元用整数坐标 (i, j) 标记,从最远处靠后的角落 (0, 0) 开始。从后向前绘制,就如左侧的图,因而后方的多边形可能被前方的遮住。

再看中间的图,在这个坐标系内,网络由三维浮点数 (x, y, z) 决定,其中x和y由i和j的线性函数决定,经过坐标转换,原点处于中央,并且坐标系按照xyrange进行缩放。高度值z由曲面函数 f(x,y) 决定。
最右边的图,这个坐标系是二维成像绘图平面(image canvas),原点在左上角。这个平面中点的坐标记作 (sx, sy)。这里用等角投影(isometric projection)将三维坐标点 (x, y, z) 映射到二维绘图平面上。若一个点的x值越大,y值越小,则其在绘图平面上看起来就越接近右方。而若一个点的x值或y值越大,且z值越小,则其在绘图平面上看起来就越接近下方。纵向 (x) 与横向 (y) 的缩放系数是由30度角的正弦值和余弦值推导而得。z方向的缩放系数为0.4,是个随意决定的参数值。
回到左边那张图的小图,二维网络中的单元由main函数处理,它算出多边形ABCD在绘图平面上四个顶点的坐标,其中B对应 (i, j) ,A、C、D则为其它三个顶点,然后再输出一条SVG指令将其绘出。

复数分形图

该篇通过复数的计算,生成 PNG 格式的分形图。

复数说明

Go具备了两种大小的复数 complex64 和 complex128,二者分别由 float32 和 float64 构成。内置的 complex 函数根据给定的实部和虚部创建复数,而内置的 real 函数和 imag 函数则分别提取复数的实部和虚部:

var x complex128 = complex(1, 2)  // 1+2i
// x := 1 + 2i
var y complex128 = complex(3, 4)  // 3+4i
// y := 3 + 4i
fmt.Println(x*y)  // -5+10i
fmt.Println(real(x*y))  // -5
fmt.Println(imag(x*y))  // 10fmt.Println(1i * 1i)  // -1

示例代码

先放上完整的示例:

// 生成一个PNG格式的Mandelbrot分形图
package mainimport ("fmt""image""image/color""image/png""math/cmplx""os"
)func main() {const (xmin, ymin, xmax, ymax = -2, -2, +2, +2width, height          = 1024, 1024)img := image.NewRGBA(image.Rect(0, 0, width, height))for py := 0; py < height; py++ {y := float64(py)/height*(ymax-ymin) + yminfor px := 0; px < width; px++ {x := float64(px)/height*(xmax-xmin) + xminz := complex(x, y)// 点(px, py)表示复数值zimg.Set(px, py, mandelbrot(z))}}f, err := os.OpenFile("p1.png", os.O_WRONLY|os.O_CREATE, 0666)if err != nil {fmt.Println("ERROR", err)return}defer f.Close()png.Encode(f, img)  // 注意:忽略错误
}func mandelbrot(z complex128) color.Color {const iterations = 200const contrast = 15var v complex128for n := uint8(0); n < iterations; n++ {v = v*v + zif cmplx.Abs(v) > 2 {return color.Gray{255 - contrast*n}}}return color.Black
}

这个程序用 complex128 运算生成一个 Mandelbrot 集。

说明

两个嵌套循环在 1024×1024 的灰度图上逐行扫描每个点,这个图表示复平面上-2~+2的区域,每个点都对应一个复数,该程序针对各个点反复迭代计算其平方与自身的和,判断其最终能否超出半径为2的圆(取模)。然后根据超出边界所需的迭代次数设定点的灰度。在设定的迭代次数内没有超出的那部分点,这些点属于 Mandelbrot 集,就是黑色的内些部分。最后输出PNG图片。

输出到Web页面

这次将PNG写到img标签里,并且不生成图片文件,而是用base64对图片进行编码:

package mainimport ("encoding/base64""fmt""image""image/color""image/png""log""math/cmplx""net/http"
)var f func(z complex128) color.Colorfunc main() {fmt.Println("http://localhost:8000/?f=newton")handler := func(w http.ResponseWriter, r *http.Request) {if err := r.ParseForm(); err != nil {log.Print(err)}if v, ok := r.Form["f"]; ok {switch v[0] {case "newton", "2":f = newtondefault:f = mandelbrot}}fmt.Fprint(w, `<body>`)fmt.Fprint(w, `<img src="data:image/png;base64,`)createPng(w)fmt.Fprint(w, `" />`)fmt.Fprint(w, `</body>`)}http.HandleFunc("/", handler)log.Fatal(http.ListenAndServe("localhost:8000", nil))return
}func createPng(w http.ResponseWriter) {const (xmin, ymin, xmax, ymax = -2, -2, +2, +2width, height          = 1024, 1024)img := image.NewRGBA(image.Rect(0, 0, width, height))for py := 0; py < height; py++ {y := float64(py)/height*(ymax-ymin) + yminfor px := 0; px < width; px++ {x := float64(px)/height*(xmax-xmin) + xminz := complex(x, y)// 点(px, py)表示复数值zimg.Set(px, py, f(z))}}b64w := base64.NewEncoder(base64.StdEncoding, w)  // 往b64w里写,就是编码后写入到wdefer b64w.Close()png.Encode(b64w, img) // 注意:忽略错误
}func mandelbrot(z complex128) color.Color {const iterations = 200const contrast = 15var v complex128for n := uint8(0); n < iterations; n++ {v = v*v + zif cmplx.Abs(v) > 2 {x := 255 - contrast*nswitch n % 3 {case 0:return color.RGBA{x, 0, 0, x}case 1:return color.RGBA{0, x, 0, x}case 2:return color.RGBA{0, 0, x, x}}}}return color.Black
}// f(x) = x^4 - 1
//
// z' = z - f(z)/f'(z)
//    = z - (z^4 - 1) / (4 * z^3)
//    = z - (z - 1/z^3) / 4
func newton(z complex128) color.Color {const iterations = 37const contrast = 7for i := uint8(0); i < iterations; i++ {z -= (z - 1/(z*z*z)) / 4if cmplx.Abs(z*z*z*z-1) < 1e-6 {// return color.Gray{255 - contrast*i}x := contrast*iswitch i % 3 {case 0:return color.RGBA{x, 0, 0, x}case 1:return color.RGBA{0, x, 0, x}case 2:return color.RGBA{0, 0, x, x}}}}return color.Black
}

这里还增加一个的图形,运用牛顿法求某个函数的复数解(z^4-1=0)。原来的图形这次做成了彩图。

图片格式转换

image 包下有3个子包:

  • image.gif
  • image.jpeg
  • image.png

所以,这3种图片格式是标准库原生支持的。

格式转换

标准库的 image 包导出了 Decode 函数,它从 io.Reader 读取数据,并且识别使用哪一种图像格式来编码数据,调用适当的解碼器,返回 image.Image 对象作为结果。使用 image.Decode 可以构建一个简单的图像转换器,读取某一种格式的图像,然后输出为另外一个格式:

// 读取 PNG 图像,并把它作为 JPEG 图像保存
package mainimport ("fmt""image""image/jpeg"_ "image/png" // 注册 PNG ×××"io""os""path/filepath"
)func main() {fileName := "test"   // 不要扩展名dir, _ := os.Getwd() // 返回当前文件路径的字符串和一个err信息,忽略errpngPath := filepath.Join(dir, fileName+".png")jpgPath := filepath.Join(dir, fileName+".jpg")// 打开 png 文件pngFile, err := os.Open(pngPath)if err != nil {// 文件可能不存在fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)os.Exit(1)}defer pngFile.Close()// 创建 jpg 文件jpgFile, err := os.Create(jpgPath)if err != nil {fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)os.Exit(1)}defer jpgFile.Close()// 调用文件转换if err := toJPEG(pngFile, jpgFile); err != nil {fmt.Fprintf(os.Stderr, "jpeg: %v\n", err)os.Exit(1)}
}func toJPEG(in io.Reader, out io.Writer) error {img, kind, err := image.Decode(in)if err != nil {return err}fmt.Fprintln(os.Stderr, "Input format =", kind)return jpeg.Encode(out, img, &jpeg.Options{Quality: 95})
}

该程序打开一个png文件,再创建一个新的jpg文件,然后进行图像格式的转换。
注意空白导入"image/png"。如果没有这一行,程序可以正常编译和链接,但是不能识别和解码 PNG 格式的输入:

PS H:\Go\src\gopl\ch10\jpeg> go run main.go
jpeg: image: unknown format
exit status 1
PS H:\Go\src\gopl\ch10\jpeg>

这个例子里是解码png格式的图片,程序能识别png格式是因为上面的一行空导入。也可以支持其他格式,并且是同时支持的,只要多导入几个包。具体看下面的展开。

格式解码

接下来解释它是如何工作的。标准库提供 GIF、PNG、JPEG 等格式的解码库,用户自己可以提供其他格式的,但是为了使可执行程序简短,除非明确需要,否则解碼器不会被包含进应用程序。image.Decode 函数查阅一个关于支持格式的表格。每一个表项由4个部分组成:

  • 格式的名字
  • 某种格式中所使用的相同的前缀字符串,用来识别编码格式
  • 一个用来解码被编码图像的函数 Decode
  • 另一个函数 DecodeConfig,它仅仅解码图像的元数据,比如尺寸和色域

对于每一种格式,通常通过在其支持的包的初始化函数中来调用 image.RegisterFormat 来向表格添加项。例如 image.png 中的实现如下:

package png // image/pngfunc Decode(r io.Reader) (image.Image, error)
func DecodeConfig(r io.Reader) (image.Config, error)const pngHeader = "\x89PNG\r\n\x1a\n"
func init() {image.RegisterFormat("png", pngHeader, Decode, DecodeConfig)
}

这个效果就是,一个应用只需要空白导入格式化所需的包,就可以让 image.Decode 函数具备应对格式的解码能力。
所以,可以多导入几个空包,这样程序就可以支持更多格式的解码了。

转载于:https://blog.51cto.com/steed/2356431

GIF、SVG、PNG、图片格式转换相关推荐

  1. python图片保存jpg、show变成bmp_Python 图片格式转换

    图片格式转换可以利用各种软件 今天介绍一下如何使用 Python 实现各种图片格式的转换 1. SVG 转其他格式 读取 SVG 格式文件,需要安装 svglib 库 pip install svgl ...

  2. 【图片格式转换】python实现批量图片格式转换:emf、jpeg等转为png

    [图片格式转换]python批量进行图片格式转换emf.jpeg等转为png 文章目录 [图片格式转换]python批量进行图片格式转换emf.jpeg等转为png 1. 代码 2. 效果 3. 总结 ...

  3. 网页在线图片格式转换源码

    网页在线图片格式转换源码. 免费在线图片格式转化器,可将jpeg.jpg.png.gif. webp.svg.ico.bmp文件转化为jpeg.png.webp.webp动画.gif文件. 无需上传文 ...

  4. 使用Qt实现图片格式转换[领卓教育]

    [领卓教育]使用Qt实现图片格式转换 主要实现代码 int app::save_file(){ QString filename1 = QFileDialog::getSaveFileName(thi ...

  5. ASP.NET2.0图片格式转换【月儿原创】

    ASP.NET2.0图片格式转换 作者:清清月儿 主页:http://blog.csdn.net/21aspnet/           时间:2007.4.20 说明:本文实现了 图片格式随意转换( ...

  6. gif透明背景动画_前端基础系列之bmp、jpg、png、gif、svg常用图片格式浅谈(二)...

    IT客栈 作者:大腰子 bmp.jpg.png.gif.svg常用图片格式 之前为大家介绍了几种WEB前端常用的图片格式,对比了它们的特点,参见<前端基础系列之bmp.jpg.png.gif.s ...

  7. Winform中实现图片格式转换(附代码下载)

    场景 选择一张照片并选择保存位置和要转换的图片格式实现图片格式转换. 项目运行效果 注: 博客主页: https://blog.csdn.net/badao_liumang_qizhi 关注公众号 霸 ...

  8. python gif_python 将png图片格式转换生成gif动画

    先看知乎上面的一个连接 用Python写过哪些[脑洞大开]的小工具? 这个哥们通过爬气象网站的气象雷达图,生成一个gif的动态图.非常有趣且很实用,那咱也实现下. 我们先实现一个从GIF提取帧的代码 ...

  9. 图片格式转换 - .webp 转格式为 .png / .jpg

    图片格式转换 - .webp 转格式为 .png / .jpg 很多时候,网页上素材图片格式出现了.webp格式的,PS无法正常打开查看或使用,所以需要转换一下其他格式(如.png / .jpg 等等 ...

  10. php怎么将网页变成图片格式,php如何实现图片格式转换

    [摘要] PHP即"超文本预处理器",是一种通用开源脚本语言.PHP是在服务器端执行的脚本语言,与C语言类似,是常用的网站编程语言.PHP独特的语法混合了C.Java.Perl以及 ...

最新文章

  1. restful API 常用的四种方式
  2. linux 从命令行自动识别文件并将其打开的命令
  3. 内网 根据计算机名查IP
  4. 任正非就“注册姚安娜商标”事件道歉:为防恶意抢注,第一次公权私用
  5. 最大连续子数组和 动态规划_每日LeetCode,乘积最大子数组
  6. linux rsh配置 A主机无需密码登录B主机
  7. 惠斯通电桥信号调理芯片_变频器通电后无反应,如何检查维修?
  8. flex项目学习包括什么内容
  9. 粉笔画粉笔字体样式_20多种很棒的粉笔字体可供下载
  10. 三星S7edge刷极光ROM的总结_我是亲民_新浪博客
  11. 第一次结对编程作业——需求分析与原型设计
  12. wpf 如何让当前窗口隐藏
  13. 图论最短路 之 弗洛伊德Floyd(详细分析)
  14. HTML系列之文本格式化标签
  15. 最简单的机器学习入门:线性回归
  16. PS如何批量处理图片大小
  17. 基于原子势函数及人工蜂群算法进行形状匹配优化(Matlab代码实现)
  18. linux cadence教程 pdf,cadence入门基础篇
  19. Kubemetes网络插件cni
  20. cmd命令窗口如何创建和删除文件、文件夹,写入内容到文件

热门文章

  1. Linux 运维是做什么的?有钱途吗?发展前景怎么样?
  2. 用VC GDI+画一颗树
  3. Another exception was thrown: The PrimaryScrollController is currently attached to more than one Scr
  4. 杭电计算机组成原理实验RISC-V 实验 取指令及指令译码实验
  5. mini-itx PC:推测Intel D525MW支持UEFI
  6. 【OpenVP* 】Centos 部署OpenVP* 证书+多客户端+密码认证
  7. BIS新增手机测试认证标准 IS16333 (Part 3)2017.6.30 印度语
  8. 如何获取手机的屏幕尺寸
  9. python爬斗鱼直播房间名和主播名_python3爬取斗鱼某些版块的主播人气
  10. html中怎样设置邮件地址,我该如何为自己的域名设置邮箱地址呢?