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