A GIF decoder: an exercise in Go interfaces
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
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:
相關推薦
A GIF decoder: an exercise in Go interfaces
25 May 2011 Introduction At the Google I/O conference in San Francisco on May 10, 2011, we announced that the Go
How big is an int in Go?
How big is an int in Go?Go has several built-in numeric types, “sets of integer or floating-point values.” Some architecture-independent types are uint8 (8
Tinyterm: A silly terminal emulator written in Go
This post is about Tinyterm, a silly hack that I presented as a lightning talk at last month’s Sydney Go User group 1. You can find the original slide
Building a Network Command Line Interface in Go
In this article, we are going to be building a very simple Command Line Interface in Go using the urfave/cli package available on Github here: http
A Proposal for Package Versioning in Go
26 March 2018 Introduction Eight years ago, the Go team introduced goinstall (which led to go get) a
pytorch報錯:UserWarning: invalid index of a 0-dim tensor. This will be an error in PyTorch 0.5.。。原因
UserWarning: invalid index of a 0-dim tensor. This will be an error in PyTorch 0.5. Use tensor.item() to convert a 0-dim tensor to a Python number
"The conversion of a datetime2 data type to a datetime data type resulted in an out-of-range value
這句話的意思是將datetime2資料型別轉換為datetime資料型別會導致超出範圍的值。宣告已經終止。 在使用EF插入資料是發生列轉換的錯誤,搞了好久,不知道問題出在哪裡! 根據提示的錯誤資訊來看是Datetime資料型別出現錯誤 後來發現 public Nullable<S
Yet another tool to mock interfaces in Go
Yet another tool to mock interfaces in Go https://itnext.io/yet-another-tool-to-mock-interfaces-in-go-73de1b02c041 As a powerful tool, uni
An Introduction to Testing in Go
Testing is hugely important in all software. Being able to ensure the correctness of your code and ensure that any changes you make don’t end up br
[PWA] Show an Error when a POST or DELETE Fails in an Offline PWA
We're storing JSON data in the cache - but that only applies to HTTP GET requests - and not to POST or DELETE requests. We'l
golang Iterate through the fields of a struct in Go
package main import ( "fmt" "reflect" ) type Log struct { Path string Level string } func (l *Log) Conversion(i interface{}) { if data, ok :=
jetty訪問jsp頁面出現PWC6345: There is an error in invoking javac. A full JDK (not just JRE) is required
應用場景: 專案架構: Emmbed Jetty + Spring + JSP+Maven。 專案通過maven-shade-plugin外掛打為jar包,通過命令:“java -jar xxx.jar” 方式執行。 注意:伺服器端安裝JRE環境,而非JDK。如果安裝JD
Troubleshoot Connecting to an Instance in a VPC
Amazon Web Services is Hiring. Amazon Web Services (AWS) is a dynamic, growing business unit within Amazon.com. We are currently hiring So
Two Go Talks: "Lexical Scanning in Go" and "Cuddle: an App Engine Demo"
1 September 2011 On Tuesday night Rob Pike and Andrew Gerrand each presented at the Sydney Google Technology
Meet Moq: Easily mock interfaces in Go
Meet Moq: Easily mock interfaces in GoMoq is an Interface mocking tool for go generate — github.com/matryer/moqIn my recent talk about Idiomatic Go Tricks
[Javascript] Identify the most important words in a document using tf-idf in Natural
req blog reac lis log require div add frequency Tf-idf, or term frequency-inverse document frequency, is a statistic that indicates how i
Search an Element in an array
integer const ase nat ted dex clas pre ring Given an integer array and an element x, find if element is present in array or not. If eleme
MySQL錯誤[ERR] 1064 - You have an error in your SQL syntax;
class creator set eight bold ont global creators nbsp MySQL打開創建函數的開關, 查詢創建函數開關是否打開: show variables like ‘%func%‘; 打開創建函數開關: SET GLOB
What happens when you type an URL in the browser and press enter?
address url log pre gpo hand ted ane into What happens when you type an URL in the browser and press enter? 1. You type maps.google.com
理解golang反射(reflection in Go)
golang reflect golang反射 go反射機制 反射(reflection)是指在運行時,動態獲取程序結構信息(元信息)的一種能力,是靜態類型語言都支持的一種特性,如Java, golang等。這裏主要詳細介紹golang reflection相關知識類型與接口(Types and