...
Run Format

Source file src/image/gif/writer_test.go

Documentation: image/gif

     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  // lzw.NewWriter wants an interface which is basically the same thing as gif's
    68  // writer interface.  This ensures we're compatible.
    69  var _ writer = blockWriter{}
    70  
    71  var testCase = []struct {
    72  	filename  string
    73  	tolerance int64
    74  }{
    75  	{"../testdata/video-001.png", 1 << 12},
    76  	{"../testdata/video-001.gif", 0},
    77  	{"../testdata/video-001.interlaced.gif", 0},
    78  }
    79  
    80  func TestWriter(t *testing.T) {
    81  	for _, tc := range testCase {
    82  		m0, err := readImg(tc.filename)
    83  		if err != nil {
    84  			t.Error(tc.filename, err)
    85  			continue
    86  		}
    87  		var buf bytes.Buffer
    88  		err = Encode(&buf, m0, nil)
    89  		if err != nil {
    90  			t.Error(tc.filename, err)
    91  			continue
    92  		}
    93  		m1, err := Decode(&buf)
    94  		if err != nil {
    95  			t.Error(tc.filename, err)
    96  			continue
    97  		}
    98  		if m0.Bounds() != m1.Bounds() {
    99  			t.Errorf("%s, bounds differ: %v and %v", tc.filename, m0.Bounds(), m1.Bounds())
   100  			continue
   101  		}
   102  		// Compare the average delta to the tolerance level.
   103  		avgDelta := averageDelta(m0, m1)
   104  		if avgDelta > tc.tolerance {
   105  			t.Errorf("%s: average delta is too high. expected: %d, got %d", tc.filename, tc.tolerance, avgDelta)
   106  			continue
   107  		}
   108  	}
   109  }
   110  
   111  func TestSubImage(t *testing.T) {
   112  	m0, err := readImg("../testdata/video-001.gif")
   113  	if err != nil {
   114  		t.Fatalf("readImg: %v", err)
   115  	}
   116  	m0 = m0.(*image.Paletted).SubImage(image.Rect(0, 0, 50, 30))
   117  	var buf bytes.Buffer
   118  	err = Encode(&buf, m0, nil)
   119  	if err != nil {
   120  		t.Fatalf("Encode: %v", err)
   121  	}
   122  	m1, err := Decode(&buf)
   123  	if err != nil {
   124  		t.Fatalf("Decode: %v", err)
   125  	}
   126  	if m0.Bounds() != m1.Bounds() {
   127  		t.Fatalf("bounds differ: %v and %v", m0.Bounds(), m1.Bounds())
   128  	}
   129  	if averageDelta(m0, m1) != 0 {
   130  		t.Fatalf("images differ")
   131  	}
   132  }
   133  
   134  // palettesEqual reports whether two color.Palette values are equal, ignoring
   135  // any trailing opaque-black palette entries.
   136  func palettesEqual(p, q color.Palette) bool {
   137  	n := len(p)
   138  	if n > len(q) {
   139  		n = len(q)
   140  	}
   141  	for i := 0; i < n; i++ {
   142  		if p[i] != q[i] {
   143  			return false
   144  		}
   145  	}
   146  	for i := n; i < len(p); i++ {
   147  		r, g, b, a := p[i].RGBA()
   148  		if r != 0 || g != 0 || b != 0 || a != 0xffff {
   149  			return false
   150  		}
   151  	}
   152  	for i := n; i < len(q); i++ {
   153  		r, g, b, a := q[i].RGBA()
   154  		if r != 0 || g != 0 || b != 0 || a != 0xffff {
   155  			return false
   156  		}
   157  	}
   158  	return true
   159  }
   160  
   161  var frames = []string{
   162  	"../testdata/video-001.gif",
   163  	"../testdata/video-005.gray.gif",
   164  }
   165  
   166  func testEncodeAll(t *testing.T, go1Dot5Fields bool, useGlobalColorModel bool) {
   167  	const width, height = 150, 103
   168  
   169  	g0 := &GIF{
   170  		Image:     make([]*image.Paletted, len(frames)),
   171  		Delay:     make([]int, len(frames)),
   172  		LoopCount: 5,
   173  	}
   174  	for i, f := range frames {
   175  		g, err := readGIF(f)
   176  		if err != nil {
   177  			t.Fatal(f, err)
   178  		}
   179  		m := g.Image[0]
   180  		if m.Bounds().Dx() != width || m.Bounds().Dy() != height {
   181  			t.Fatalf("frame %d had unexpected bounds: got %v, want width/height = %d/%d",
   182  				i, m.Bounds(), width, height)
   183  		}
   184  		g0.Image[i] = m
   185  	}
   186  	// The GIF.Disposal, GIF.Config and GIF.BackgroundIndex fields were added
   187  	// in Go 1.5. Valid Go 1.4 or earlier code should still produce valid GIFs.
   188  	//
   189  	// On the following line, color.Model is an interface type, and
   190  	// color.Palette is a concrete (slice) type.
   191  	globalColorModel, backgroundIndex := color.Model(color.Palette(nil)), uint8(0)
   192  	if useGlobalColorModel {
   193  		globalColorModel, backgroundIndex = color.Palette(palette.WebSafe), uint8(1)
   194  	}
   195  	if go1Dot5Fields {
   196  		g0.Disposal = make([]byte, len(g0.Image))
   197  		for i := range g0.Disposal {
   198  			g0.Disposal[i] = DisposalNone
   199  		}
   200  		g0.Config = image.Config{
   201  			ColorModel: globalColorModel,
   202  			Width:      width,
   203  			Height:     height,
   204  		}
   205  		g0.BackgroundIndex = backgroundIndex
   206  	}
   207  
   208  	var buf bytes.Buffer
   209  	if err := EncodeAll(&buf, g0); err != nil {
   210  		t.Fatal("EncodeAll:", err)
   211  	}
   212  	encoded := buf.Bytes()
   213  	config, err := DecodeConfig(bytes.NewReader(encoded))
   214  	if err != nil {
   215  		t.Fatal("DecodeConfig:", err)
   216  	}
   217  	g1, err := DecodeAll(bytes.NewReader(encoded))
   218  	if err != nil {
   219  		t.Fatal("DecodeAll:", err)
   220  	}
   221  
   222  	if !reflect.DeepEqual(config, g1.Config) {
   223  		t.Errorf("DecodeConfig inconsistent with DecodeAll")
   224  	}
   225  	if !palettesEqual(g1.Config.ColorModel.(color.Palette), globalColorModel.(color.Palette)) {
   226  		t.Errorf("unexpected global color model")
   227  	}
   228  	if w, h := g1.Config.Width, g1.Config.Height; w != width || h != height {
   229  		t.Errorf("got config width * height = %d * %d, want %d * %d", w, h, width, height)
   230  	}
   231  
   232  	if g0.LoopCount != g1.LoopCount {
   233  		t.Errorf("loop counts differ: %d and %d", g0.LoopCount, g1.LoopCount)
   234  	}
   235  	if backgroundIndex != g1.BackgroundIndex {
   236  		t.Errorf("background indexes differ: %d and %d", backgroundIndex, g1.BackgroundIndex)
   237  	}
   238  	if len(g0.Image) != len(g1.Image) {
   239  		t.Fatalf("image lengths differ: %d and %d", len(g0.Image), len(g1.Image))
   240  	}
   241  	if len(g1.Image) != len(g1.Delay) {
   242  		t.Fatalf("image and delay lengths differ: %d and %d", len(g1.Image), len(g1.Delay))
   243  	}
   244  	if len(g1.Image) != len(g1.Disposal) {
   245  		t.Fatalf("image and disposal lengths differ: %d and %d", len(g1.Image), len(g1.Disposal))
   246  	}
   247  
   248  	for i := range g0.Image {
   249  		m0, m1 := g0.Image[i], g1.Image[i]
   250  		if m0.Bounds() != m1.Bounds() {
   251  			t.Errorf("frame %d: bounds differ: %v and %v", i, m0.Bounds(), m1.Bounds())
   252  		}
   253  		d0, d1 := g0.Delay[i], g1.Delay[i]
   254  		if d0 != d1 {
   255  			t.Errorf("frame %d: delay values differ: %d and %d", i, d0, d1)
   256  		}
   257  		p0, p1 := uint8(0), g1.Disposal[i]
   258  		if go1Dot5Fields {
   259  			p0 = DisposalNone
   260  		}
   261  		if p0 != p1 {
   262  			t.Errorf("frame %d: disposal values differ: %d and %d", i, p0, p1)
   263  		}
   264  	}
   265  }
   266  
   267  func TestEncodeAllGo1Dot4(t *testing.T)                 { testEncodeAll(t, false, false) }
   268  func TestEncodeAllGo1Dot5(t *testing.T)                 { testEncodeAll(t, true, false) }
   269  func TestEncodeAllGo1Dot5GlobalColorModel(t *testing.T) { testEncodeAll(t, true, true) }
   270  
   271  func TestEncodeMismatchDelay(t *testing.T) {
   272  	images := make([]*image.Paletted, 2)
   273  	for i := range images {
   274  		images[i] = image.NewPaletted(image.Rect(0, 0, 5, 5), palette.Plan9)
   275  	}
   276  
   277  	g0 := &GIF{
   278  		Image: images,
   279  		Delay: make([]int, 1),
   280  	}
   281  	if err := EncodeAll(ioutil.Discard, g0); err == nil {
   282  		t.Error("expected error from mismatched delay and image slice lengths")
   283  	}
   284  
   285  	g1 := &GIF{
   286  		Image:    images,
   287  		Delay:    make([]int, len(images)),
   288  		Disposal: make([]byte, 1),
   289  	}
   290  	for i := range g1.Disposal {
   291  		g1.Disposal[i] = DisposalNone
   292  	}
   293  	if err := EncodeAll(ioutil.Discard, g1); err == nil {
   294  		t.Error("expected error from mismatched disposal and image slice lengths")
   295  	}
   296  }
   297  
   298  func TestEncodeZeroGIF(t *testing.T) {
   299  	if err := EncodeAll(ioutil.Discard, &GIF{}); err == nil {
   300  		t.Error("expected error from providing empty gif")
   301  	}
   302  }
   303  
   304  func TestEncodeAllFramesOutOfBounds(t *testing.T) {
   305  	images := []*image.Paletted{
   306  		image.NewPaletted(image.Rect(0, 0, 5, 5), palette.Plan9),
   307  		image.NewPaletted(image.Rect(2, 2, 8, 8), palette.Plan9),
   308  		image.NewPaletted(image.Rect(3, 3, 4, 4), palette.Plan9),
   309  	}
   310  	for _, upperBound := range []int{6, 10} {
   311  		g := &GIF{
   312  			Image:    images,
   313  			Delay:    make([]int, len(images)),
   314  			Disposal: make([]byte, len(images)),
   315  			Config: image.Config{
   316  				Width:  upperBound,
   317  				Height: upperBound,
   318  			},
   319  		}
   320  		err := EncodeAll(ioutil.Discard, g)
   321  		if upperBound >= 8 {
   322  			if err != nil {
   323  				t.Errorf("upperBound=%d: %v", upperBound, err)
   324  			}
   325  		} else {
   326  			if err == nil {
   327  				t.Errorf("upperBound=%d: got nil error, want non-nil", upperBound)
   328  			}
   329  		}
   330  	}
   331  }
   332  
   333  func TestEncodeNonZeroMinPoint(t *testing.T) {
   334  	points := []image.Point{
   335  		{-8, -9},
   336  		{-4, -4},
   337  		{-3, +3},
   338  		{+0, +0},
   339  		{+2, +2},
   340  	}
   341  	for _, p := range points {
   342  		src := image.NewPaletted(image.Rectangle{Min: p, Max: p.Add(image.Point{6, 6})}, palette.Plan9)
   343  		var buf bytes.Buffer
   344  		if err := Encode(&buf, src, nil); err != nil {
   345  			t.Errorf("p=%v: Encode: %v", p, err)
   346  			continue
   347  		}
   348  		m, err := Decode(&buf)
   349  		if err != nil {
   350  			t.Errorf("p=%v: Decode: %v", p, err)
   351  			continue
   352  		}
   353  		if got, want := m.Bounds(), image.Rect(0, 0, 6, 6); got != want {
   354  			t.Errorf("p=%v: got %v, want %v", p, got, want)
   355  		}
   356  	}
   357  }
   358  
   359  func TestEncodeImplicitConfigSize(t *testing.T) {
   360  	// For backwards compatibility for Go 1.4 and earlier code, the Config
   361  	// field is optional, and if zero, the width and height is implied by the
   362  	// first (and in this case only) frame's width and height.
   363  	//
   364  	// A Config only specifies a width and height (two integers) while an
   365  	// image.Image's Bounds method returns an image.Rectangle (four integers).
   366  	// For a gif.GIF, the overall bounds' top-left point is always implicitly
   367  	// (0, 0), and any frame whose bounds have a negative X or Y will be
   368  	// outside those overall bounds, so encoding should fail.
   369  	for _, lowerBound := range []int{-1, 0, 1} {
   370  		images := []*image.Paletted{
   371  			image.NewPaletted(image.Rect(lowerBound, lowerBound, 4, 4), palette.Plan9),
   372  		}
   373  		g := &GIF{
   374  			Image: images,
   375  			Delay: make([]int, len(images)),
   376  		}
   377  		err := EncodeAll(ioutil.Discard, g)
   378  		if lowerBound >= 0 {
   379  			if err != nil {
   380  				t.Errorf("lowerBound=%d: %v", lowerBound, err)
   381  			}
   382  		} else {
   383  			if err == nil {
   384  				t.Errorf("lowerBound=%d: got nil error, want non-nil", lowerBound)
   385  			}
   386  		}
   387  	}
   388  }
   389  
   390  func TestEncodePalettes(t *testing.T) {
   391  	const w, h = 5, 5
   392  	pals := []color.Palette{{
   393  		color.RGBA{0x00, 0x00, 0x00, 0xff},
   394  		color.RGBA{0x01, 0x00, 0x00, 0xff},
   395  		color.RGBA{0x02, 0x00, 0x00, 0xff},
   396  	}, {
   397  		color.RGBA{0x00, 0x00, 0x00, 0xff},
   398  		color.RGBA{0x00, 0x01, 0x00, 0xff},
   399  	}, {
   400  		color.RGBA{0x00, 0x00, 0x03, 0xff},
   401  		color.RGBA{0x00, 0x00, 0x02, 0xff},
   402  		color.RGBA{0x00, 0x00, 0x01, 0xff},
   403  		color.RGBA{0x00, 0x00, 0x00, 0xff},
   404  	}, {
   405  		color.RGBA{0x10, 0x07, 0xf0, 0xff},
   406  		color.RGBA{0x20, 0x07, 0xf0, 0xff},
   407  		color.RGBA{0x30, 0x07, 0xf0, 0xff},
   408  		color.RGBA{0x40, 0x07, 0xf0, 0xff},
   409  		color.RGBA{0x50, 0x07, 0xf0, 0xff},
   410  	}}
   411  	g0 := &GIF{
   412  		Image: []*image.Paletted{
   413  			image.NewPaletted(image.Rect(0, 0, w, h), pals[0]),
   414  			image.NewPaletted(image.Rect(0, 0, w, h), pals[1]),
   415  			image.NewPaletted(image.Rect(0, 0, w, h), pals[2]),
   416  			image.NewPaletted(image.Rect(0, 0, w, h), pals[3]),
   417  		},
   418  		Delay:    make([]int, len(pals)),
   419  		Disposal: make([]byte, len(pals)),
   420  		Config: image.Config{
   421  			ColorModel: pals[2],
   422  			Width:      w,
   423  			Height:     h,
   424  		},
   425  	}
   426  
   427  	var buf bytes.Buffer
   428  	if err := EncodeAll(&buf, g0); err != nil {
   429  		t.Fatalf("EncodeAll: %v", err)
   430  	}
   431  	g1, err := DecodeAll(&buf)
   432  	if err != nil {
   433  		t.Fatalf("DecodeAll: %v", err)
   434  	}
   435  	if len(g0.Image) != len(g1.Image) {
   436  		t.Fatalf("image lengths differ: %d and %d", len(g0.Image), len(g1.Image))
   437  	}
   438  	for i, m := range g1.Image {
   439  		if got, want := m.Palette, pals[i]; !palettesEqual(got, want) {
   440  			t.Errorf("frame %d:\ngot  %v\nwant %v", i, got, want)
   441  		}
   442  	}
   443  }
   444  
   445  func TestEncodeBadPalettes(t *testing.T) {
   446  	const w, h = 5, 5
   447  	for _, n := range []int{256, 257} {
   448  		for _, nilColors := range []bool{false, true} {
   449  			pal := make(color.Palette, n)
   450  			if !nilColors {
   451  				for i := range pal {
   452  					pal[i] = color.Black
   453  				}
   454  			}
   455  
   456  			err := EncodeAll(ioutil.Discard, &GIF{
   457  				Image: []*image.Paletted{
   458  					image.NewPaletted(image.Rect(0, 0, w, h), pal),
   459  				},
   460  				Delay:    make([]int, 1),
   461  				Disposal: make([]byte, 1),
   462  				Config: image.Config{
   463  					ColorModel: pal,
   464  					Width:      w,
   465  					Height:     h,
   466  				},
   467  			})
   468  
   469  			got := err != nil
   470  			want := n > 256 || nilColors
   471  			if got != want {
   472  				t.Errorf("n=%d, nilColors=%t: err != nil: got %t, want %t", n, nilColors, got, want)
   473  			}
   474  		}
   475  	}
   476  }
   477  
   478  func TestColorTablesMatch(t *testing.T) {
   479  	const trIdx = 100
   480  	global := color.Palette(palette.Plan9)
   481  	if rgb := global[trIdx].(color.RGBA); rgb.R == 0 && rgb.G == 0 && rgb.B == 0 {
   482  		t.Fatalf("trIdx (%d) is already black", trIdx)
   483  	}
   484  
   485  	// Make a copy of the palette, substituting trIdx's slot with transparent,
   486  	// just like decoder.decode.
   487  	local := append(color.Palette(nil), global...)
   488  	local[trIdx] = color.RGBA{}
   489  
   490  	const testLen = 3 * 256
   491  	const padded = 7
   492  	e := new(encoder)
   493  	if l, err := encodeColorTable(e.globalColorTable[:], global, padded); err != nil || l != testLen {
   494  		t.Fatalf("Failed to encode global color table: got %d, %v; want nil, %d", l, err, testLen)
   495  	}
   496  	if l, err := encodeColorTable(e.localColorTable[:], local, padded); err != nil || l != testLen {
   497  		t.Fatalf("Failed to encode local color table: got %d, %v; want nil, %d", l, err, testLen)
   498  	}
   499  	if bytes.Equal(e.globalColorTable[:testLen], e.localColorTable[:testLen]) {
   500  		t.Fatal("Encoded color tables are equal, expected mismatch")
   501  	}
   502  	if !e.colorTablesMatch(len(local), trIdx) {
   503  		t.Fatal("colorTablesMatch() == false, expected true")
   504  	}
   505  }
   506  
   507  func TestEncodeCroppedSubImages(t *testing.T) {
   508  	// This test means to ensure that Encode honors the Bounds and Strides of
   509  	// images correctly when encoding.
   510  	whole := image.NewPaletted(image.Rect(0, 0, 100, 100), palette.Plan9)
   511  	subImages := []image.Rectangle{
   512  		image.Rect(0, 0, 50, 50),
   513  		image.Rect(50, 0, 100, 50),
   514  		image.Rect(0, 50, 50, 50),
   515  		image.Rect(50, 50, 100, 100),
   516  		image.Rect(25, 25, 75, 75),
   517  		image.Rect(0, 0, 100, 50),
   518  		image.Rect(0, 50, 100, 100),
   519  		image.Rect(0, 0, 50, 100),
   520  		image.Rect(50, 0, 100, 100),
   521  	}
   522  	for _, sr := range subImages {
   523  		si := whole.SubImage(sr)
   524  		buf := bytes.NewBuffer(nil)
   525  		if err := Encode(buf, si, nil); err != nil {
   526  			t.Errorf("Encode: sr=%v: %v", sr, err)
   527  			continue
   528  		}
   529  		if _, err := Decode(buf); err != nil {
   530  			t.Errorf("Decode: sr=%v: %v", sr, err)
   531  		}
   532  	}
   533  }
   534  
   535  func BenchmarkEncode(b *testing.B) {
   536  	bo := image.Rect(0, 0, 640, 480)
   537  	rnd := rand.New(rand.NewSource(123))
   538  
   539  	// Restrict to a 256-color paletted image to avoid quantization path.
   540  	palette := make(color.Palette, 256)
   541  	for i := range palette {
   542  		palette[i] = color.RGBA{
   543  			uint8(rnd.Intn(256)),
   544  			uint8(rnd.Intn(256)),
   545  			uint8(rnd.Intn(256)),
   546  			255,
   547  		}
   548  	}
   549  	img := image.NewPaletted(image.Rect(0, 0, 640, 480), palette)
   550  	for y := bo.Min.Y; y < bo.Max.Y; y++ {
   551  		for x := bo.Min.X; x < bo.Max.X; x++ {
   552  			img.Set(x, y, palette[rnd.Intn(256)])
   553  		}
   554  	}
   555  
   556  	b.SetBytes(640 * 480 * 4)
   557  	b.ReportAllocs()
   558  	b.ResetTimer()
   559  	for i := 0; i < b.N; i++ {
   560  		Encode(ioutil.Discard, img, nil)
   561  	}
   562  }
   563  
   564  func BenchmarkQuantizedEncode(b *testing.B) {
   565  	img := image.NewRGBA(image.Rect(0, 0, 640, 480))
   566  	bo := img.Bounds()
   567  	rnd := rand.New(rand.NewSource(123))
   568  	for y := bo.Min.Y; y < bo.Max.Y; y++ {
   569  		for x := bo.Min.X; x < bo.Max.X; x++ {
   570  			img.SetRGBA(x, y, color.RGBA{
   571  				uint8(rnd.Intn(256)),
   572  				uint8(rnd.Intn(256)),
   573  				uint8(rnd.Intn(256)),
   574  				255,
   575  			})
   576  		}
   577  	}
   578  	b.SetBytes(640 * 480 * 4)
   579  	b.ReportAllocs()
   580  	b.ResetTimer()
   581  	for i := 0; i < b.N; i++ {
   582  		Encode(ioutil.Discard, img, nil)
   583  	}
   584  }
   585  

View as plain text