Home | History | Annotate | Download | only in gif
      1 // Copyright 2013 The Go Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style
      3 // license that can be found in the LICENSE file.
      4 
      5 package gif
      6 
      7 import (
      8 	"bytes"
      9 	"compress/lzw"
     10 	"image"
     11 	"image/color"
     12 	"reflect"
     13 	"strings"
     14 	"testing"
     15 )
     16 
     17 // header, palette and trailer are parts of a valid 2x1 GIF image.
     18 const (
     19 	headerStr = "GIF89a" +
     20 		"\x02\x00\x01\x00" + // width=2, height=1
     21 		"\x80\x00\x00" // headerFields=(a color table of 2 pixels), backgroundIndex, aspect
     22 	paletteStr = "\x10\x20\x30\x40\x50\x60" // the color table, also known as a palette
     23 	trailerStr = "\x3b"
     24 )
     25 
     26 // lzwEncode returns an LZW encoding (with 2-bit literals) of in.
     27 func lzwEncode(in []byte) []byte {
     28 	b := &bytes.Buffer{}
     29 	w := lzw.NewWriter(b, lzw.LSB, 2)
     30 	if _, err := w.Write(in); err != nil {
     31 		panic(err)
     32 	}
     33 	if err := w.Close(); err != nil {
     34 		panic(err)
     35 	}
     36 	return b.Bytes()
     37 }
     38 
     39 func TestDecode(t *testing.T) {
     40 	testCases := []struct {
     41 		nPix    int  // The number of pixels in the image data.
     42 		extra   bool // Whether to write an extra block after the LZW-encoded data.
     43 		wantErr error
     44 	}{
     45 		{0, false, errNotEnough},
     46 		{1, false, errNotEnough},
     47 		{2, false, nil},
     48 		{2, true, errTooMuch},
     49 		{3, false, errTooMuch},
     50 	}
     51 	for _, tc := range testCases {
     52 		b := &bytes.Buffer{}
     53 		b.WriteString(headerStr)
     54 		b.WriteString(paletteStr)
     55 		// Write an image with bounds 2x1 but tc.nPix pixels. If tc.nPix != 2
     56 		// then this should result in an invalid GIF image. First, write a
     57 		// magic 0x2c (image descriptor) byte, bounds=(0,0)-(2,1), a flags
     58 		// byte, and 2-bit LZW literals.
     59 		b.WriteString("\x2c\x00\x00\x00\x00\x02\x00\x01\x00\x00\x02")
     60 		if tc.nPix > 0 {
     61 			enc := lzwEncode(make([]byte, tc.nPix))
     62 			if len(enc) > 0xff {
     63 				t.Errorf("nPix=%d, extra=%t: compressed length %d is too large", tc.nPix, tc.extra, len(enc))
     64 				continue
     65 			}
     66 			b.WriteByte(byte(len(enc)))
     67 			b.Write(enc)
     68 		}
     69 		if tc.extra {
     70 			b.WriteString("\x01\x02") // A 1-byte payload with an 0x02 byte.
     71 		}
     72 		b.WriteByte(0x00) // An empty block signifies the end of the image data.
     73 		b.WriteString(trailerStr)
     74 
     75 		got, err := Decode(b)
     76 		if err != tc.wantErr {
     77 			t.Errorf("nPix=%d, extra=%t\ngot  %v\nwant %v", tc.nPix, tc.extra, err, tc.wantErr)
     78 		}
     79 
     80 		if tc.wantErr != nil {
     81 			continue
     82 		}
     83 		want := &image.Paletted{
     84 			Pix:    []uint8{0, 0},
     85 			Stride: 2,
     86 			Rect:   image.Rect(0, 0, 2, 1),
     87 			Palette: color.Palette{
     88 				color.RGBA{0x10, 0x20, 0x30, 0xff},
     89 				color.RGBA{0x40, 0x50, 0x60, 0xff},
     90 			},
     91 		}
     92 		if !reflect.DeepEqual(got, want) {
     93 			t.Errorf("nPix=%d, extra=%t\ngot  %v\nwant %v", tc.nPix, tc.extra, got, want)
     94 		}
     95 	}
     96 }
     97 
     98 func TestTransparentIndex(t *testing.T) {
     99 	b := &bytes.Buffer{}
    100 	b.WriteString(headerStr)
    101 	b.WriteString(paletteStr)
    102 	for transparentIndex := 0; transparentIndex < 3; transparentIndex++ {
    103 		if transparentIndex < 2 {
    104 			// Write the graphic control for the transparent index.
    105 			b.WriteString("\x21\xf9\x04\x01\x00\x00")
    106 			b.WriteByte(byte(transparentIndex))
    107 			b.WriteByte(0)
    108 		}
    109 		// Write an image with bounds 2x1, as per TestDecode.
    110 		b.WriteString("\x2c\x00\x00\x00\x00\x02\x00\x01\x00\x00\x02")
    111 		enc := lzwEncode([]byte{0x00, 0x00})
    112 		if len(enc) > 0xff {
    113 			t.Fatalf("compressed length %d is too large", len(enc))
    114 		}
    115 		b.WriteByte(byte(len(enc)))
    116 		b.Write(enc)
    117 		b.WriteByte(0x00)
    118 	}
    119 	b.WriteString(trailerStr)
    120 
    121 	g, err := DecodeAll(b)
    122 	if err != nil {
    123 		t.Fatalf("DecodeAll: %v", err)
    124 	}
    125 	c0 := color.RGBA{paletteStr[0], paletteStr[1], paletteStr[2], 0xff}
    126 	c1 := color.RGBA{paletteStr[3], paletteStr[4], paletteStr[5], 0xff}
    127 	cz := color.RGBA{}
    128 	wants := []color.Palette{
    129 		{cz, c1},
    130 		{c0, cz},
    131 		{c0, c1},
    132 	}
    133 	if len(g.Image) != len(wants) {
    134 		t.Fatalf("got %d images, want %d", len(g.Image), len(wants))
    135 	}
    136 	for i, want := range wants {
    137 		got := g.Image[i].Palette
    138 		if !reflect.DeepEqual(got, want) {
    139 			t.Errorf("palette #%d:\ngot  %v\nwant %v", i, got, want)
    140 		}
    141 	}
    142 }
    143 
    144 // testGIF is a simple GIF that we can modify to test different scenarios.
    145 var testGIF = []byte{
    146 	'G', 'I', 'F', '8', '9', 'a',
    147 	1, 0, 1, 0, // w=1, h=1 (6)
    148 	128, 0, 0, // headerFields, bg, aspect (10)
    149 	0, 0, 0, 1, 1, 1, // color table and graphics control (13)
    150 	0x21, 0xf9, 0x04, 0x00, 0x00, 0x00, 0xff, 0x00, // (19)
    151 	// frame 1 (0,0 - 1,1)
    152 	0x2c,
    153 	0x00, 0x00, 0x00, 0x00,
    154 	0x01, 0x00, 0x01, 0x00, // (32)
    155 	0x00,
    156 	0x02, 0x02, 0x4c, 0x01, 0x00, // lzw pixels
    157 	// trailer
    158 	0x3b,
    159 }
    160 
    161 func try(t *testing.T, b []byte, want string) {
    162 	_, err := DecodeAll(bytes.NewReader(b))
    163 	var got string
    164 	if err != nil {
    165 		got = err.Error()
    166 	}
    167 	if got != want {
    168 		t.Fatalf("got %v, want %v", got, want)
    169 	}
    170 }
    171 
    172 func TestBounds(t *testing.T) {
    173 	// Make a local copy of testGIF.
    174 	gif := make([]byte, len(testGIF))
    175 	copy(gif, testGIF)
    176 	// Make the bounds too big, just by one.
    177 	gif[32] = 2
    178 	want := "gif: frame bounds larger than image bounds"
    179 	try(t, gif, want)
    180 
    181 	// Make the bounds too small; does not trigger bounds
    182 	// check, but now there's too much data.
    183 	gif[32] = 0
    184 	want = "gif: too much image data"
    185 	try(t, gif, want)
    186 	gif[32] = 1
    187 
    188 	// Make the bounds really big, expect an error.
    189 	want = "gif: frame bounds larger than image bounds"
    190 	for i := 0; i < 4; i++ {
    191 		gif[32+i] = 0xff
    192 	}
    193 	try(t, gif, want)
    194 }
    195 
    196 func TestNoPalette(t *testing.T) {
    197 	b := &bytes.Buffer{}
    198 
    199 	// Manufacture a GIF with no palette, so any pixel at all
    200 	// will be invalid.
    201 	b.WriteString(headerStr[:len(headerStr)-3])
    202 	b.WriteString("\x00\x00\x00") // No global palette.
    203 
    204 	// Image descriptor: 2x1, no local palette, and 2-bit LZW literals.
    205 	b.WriteString("\x2c\x00\x00\x00\x00\x02\x00\x01\x00\x00\x02")
    206 
    207 	// Encode the pixels: neither is in range, because there is no palette.
    208 	enc := lzwEncode([]byte{0x00, 0x03})
    209 	b.WriteByte(byte(len(enc)))
    210 	b.Write(enc)
    211 	b.WriteByte(0x00) // An empty block signifies the end of the image data.
    212 
    213 	b.WriteString(trailerStr)
    214 
    215 	try(t, b.Bytes(), "gif: no color table")
    216 }
    217 
    218 func TestPixelOutsidePaletteRange(t *testing.T) {
    219 	for _, pval := range []byte{0, 1, 2, 3} {
    220 		b := &bytes.Buffer{}
    221 
    222 		// Manufacture a GIF with a 2 color palette.
    223 		b.WriteString(headerStr)
    224 		b.WriteString(paletteStr)
    225 
    226 		// Image descriptor: 2x1, no local palette, and 2-bit LZW literals.
    227 		b.WriteString("\x2c\x00\x00\x00\x00\x02\x00\x01\x00\x00\x02")
    228 
    229 		// Encode the pixels; some pvals trigger the expected error.
    230 		enc := lzwEncode([]byte{pval, pval})
    231 		b.WriteByte(byte(len(enc)))
    232 		b.Write(enc)
    233 		b.WriteByte(0x00) // An empty block signifies the end of the image data.
    234 
    235 		b.WriteString(trailerStr)
    236 
    237 		// No error expected, unless the pixels are beyond the 2 color palette.
    238 		want := ""
    239 		if pval >= 2 {
    240 			want = "gif: invalid pixel value"
    241 		}
    242 		try(t, b.Bytes(), want)
    243 	}
    244 }
    245 
    246 func TestTransparentPixelOutsidePaletteRange(t *testing.T) {
    247 	b := &bytes.Buffer{}
    248 
    249 	// Manufacture a GIF with a 2 color palette.
    250 	b.WriteString(headerStr)
    251 	b.WriteString(paletteStr)
    252 
    253 	// Graphic Control Extension: transparency, transparent color index = 3.
    254 	//
    255 	// This index, 3, is out of range of the global palette and there is no
    256 	// local palette in the subsequent image descriptor. This is an error
    257 	// according to the spec, but Firefox and Google Chrome seem OK with this.
    258 	//
    259 	// See golang.org/issue/15059.
    260 	b.WriteString("\x21\xf9\x04\x01\x00\x00\x03\x00")
    261 
    262 	// Image descriptor: 2x1, no local palette, and 2-bit LZW literals.
    263 	b.WriteString("\x2c\x00\x00\x00\x00\x02\x00\x01\x00\x00\x02")
    264 
    265 	// Encode the pixels.
    266 	enc := lzwEncode([]byte{0x03, 0x03})
    267 	b.WriteByte(byte(len(enc)))
    268 	b.Write(enc)
    269 	b.WriteByte(0x00) // An empty block signifies the end of the image data.
    270 
    271 	b.WriteString(trailerStr)
    272 
    273 	try(t, b.Bytes(), "")
    274 }
    275 
    276 func TestLoopCount(t *testing.T) {
    277 	data := []byte("GIF89a000\x00000,0\x00\x00\x00\n\x00" +
    278 		"\n\x00\x80000000\x02\b\xf01u\xb9\xfdal\x05\x00;")
    279 	img, err := DecodeAll(bytes.NewReader(data))
    280 	if err != nil {
    281 		t.Fatal("DecodeAll:", err)
    282 	}
    283 	w := new(bytes.Buffer)
    284 	err = EncodeAll(w, img)
    285 	if err != nil {
    286 		t.Fatal("EncodeAll:", err)
    287 	}
    288 	img1, err := DecodeAll(w)
    289 	if err != nil {
    290 		t.Fatal("DecodeAll:", err)
    291 	}
    292 	if img.LoopCount != img1.LoopCount {
    293 		t.Errorf("loop count mismatch: %d vs %d", img.LoopCount, img1.LoopCount)
    294 	}
    295 }
    296 
    297 func TestUnexpectedEOF(t *testing.T) {
    298 	for i := len(testGIF) - 1; i >= 0; i-- {
    299 		_, err := Decode(bytes.NewReader(testGIF[:i]))
    300 		if err == errNotEnough {
    301 			continue
    302 		}
    303 		text := ""
    304 		if err != nil {
    305 			text = err.Error()
    306 		}
    307 		if !strings.HasPrefix(text, "gif:") || !strings.HasSuffix(text, ": unexpected EOF") {
    308 			t.Errorf("Decode(testGIF[:%d]) = %v, want gif: ...: unexpected EOF", i, err)
    309 		}
    310 	}
    311 }
    312