summaryrefslogtreecommitdiff
path: root/src/image/gif
diff options
context:
space:
mode:
Diffstat (limited to 'src/image/gif')
-rw-r--r--src/image/gif/reader.go465
-rw-r--r--src/image/gif/reader_test.go247
-rw-r--r--src/image/gif/writer.go333
-rw-r--r--src/image/gif/writer_test.go227
4 files changed, 1272 insertions, 0 deletions
diff --git a/src/image/gif/reader.go b/src/image/gif/reader.go
new file mode 100644
index 000000000..5a863e204
--- /dev/null
+++ b/src/image/gif/reader.go
@@ -0,0 +1,465 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package gif implements a GIF image decoder and encoder.
+//
+// The GIF specification is at http://www.w3.org/Graphics/GIF/spec-gif89a.txt.
+package gif
+
+import (
+ "bufio"
+ "compress/lzw"
+ "errors"
+ "fmt"
+ "image"
+ "image/color"
+ "io"
+)
+
+var (
+ errNotEnough = errors.New("gif: not enough image data")
+ errTooMuch = errors.New("gif: too much image data")
+ errBadPixel = errors.New("gif: invalid pixel value")
+)
+
+// If the io.Reader does not also have ReadByte, then decode will introduce its own buffering.
+type reader interface {
+ io.Reader
+ io.ByteReader
+}
+
+// Masks etc.
+const (
+ // Fields.
+ fColorMapFollows = 1 << 7
+
+ // Image fields.
+ ifLocalColorTable = 1 << 7
+ ifInterlace = 1 << 6
+ ifPixelSizeMask = 7
+
+ // Graphic control flags.
+ gcTransparentColorSet = 1 << 0
+)
+
+// Section indicators.
+const (
+ sExtension = 0x21
+ sImageDescriptor = 0x2C
+ sTrailer = 0x3B
+)
+
+// Extensions.
+const (
+ eText = 0x01 // Plain Text
+ eGraphicControl = 0xF9 // Graphic Control
+ eComment = 0xFE // Comment
+ eApplication = 0xFF // Application
+)
+
+// decoder is the type used to decode a GIF file.
+type decoder struct {
+ r reader
+
+ // From header.
+ vers string
+ width int
+ height int
+ flags byte
+ headerFields byte
+ backgroundIndex byte
+ loopCount int
+ delayTime int
+
+ // Unused from header.
+ aspect byte
+
+ // From image descriptor.
+ imageFields byte
+
+ // From graphics control.
+ transparentIndex byte
+ hasTransparentIndex bool
+
+ // Computed.
+ pixelSize uint
+ globalColorMap color.Palette
+
+ // Used when decoding.
+ delay []int
+ image []*image.Paletted
+ tmp [1024]byte // must be at least 768 so we can read color map
+}
+
+// blockReader parses the block structure of GIF image data, which
+// comprises (n, (n bytes)) blocks, with 1 <= n <= 255. It is the
+// reader given to the LZW decoder, which is thus immune to the
+// blocking. After the LZW decoder completes, there will be a 0-byte
+// block remaining (0, ()), which is consumed when checking that the
+// blockReader is exhausted.
+type blockReader struct {
+ r reader
+ slice []byte
+ err error
+ tmp [256]byte
+}
+
+func (b *blockReader) Read(p []byte) (int, error) {
+ if b.err != nil {
+ return 0, b.err
+ }
+ if len(p) == 0 {
+ return 0, nil
+ }
+ if len(b.slice) == 0 {
+ var blockLen uint8
+ blockLen, b.err = b.r.ReadByte()
+ if b.err != nil {
+ return 0, b.err
+ }
+ if blockLen == 0 {
+ b.err = io.EOF
+ return 0, b.err
+ }
+ b.slice = b.tmp[0:blockLen]
+ if _, b.err = io.ReadFull(b.r, b.slice); b.err != nil {
+ return 0, b.err
+ }
+ }
+ n := copy(p, b.slice)
+ b.slice = b.slice[n:]
+ return n, nil
+}
+
+// decode reads a GIF image from r and stores the result in d.
+func (d *decoder) decode(r io.Reader, configOnly bool) error {
+ // Add buffering if r does not provide ReadByte.
+ if rr, ok := r.(reader); ok {
+ d.r = rr
+ } else {
+ d.r = bufio.NewReader(r)
+ }
+
+ err := d.readHeaderAndScreenDescriptor()
+ if err != nil {
+ return err
+ }
+ if configOnly {
+ return nil
+ }
+
+ if d.headerFields&fColorMapFollows != 0 {
+ if d.globalColorMap, err = d.readColorMap(); err != nil {
+ return err
+ }
+ }
+
+ for {
+ c, err := d.r.ReadByte()
+ if err != nil {
+ return err
+ }
+ switch c {
+ case sExtension:
+ if err = d.readExtension(); err != nil {
+ return err
+ }
+
+ case sImageDescriptor:
+ m, err := d.newImageFromDescriptor()
+ if err != nil {
+ return err
+ }
+ useLocalColorMap := d.imageFields&fColorMapFollows != 0
+ if useLocalColorMap {
+ m.Palette, err = d.readColorMap()
+ if err != nil {
+ return err
+ }
+ } else {
+ m.Palette = d.globalColorMap
+ }
+ if d.hasTransparentIndex && int(d.transparentIndex) < len(m.Palette) {
+ if !useLocalColorMap {
+ // Clone the global color map.
+ m.Palette = append(color.Palette(nil), d.globalColorMap...)
+ }
+ m.Palette[d.transparentIndex] = color.RGBA{}
+ }
+ litWidth, err := d.r.ReadByte()
+ if err != nil {
+ return err
+ }
+ if litWidth < 2 || litWidth > 8 {
+ return fmt.Errorf("gif: pixel size in decode out of range: %d", litWidth)
+ }
+ // A wonderfully Go-like piece of magic.
+ br := &blockReader{r: d.r}
+ lzwr := lzw.NewReader(br, lzw.LSB, int(litWidth))
+ defer lzwr.Close()
+ if _, err = io.ReadFull(lzwr, m.Pix); err != nil {
+ if err != io.ErrUnexpectedEOF {
+ return err
+ }
+ return errNotEnough
+ }
+ // Both lzwr and br should be exhausted. Reading from them
+ // should yield (0, io.EOF).
+ if n, err := lzwr.Read(d.tmp[:1]); n != 0 || err != io.EOF {
+ if err != nil {
+ return err
+ }
+ return errTooMuch
+ }
+ if n, err := br.Read(d.tmp[:1]); n != 0 || err != io.EOF {
+ if err != nil {
+ return err
+ }
+ return errTooMuch
+ }
+
+ // Check that the color indexes are inside the palette.
+ if len(m.Palette) < 256 {
+ for _, pixel := range m.Pix {
+ if int(pixel) >= len(m.Palette) {
+ return errBadPixel
+ }
+ }
+ }
+
+ // Undo the interlacing if necessary.
+ if d.imageFields&ifInterlace != 0 {
+ uninterlace(m)
+ }
+
+ d.image = append(d.image, m)
+ d.delay = append(d.delay, d.delayTime)
+ // The GIF89a spec, Section 23 (Graphic Control Extension) says:
+ // "The scope of this extension is the first graphic rendering block
+ // to follow." We therefore reset the GCE fields to zero.
+ d.delayTime = 0
+ d.hasTransparentIndex = false
+
+ case sTrailer:
+ if len(d.image) == 0 {
+ return io.ErrUnexpectedEOF
+ }
+ return nil
+
+ default:
+ return fmt.Errorf("gif: unknown block type: 0x%.2x", c)
+ }
+ }
+}
+
+func (d *decoder) readHeaderAndScreenDescriptor() error {
+ _, err := io.ReadFull(d.r, d.tmp[0:13])
+ if err != nil {
+ return err
+ }
+ d.vers = string(d.tmp[0:6])
+ if d.vers != "GIF87a" && d.vers != "GIF89a" {
+ return fmt.Errorf("gif: can't recognize format %s", d.vers)
+ }
+ d.width = int(d.tmp[6]) + int(d.tmp[7])<<8
+ d.height = int(d.tmp[8]) + int(d.tmp[9])<<8
+ d.headerFields = d.tmp[10]
+ d.backgroundIndex = d.tmp[11]
+ d.aspect = d.tmp[12]
+ d.loopCount = -1
+ d.pixelSize = uint(d.headerFields&7) + 1
+ return nil
+}
+
+func (d *decoder) readColorMap() (color.Palette, error) {
+ if d.pixelSize > 8 {
+ return nil, fmt.Errorf("gif: can't handle %d bits per pixel", d.pixelSize)
+ }
+ numColors := 1 << d.pixelSize
+ if d.imageFields&ifLocalColorTable != 0 {
+ numColors = 1 << ((d.imageFields & ifPixelSizeMask) + 1)
+ }
+ numValues := 3 * numColors
+ _, err := io.ReadFull(d.r, d.tmp[0:numValues])
+ if err != nil {
+ return nil, fmt.Errorf("gif: short read on color map: %s", err)
+ }
+ colorMap := make(color.Palette, numColors)
+ j := 0
+ for i := range colorMap {
+ colorMap[i] = color.RGBA{d.tmp[j+0], d.tmp[j+1], d.tmp[j+2], 0xFF}
+ j += 3
+ }
+ return colorMap, nil
+}
+
+func (d *decoder) readExtension() error {
+ extension, err := d.r.ReadByte()
+ if err != nil {
+ return err
+ }
+ size := 0
+ switch extension {
+ case eText:
+ size = 13
+ case eGraphicControl:
+ return d.readGraphicControl()
+ case eComment:
+ // nothing to do but read the data.
+ case eApplication:
+ b, err := d.r.ReadByte()
+ if err != nil {
+ return err
+ }
+ // The spec requires size be 11, but Adobe sometimes uses 10.
+ size = int(b)
+ default:
+ return fmt.Errorf("gif: unknown extension 0x%.2x", extension)
+ }
+ if size > 0 {
+ if _, err := io.ReadFull(d.r, d.tmp[0:size]); err != nil {
+ return err
+ }
+ }
+
+ // Application Extension with "NETSCAPE2.0" as string and 1 in data means
+ // this extension defines a loop count.
+ if extension == eApplication && string(d.tmp[:size]) == "NETSCAPE2.0" {
+ n, err := d.readBlock()
+ if n == 0 || err != nil {
+ return err
+ }
+ if n == 3 && d.tmp[0] == 1 {
+ d.loopCount = int(d.tmp[1]) | int(d.tmp[2])<<8
+ }
+ }
+ for {
+ n, err := d.readBlock()
+ if n == 0 || err != nil {
+ return err
+ }
+ }
+}
+
+func (d *decoder) readGraphicControl() error {
+ if _, err := io.ReadFull(d.r, d.tmp[0:6]); err != nil {
+ return fmt.Errorf("gif: can't read graphic control: %s", err)
+ }
+ d.flags = d.tmp[1]
+ d.delayTime = int(d.tmp[2]) | int(d.tmp[3])<<8
+ if d.flags&gcTransparentColorSet != 0 {
+ d.transparentIndex = d.tmp[4]
+ d.hasTransparentIndex = true
+ }
+ return nil
+}
+
+func (d *decoder) newImageFromDescriptor() (*image.Paletted, error) {
+ if _, err := io.ReadFull(d.r, d.tmp[0:9]); err != nil {
+ return nil, fmt.Errorf("gif: can't read image descriptor: %s", err)
+ }
+ left := int(d.tmp[0]) + int(d.tmp[1])<<8
+ top := int(d.tmp[2]) + int(d.tmp[3])<<8
+ width := int(d.tmp[4]) + int(d.tmp[5])<<8
+ height := int(d.tmp[6]) + int(d.tmp[7])<<8
+ d.imageFields = d.tmp[8]
+
+ // The GIF89a spec, Section 20 (Image Descriptor) says:
+ // "Each image must fit within the boundaries of the Logical
+ // Screen, as defined in the Logical Screen Descriptor."
+ bounds := image.Rect(left, top, left+width, top+height)
+ if bounds != bounds.Intersect(image.Rect(0, 0, d.width, d.height)) {
+ return nil, errors.New("gif: frame bounds larger than image bounds")
+ }
+ return image.NewPaletted(bounds, nil), nil
+}
+
+func (d *decoder) readBlock() (int, error) {
+ n, err := d.r.ReadByte()
+ if n == 0 || err != nil {
+ return 0, err
+ }
+ return io.ReadFull(d.r, d.tmp[0:n])
+}
+
+// interlaceScan defines the ordering for a pass of the interlace algorithm.
+type interlaceScan struct {
+ skip, start int
+}
+
+// interlacing represents the set of scans in an interlaced GIF image.
+var interlacing = []interlaceScan{
+ {8, 0}, // Group 1 : Every 8th. row, starting with row 0.
+ {8, 4}, // Group 2 : Every 8th. row, starting with row 4.
+ {4, 2}, // Group 3 : Every 4th. row, starting with row 2.
+ {2, 1}, // Group 4 : Every 2nd. row, starting with row 1.
+}
+
+// uninterlace rearranges the pixels in m to account for interlaced input.
+func uninterlace(m *image.Paletted) {
+ var nPix []uint8
+ dx := m.Bounds().Dx()
+ dy := m.Bounds().Dy()
+ nPix = make([]uint8, dx*dy)
+ offset := 0 // steps through the input by sequential scan lines.
+ for _, pass := range interlacing {
+ nOffset := pass.start * dx // steps through the output as defined by pass.
+ for y := pass.start; y < dy; y += pass.skip {
+ copy(nPix[nOffset:nOffset+dx], m.Pix[offset:offset+dx])
+ offset += dx
+ nOffset += dx * pass.skip
+ }
+ }
+ m.Pix = nPix
+}
+
+// Decode reads a GIF image from r and returns the first embedded
+// image as an image.Image.
+func Decode(r io.Reader) (image.Image, error) {
+ var d decoder
+ if err := d.decode(r, false); err != nil {
+ return nil, err
+ }
+ return d.image[0], nil
+}
+
+// GIF represents the possibly multiple images stored in a GIF file.
+type GIF struct {
+ Image []*image.Paletted // The successive images.
+ Delay []int // The successive delay times, one per frame, in 100ths of a second.
+ LoopCount int // The loop count.
+}
+
+// DecodeAll reads a GIF image from r and returns the sequential frames
+// and timing information.
+func DecodeAll(r io.Reader) (*GIF, error) {
+ var d decoder
+ if err := d.decode(r, false); err != nil {
+ return nil, err
+ }
+ gif := &GIF{
+ Image: d.image,
+ LoopCount: d.loopCount,
+ Delay: d.delay,
+ }
+ return gif, nil
+}
+
+// DecodeConfig returns the global color model and dimensions of a GIF image
+// without decoding the entire image.
+func DecodeConfig(r io.Reader) (image.Config, error) {
+ var d decoder
+ if err := d.decode(r, true); err != nil {
+ return image.Config{}, err
+ }
+ return image.Config{
+ ColorModel: d.globalColorMap,
+ Width: d.width,
+ Height: d.height,
+ }, nil
+}
+
+func init() {
+ image.RegisterFormat("gif", "GIF8?a", Decode, DecodeConfig)
+}
diff --git a/src/image/gif/reader_test.go b/src/image/gif/reader_test.go
new file mode 100644
index 000000000..7b6f50436
--- /dev/null
+++ b/src/image/gif/reader_test.go
@@ -0,0 +1,247 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package gif
+
+import (
+ "bytes"
+ "compress/lzw"
+ "image"
+ "image/color"
+ "reflect"
+ "testing"
+)
+
+// header, palette and trailer are parts of a valid 2x1 GIF image.
+const (
+ headerStr = "GIF89a" +
+ "\x02\x00\x01\x00" + // width=2, height=1
+ "\x80\x00\x00" // headerFields=(a color map of 2 pixels), backgroundIndex, aspect
+ paletteStr = "\x10\x20\x30\x40\x50\x60" // the color map, also known as a palette
+ trailerStr = "\x3b"
+)
+
+// lzwEncode returns an LZW encoding (with 2-bit literals) of n zeroes.
+func lzwEncode(n int) []byte {
+ b := &bytes.Buffer{}
+ w := lzw.NewWriter(b, lzw.LSB, 2)
+ w.Write(make([]byte, n))
+ w.Close()
+ return b.Bytes()
+}
+
+func TestDecode(t *testing.T) {
+ testCases := []struct {
+ nPix int // The number of pixels in the image data.
+ extra bool // Whether to write an extra block after the LZW-encoded data.
+ wantErr error
+ }{
+ {0, false, errNotEnough},
+ {1, false, errNotEnough},
+ {2, false, nil},
+ {2, true, errTooMuch},
+ {3, false, errTooMuch},
+ }
+ for _, tc := range testCases {
+ b := &bytes.Buffer{}
+ b.WriteString(headerStr)
+ b.WriteString(paletteStr)
+ // Write an image with bounds 2x1 but tc.nPix pixels. If tc.nPix != 2
+ // then this should result in an invalid GIF image. First, write a
+ // magic 0x2c (image descriptor) byte, bounds=(0,0)-(2,1), a flags
+ // byte, and 2-bit LZW literals.
+ b.WriteString("\x2c\x00\x00\x00\x00\x02\x00\x01\x00\x00\x02")
+ if tc.nPix > 0 {
+ enc := lzwEncode(tc.nPix)
+ if len(enc) > 0xff {
+ t.Errorf("nPix=%d, extra=%t: compressed length %d is too large", tc.nPix, tc.extra, len(enc))
+ continue
+ }
+ b.WriteByte(byte(len(enc)))
+ b.Write(enc)
+ }
+ if tc.extra {
+ b.WriteString("\x01\x02") // A 1-byte payload with an 0x02 byte.
+ }
+ b.WriteByte(0x00) // An empty block signifies the end of the image data.
+ b.WriteString(trailerStr)
+
+ got, err := Decode(b)
+ if err != tc.wantErr {
+ t.Errorf("nPix=%d, extra=%t\ngot %v\nwant %v", tc.nPix, tc.extra, err, tc.wantErr)
+ }
+
+ if tc.wantErr != nil {
+ continue
+ }
+ want := &image.Paletted{
+ Pix: []uint8{0, 0},
+ Stride: 2,
+ Rect: image.Rect(0, 0, 2, 1),
+ Palette: color.Palette{
+ color.RGBA{0x10, 0x20, 0x30, 0xff},
+ color.RGBA{0x40, 0x50, 0x60, 0xff},
+ },
+ }
+ if !reflect.DeepEqual(got, want) {
+ t.Errorf("nPix=%d, extra=%t\ngot %v\nwant %v", tc.nPix, tc.extra, got, want)
+ }
+ }
+}
+
+func TestTransparentIndex(t *testing.T) {
+ b := &bytes.Buffer{}
+ b.WriteString(headerStr)
+ b.WriteString(paletteStr)
+ for transparentIndex := 0; transparentIndex < 3; transparentIndex++ {
+ if transparentIndex < 2 {
+ // Write the graphic control for the transparent index.
+ b.WriteString("\x21\xf9\x00\x01\x00\x00")
+ b.WriteByte(byte(transparentIndex))
+ b.WriteByte(0)
+ }
+ // Write an image with bounds 2x1, as per TestDecode.
+ b.WriteString("\x2c\x00\x00\x00\x00\x02\x00\x01\x00\x00\x02")
+ enc := lzwEncode(2)
+ if len(enc) > 0xff {
+ t.Fatalf("compressed length %d is too large", len(enc))
+ }
+ b.WriteByte(byte(len(enc)))
+ b.Write(enc)
+ b.WriteByte(0x00)
+ }
+ b.WriteString(trailerStr)
+
+ g, err := DecodeAll(b)
+ if err != nil {
+ t.Fatalf("DecodeAll: %v", err)
+ }
+ c0 := color.RGBA{paletteStr[0], paletteStr[1], paletteStr[2], 0xff}
+ c1 := color.RGBA{paletteStr[3], paletteStr[4], paletteStr[5], 0xff}
+ cz := color.RGBA{}
+ wants := []color.Palette{
+ {cz, c1},
+ {c0, cz},
+ {c0, c1},
+ }
+ if len(g.Image) != len(wants) {
+ t.Fatalf("got %d images, want %d", len(g.Image), len(wants))
+ }
+ for i, want := range wants {
+ got := g.Image[i].Palette
+ if !reflect.DeepEqual(got, want) {
+ t.Errorf("palette #%d:\ngot %v\nwant %v", i, got, want)
+ }
+ }
+}
+
+// testGIF is a simple GIF that we can modify to test different scenarios.
+var testGIF = []byte{
+ 'G', 'I', 'F', '8', '9', 'a',
+ 1, 0, 1, 0, // w=1, h=1 (6)
+ 128, 0, 0, // headerFields, bg, aspect (10)
+ 0, 0, 0, 1, 1, 1, // color map and graphics control (13)
+ 0x21, 0xf9, 0x04, 0x00, 0x00, 0x00, 0xff, 0x00, // (19)
+ // frame 1 (0,0 - 1,1)
+ 0x2c,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x01, 0x00, // (32)
+ 0x00,
+ 0x02, 0x02, 0x4c, 0x01, 0x00, // lzw pixels
+ // trailer
+ 0x3b,
+}
+
+func try(t *testing.T, b []byte, want string) {
+ _, err := DecodeAll(bytes.NewReader(b))
+ var got string
+ if err != nil {
+ got = err.Error()
+ }
+ if got != want {
+ t.Fatalf("got %v, want %v", got, want)
+ }
+}
+
+func TestBounds(t *testing.T) {
+ // Make a local copy of testGIF.
+ gif := make([]byte, len(testGIF))
+ copy(gif, testGIF)
+ // Make the bounds too big, just by one.
+ gif[32] = 2
+ want := "gif: frame bounds larger than image bounds"
+ try(t, gif, want)
+
+ // Make the bounds too small; does not trigger bounds
+ // check, but now there's too much data.
+ gif[32] = 0
+ want = "gif: too much image data"
+ try(t, gif, want)
+ gif[32] = 1
+
+ // Make the bounds really big, expect an error.
+ want = "gif: frame bounds larger than image bounds"
+ for i := 0; i < 4; i++ {
+ gif[32+i] = 0xff
+ }
+ try(t, gif, want)
+}
+
+func TestNoPalette(t *testing.T) {
+ b := &bytes.Buffer{}
+
+ // Manufacture a GIF with no palette, so any pixel at all
+ // will be invalid.
+ b.WriteString(headerStr[:len(headerStr)-3])
+ b.WriteString("\x00\x00\x00") // No global palette.
+
+ // Image descriptor: 2x1, no local palette.
+ b.WriteString("\x2c\x00\x00\x00\x00\x02\x00\x01\x00\x00\x02")
+
+ // Encode the pixels: neither is in range, because there is no palette.
+ pix := []byte{0, 128}
+ enc := &bytes.Buffer{}
+ w := lzw.NewWriter(enc, lzw.LSB, 2)
+ w.Write(pix)
+ w.Close()
+ b.WriteByte(byte(len(enc.Bytes())))
+ b.Write(enc.Bytes())
+ b.WriteByte(0x00) // An empty block signifies the end of the image data.
+
+ b.WriteString(trailerStr)
+
+ try(t, b.Bytes(), "gif: invalid pixel value")
+}
+
+func TestPixelOutsidePaletteRange(t *testing.T) {
+ for _, pval := range []byte{0, 1, 2, 3, 255} {
+ b := &bytes.Buffer{}
+
+ // Manufacture a GIF with a 2 color palette.
+ b.WriteString(headerStr)
+ b.WriteString(paletteStr)
+
+ // Image descriptor: 2x1, no local palette.
+ b.WriteString("\x2c\x00\x00\x00\x00\x02\x00\x01\x00\x00\x02")
+
+ // Encode the pixels; some pvals trigger the expected error.
+ pix := []byte{pval, pval}
+ enc := &bytes.Buffer{}
+ w := lzw.NewWriter(enc, lzw.LSB, 2)
+ w.Write(pix)
+ w.Close()
+ b.WriteByte(byte(len(enc.Bytes())))
+ b.Write(enc.Bytes())
+ b.WriteByte(0x00) // An empty block signifies the end of the image data.
+
+ b.WriteString(trailerStr)
+
+ // No error expected, unless the pixels are beyond the 2 color palette.
+ want := ""
+ if pval >= 2 {
+ want = "gif: invalid pixel value"
+ }
+ try(t, b.Bytes(), want)
+ }
+}
diff --git a/src/image/gif/writer.go b/src/image/gif/writer.go
new file mode 100644
index 000000000..49abde704
--- /dev/null
+++ b/src/image/gif/writer.go
@@ -0,0 +1,333 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package gif
+
+import (
+ "bufio"
+ "compress/lzw"
+ "errors"
+ "image"
+ "image/color"
+ "image/color/palette"
+ "image/draw"
+ "io"
+)
+
+// Graphic control extension fields.
+const (
+ gcLabel = 0xF9
+ gcBlockSize = 0x04
+)
+
+var log2Lookup = [8]int{2, 4, 8, 16, 32, 64, 128, 256}
+
+func log2(x int) int {
+ for i, v := range log2Lookup {
+ if x <= v {
+ return i
+ }
+ }
+ return -1
+}
+
+// Little-endian.
+func writeUint16(b []uint8, u uint16) {
+ b[0] = uint8(u)
+ b[1] = uint8(u >> 8)
+}
+
+// writer is a buffered writer.
+type writer interface {
+ Flush() error
+ io.Writer
+ io.ByteWriter
+}
+
+// encoder encodes an image to the GIF format.
+type encoder struct {
+ // w is the writer to write to. err is the first error encountered during
+ // writing. All attempted writes after the first error become no-ops.
+ w writer
+ err error
+ // g is a reference to the data that is being encoded.
+ g *GIF
+ // buf is a scratch buffer. It must be at least 768 so we can write the color map.
+ buf [1024]byte
+}
+
+// blockWriter writes the block structure of GIF image data, which
+// comprises (n, (n bytes)) blocks, with 1 <= n <= 255. It is the
+// writer given to the LZW encoder, which is thus immune to the
+// blocking.
+type blockWriter struct {
+ e *encoder
+}
+
+func (b blockWriter) Write(data []byte) (int, error) {
+ if b.e.err != nil {
+ return 0, b.e.err
+ }
+ if len(data) == 0 {
+ return 0, nil
+ }
+ total := 0
+ for total < len(data) {
+ n := copy(b.e.buf[1:256], data[total:])
+ total += n
+ b.e.buf[0] = uint8(n)
+
+ n, b.e.err = b.e.w.Write(b.e.buf[:n+1])
+ if b.e.err != nil {
+ return 0, b.e.err
+ }
+ }
+ return total, b.e.err
+}
+
+func (e *encoder) flush() {
+ if e.err != nil {
+ return
+ }
+ e.err = e.w.Flush()
+}
+
+func (e *encoder) write(p []byte) {
+ if e.err != nil {
+ return
+ }
+ _, e.err = e.w.Write(p)
+}
+
+func (e *encoder) writeByte(b byte) {
+ if e.err != nil {
+ return
+ }
+ e.err = e.w.WriteByte(b)
+}
+
+func (e *encoder) writeHeader() {
+ if e.err != nil {
+ return
+ }
+ _, e.err = io.WriteString(e.w, "GIF89a")
+ if e.err != nil {
+ return
+ }
+
+ pm := e.g.Image[0]
+ // Logical screen width and height.
+ writeUint16(e.buf[0:2], uint16(pm.Bounds().Dx()))
+ writeUint16(e.buf[2:4], uint16(pm.Bounds().Dy()))
+ e.write(e.buf[:4])
+
+ // All frames have a local color table, so a global color table
+ // is not needed.
+ e.buf[0] = 0x00
+ e.buf[1] = 0x00 // Background Color Index.
+ e.buf[2] = 0x00 // Pixel Aspect Ratio.
+ e.write(e.buf[:3])
+
+ // Add animation info if necessary.
+ if len(e.g.Image) > 1 {
+ e.buf[0] = 0x21 // Extension Introducer.
+ e.buf[1] = 0xff // Application Label.
+ e.buf[2] = 0x0b // Block Size.
+ e.write(e.buf[:3])
+ _, e.err = io.WriteString(e.w, "NETSCAPE2.0") // Application Identifier.
+ if e.err != nil {
+ return
+ }
+ e.buf[0] = 0x03 // Block Size.
+ e.buf[1] = 0x01 // Sub-block Index.
+ writeUint16(e.buf[2:4], uint16(e.g.LoopCount))
+ e.buf[4] = 0x00 // Block Terminator.
+ e.write(e.buf[:5])
+ }
+}
+
+func (e *encoder) writeColorTable(p color.Palette, size int) {
+ if e.err != nil {
+ return
+ }
+
+ for i := 0; i < log2Lookup[size]; i++ {
+ if i < len(p) {
+ r, g, b, _ := p[i].RGBA()
+ e.buf[3*i+0] = uint8(r >> 8)
+ e.buf[3*i+1] = uint8(g >> 8)
+ e.buf[3*i+2] = uint8(b >> 8)
+ } else {
+ // Pad with black.
+ e.buf[3*i+0] = 0x00
+ e.buf[3*i+1] = 0x00
+ e.buf[3*i+2] = 0x00
+ }
+ }
+ e.write(e.buf[:3*log2Lookup[size]])
+}
+
+func (e *encoder) writeImageBlock(pm *image.Paletted, delay int) {
+ if e.err != nil {
+ return
+ }
+
+ if len(pm.Palette) == 0 {
+ e.err = errors.New("gif: cannot encode image block with empty palette")
+ return
+ }
+
+ b := pm.Bounds()
+ if b.Dx() >= 1<<16 || b.Dy() >= 1<<16 || b.Min.X < 0 || b.Min.X >= 1<<16 || b.Min.Y < 0 || b.Min.Y >= 1<<16 {
+ e.err = errors.New("gif: image block is too large to encode")
+ return
+ }
+
+ transparentIndex := -1
+ for i, c := range pm.Palette {
+ if _, _, _, a := c.RGBA(); a == 0 {
+ transparentIndex = i
+ break
+ }
+ }
+
+ if delay > 0 || transparentIndex != -1 {
+ e.buf[0] = sExtension // Extension Introducer.
+ e.buf[1] = gcLabel // Graphic Control Label.
+ e.buf[2] = gcBlockSize // Block Size.
+ if transparentIndex != -1 {
+ e.buf[3] = 0x01
+ } else {
+ e.buf[3] = 0x00
+ }
+ writeUint16(e.buf[4:6], uint16(delay)) // Delay Time (1/100ths of a second)
+
+ // Transparent color index.
+ if transparentIndex != -1 {
+ e.buf[6] = uint8(transparentIndex)
+ } else {
+ e.buf[6] = 0x00
+ }
+ e.buf[7] = 0x00 // Block Terminator.
+ e.write(e.buf[:8])
+ }
+ e.buf[0] = sImageDescriptor
+ writeUint16(e.buf[1:3], uint16(b.Min.X))
+ writeUint16(e.buf[3:5], uint16(b.Min.Y))
+ writeUint16(e.buf[5:7], uint16(b.Dx()))
+ writeUint16(e.buf[7:9], uint16(b.Dy()))
+ e.write(e.buf[:9])
+
+ paddedSize := log2(len(pm.Palette)) // Size of Local Color Table: 2^(1+n).
+ // Interlacing is not supported.
+ e.writeByte(0x80 | uint8(paddedSize))
+
+ // Local Color Table.
+ e.writeColorTable(pm.Palette, paddedSize)
+
+ litWidth := paddedSize + 1
+ if litWidth < 2 {
+ litWidth = 2
+ }
+ e.writeByte(uint8(litWidth)) // LZW Minimum Code Size.
+
+ lzww := lzw.NewWriter(blockWriter{e: e}, lzw.LSB, litWidth)
+ if dx := b.Dx(); dx == pm.Stride {
+ _, e.err = lzww.Write(pm.Pix)
+ if e.err != nil {
+ lzww.Close()
+ return
+ }
+ } else {
+ for i, y := 0, b.Min.Y; y < b.Max.Y; i, y = i+pm.Stride, y+1 {
+ _, e.err = lzww.Write(pm.Pix[i : i+dx])
+ if e.err != nil {
+ lzww.Close()
+ return
+ }
+ }
+ }
+ lzww.Close()
+ e.writeByte(0x00) // Block Terminator.
+}
+
+// Options are the encoding parameters.
+type Options struct {
+ // NumColors is the maximum number of colors used in the image.
+ // It ranges from 1 to 256.
+ NumColors int
+
+ // Quantizer is used to produce a palette with size NumColors.
+ // palette.Plan9 is used in place of a nil Quantizer.
+ Quantizer draw.Quantizer
+
+ // Drawer is used to convert the source image to the desired palette.
+ // draw.FloydSteinberg is used in place of a nil Drawer.
+ Drawer draw.Drawer
+}
+
+// EncodeAll writes the images in g to w in GIF format with the
+// given loop count and delay between frames.
+func EncodeAll(w io.Writer, g *GIF) error {
+ if len(g.Image) == 0 {
+ return errors.New("gif: must provide at least one image")
+ }
+
+ if len(g.Image) != len(g.Delay) {
+ return errors.New("gif: mismatched image and delay lengths")
+ }
+ if g.LoopCount < 0 {
+ g.LoopCount = 0
+ }
+
+ e := encoder{g: g}
+ if ww, ok := w.(writer); ok {
+ e.w = ww
+ } else {
+ e.w = bufio.NewWriter(w)
+ }
+
+ e.writeHeader()
+ for i, pm := range g.Image {
+ e.writeImageBlock(pm, g.Delay[i])
+ }
+ e.writeByte(sTrailer)
+ e.flush()
+ return e.err
+}
+
+// Encode writes the Image m to w in GIF format.
+func Encode(w io.Writer, m image.Image, o *Options) error {
+ // Check for bounds and size restrictions.
+ b := m.Bounds()
+ if b.Dx() >= 1<<16 || b.Dy() >= 1<<16 {
+ return errors.New("gif: image is too large to encode")
+ }
+
+ opts := Options{}
+ if o != nil {
+ opts = *o
+ }
+ if opts.NumColors < 1 || 256 < opts.NumColors {
+ opts.NumColors = 256
+ }
+ if opts.Drawer == nil {
+ opts.Drawer = draw.FloydSteinberg
+ }
+
+ pm, ok := m.(*image.Paletted)
+ if !ok || len(pm.Palette) > opts.NumColors {
+ // TODO: Pick a better sub-sample of the Plan 9 palette.
+ pm = image.NewPaletted(b, palette.Plan9[:opts.NumColors])
+ if opts.Quantizer != nil {
+ pm.Palette = opts.Quantizer.Quantize(make(color.Palette, 0, opts.NumColors), m)
+ }
+ opts.Drawer.Draw(pm, b, m, image.ZP)
+ }
+
+ return EncodeAll(w, &GIF{
+ Image: []*image.Paletted{pm},
+ Delay: []int{0},
+ })
+}
diff --git a/src/image/gif/writer_test.go b/src/image/gif/writer_test.go
new file mode 100644
index 000000000..93306ffdb
--- /dev/null
+++ b/src/image/gif/writer_test.go
@@ -0,0 +1,227 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package gif
+
+import (
+ "bytes"
+ "image"
+ "image/color"
+ _ "image/png"
+ "io/ioutil"
+ "math/rand"
+ "os"
+ "testing"
+)
+
+func readImg(filename string) (image.Image, error) {
+ f, err := os.Open(filename)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+ m, _, err := image.Decode(f)
+ return m, err
+}
+
+func readGIF(filename string) (*GIF, error) {
+ f, err := os.Open(filename)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+ return DecodeAll(f)
+}
+
+func delta(u0, u1 uint32) int64 {
+ d := int64(u0) - int64(u1)
+ if d < 0 {
+ return -d
+ }
+ return d
+}
+
+// averageDelta returns the average delta in RGB space. The two images must
+// have the same bounds.
+func averageDelta(m0, m1 image.Image) int64 {
+ b := m0.Bounds()
+ var sum, n int64
+ for y := b.Min.Y; y < b.Max.Y; y++ {
+ for x := b.Min.X; x < b.Max.X; x++ {
+ c0 := m0.At(x, y)
+ c1 := m1.At(x, y)
+ r0, g0, b0, _ := c0.RGBA()
+ r1, g1, b1, _ := c1.RGBA()
+ sum += delta(r0, r1)
+ sum += delta(g0, g1)
+ sum += delta(b0, b1)
+ n += 3
+ }
+ }
+ return sum / n
+}
+
+var testCase = []struct {
+ filename string
+ tolerance int64
+}{
+ {"../testdata/video-001.png", 1 << 12},
+ {"../testdata/video-001.gif", 0},
+ {"../testdata/video-001.interlaced.gif", 0},
+}
+
+func TestWriter(t *testing.T) {
+ for _, tc := range testCase {
+ m0, err := readImg(tc.filename)
+ if err != nil {
+ t.Error(tc.filename, err)
+ continue
+ }
+ var buf bytes.Buffer
+ err = Encode(&buf, m0, nil)
+ if err != nil {
+ t.Error(tc.filename, err)
+ continue
+ }
+ m1, err := Decode(&buf)
+ if err != nil {
+ t.Error(tc.filename, err)
+ continue
+ }
+ if m0.Bounds() != m1.Bounds() {
+ t.Errorf("%s, bounds differ: %v and %v", tc.filename, m0.Bounds(), m1.Bounds())
+ continue
+ }
+ // Compare the average delta to the tolerance level.
+ avgDelta := averageDelta(m0, m1)
+ if avgDelta > tc.tolerance {
+ t.Errorf("%s: average delta is too high. expected: %d, got %d", tc.filename, tc.tolerance, avgDelta)
+ continue
+ }
+ }
+}
+
+func TestSubImage(t *testing.T) {
+ m0, err := readImg("../testdata/video-001.gif")
+ if err != nil {
+ t.Fatalf("readImg: %v", err)
+ }
+ m0 = m0.(*image.Paletted).SubImage(image.Rect(0, 0, 50, 30))
+ var buf bytes.Buffer
+ err = Encode(&buf, m0, nil)
+ if err != nil {
+ t.Fatalf("Encode: %v", err)
+ }
+ m1, err := Decode(&buf)
+ if err != nil {
+ t.Fatalf("Decode: %v", err)
+ }
+ if m0.Bounds() != m1.Bounds() {
+ t.Fatalf("bounds differ: %v and %v", m0.Bounds(), m1.Bounds())
+ }
+ if averageDelta(m0, m1) != 0 {
+ t.Fatalf("images differ")
+ }
+}
+
+var frames = []string{
+ "../testdata/video-001.gif",
+ "../testdata/video-005.gray.gif",
+}
+
+func TestEncodeAll(t *testing.T) {
+ g0 := &GIF{
+ Image: make([]*image.Paletted, len(frames)),
+ Delay: make([]int, len(frames)),
+ LoopCount: 5,
+ }
+ for i, f := range frames {
+ m, err := readGIF(f)
+ if err != nil {
+ t.Fatal(f, err)
+ }
+ g0.Image[i] = m.Image[0]
+ }
+ var buf bytes.Buffer
+ if err := EncodeAll(&buf, g0); err != nil {
+ t.Fatal("EncodeAll:", err)
+ }
+ g1, err := DecodeAll(&buf)
+ if err != nil {
+ t.Fatal("DecodeAll:", err)
+ }
+ if g0.LoopCount != g1.LoopCount {
+ t.Errorf("loop counts differ: %d and %d", g0.LoopCount, g1.LoopCount)
+ }
+ for i := range g0.Image {
+ m0, m1 := g0.Image[i], g1.Image[i]
+ if m0.Bounds() != m1.Bounds() {
+ t.Errorf("%s, bounds differ: %v and %v", frames[i], m0.Bounds(), m1.Bounds())
+ }
+ d0, d1 := g0.Delay[i], g1.Delay[i]
+ if d0 != d1 {
+ t.Errorf("%s: delay values differ: %d and %d", frames[i], d0, d1)
+ }
+ }
+
+ g1.Delay = make([]int, 1)
+ if err := EncodeAll(ioutil.Discard, g1); err == nil {
+ t.Error("expected error from mismatched delay and image slice lengths")
+ }
+ if err := EncodeAll(ioutil.Discard, &GIF{}); err == nil {
+ t.Error("expected error from providing empty gif")
+ }
+}
+
+func BenchmarkEncode(b *testing.B) {
+ b.StopTimer()
+
+ bo := image.Rect(0, 0, 640, 480)
+ rnd := rand.New(rand.NewSource(123))
+
+ // Restrict to a 256-color paletted image to avoid quantization path.
+ palette := make(color.Palette, 256)
+ for i := range palette {
+ palette[i] = color.RGBA{
+ uint8(rnd.Intn(256)),
+ uint8(rnd.Intn(256)),
+ uint8(rnd.Intn(256)),
+ 255,
+ }
+ }
+ img := image.NewPaletted(image.Rect(0, 0, 640, 480), palette)
+ for y := bo.Min.Y; y < bo.Max.Y; y++ {
+ for x := bo.Min.X; x < bo.Max.X; x++ {
+ img.Set(x, y, palette[rnd.Intn(256)])
+ }
+ }
+
+ b.SetBytes(640 * 480 * 4)
+ b.StartTimer()
+ for i := 0; i < b.N; i++ {
+ Encode(ioutil.Discard, img, nil)
+ }
+}
+
+func BenchmarkQuantizedEncode(b *testing.B) {
+ b.StopTimer()
+ img := image.NewRGBA(image.Rect(0, 0, 640, 480))
+ bo := img.Bounds()
+ rnd := rand.New(rand.NewSource(123))
+ for y := bo.Min.Y; y < bo.Max.Y; y++ {
+ for x := bo.Min.X; x < bo.Max.X; x++ {
+ img.SetRGBA(x, y, color.RGBA{
+ uint8(rnd.Intn(256)),
+ uint8(rnd.Intn(256)),
+ uint8(rnd.Intn(256)),
+ 255,
+ })
+ }
+ }
+ b.SetBytes(640 * 480 * 4)
+ b.StartTimer()
+ for i := 0; i < b.N; i++ {
+ Encode(ioutil.Discard, img, nil)
+ }
+}