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