18 A GIF decoder: an exercise in Go interfaces 一个GIF解码器:go语言接口训练
A GIF decoder: an exercise in Go interfaces 一个GIF解码器:go语言接口训练
25 May 2011
Introduction
At the Google I/O conference in San Francisco on May 10, 2011, we announced that the Go language is now available on Google App Engine. Go is the first language to be made available on App Engine that compiles directly to machine code, which makes it a good choice for CPU-intensive tasks such as image manipulation.
In that vein, we demonstrated a program called Moustachio that makes it easy to improve a picture such as this one:
by adding a moustache and sharing the result:
All the graphical processing, including rendering the antialiased moustache, is done by a Go program running on App Engine. (The source is available at the appengine-go project.)
Although most images on the web—at least those likely to be moustachioed—are JPEGs, there are countless other formats floating around, and it seemed reasonable for Moustachio to accept uploaded images in a few of them. JPEG and PNG decoders already existed in the Go image library, but the venerable GIF format was not represented, so we decided to write a GIF decoder in time for the announcement. That decoder contains a few pieces that demonstrate how Go's interfaces make some problems easier to solve. The rest of this blog post describes a couple of instances.
The GIF format
First, a quick tour of the GIF format. A GIF image file is paletted, that is, each pixel value is an index into a fixed color map that is included in the file. The GIF format dates from a time when there were usually no more than 8 bits per pixel on the display, and a color map was used to convert the limited set of values into the RGB (red, green, blue) triples needed to light the screen. (This is in contrast to a JPEG, for example, which has no color map because the encoding represents the distinct color signals separately.)
A GIF image can contain anywhere from 1 to 8 bits per pixel, inclusive, but 8 bits per pixel is the most common.
Simplifying somewhat, a GIF file contains a header defining the pixel depth and image dimensions, a color map (256 RGB triples for an 8-bit image), and then the pixel data. The pixel data is stored as a one-dimensional bit stream, compressed using the LZW algorithm, which is quite effective for computer-generated graphics although not so good for photographic imagery. The compressed data is then broken into length-delimited blocks with a one-byte count (0-255) followed by that many bytes:
Deblocking the pixel data
To decode GIF pixel data in Go, we can use the LZW decompressor from the compress/lzw
package. It has a NewReader function that returns an object that, as the documentation says, "satisfies reads by decompressing the data read from r":
func NewReader(r io.Reader, order Order, litWidth int) io.ReadCloser
Here order
defines the bit-packing order and litWidth
is the word size in bits, which for a GIF file corresponds to the pixel depth, typically 8.
But we can't just give NewReader
the input file as its first argument because the decompressor needs a stream of bytes but the GIF data is a stream of blocks that must be unpacked. To address this problem, we can wrap the input io.Reader
with some code to deblock it, and make that code again implement Reader
. In other words, we put the deblocking code into the Read
method of a new type, which we call blockReader
.
Here's the data structure for a blockReader
.
type blockReader struct {
r reader // Input source; implements io.Reader and io.ByteReader.
slice []byte // Buffer of unread data.
tmp [256]byte // Storage for slice.
}
The reader, r
, will be the source of the image data, perhaps a file or HTTP connection. The slice
and tmp
fields will be used to manage the deblocking. Here's the Read
method in its entirety. It's a nice example of the use of slices and arrays in Go.
1 func (b *blockReader) Read(p []byte) (int, os.Error) {
2 if len(p) == 0 {
3 return 0, nil
4 }
5 if len(b.slice) == 0 {
6 blockLen, err := b.r.ReadByte()
7 if err != nil {
8 return 0, err
9 }
10 if blockLen == 0 {
11 return 0, os.EOF
12 }
13 b.slice = b.tmp[0:blockLen]
14 if _, err = io.ReadFull(b.r, b.slice); err != nil {
15 return 0, err
16 }
17 }
18 n := copy(p, b.slice)
19 b.slice = b.slice[n:]
20 return n, nil
21 }
Lines 2-4 are just a sanity check: if there's no place to put data, return zero. That should never happen, but it's good to be safe.
Line 5 asks if there's data left over from a previous call by checking the length of b.slice
. If there isn't, the slice will have length zero and we need to read the next block from r
.
A GIF block starts with a byte count, read on line 6. If the count is zero, GIF defines this to be a terminating block, so we return EOF
on line 11.
Now we know we should read blockLen
bytes, so we point b.slice
to the first blockLen
bytes of b.tmp
and then use the helper function io.ReadFull
to read that many bytes. That function will return an error if it can't read exactly that many bytes, which should never happen. Otherwise we have blockLen
bytes ready to read.
Lines 18-19 copy the data from b.slice
to the caller's buffer. We are implementing Read
, not ReadFull
, so we are allowed to return fewer than the requested number of bytes. That makes it easy: we just copy the data from b.slice
to the caller's buffer (p
), and the return value from copy is the number of bytes transferred. Then we reslice b.slice
to drop the first n
bytes, ready for the next call.
It's a nice technique in Go programming to couple a slice (b.slice
) to an array (b.tmp
). In this case, it means blockReader
type's Read
method never does any allocations. It also means we don't need to keep a count around (it's implicit in the slice length), and the built-in copy
function guarantees we never copy more than we should. (For more about slices, see this post from the Go Blog.)
Given the blockReader
type, we can unblock the image data stream just by wrapping the input reader, say a file, like this:
deblockingReader := &blockReader{r: imageFile}
This wrapping turns a block-delimited GIF image stream into a simple stream of bytes accessible by calls to the Read
method of the blockReader
.
Connecting the pieces
With blockReader
implemented and the LZW compressor available from the library, we have all the pieces we need to decode the image data stream. We stitch them together with this thunderclap, straight from the code:
lzwr := lzw.NewReader(&blockReader{r: d.r}, lzw.LSB, int(litWidth))
if _, err = io.ReadFull(lzwr, m.Pix); err != nil {
break
}
That's it.
The first line creates a blockReader
and passes it to lzw.NewReader
to create a decompressor. Here d.r
is the io.Reader
holding the image data, lzw.LSB
defines the byte order in the LZW decompressor, and litWidth
is the pixel depth.
Given the decompressor, the second line calls io.ReadFull
to decompress the data and store it in the image, m.Pix
. When ReadFull
returns, the image data is decompressed and stored in the image, m
, ready to be displayed.
This code worked first time. Really.
We could avoid the temporary variable lzwr
by placing the NewReader
call into the argument list for ReadFull
, just as we built the blockReader
inside the call to NewReader
, but that might be packing too much into a single line of code.
Conclusion
Go's interfaces make it easy to construct software by assembling piece parts like this to restructure data. In this example, we implemented GIF decoding by chaining together a deblocker and a decompressor using the io.Reader
interface, analogous to a type-safe Unix pipeline. Also, we wrote the deblocker as an (implicit) implementation of a Reader
interface, which then required no extra declaration or boilerplate to fit it into the processing pipeline. It's hard to implement this decoder so compactly yet cleanly and safely in most languages, but the interface mechanism plus a few conventions make it almost natural in Go.
That deserves another picture, a GIF this time:
The GIF format is defined at http://www.w3.org/Graphics/GIF/spec-gif89a.txt.
By Rob Pike
Related articles
- HTTP/2 Server Push
- Introducing HTTP Tracing
- GopherCon 2015 Roundup
- Generating code
- The Go Gopher
- Introducing the Go Race Detector
- Go maps in action
- go fmt your code
- Organizing Go code
- The Go Programming Language turns two
- Debugging Go programs with the GNU Debugger
- The Go image/draw package
- The Go image package
- The Laws of Reflection
- Error handling and Go
- "First Class Functions in Go"
- Profiling Go Programs
- Go at Google I/O 2011: videos
- Introducing Gofix
- Godoc: documenting Go code
- Gobs of data
- C? Go? Cgo!
- JSON and Go
- Go Slices: usage and internals
- Go Concurrency Patterns: Timing out, moving on
- Defer, Panic, and Recover
- Share Memory By Communicating
- JSON-RPC: a tale of interfaces
18 A GIF decoder: an exercise in Go interfaces 一个GIF解码器:go语言接口训练的更多相关文章
- go语言 documentation
Documentation文档 The Go programming language is an open source project to make programmers more pro ...
- 33 Introducing the Go Race Detector
Introducing the Go Race Detector 26 June 2013 Introduction Race conditions are among the most insidi ...
- 32 Profiling Go Programs 分析go语言项目
Profiling Go Programs 分析go语言项目 24 June 2011 At Scala Days 2011, Robert Hundt presented a paper titl ...
- 31 Godoc: documenting Go code 编写良好的文档关于godoc
Godoc: documenting Go code 编写良好的文档关于godoc 31 March 2011 The Go project takes documentation seriousl ...
- 30 C? Go? Cgo!
C? Go? Cgo! 17 March 2011 Introduction Cgo lets Go packages call C code. Given a Go source file writ ...
- 25 The Go image/draw package go图片/描绘包:图片/描绘包的基本原理
The Go image/draw package go图片/描绘包:图片/描绘包的基本原理 29 September 2011 Introduction Package image/draw de ...
- 24 The Go image package go图片包:图片包的基本原理
The Go image package go图片包:图片包的基本原理 21 September 2011 Introduction The image and image/color packag ...
- 23 The Laws of Reflection 反射定律:反射包的基本原理
The Laws of Reflection 反射定律:反射包的基本原理 6 September 2011 Introduction 介绍 Reflection in computing is th ...
- 22 Gobs of data 设计和使用采集数据的包
Gobs of data 24 March 2011 Introduction To transmit a data structure across a network or to store it ...
随机推荐
- Spring点滴六:Spring中定义bean的继承
在基于XML配置元数据中,bean标签可以包含很多配置信息,可以包含构造函数的参数,属性值以及其他一些初始化方法.子bean的定义可以继承父bean定义元数据,子bean定义可以根据需要重写父bean ...
- 【hdu4285】 circuits
http://acm.hdu.edu.cn/showproblem.php?pid=4285 (题目链接) 题意 求不不能嵌套的回路个数为K的路径方案数. Solution 插头dp,时限卡得太紧了, ...
- BZOJ.1036 [ZJOI2008]树的统计Count ( 点权树链剖分 线段树维护和与最值)
BZOJ.1036 [ZJOI2008]树的统计Count (树链剖分 线段树维护和与最值) 题意分析 (题目图片来自于 这里) 第一道树链剖分的题目,谈一下自己的理解. 树链剖分能解决的问题是,题目 ...
- centos6.5搭建LVS+Keepalived
1.配置LVS负载调度器 (1)为eth0配置IP地址,为eth0:0配置VIP地址. vi /etc/sysconfig/network-scripts/ifcfg-eth0 …… DEVICE=e ...
- MySQL 快速入门
MySQL的相关概念介绍 MySQL 为关系型数据库(Relational Database Management System), 这种所谓的"关系型"可以理解为"表格 ...
- Zabbix应用四:Zabbix监控Nginx
利用Zabbix监控Nginx 一.准备nginx监控模版: 1.1.下载nginx监控模版: 点此下载 1.2.导入模版: Zabbix管理页面,选择'配置'->'模版'->'导入': ...
- Hadoop基础-序列化与反序列化(实现Writable接口)
Hadoop基础-序列化与反序列化(实现Writable接口) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.序列化简介 1>.什么是序列化 序列化也称串行化,是将结构化 ...
- JAVA核心技术I---JAVA开发环境配置
一:常常有看到Java SE,Java EE,Java ME,那么他们的区别呢? 1. Java SE(Java Platform,Standard Edition java平台标准版). Java ...
- webapi框架搭建-安全机制(一)
本系列博客链接:webapi框架搭建系列博客 前言 webapi接口是开放给外部使用的,包括接口的地址,传参的规范,还有返回结果的说明.正因为接口的开放性,使得接口的安全很重要.试想一下,用抓包工具( ...
- spring-data-redis,jedis和redis主从集成和遇到的问题
Redis主从加哨兵的部署详见http://www.cnblogs.com/dupang/p/6414365.html spring-data-redis和jedis集成代码总体结构 代码地址http ...