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  }