...

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/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: 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  
	 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