github.com/hlts2/go@v0.0.0-20170904000733-812b34efaed8/src/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  	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
    62  		}
    63  	}
    64  	return sum / n
    65  }
    66  
    67  var testCase = []struct {
    68  	filename  string
    69  	tolerance int64
    70  }{
    71  	{"../testdata/video-001.png", 1 << 12},
    72  	{"../testdata/video-001.gif", 0},
    73  	{"../testdata/video-001.interlaced.gif", 0},
    74  }
    75  
    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
    82  		}
    83  		var buf bytes.Buffer
    84  		err = Encode(&buf, m0, nil)
    85  		if err != nil {
    86  			t.Error(tc.filename, err)
    87  			continue
    88  		}
    89  		m1, err := Decode(&buf)
    90  		if err != nil {
    91  			t.Error(tc.filename, err)
    92  			continue
    93  		}
    94  		if m0.Bounds() != m1.Bounds() {
    95  			t.Errorf("%s, bounds differ: %v and %v", tc.filename, m0.Bounds(), m1.Bounds())
    96  			continue
    97  		}
    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
   103  		}
   104  	}
   105  }
   106  
   107  func TestSubImage(t *testing.T) {
   108  	m0, err := readImg("../testdata/video-001.gif")
   109  	if err != nil {
   110  		t.Fatalf("readImg: %v", err)
   111  	}
   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)
   117  	}
   118  	m1, err := Decode(&buf)
   119  	if err != nil {
   120  		t.Fatalf("Decode: %v", err)
   121  	}
   122  	if m0.Bounds() != m1.Bounds() {
   123  		t.Fatalf("bounds differ: %v and %v", m0.Bounds(), m1.Bounds())
   124  	}
   125  	if averageDelta(m0, m1) != 0 {
   126  		t.Fatalf("images differ")
   127  	}
   128  }
   129  
   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)
   136  	}
   137  	for i := 0; i < n; i++ {
   138  		if p[i] != q[i] {
   139  			return false
   140  		}
   141  	}
   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
   146  		}
   147  	}
   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
   152  		}
   153  	}
   154  	return true
   155  }
   156  
   157  var frames = []string{
   158  	"../testdata/video-001.gif",
   159  	"../testdata/video-005.gray.gif",
   160  }
   161  
   162  func testEncodeAll(t *testing.T, go1Dot5Fields bool, useGlobalColorModel bool) {
   163  	const width, height = 150, 103
   164  
   165  	g0 := &GIF{
   166  		Image:     make([]*image.Paletted, len(frames)),
   167  		Delay:     make([]int, len(frames)),
   168  		LoopCount: 5,
   169  	}
   170  	for i, f := range frames {
   171  		g, err := readGIF(f)
   172  		if err != nil {
   173  			t.Fatal(f, err)
   174  		}
   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)
   179  		}
   180  		g0.Image[i] = m
   181  	}
   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.
   184  	//
   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)
   190  	}
   191  	if go1Dot5Fields {
   192  		g0.Disposal = make([]byte, len(g0.Image))
   193  		for i := range g0.Disposal {
   194  			g0.Disposal[i] = DisposalNone
   195  		}
   196  		g0.Config = image.Config{
   197  			ColorModel: globalColorModel,
   198  			Width:      width,
   199  			Height:     height,
   200  		}
   201  		g0.BackgroundIndex = backgroundIndex
   202  	}
   203  
   204  	var buf bytes.Buffer
   205  	if err := EncodeAll(&buf, g0); err != nil {
   206  		t.Fatal("EncodeAll:", err)
   207  	}
   208  	encoded := buf.Bytes()
   209  	config, err := DecodeConfig(bytes.NewReader(encoded))
   210  	if err != nil {
   211  		t.Fatal("DecodeConfig:", err)
   212  	}
   213  	g1, err := DecodeAll(bytes.NewReader(encoded))
   214  	if err != nil {
   215  		t.Fatal("DecodeAll:", err)
   216  	}
   217  
   218  	if !reflect.DeepEqual(config, g1.Config) {
   219  		t.Errorf("DecodeConfig inconsistent with DecodeAll")
   220  	}
   221  	if !palettesEqual(g1.Config.ColorModel.(color.Palette), globalColorModel.(color.Palette)) {
   222  		t.Errorf("unexpected global color model")
   223  	}
   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)
   226  	}
   227  
   228  	if g0.LoopCount != g1.LoopCount {
   229  		t.Errorf("loop counts differ: %d and %d", g0.LoopCount, g1.LoopCount)
   230  	}
   231  	if backgroundIndex != g1.BackgroundIndex {
   232  		t.Errorf("background indexes differ: %d and %d", backgroundIndex, g1.BackgroundIndex)
   233  	}
   234  	if len(g0.Image) != len(g1.Image) {
   235  		t.Fatalf("image lengths differ: %d and %d", len(g0.Image), len(g1.Image))
   236  	}
   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))
   239  	}
   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))
   242  	}
   243  
   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())
   248  		}
   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)
   252  		}
   253  		p0, p1 := uint8(0), g1.Disposal[i]
   254  		if go1Dot5Fields {
   255  			p0 = DisposalNone
   256  		}
   257  		if p0 != p1 {
   258  			t.Errorf("frame %d: disposal values differ: %d and %d", i, p0, p1)
   259  		}
   260  	}
   261  }
   262  
   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) }
   266  
   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)
   271  	}
   272  
   273  	g0 := &GIF{
   274  		Image: images,
   275  		Delay: make([]int, 1),
   276  	}
   277  	if err := EncodeAll(ioutil.Discard, g0); err == nil {
   278  		t.Error("expected error from mismatched delay and image slice lengths")
   279  	}
   280  
   281  	g1 := &GIF{
   282  		Image:    images,
   283  		Delay:    make([]int, len(images)),
   284  		Disposal: make([]byte, 1),
   285  	}
   286  	for i := range g1.Disposal {
   287  		g1.Disposal[i] = DisposalNone
   288  	}
   289  	if err := EncodeAll(ioutil.Discard, g1); err == nil {
   290  		t.Error("expected error from mismatched disposal and image slice lengths")
   291  	}
   292  }
   293  
   294  func TestEncodeZeroGIF(t *testing.T) {
   295  	if err := EncodeAll(ioutil.Discard, &GIF{}); err == nil {
   296  		t.Error("expected error from providing empty gif")
   297  	}
   298  }
   299  
   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),
   305  	}
   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,
   314  			},
   315  		}
   316  		err := EncodeAll(ioutil.Discard, g)
   317  		if upperBound >= 8 {
   318  			if err != nil {
   319  				t.Errorf("upperBound=%d: %v", upperBound, err)
   320  			}
   321  		} else {
   322  			if err == nil {
   323  				t.Errorf("upperBound=%d: got nil error, want non-nil", upperBound)
   324  			}
   325  		}
   326  	}
   327  }
   328  
   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},
   336  	}
   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
   343  		}
   344  		m, err := Decode(&buf)
   345  		if err != nil {
   346  			t.Errorf("p=%v: Decode: %v", p, err)
   347  			continue
   348  		}
   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)
   351  		}
   352  	}
   353  }
   354  
   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.
   359  	//
   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),
   368  		}
   369  		g := &GIF{
   370  			Image: images,
   371  			Delay: make([]int, len(images)),
   372  		}
   373  		err := EncodeAll(ioutil.Discard, g)
   374  		if lowerBound >= 0 {
   375  			if err != nil {
   376  				t.Errorf("lowerBound=%d: %v", lowerBound, err)
   377  			}
   378  		} else {
   379  			if err == nil {
   380  				t.Errorf("lowerBound=%d: got nil error, want non-nil", lowerBound)
   381  			}
   382  		}
   383  	}
   384  }
   385  
   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},
   406  	}}
   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]),
   413  		},
   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,
   420  		},
   421  	}
   422  
   423  	var buf bytes.Buffer
   424  	if err := EncodeAll(&buf, g0); err != nil {
   425  		t.Fatalf("EncodeAll: %v", err)
   426  	}
   427  	g1, err := DecodeAll(&buf)
   428  	if err != nil {
   429  		t.Fatalf("DecodeAll: %v", err)
   430  	}
   431  	if len(g0.Image) != len(g1.Image) {
   432  		t.Fatalf("image lengths differ: %d and %d", len(g0.Image), len(g1.Image))
   433  	}
   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)
   437  		}
   438  	}
   439  }
   440  
   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
   449  				}
   450  			}
   451  
   452  			err := EncodeAll(ioutil.Discard, &GIF{
   453  				Image: []*image.Paletted{
   454  					image.NewPaletted(image.Rect(0, 0, w, h), pal),
   455  				},
   456  				Delay:    make([]int, 1),
   457  				Disposal: make([]byte, 1),
   458  				Config: image.Config{
   459  					ColorModel: pal,
   460  					Width:      w,
   461  					Height:     h,
   462  				},
   463  			})
   464  
   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)
   469  			}
   470  		}
   471  	}
   472  }
   473  
   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),
   488  	}
   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
   495  		}
   496  		if _, err := Decode(buf); err != nil {
   497  			t.Errorf("Decode: sr=%v: %v", sr, err)
   498  		}
   499  	}
   500  }
   501  
   502  func BenchmarkEncode(b *testing.B) {
   503  	b.StopTimer()
   504  
   505  	bo := image.Rect(0, 0, 640, 480)
   506  	rnd := rand.New(rand.NewSource(123))
   507  
   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,
   516  		}
   517  	}
   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)])
   522  		}
   523  	}
   524  
   525  	b.SetBytes(640 * 480 * 4)
   526  	b.StartTimer()
   527  	for i := 0; i < b.N; i++ {
   528  		Encode(ioutil.Discard, img, nil)
   529  	}
   530  }
   531  
   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,
   544  			})
   545  		}
   546  	}
   547  	b.SetBytes(640 * 480 * 4)
   548  	b.StartTimer()
   549  	for i := 0; i < b.N; i++ {
   550  		Encode(ioutil.Discard, img, nil)
   551  	}
   552  }