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