Source file src/image/gif/writer_test.go

     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/draw"
    13  	_ "image/png"
    14  	"io"
    15  	"math/rand"
    16  	"os"
    17  	"reflect"
    18  	"testing"
    19  )
    20  
    21  func 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  
    31  func 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  
    40  func 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.
    50  func 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.
    57  func 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.
    76  var _ writer = blockWriter{}
    77  
    78  var 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  
    87  func 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  
   118  func 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.
   143  func 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  
   168  var frames = []string{
   169  	"../testdata/video-001.gif",
   170  	"../testdata/video-005.gray.gif",
   171  }
   172  
   173  func 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  
   274  func TestEncodeAllGo1Dot4(t *testing.T)                 { testEncodeAll(t, false, false) }
   275  func TestEncodeAllGo1Dot5(t *testing.T)                 { testEncodeAll(t, true, false) }
   276  func TestEncodeAllGo1Dot5GlobalColorModel(t *testing.T) { testEncodeAll(t, true, true) }
   277  
   278  func 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  
   305  func 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  
   311  func 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  
   340  func 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  
   415  func 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  
   446  func 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  
   501  func 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  
   534  func 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  
   563  func 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  
   591  type offsetImage struct {
   592  	image.Image
   593  	Rect image.Rectangle
   594  }
   595  
   596  func (i offsetImage) Bounds() image.Rectangle {
   597  	return i.Rect
   598  }
   599  
   600  func 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: Encode 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: Encode 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  
   660  func 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  
   675  func 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  
   698  func 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  
   715  func 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  

View as plain text