libgo: update to go1.9
[official-gcc.git] / libgo / go / image / gif / writer_test.go
blob1bba9b8ece58290bd2ad76b72a47f2f252b8180d
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.
5 package gif
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"
20 func readImg(filename string) (image.Image, error) {
21 f, err := os.Open(filename)
22 if err != nil {
23 return nil, err
25 defer f.Close()
26 m, _, err := image.Decode(f)
27 return m, err
30 func readGIF(filename string) (*GIF, error) {
31 f, err := os.Open(filename)
32 if err != nil {
33 return nil, err
35 defer f.Close()
36 return DecodeAll(f)
39 func delta(u0, u1 uint32) int64 {
40 d := int64(u0) - int64(u1)
41 if d < 0 {
42 return -d
44 return d
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
64 return sum / n
67 var testCase = []struct {
68 filename string
69 tolerance int64
71 {"../testdata/video-001.png", 1 << 12},
72 {"../testdata/video-001.gif", 0},
73 {"../testdata/video-001.interlaced.gif", 0},
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
83 var buf bytes.Buffer
84 err = Encode(&buf, m0, nil)
85 if err != nil {
86 t.Error(tc.filename, err)
87 continue
89 m1, err := Decode(&buf)
90 if err != nil {
91 t.Error(tc.filename, err)
92 continue
94 if m0.Bounds() != m1.Bounds() {
95 t.Errorf("%s, bounds differ: %v and %v", tc.filename, m0.Bounds(), m1.Bounds())
96 continue
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
107 func TestSubImage(t *testing.T) {
108 m0, err := readImg("../testdata/video-001.gif")
109 if err != nil {
110 t.Fatalf("readImg: %v", err)
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)
118 m1, err := Decode(&buf)
119 if err != nil {
120 t.Fatalf("Decode: %v", err)
122 if m0.Bounds() != m1.Bounds() {
123 t.Fatalf("bounds differ: %v and %v", m0.Bounds(), m1.Bounds())
125 if averageDelta(m0, m1) != 0 {
126 t.Fatalf("images differ")
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)
137 for i := 0; i < n; i++ {
138 if p[i] != q[i] {
139 return false
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
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
154 return true
157 var frames = []string{
158 "../testdata/video-001.gif",
159 "../testdata/video-005.gray.gif",
162 func testEncodeAll(t *testing.T, go1Dot5Fields bool, useGlobalColorModel bool) {
163 const width, height = 150, 103
165 g0 := &GIF{
166 Image: make([]*image.Paletted, len(frames)),
167 Delay: make([]int, len(frames)),
168 LoopCount: 5,
170 for i, f := range frames {
171 g, err := readGIF(f)
172 if err != nil {
173 t.Fatal(f, err)
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)
180 g0.Image[i] = m
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.
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)
191 if go1Dot5Fields {
192 g0.Disposal = make([]byte, len(g0.Image))
193 for i := range g0.Disposal {
194 g0.Disposal[i] = DisposalNone
196 g0.Config = image.Config{
197 ColorModel: globalColorModel,
198 Width: width,
199 Height: height,
201 g0.BackgroundIndex = backgroundIndex
204 var buf bytes.Buffer
205 if err := EncodeAll(&buf, g0); err != nil {
206 t.Fatal("EncodeAll:", err)
208 encoded := buf.Bytes()
209 config, err := DecodeConfig(bytes.NewReader(encoded))
210 if err != nil {
211 t.Fatal("DecodeConfig:", err)
213 g1, err := DecodeAll(bytes.NewReader(encoded))
214 if err != nil {
215 t.Fatal("DecodeAll:", err)
218 if !reflect.DeepEqual(config, g1.Config) {
219 t.Errorf("DecodeConfig inconsistent with DecodeAll")
221 if !palettesEqual(g1.Config.ColorModel.(color.Palette), globalColorModel.(color.Palette)) {
222 t.Errorf("unexpected global color model")
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)
228 if g0.LoopCount != g1.LoopCount {
229 t.Errorf("loop counts differ: %d and %d", g0.LoopCount, g1.LoopCount)
231 if backgroundIndex != g1.BackgroundIndex {
232 t.Errorf("background indexes differ: %d and %d", backgroundIndex, g1.BackgroundIndex)
234 if len(g0.Image) != len(g1.Image) {
235 t.Fatalf("image lengths differ: %d and %d", len(g0.Image), len(g1.Image))
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))
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))
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())
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)
253 p0, p1 := uint8(0), g1.Disposal[i]
254 if go1Dot5Fields {
255 p0 = DisposalNone
257 if p0 != p1 {
258 t.Errorf("frame %d: disposal values differ: %d and %d", i, p0, p1)
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) }
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)
273 g0 := &GIF{
274 Image: images,
275 Delay: make([]int, 1),
277 if err := EncodeAll(ioutil.Discard, g0); err == nil {
278 t.Error("expected error from mismatched delay and image slice lengths")
281 g1 := &GIF{
282 Image: images,
283 Delay: make([]int, len(images)),
284 Disposal: make([]byte, 1),
286 for i := range g1.Disposal {
287 g1.Disposal[i] = DisposalNone
289 if err := EncodeAll(ioutil.Discard, g1); err == nil {
290 t.Error("expected error from mismatched disposal and image slice lengths")
294 func TestEncodeZeroGIF(t *testing.T) {
295 if err := EncodeAll(ioutil.Discard, &GIF{}); err == nil {
296 t.Error("expected error from providing empty gif")
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),
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,
316 err := EncodeAll(ioutil.Discard, g)
317 if upperBound >= 8 {
318 if err != nil {
319 t.Errorf("upperBound=%d: %v", upperBound, err)
321 } else {
322 if err == nil {
323 t.Errorf("upperBound=%d: got nil error, want non-nil", upperBound)
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},
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
344 m, err := Decode(&buf)
345 if err != nil {
346 t.Errorf("p=%v: Decode: %v", p, err)
347 continue
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)
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.
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),
369 g := &GIF{
370 Image: images,
371 Delay: make([]int, len(images)),
373 err := EncodeAll(ioutil.Discard, g)
374 if lowerBound >= 0 {
375 if err != nil {
376 t.Errorf("lowerBound=%d: %v", lowerBound, err)
378 } else {
379 if err == nil {
380 t.Errorf("lowerBound=%d: got nil error, want non-nil", lowerBound)
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},
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]),
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,
423 var buf bytes.Buffer
424 if err := EncodeAll(&buf, g0); err != nil {
425 t.Fatalf("EncodeAll: %v", err)
427 g1, err := DecodeAll(&buf)
428 if err != nil {
429 t.Fatalf("DecodeAll: %v", err)
431 if len(g0.Image) != len(g1.Image) {
432 t.Fatalf("image lengths differ: %d and %d", len(g0.Image), len(g1.Image))
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)
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
452 err := EncodeAll(ioutil.Discard, &GIF{
453 Image: []*image.Paletted{
454 image.NewPaletted(image.Rect(0, 0, w, h), pal),
456 Delay: make([]int, 1),
457 Disposal: make([]byte, 1),
458 Config: image.Config{
459 ColorModel: pal,
460 Width: w,
461 Height: h,
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)
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),
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
496 if _, err := Decode(buf); err != nil {
497 t.Errorf("Decode: sr=%v: %v", sr, err)
502 func BenchmarkEncode(b *testing.B) {
503 b.StopTimer()
505 bo := image.Rect(0, 0, 640, 480)
506 rnd := rand.New(rand.NewSource(123))
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,
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)])
525 b.SetBytes(640 * 480 * 4)
526 b.StartTimer()
527 for i := 0; i < b.N; i++ {
528 Encode(ioutil.Discard, img, nil)
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,
547 b.SetBytes(640 * 480 * 4)
548 b.StartTimer()
549 for i := 0; i < b.N; i++ {
550 Encode(ioutil.Discard, img, nil)