diff options
Diffstat (limited to 'src/image/gif/reader_test.go')
-rw-r--r-- | src/image/gif/reader_test.go | 247 |
1 files changed, 247 insertions, 0 deletions
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) + } +} |