github.com/kdevb0x/go@v0.0.0-20180115030120-39687051e9e7/src/image/gif/writer_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 "image" 10 "image/color" 11 "image/color/palette" 12 _ "image/png" 13 "io/ioutil" 14 "math/rand" 15 "os" 16 "reflect" 17 "testing" 18 ) 19 20 func readImg(filename string) (image.Image, error) { 21 f, err := os.Open(filename) 22 if err != nil { 23 return nil, err 24 } 25 defer f.Close() 26 m, _, err := image.Decode(f) 27 return m, err 28 } 29 30 func readGIF(filename string) (*GIF, error) { 31 f, err := os.Open(filename) 32 if err != nil { 33 return nil, err 34 } 35 defer f.Close() 36 return DecodeAll(f) 37 } 38 39 func delta(u0, u1 uint32) int64 { 40 d := int64(u0) - int64(u1) 41 if d < 0 { 42 return -d 43 } 44 return d 45 } 46 47 // averageDelta returns the average delta in RGB space. The two images must 48 // have the same bounds. 49 func averageDelta(m0, m1 image.Image) int64 { 50 b := m0.Bounds() 51 var sum, n int64 52 for y := b.Min.Y; y < b.Max.Y; y++ { 53 for x := b.Min.X; x < b.Max.X; x++ { 54 c0 := m0.At(x, y) 55 c1 := m1.At(x, y) 56 r0, g0, b0, _ := c0.RGBA() 57 r1, g1, b1, _ := c1.RGBA() 58 sum += delta(r0, r1) 59 sum += delta(g0, g1) 60 sum += delta(b0, b1) 61 n += 3 62 } 63 } 64 return sum / n 65 } 66 67 // lzw.NewWriter wants an interface which is basically the same thing as gif's 68 // writer interface. This ensures we're compatible. 69 var _ writer = blockWriter{} 70 71 var testCase = []struct { 72 filename string 73 tolerance int64 74 }{ 75 {"../testdata/video-001.png", 1 << 12}, 76 {"../testdata/video-001.gif", 0}, 77 {"../testdata/video-001.interlaced.gif", 0}, 78 } 79 80 func TestWriter(t *testing.T) { 81 for _, tc := range testCase { 82 m0, err := readImg(tc.filename) 83 if err != nil { 84 t.Error(tc.filename, err) 85 continue 86 } 87 var buf bytes.Buffer 88 err = Encode(&buf, m0, nil) 89 if err != nil { 90 t.Error(tc.filename, err) 91 continue 92 } 93 m1, err := Decode(&buf) 94 if err != nil { 95 t.Error(tc.filename, err) 96 continue 97 } 98 if m0.Bounds() != m1.Bounds() { 99 t.Errorf("%s, bounds differ: %v and %v", tc.filename, m0.Bounds(), m1.Bounds()) 100 continue 101 } 102 // Compare the average delta to the tolerance level. 103 avgDelta := averageDelta(m0, m1) 104 if avgDelta > tc.tolerance { 105 t.Errorf("%s: average delta is too high. expected: %d, got %d", tc.filename, tc.tolerance, avgDelta) 106 continue 107 } 108 } 109 } 110 111 func TestSubImage(t *testing.T) { 112 m0, err := readImg("../testdata/video-001.gif") 113 if err != nil { 114 t.Fatalf("readImg: %v", err) 115 } 116 m0 = m0.(*image.Paletted).SubImage(image.Rect(0, 0, 50, 30)) 117 var buf bytes.Buffer 118 err = Encode(&buf, m0, nil) 119 if err != nil { 120 t.Fatalf("Encode: %v", err) 121 } 122 m1, err := Decode(&buf) 123 if err != nil { 124 t.Fatalf("Decode: %v", err) 125 } 126 if m0.Bounds() != m1.Bounds() { 127 t.Fatalf("bounds differ: %v and %v", m0.Bounds(), m1.Bounds()) 128 } 129 if averageDelta(m0, m1) != 0 { 130 t.Fatalf("images differ") 131 } 132 } 133 134 // palettesEqual reports whether two color.Palette values are equal, ignoring 135 // any trailing opaque-black palette entries. 136 func palettesEqual(p, q color.Palette) bool { 137 n := len(p) 138 if n > len(q) { 139 n = len(q) 140 } 141 for i := 0; i < n; i++ { 142 if p[i] != q[i] { 143 return false 144 } 145 } 146 for i := n; i < len(p); i++ { 147 r, g, b, a := p[i].RGBA() 148 if r != 0 || g != 0 || b != 0 || a != 0xffff { 149 return false 150 } 151 } 152 for i := n; i < len(q); i++ { 153 r, g, b, a := q[i].RGBA() 154 if r != 0 || g != 0 || b != 0 || a != 0xffff { 155 return false 156 } 157 } 158 return true 159 } 160 161 var frames = []string{ 162 "../testdata/video-001.gif", 163 "../testdata/video-005.gray.gif", 164 } 165 166 func testEncodeAll(t *testing.T, go1Dot5Fields bool, useGlobalColorModel bool) { 167 const width, height = 150, 103 168 169 g0 := &GIF{ 170 Image: make([]*image.Paletted, len(frames)), 171 Delay: make([]int, len(frames)), 172 LoopCount: 5, 173 } 174 for i, f := range frames { 175 g, err := readGIF(f) 176 if err != nil { 177 t.Fatal(f, err) 178 } 179 m := g.Image[0] 180 if m.Bounds().Dx() != width || m.Bounds().Dy() != height { 181 t.Fatalf("frame %d had unexpected bounds: got %v, want width/height = %d/%d", 182 i, m.Bounds(), width, height) 183 } 184 g0.Image[i] = m 185 } 186 // The GIF.Disposal, GIF.Config and GIF.BackgroundIndex fields were added 187 // in Go 1.5. Valid Go 1.4 or earlier code should still produce valid GIFs. 188 // 189 // On the following line, color.Model is an interface type, and 190 // color.Palette is a concrete (slice) type. 191 globalColorModel, backgroundIndex := color.Model(color.Palette(nil)), uint8(0) 192 if useGlobalColorModel { 193 globalColorModel, backgroundIndex = color.Palette(palette.WebSafe), uint8(1) 194 } 195 if go1Dot5Fields { 196 g0.Disposal = make([]byte, len(g0.Image)) 197 for i := range g0.Disposal { 198 g0.Disposal[i] = DisposalNone 199 } 200 g0.Config = image.Config{ 201 ColorModel: globalColorModel, 202 Width: width, 203 Height: height, 204 } 205 g0.BackgroundIndex = backgroundIndex 206 } 207 208 var buf bytes.Buffer 209 if err := EncodeAll(&buf, g0); err != nil { 210 t.Fatal("EncodeAll:", err) 211 } 212 encoded := buf.Bytes() 213 config, err := DecodeConfig(bytes.NewReader(encoded)) 214 if err != nil { 215 t.Fatal("DecodeConfig:", err) 216 } 217 g1, err := DecodeAll(bytes.NewReader(encoded)) 218 if err != nil { 219 t.Fatal("DecodeAll:", err) 220 } 221 222 if !reflect.DeepEqual(config, g1.Config) { 223 t.Errorf("DecodeConfig inconsistent with DecodeAll") 224 } 225 if !palettesEqual(g1.Config.ColorModel.(color.Palette), globalColorModel.(color.Palette)) { 226 t.Errorf("unexpected global color model") 227 } 228 if w, h := g1.Config.Width, g1.Config.Height; w != width || h != height { 229 t.Errorf("got config width * height = %d * %d, want %d * %d", w, h, width, height) 230 } 231 232 if g0.LoopCount != g1.LoopCount { 233 t.Errorf("loop counts differ: %d and %d", g0.LoopCount, g1.LoopCount) 234 } 235 if backgroundIndex != g1.BackgroundIndex { 236 t.Errorf("background indexes differ: %d and %d", backgroundIndex, g1.BackgroundIndex) 237 } 238 if len(g0.Image) != len(g1.Image) { 239 t.Fatalf("image lengths differ: %d and %d", len(g0.Image), len(g1.Image)) 240 } 241 if len(g1.Image) != len(g1.Delay) { 242 t.Fatalf("image and delay lengths differ: %d and %d", len(g1.Image), len(g1.Delay)) 243 } 244 if len(g1.Image) != len(g1.Disposal) { 245 t.Fatalf("image and disposal lengths differ: %d and %d", len(g1.Image), len(g1.Disposal)) 246 } 247 248 for i := range g0.Image { 249 m0, m1 := g0.Image[i], g1.Image[i] 250 if m0.Bounds() != m1.Bounds() { 251 t.Errorf("frame %d: bounds differ: %v and %v", i, m0.Bounds(), m1.Bounds()) 252 } 253 d0, d1 := g0.Delay[i], g1.Delay[i] 254 if d0 != d1 { 255 t.Errorf("frame %d: delay values differ: %d and %d", i, d0, d1) 256 } 257 p0, p1 := uint8(0), g1.Disposal[i] 258 if go1Dot5Fields { 259 p0 = DisposalNone 260 } 261 if p0 != p1 { 262 t.Errorf("frame %d: disposal values differ: %d and %d", i, p0, p1) 263 } 264 } 265 } 266 267 func TestEncodeAllGo1Dot4(t *testing.T) { testEncodeAll(t, false, false) } 268 func TestEncodeAllGo1Dot5(t *testing.T) { testEncodeAll(t, true, false) } 269 func TestEncodeAllGo1Dot5GlobalColorModel(t *testing.T) { testEncodeAll(t, true, true) } 270 271 func TestEncodeMismatchDelay(t *testing.T) { 272 images := make([]*image.Paletted, 2) 273 for i := range images { 274 images[i] = image.NewPaletted(image.Rect(0, 0, 5, 5), palette.Plan9) 275 } 276 277 g0 := &GIF{ 278 Image: images, 279 Delay: make([]int, 1), 280 } 281 if err := EncodeAll(ioutil.Discard, g0); err == nil { 282 t.Error("expected error from mismatched delay and image slice lengths") 283 } 284 285 g1 := &GIF{ 286 Image: images, 287 Delay: make([]int, len(images)), 288 Disposal: make([]byte, 1), 289 } 290 for i := range g1.Disposal { 291 g1.Disposal[i] = DisposalNone 292 } 293 if err := EncodeAll(ioutil.Discard, g1); err == nil { 294 t.Error("expected error from mismatched disposal and image slice lengths") 295 } 296 } 297 298 func TestEncodeZeroGIF(t *testing.T) { 299 if err := EncodeAll(ioutil.Discard, &GIF{}); err == nil { 300 t.Error("expected error from providing empty gif") 301 } 302 } 303 304 func TestEncodeAllFramesOutOfBounds(t *testing.T) { 305 images := []*image.Paletted{ 306 image.NewPaletted(image.Rect(0, 0, 5, 5), palette.Plan9), 307 image.NewPaletted(image.Rect(2, 2, 8, 8), palette.Plan9), 308 image.NewPaletted(image.Rect(3, 3, 4, 4), palette.Plan9), 309 } 310 for _, upperBound := range []int{6, 10} { 311 g := &GIF{ 312 Image: images, 313 Delay: make([]int, len(images)), 314 Disposal: make([]byte, len(images)), 315 Config: image.Config{ 316 Width: upperBound, 317 Height: upperBound, 318 }, 319 } 320 err := EncodeAll(ioutil.Discard, g) 321 if upperBound >= 8 { 322 if err != nil { 323 t.Errorf("upperBound=%d: %v", upperBound, err) 324 } 325 } else { 326 if err == nil { 327 t.Errorf("upperBound=%d: got nil error, want non-nil", upperBound) 328 } 329 } 330 } 331 } 332 333 func TestEncodeNonZeroMinPoint(t *testing.T) { 334 points := []image.Point{ 335 {-8, -9}, 336 {-4, -4}, 337 {-3, +3}, 338 {+0, +0}, 339 {+2, +2}, 340 } 341 for _, p := range points { 342 src := image.NewPaletted(image.Rectangle{Min: p, Max: p.Add(image.Point{6, 6})}, palette.Plan9) 343 var buf bytes.Buffer 344 if err := Encode(&buf, src, nil); err != nil { 345 t.Errorf("p=%v: Encode: %v", p, err) 346 continue 347 } 348 m, err := Decode(&buf) 349 if err != nil { 350 t.Errorf("p=%v: Decode: %v", p, err) 351 continue 352 } 353 if got, want := m.Bounds(), image.Rect(0, 0, 6, 6); got != want { 354 t.Errorf("p=%v: got %v, want %v", p, got, want) 355 } 356 } 357 } 358 359 func TestEncodeImplicitConfigSize(t *testing.T) { 360 // For backwards compatibility for Go 1.4 and earlier code, the Config 361 // field is optional, and if zero, the width and height is implied by the 362 // first (and in this case only) frame's width and height. 363 // 364 // A Config only specifies a width and height (two integers) while an 365 // image.Image's Bounds method returns an image.Rectangle (four integers). 366 // For a gif.GIF, the overall bounds' top-left point is always implicitly 367 // (0, 0), and any frame whose bounds have a negative X or Y will be 368 // outside those overall bounds, so encoding should fail. 369 for _, lowerBound := range []int{-1, 0, 1} { 370 images := []*image.Paletted{ 371 image.NewPaletted(image.Rect(lowerBound, lowerBound, 4, 4), palette.Plan9), 372 } 373 g := &GIF{ 374 Image: images, 375 Delay: make([]int, len(images)), 376 } 377 err := EncodeAll(ioutil.Discard, g) 378 if lowerBound >= 0 { 379 if err != nil { 380 t.Errorf("lowerBound=%d: %v", lowerBound, err) 381 } 382 } else { 383 if err == nil { 384 t.Errorf("lowerBound=%d: got nil error, want non-nil", lowerBound) 385 } 386 } 387 } 388 } 389 390 func TestEncodePalettes(t *testing.T) { 391 const w, h = 5, 5 392 pals := []color.Palette{{ 393 color.RGBA{0x00, 0x00, 0x00, 0xff}, 394 color.RGBA{0x01, 0x00, 0x00, 0xff}, 395 color.RGBA{0x02, 0x00, 0x00, 0xff}, 396 }, { 397 color.RGBA{0x00, 0x00, 0x00, 0xff}, 398 color.RGBA{0x00, 0x01, 0x00, 0xff}, 399 }, { 400 color.RGBA{0x00, 0x00, 0x03, 0xff}, 401 color.RGBA{0x00, 0x00, 0x02, 0xff}, 402 color.RGBA{0x00, 0x00, 0x01, 0xff}, 403 color.RGBA{0x00, 0x00, 0x00, 0xff}, 404 }, { 405 color.RGBA{0x10, 0x07, 0xf0, 0xff}, 406 color.RGBA{0x20, 0x07, 0xf0, 0xff}, 407 color.RGBA{0x30, 0x07, 0xf0, 0xff}, 408 color.RGBA{0x40, 0x07, 0xf0, 0xff}, 409 color.RGBA{0x50, 0x07, 0xf0, 0xff}, 410 }} 411 g0 := &GIF{ 412 Image: []*image.Paletted{ 413 image.NewPaletted(image.Rect(0, 0, w, h), pals[0]), 414 image.NewPaletted(image.Rect(0, 0, w, h), pals[1]), 415 image.NewPaletted(image.Rect(0, 0, w, h), pals[2]), 416 image.NewPaletted(image.Rect(0, 0, w, h), pals[3]), 417 }, 418 Delay: make([]int, len(pals)), 419 Disposal: make([]byte, len(pals)), 420 Config: image.Config{ 421 ColorModel: pals[2], 422 Width: w, 423 Height: h, 424 }, 425 } 426 427 var buf bytes.Buffer 428 if err := EncodeAll(&buf, g0); err != nil { 429 t.Fatalf("EncodeAll: %v", err) 430 } 431 g1, err := DecodeAll(&buf) 432 if err != nil { 433 t.Fatalf("DecodeAll: %v", err) 434 } 435 if len(g0.Image) != len(g1.Image) { 436 t.Fatalf("image lengths differ: %d and %d", len(g0.Image), len(g1.Image)) 437 } 438 for i, m := range g1.Image { 439 if got, want := m.Palette, pals[i]; !palettesEqual(got, want) { 440 t.Errorf("frame %d:\ngot %v\nwant %v", i, got, want) 441 } 442 } 443 } 444 445 func TestEncodeBadPalettes(t *testing.T) { 446 const w, h = 5, 5 447 for _, n := range []int{256, 257} { 448 for _, nilColors := range []bool{false, true} { 449 pal := make(color.Palette, n) 450 if !nilColors { 451 for i := range pal { 452 pal[i] = color.Black 453 } 454 } 455 456 err := EncodeAll(ioutil.Discard, &GIF{ 457 Image: []*image.Paletted{ 458 image.NewPaletted(image.Rect(0, 0, w, h), pal), 459 }, 460 Delay: make([]int, 1), 461 Disposal: make([]byte, 1), 462 Config: image.Config{ 463 ColorModel: pal, 464 Width: w, 465 Height: h, 466 }, 467 }) 468 469 got := err != nil 470 want := n > 256 || nilColors 471 if got != want { 472 t.Errorf("n=%d, nilColors=%t: err != nil: got %t, want %t", n, nilColors, got, want) 473 } 474 } 475 } 476 } 477 478 func TestColorTablesMatch(t *testing.T) { 479 const trIdx = 100 480 global := color.Palette(palette.Plan9) 481 if rgb := global[trIdx].(color.RGBA); rgb.R == 0 && rgb.G == 0 && rgb.B == 0 { 482 t.Fatalf("trIdx (%d) is already black", trIdx) 483 } 484 485 // Make a copy of the palette, substituting trIdx's slot with transparent, 486 // just like decoder.decode. 487 local := append(color.Palette(nil), global...) 488 local[trIdx] = color.RGBA{} 489 490 const testLen = 3 * 256 491 const padded = 7 492 e := new(encoder) 493 if l, err := encodeColorTable(e.globalColorTable[:], global, padded); err != nil || l != testLen { 494 t.Fatalf("Failed to encode global color table: got %d, %v; want nil, %d", l, err, testLen) 495 } 496 if l, err := encodeColorTable(e.localColorTable[:], local, padded); err != nil || l != testLen { 497 t.Fatalf("Failed to encode local color table: got %d, %v; want nil, %d", l, err, testLen) 498 } 499 if bytes.Equal(e.globalColorTable[:testLen], e.localColorTable[:testLen]) { 500 t.Fatal("Encoded color tables are equal, expected mismatch") 501 } 502 if !e.colorTablesMatch(len(local), trIdx) { 503 t.Fatal("colorTablesMatch() == false, expected true") 504 } 505 } 506 507 func TestEncodeCroppedSubImages(t *testing.T) { 508 // This test means to ensure that Encode honors the Bounds and Strides of 509 // images correctly when encoding. 510 whole := image.NewPaletted(image.Rect(0, 0, 100, 100), palette.Plan9) 511 subImages := []image.Rectangle{ 512 image.Rect(0, 0, 50, 50), 513 image.Rect(50, 0, 100, 50), 514 image.Rect(0, 50, 50, 50), 515 image.Rect(50, 50, 100, 100), 516 image.Rect(25, 25, 75, 75), 517 image.Rect(0, 0, 100, 50), 518 image.Rect(0, 50, 100, 100), 519 image.Rect(0, 0, 50, 100), 520 image.Rect(50, 0, 100, 100), 521 } 522 for _, sr := range subImages { 523 si := whole.SubImage(sr) 524 buf := bytes.NewBuffer(nil) 525 if err := Encode(buf, si, nil); err != nil { 526 t.Errorf("Encode: sr=%v: %v", sr, err) 527 continue 528 } 529 if _, err := Decode(buf); err != nil { 530 t.Errorf("Decode: sr=%v: %v", sr, err) 531 } 532 } 533 } 534 535 func BenchmarkEncode(b *testing.B) { 536 bo := image.Rect(0, 0, 640, 480) 537 rnd := rand.New(rand.NewSource(123)) 538 539 // Restrict to a 256-color paletted image to avoid quantization path. 540 palette := make(color.Palette, 256) 541 for i := range palette { 542 palette[i] = color.RGBA{ 543 uint8(rnd.Intn(256)), 544 uint8(rnd.Intn(256)), 545 uint8(rnd.Intn(256)), 546 255, 547 } 548 } 549 img := image.NewPaletted(image.Rect(0, 0, 640, 480), palette) 550 for y := bo.Min.Y; y < bo.Max.Y; y++ { 551 for x := bo.Min.X; x < bo.Max.X; x++ { 552 img.Set(x, y, palette[rnd.Intn(256)]) 553 } 554 } 555 556 b.SetBytes(640 * 480 * 4) 557 b.ReportAllocs() 558 b.ResetTimer() 559 for i := 0; i < b.N; i++ { 560 Encode(ioutil.Discard, img, nil) 561 } 562 } 563 564 func BenchmarkQuantizedEncode(b *testing.B) { 565 img := image.NewRGBA(image.Rect(0, 0, 640, 480)) 566 bo := img.Bounds() 567 rnd := rand.New(rand.NewSource(123)) 568 for y := bo.Min.Y; y < bo.Max.Y; y++ { 569 for x := bo.Min.X; x < bo.Max.X; x++ { 570 img.SetRGBA(x, y, color.RGBA{ 571 uint8(rnd.Intn(256)), 572 uint8(rnd.Intn(256)), 573 uint8(rnd.Intn(256)), 574 255, 575 }) 576 } 577 } 578 b.SetBytes(640 * 480 * 4) 579 b.ReportAllocs() 580 b.ResetTimer() 581 for i := 0; i < b.N; i++ { 582 Encode(ioutil.Discard, img, nil) 583 } 584 }