github.com/mattn/go@v0.0.0-20171011075504-07f7db3ea99f/src/image/gif/reader_test.go (about) 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 "io/ioutil" 13 "reflect" 14 "strings" 15 "testing" 16 ) 17 18 // header, palette and trailer are parts of a valid 2x1 GIF image. 19 const ( 20 headerStr = "GIF89a" + 21 "\x02\x00\x01\x00" + // width=2, height=1 22 "\x80\x00\x00" // headerFields=(a color table of 2 pixels), backgroundIndex, aspect 23 paletteStr = "\x10\x20\x30\x40\x50\x60" // the color table, also known as a palette 24 trailerStr = "\x3b" 25 ) 26 27 // lzwEncode returns an LZW encoding (with 2-bit literals) of in. 28 func lzwEncode(in []byte) []byte { 29 b := &bytes.Buffer{} 30 w := lzw.NewWriter(b, lzw.LSB, 2) 31 if _, err := w.Write(in); err != nil { 32 panic(err) 33 } 34 if err := w.Close(); err != nil { 35 panic(err) 36 } 37 return b.Bytes() 38 } 39 40 func TestDecode(t *testing.T) { 41 // extra contains superfluous bytes to inject into the GIF, either at the end 42 // of an existing data sub-block (past the LZW End of Information code) or in 43 // a separate data sub-block. The 0x02 values are arbitrary. 44 const extra = "\x02\x02\x02\x02" 45 46 testCases := []struct { 47 nPix int // The number of pixels in the image data. 48 // If non-zero, write this many extra bytes inside the data sub-block 49 // containing the LZW end code. 50 extraExisting int 51 // If non-zero, write an extra block of this many bytes. 52 extraSeparate int 53 wantErr error 54 }{ 55 {0, 0, 0, errNotEnough}, 56 {1, 0, 0, errNotEnough}, 57 {2, 0, 0, nil}, 58 // An extra data sub-block after the compressed section with 1 byte which we 59 // silently skip. 60 {2, 0, 1, nil}, 61 // An extra data sub-block after the compressed section with 2 bytes. In 62 // this case we complain that there is too much data. 63 {2, 0, 2, errTooMuch}, 64 // Too much pixel data. 65 {3, 0, 0, errTooMuch}, 66 // An extra byte after LZW data, but inside the same data sub-block. 67 {2, 1, 0, nil}, 68 // Two extra bytes after LZW data, but inside the same data sub-block. 69 {2, 2, 0, nil}, 70 } 71 for _, tc := range testCases { 72 b := &bytes.Buffer{} 73 b.WriteString(headerStr) 74 b.WriteString(paletteStr) 75 // Write an image with bounds 2x1 but tc.nPix pixels. If tc.nPix != 2 76 // then this should result in an invalid GIF image. First, write a 77 // magic 0x2c (image descriptor) byte, bounds=(0,0)-(2,1), a flags 78 // byte, and 2-bit LZW literals. 79 b.WriteString("\x2c\x00\x00\x00\x00\x02\x00\x01\x00\x00\x02") 80 if tc.nPix > 0 { 81 enc := lzwEncode(make([]byte, tc.nPix)) 82 if len(enc)+tc.extraExisting > 0xff { 83 t.Errorf("nPix=%d, extraExisting=%d, extraSeparate=%d: compressed length %d is too large", 84 tc.nPix, tc.extraExisting, tc.extraSeparate, len(enc)) 85 continue 86 } 87 88 // Write the size of the data sub-block containing the LZW data. 89 b.WriteByte(byte(len(enc) + tc.extraExisting)) 90 91 // Write the LZW data. 92 b.Write(enc) 93 94 // Write extra bytes inside the same data sub-block where LZW data 95 // ended. Each arbitrarily 0x02. 96 b.WriteString(extra[:tc.extraExisting]) 97 } 98 99 if tc.extraSeparate > 0 { 100 // Data sub-block size. This indicates how many extra bytes follow. 101 b.WriteByte(byte(tc.extraSeparate)) 102 b.WriteString(extra[:tc.extraSeparate]) 103 } 104 b.WriteByte(0x00) // An empty block signifies the end of the image data. 105 b.WriteString(trailerStr) 106 107 got, err := Decode(b) 108 if err != tc.wantErr { 109 t.Errorf("nPix=%d, extraExisting=%d, extraSeparate=%d\ngot %v\nwant %v", 110 tc.nPix, tc.extraExisting, tc.extraSeparate, err, tc.wantErr) 111 } 112 113 if tc.wantErr != nil { 114 continue 115 } 116 want := &image.Paletted{ 117 Pix: []uint8{0, 0}, 118 Stride: 2, 119 Rect: image.Rect(0, 0, 2, 1), 120 Palette: color.Palette{ 121 color.RGBA{0x10, 0x20, 0x30, 0xff}, 122 color.RGBA{0x40, 0x50, 0x60, 0xff}, 123 }, 124 } 125 if !reflect.DeepEqual(got, want) { 126 t.Errorf("nPix=%d, extraExisting=%d, extraSeparate=%d\ngot %v\nwant %v", 127 tc.nPix, tc.extraExisting, tc.extraSeparate, got, want) 128 } 129 } 130 } 131 132 func TestTransparentIndex(t *testing.T) { 133 b := &bytes.Buffer{} 134 b.WriteString(headerStr) 135 b.WriteString(paletteStr) 136 for transparentIndex := 0; transparentIndex < 3; transparentIndex++ { 137 if transparentIndex < 2 { 138 // Write the graphic control for the transparent index. 139 b.WriteString("\x21\xf9\x04\x01\x00\x00") 140 b.WriteByte(byte(transparentIndex)) 141 b.WriteByte(0) 142 } 143 // Write an image with bounds 2x1, as per TestDecode. 144 b.WriteString("\x2c\x00\x00\x00\x00\x02\x00\x01\x00\x00\x02") 145 enc := lzwEncode([]byte{0x00, 0x00}) 146 if len(enc) > 0xff { 147 t.Fatalf("compressed length %d is too large", len(enc)) 148 } 149 b.WriteByte(byte(len(enc))) 150 b.Write(enc) 151 b.WriteByte(0x00) 152 } 153 b.WriteString(trailerStr) 154 155 g, err := DecodeAll(b) 156 if err != nil { 157 t.Fatalf("DecodeAll: %v", err) 158 } 159 c0 := color.RGBA{paletteStr[0], paletteStr[1], paletteStr[2], 0xff} 160 c1 := color.RGBA{paletteStr[3], paletteStr[4], paletteStr[5], 0xff} 161 cz := color.RGBA{} 162 wants := []color.Palette{ 163 {cz, c1}, 164 {c0, cz}, 165 {c0, c1}, 166 } 167 if len(g.Image) != len(wants) { 168 t.Fatalf("got %d images, want %d", len(g.Image), len(wants)) 169 } 170 for i, want := range wants { 171 got := g.Image[i].Palette 172 if !reflect.DeepEqual(got, want) { 173 t.Errorf("palette #%d:\ngot %v\nwant %v", i, got, want) 174 } 175 } 176 } 177 178 // testGIF is a simple GIF that we can modify to test different scenarios. 179 var testGIF = []byte{ 180 'G', 'I', 'F', '8', '9', 'a', 181 1, 0, 1, 0, // w=1, h=1 (6) 182 128, 0, 0, // headerFields, bg, aspect (10) 183 0, 0, 0, 1, 1, 1, // color table and graphics control (13) 184 0x21, 0xf9, 0x04, 0x00, 0x00, 0x00, 0xff, 0x00, // (19) 185 // frame 1 (0,0 - 1,1) 186 0x2c, 187 0x00, 0x00, 0x00, 0x00, 188 0x01, 0x00, 0x01, 0x00, // (32) 189 0x00, 190 0x02, 0x02, 0x4c, 0x01, 0x00, // lzw pixels 191 // trailer 192 0x3b, 193 } 194 195 func try(t *testing.T, b []byte, want string) { 196 _, err := DecodeAll(bytes.NewReader(b)) 197 var got string 198 if err != nil { 199 got = err.Error() 200 } 201 if got != want { 202 t.Fatalf("got %v, want %v", got, want) 203 } 204 } 205 206 func TestBounds(t *testing.T) { 207 // Make a local copy of testGIF. 208 gif := make([]byte, len(testGIF)) 209 copy(gif, testGIF) 210 // Make the bounds too big, just by one. 211 gif[32] = 2 212 want := "gif: frame bounds larger than image bounds" 213 try(t, gif, want) 214 215 // Make the bounds too small; does not trigger bounds 216 // check, but now there's too much data. 217 gif[32] = 0 218 want = "gif: too much image data" 219 try(t, gif, want) 220 gif[32] = 1 221 222 // Make the bounds really big, expect an error. 223 want = "gif: frame bounds larger than image bounds" 224 for i := 0; i < 4; i++ { 225 gif[32+i] = 0xff 226 } 227 try(t, gif, want) 228 } 229 230 func TestNoPalette(t *testing.T) { 231 b := &bytes.Buffer{} 232 233 // Manufacture a GIF with no palette, so any pixel at all 234 // will be invalid. 235 b.WriteString(headerStr[:len(headerStr)-3]) 236 b.WriteString("\x00\x00\x00") // No global palette. 237 238 // Image descriptor: 2x1, no local palette, and 2-bit LZW literals. 239 b.WriteString("\x2c\x00\x00\x00\x00\x02\x00\x01\x00\x00\x02") 240 241 // Encode the pixels: neither is in range, because there is no palette. 242 enc := lzwEncode([]byte{0x00, 0x03}) 243 b.WriteByte(byte(len(enc))) 244 b.Write(enc) 245 b.WriteByte(0x00) // An empty block signifies the end of the image data. 246 247 b.WriteString(trailerStr) 248 249 try(t, b.Bytes(), "gif: no color table") 250 } 251 252 func TestPixelOutsidePaletteRange(t *testing.T) { 253 for _, pval := range []byte{0, 1, 2, 3} { 254 b := &bytes.Buffer{} 255 256 // Manufacture a GIF with a 2 color palette. 257 b.WriteString(headerStr) 258 b.WriteString(paletteStr) 259 260 // Image descriptor: 2x1, no local palette, and 2-bit LZW literals. 261 b.WriteString("\x2c\x00\x00\x00\x00\x02\x00\x01\x00\x00\x02") 262 263 // Encode the pixels; some pvals trigger the expected error. 264 enc := lzwEncode([]byte{pval, pval}) 265 b.WriteByte(byte(len(enc))) 266 b.Write(enc) 267 b.WriteByte(0x00) // An empty block signifies the end of the image data. 268 269 b.WriteString(trailerStr) 270 271 // No error expected, unless the pixels are beyond the 2 color palette. 272 want := "" 273 if pval >= 2 { 274 want = "gif: invalid pixel value" 275 } 276 try(t, b.Bytes(), want) 277 } 278 } 279 280 func TestTransparentPixelOutsidePaletteRange(t *testing.T) { 281 b := &bytes.Buffer{} 282 283 // Manufacture a GIF with a 2 color palette. 284 b.WriteString(headerStr) 285 b.WriteString(paletteStr) 286 287 // Graphic Control Extension: transparency, transparent color index = 3. 288 // 289 // This index, 3, is out of range of the global palette and there is no 290 // local palette in the subsequent image descriptor. This is an error 291 // according to the spec, but Firefox and Google Chrome seem OK with this. 292 // 293 // See golang.org/issue/15059. 294 b.WriteString("\x21\xf9\x04\x01\x00\x00\x03\x00") 295 296 // Image descriptor: 2x1, no local palette, and 2-bit LZW literals. 297 b.WriteString("\x2c\x00\x00\x00\x00\x02\x00\x01\x00\x00\x02") 298 299 // Encode the pixels. 300 enc := lzwEncode([]byte{0x03, 0x03}) 301 b.WriteByte(byte(len(enc))) 302 b.Write(enc) 303 b.WriteByte(0x00) // An empty block signifies the end of the image data. 304 305 b.WriteString(trailerStr) 306 307 try(t, b.Bytes(), "") 308 } 309 310 func TestLoopCount(t *testing.T) { 311 data := []byte("GIF89a000\x00000,0\x00\x00\x00\n\x00" + 312 "\n\x00\x80000000\x02\b\xf01u\xb9\xfdal\x05\x00;") 313 img, err := DecodeAll(bytes.NewReader(data)) 314 if err != nil { 315 t.Fatal("DecodeAll:", err) 316 } 317 w := new(bytes.Buffer) 318 err = EncodeAll(w, img) 319 if err != nil { 320 t.Fatal("EncodeAll:", err) 321 } 322 img1, err := DecodeAll(w) 323 if err != nil { 324 t.Fatal("DecodeAll:", err) 325 } 326 if img.LoopCount != img1.LoopCount { 327 t.Errorf("loop count mismatch: %d vs %d", img.LoopCount, img1.LoopCount) 328 } 329 } 330 331 func TestUnexpectedEOF(t *testing.T) { 332 for i := len(testGIF) - 1; i >= 0; i-- { 333 _, err := Decode(bytes.NewReader(testGIF[:i])) 334 if err == errNotEnough { 335 continue 336 } 337 text := "" 338 if err != nil { 339 text = err.Error() 340 } 341 if !strings.HasPrefix(text, "gif:") || !strings.HasSuffix(text, ": unexpected EOF") { 342 t.Errorf("Decode(testGIF[:%d]) = %v, want gif: ...: unexpected EOF", i, err) 343 } 344 } 345 } 346 347 func BenchmarkDecode(b *testing.B) { 348 data, err := ioutil.ReadFile("../testdata/video-001.gif") 349 if err != nil { 350 b.Fatal(err) 351 } 352 cfg, err := DecodeConfig(bytes.NewReader(data)) 353 if err != nil { 354 b.Fatal(err) 355 } 356 b.SetBytes(int64(cfg.Width * cfg.Height)) 357 b.ReportAllocs() 358 b.ResetTimer() 359 for i := 0; i < b.N; i++ { 360 Decode(bytes.NewReader(data)) 361 } 362 }