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