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