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.
20 func readImg(filename
string) (image
.Image
, error
) {
21 f
, err
:= os
.Open(filename
)
26 m
, _
, err
:= image
.Decode(f
)
30 func readGIF(filename
string) (*GIF
, error
) {
31 f
, err
:= os
.Open(filename
)
39 func delta(u0
, u1
uint32) int64 {
40 d
:= int64(u0
) - int64(u1
)
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 {
52 for y
:= b
.Min
.Y
; y
< b
.Max
.Y
; y
++ {
53 for x
:= b
.Min
.X
; x
< b
.Max
.X
; x
++ {
56 r0
, g0
, b0
, _
:= c0
.RGBA()
57 r1
, g1
, b1
, _
:= c1
.RGBA()
67 var testCase
= []struct {
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
)
80 t
.Error(tc
.filename
, err
)
84 err
= Encode(&buf
, m0
, nil)
86 t
.Error(tc
.filename
, err
)
89 m1
, err
:= Decode(&buf
)
91 t
.Error(tc
.filename
, err
)
94 if m0
.Bounds() != m1
.Bounds() {
95 t
.Errorf("%s, bounds differ: %v and %v", tc
.filename
, m0
.Bounds(), m1
.Bounds())
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
)
107 func TestSubImage(t
*testing
.T
) {
108 m0
, err
:= readImg("../testdata/video-001.gif")
110 t
.Fatalf("readImg: %v", err
)
112 m0
= m0
.(*image
.Paletted
).SubImage(image
.Rect(0, 0, 50, 30))
114 err
= Encode(&buf
, m0
, nil)
116 t
.Fatalf("Encode: %v", err
)
118 m1
, err
:= Decode(&buf
)
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 {
137 for i
:= 0; i
< n
; i
++ {
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 {
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 {
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
166 Image
: make([]*image
.Paletted
, len(frames
)),
167 Delay
: make([]int, len(frames
)),
170 for i
, f
:= range frames
{
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
)
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)
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
,
201 g0
.BackgroundIndex
= backgroundIndex
205 if err
:= EncodeAll(&buf
, g0
); err
!= nil {
206 t
.Fatal("EncodeAll:", err
)
208 encoded
:= buf
.Bytes()
209 config
, err
:= DecodeConfig(bytes
.NewReader(encoded
))
211 t
.Fatal("DecodeConfig:", err
)
213 g1
, err
:= DecodeAll(bytes
.NewReader(encoded
))
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
]
251 t
.Errorf("frame %d: delay values differ: %d and %d", i
, d0
, d1
)
253 p0
, p1
:= uint8(0), g1
.Disposal
[i
]
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
)
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")
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} {
309 Delay
: make([]int, len(images
)),
310 Disposal
: make([]byte, len(images
)),
311 Config
: image
.Config
{
316 err
:= EncodeAll(ioutil
.Discard
, g
)
319 t
.Errorf("upperBound=%d: %v", upperBound
, err
)
323 t
.Errorf("upperBound=%d: got nil error, want non-nil", upperBound
)
329 func TestEncodeNonZeroMinPoint(t
*testing
.T
) {
330 points
:= []image
.Point
{
337 for _
, p
:= range points
{
338 src
:= image
.NewPaletted(image
.Rectangle
{Min
: p
, Max
: p
.Add(image
.Point
{6, 6})}, palette
.Plan9
)
340 if err
:= Encode(&buf
, src
, nil); err
!= nil {
341 t
.Errorf("p=%v: Encode: %v", p
, err
)
344 m
, err
:= Decode(&buf
)
346 t
.Errorf("p=%v: Decode: %v", p
, err
)
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
),
371 Delay
: make([]int, len(images
)),
373 err
:= EncodeAll(ioutil
.Discard
, g
)
376 t
.Errorf("lowerBound=%d: %v", lowerBound
, err
)
380 t
.Errorf("lowerBound=%d: got nil error, want non-nil", lowerBound
)
386 func TestEncodePalettes(t
*testing
.T
) {
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},
393 color
.RGBA
{0x00, 0x00, 0x00, 0xff},
394 color
.RGBA
{0x00, 0x01, 0x00, 0xff},
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},
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},
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
{
424 if err
:= EncodeAll(&buf
, g0
); err
!= nil {
425 t
.Fatalf("EncodeAll: %v", err
)
427 g1
, err
:= DecodeAll(&buf
)
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
) {
443 for _
, n
:= range []int{256, 257} {
444 for _
, nilColors
:= range []bool{false, true} {
445 pal
:= make(color
.Palette
, n
)
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
{
466 want
:= n
> 256 || nilColors
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
)
496 if _
, err
:= Decode(buf
); err
!= nil {
497 t
.Errorf("Decode: sr=%v: %v", sr
, err
)
502 func BenchmarkEncode(b
*testing
.B
) {
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)),
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)
527 for i
:= 0; i
< b
.N
; i
++ {
528 Encode(ioutil
.Discard
, img
, nil)
532 func BenchmarkQuantizedEncode(b
*testing
.B
) {
534 img
:= image
.NewRGBA(image
.Rect(0, 0, 640, 480))
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)),
547 b
.SetBytes(640 * 480 * 4)
549 for i
:= 0; i
< b
.N
; i
++ {
550 Encode(ioutil
.Discard
, img
, nil)