...

Source file src/image/gif/reader_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  	"compress/lzw"
		10  	"image"
		11  	"image/color"
		12  	"image/color/palette"
		13  	"io"
		14  	"os"
		15  	"reflect"
		16  	"runtime"
		17  	"runtime/debug"
		18  	"strings"
		19  	"testing"
		20  )
		21  
		22  // header, palette and trailer are parts of a valid 2x1 GIF image.
		23  const (
		24  	headerStr = "GIF89a" +
		25  		"\x02\x00\x01\x00" + // width=2, height=1
		26  		"\x80\x00\x00" // headerFields=(a color table of 2 pixels), backgroundIndex, aspect
		27  	paletteStr = "\x10\x20\x30\x40\x50\x60" // the color table, also known as a palette
		28  	trailerStr = "\x3b"
		29  )
		30  
		31  // lzw.NewReader wants a io.ByteReader, this ensures we're compatible.
		32  var _ io.ByteReader = (*blockReader)(nil)
		33  
		34  // lzwEncode returns an LZW encoding (with 2-bit literals) of in.
		35  func lzwEncode(in []byte) []byte {
		36  	b := &bytes.Buffer{}
		37  	w := lzw.NewWriter(b, lzw.LSB, 2)
		38  	if _, err := w.Write(in); err != nil {
		39  		panic(err)
		40  	}
		41  	if err := w.Close(); err != nil {
		42  		panic(err)
		43  	}
		44  	return b.Bytes()
		45  }
		46  
		47  func TestDecode(t *testing.T) {
		48  	// extra contains superfluous bytes to inject into the GIF, either at the end
		49  	// of an existing data sub-block (past the LZW End of Information code) or in
		50  	// a separate data sub-block. The 0x02 values are arbitrary.
		51  	const extra = "\x02\x02\x02\x02"
		52  
		53  	testCases := []struct {
		54  		nPix int // The number of pixels in the image data.
		55  		// If non-zero, write this many extra bytes inside the data sub-block
		56  		// containing the LZW end code.
		57  		extraExisting int
		58  		// If non-zero, write an extra block of this many bytes.
		59  		extraSeparate int
		60  		wantErr			 error
		61  	}{
		62  		{0, 0, 0, errNotEnough},
		63  		{1, 0, 0, errNotEnough},
		64  		{2, 0, 0, nil},
		65  		// An extra data sub-block after the compressed section with 1 byte which we
		66  		// silently skip.
		67  		{2, 0, 1, nil},
		68  		// An extra data sub-block after the compressed section with 2 bytes. In
		69  		// this case we complain that there is too much data.
		70  		{2, 0, 2, errTooMuch},
		71  		// Too much pixel data.
		72  		{3, 0, 0, errTooMuch},
		73  		// An extra byte after LZW data, but inside the same data sub-block.
		74  		{2, 1, 0, nil},
		75  		// Two extra bytes after LZW data, but inside the same data sub-block.
		76  		{2, 2, 0, nil},
		77  		// Extra data exists in the final sub-block with LZW data, AND there is
		78  		// a bogus sub-block following.
		79  		{2, 1, 1, errTooMuch},
		80  	}
		81  	for _, tc := range testCases {
		82  		b := &bytes.Buffer{}
		83  		b.WriteString(headerStr)
		84  		b.WriteString(paletteStr)
		85  		// Write an image with bounds 2x1 but tc.nPix pixels. If tc.nPix != 2
		86  		// then this should result in an invalid GIF image. First, write a
		87  		// magic 0x2c (image descriptor) byte, bounds=(0,0)-(2,1), a flags
		88  		// byte, and 2-bit LZW literals.
		89  		b.WriteString("\x2c\x00\x00\x00\x00\x02\x00\x01\x00\x00\x02")
		90  		if tc.nPix > 0 {
		91  			enc := lzwEncode(make([]byte, tc.nPix))
		92  			if len(enc)+tc.extraExisting > 0xff {
		93  				t.Errorf("nPix=%d, extraExisting=%d, extraSeparate=%d: compressed length %d is too large",
		94  					tc.nPix, tc.extraExisting, tc.extraSeparate, len(enc))
		95  				continue
		96  			}
		97  
		98  			// Write the size of the data sub-block containing the LZW data.
		99  			b.WriteByte(byte(len(enc) + tc.extraExisting))
	 100  
	 101  			// Write the LZW data.
	 102  			b.Write(enc)
	 103  
	 104  			// Write extra bytes inside the same data sub-block where LZW data
	 105  			// ended. Each arbitrarily 0x02.
	 106  			b.WriteString(extra[:tc.extraExisting])
	 107  		}
	 108  
	 109  		if tc.extraSeparate > 0 {
	 110  			// Data sub-block size. This indicates how many extra bytes follow.
	 111  			b.WriteByte(byte(tc.extraSeparate))
	 112  			b.WriteString(extra[:tc.extraSeparate])
	 113  		}
	 114  		b.WriteByte(0x00) // An empty block signifies the end of the image data.
	 115  		b.WriteString(trailerStr)
	 116  
	 117  		got, err := Decode(b)
	 118  		if err != tc.wantErr {
	 119  			t.Errorf("nPix=%d, extraExisting=%d, extraSeparate=%d\ngot	%v\nwant %v",
	 120  				tc.nPix, tc.extraExisting, tc.extraSeparate, err, tc.wantErr)
	 121  		}
	 122  
	 123  		if tc.wantErr != nil {
	 124  			continue
	 125  		}
	 126  		want := &image.Paletted{
	 127  			Pix:		[]uint8{0, 0},
	 128  			Stride: 2,
	 129  			Rect:	 image.Rect(0, 0, 2, 1),
	 130  			Palette: color.Palette{
	 131  				color.RGBA{0x10, 0x20, 0x30, 0xff},
	 132  				color.RGBA{0x40, 0x50, 0x60, 0xff},
	 133  			},
	 134  		}
	 135  		if !reflect.DeepEqual(got, want) {
	 136  			t.Errorf("nPix=%d, extraExisting=%d, extraSeparate=%d\ngot	%v\nwant %v",
	 137  				tc.nPix, tc.extraExisting, tc.extraSeparate, got, want)
	 138  		}
	 139  	}
	 140  }
	 141  
	 142  func TestTransparentIndex(t *testing.T) {
	 143  	b := &bytes.Buffer{}
	 144  	b.WriteString(headerStr)
	 145  	b.WriteString(paletteStr)
	 146  	for transparentIndex := 0; transparentIndex < 3; transparentIndex++ {
	 147  		if transparentIndex < 2 {
	 148  			// Write the graphic control for the transparent index.
	 149  			b.WriteString("\x21\xf9\x04\x01\x00\x00")
	 150  			b.WriteByte(byte(transparentIndex))
	 151  			b.WriteByte(0)
	 152  		}
	 153  		// Write an image with bounds 2x1, as per TestDecode.
	 154  		b.WriteString("\x2c\x00\x00\x00\x00\x02\x00\x01\x00\x00\x02")
	 155  		enc := lzwEncode([]byte{0x00, 0x00})
	 156  		if len(enc) > 0xff {
	 157  			t.Fatalf("compressed length %d is too large", len(enc))
	 158  		}
	 159  		b.WriteByte(byte(len(enc)))
	 160  		b.Write(enc)
	 161  		b.WriteByte(0x00)
	 162  	}
	 163  	b.WriteString(trailerStr)
	 164  
	 165  	g, err := DecodeAll(b)
	 166  	if err != nil {
	 167  		t.Fatalf("DecodeAll: %v", err)
	 168  	}
	 169  	c0 := color.RGBA{paletteStr[0], paletteStr[1], paletteStr[2], 0xff}
	 170  	c1 := color.RGBA{paletteStr[3], paletteStr[4], paletteStr[5], 0xff}
	 171  	cz := color.RGBA{}
	 172  	wants := []color.Palette{
	 173  		{cz, c1},
	 174  		{c0, cz},
	 175  		{c0, c1},
	 176  	}
	 177  	if len(g.Image) != len(wants) {
	 178  		t.Fatalf("got %d images, want %d", len(g.Image), len(wants))
	 179  	}
	 180  	for i, want := range wants {
	 181  		got := g.Image[i].Palette
	 182  		if !reflect.DeepEqual(got, want) {
	 183  			t.Errorf("palette #%d:\ngot	%v\nwant %v", i, got, want)
	 184  		}
	 185  	}
	 186  }
	 187  
	 188  // testGIF is a simple GIF that we can modify to test different scenarios.
	 189  var testGIF = []byte{
	 190  	'G', 'I', 'F', '8', '9', 'a',
	 191  	1, 0, 1, 0, // w=1, h=1 (6)
	 192  	128, 0, 0, // headerFields, bg, aspect (10)
	 193  	0, 0, 0, 1, 1, 1, // color table and graphics control (13)
	 194  	0x21, 0xf9, 0x04, 0x00, 0x00, 0x00, 0xff, 0x00, // (19)
	 195  	// frame 1 (0,0 - 1,1)
	 196  	0x2c,
	 197  	0x00, 0x00, 0x00, 0x00,
	 198  	0x01, 0x00, 0x01, 0x00, // (32)
	 199  	0x00,
	 200  	0x02, 0x02, 0x4c, 0x01, 0x00, // lzw pixels
	 201  	// trailer
	 202  	0x3b,
	 203  }
	 204  
	 205  func try(t *testing.T, b []byte, want string) {
	 206  	_, err := DecodeAll(bytes.NewReader(b))
	 207  	var got string
	 208  	if err != nil {
	 209  		got = err.Error()
	 210  	}
	 211  	if got != want {
	 212  		t.Fatalf("got %v, want %v", got, want)
	 213  	}
	 214  }
	 215  
	 216  func TestBounds(t *testing.T) {
	 217  	// Make a local copy of testGIF.
	 218  	gif := make([]byte, len(testGIF))
	 219  	copy(gif, testGIF)
	 220  	// Make the bounds too big, just by one.
	 221  	gif[32] = 2
	 222  	want := "gif: frame bounds larger than image bounds"
	 223  	try(t, gif, want)
	 224  
	 225  	// Make the bounds too small; does not trigger bounds
	 226  	// check, but now there's too much data.
	 227  	gif[32] = 0
	 228  	want = "gif: too much image data"
	 229  	try(t, gif, want)
	 230  	gif[32] = 1
	 231  
	 232  	// Make the bounds really big, expect an error.
	 233  	want = "gif: frame bounds larger than image bounds"
	 234  	for i := 0; i < 4; i++ {
	 235  		gif[32+i] = 0xff
	 236  	}
	 237  	try(t, gif, want)
	 238  }
	 239  
	 240  func TestNoPalette(t *testing.T) {
	 241  	b := &bytes.Buffer{}
	 242  
	 243  	// Manufacture a GIF with no palette, so any pixel at all
	 244  	// will be invalid.
	 245  	b.WriteString(headerStr[:len(headerStr)-3])
	 246  	b.WriteString("\x00\x00\x00") // No global palette.
	 247  
	 248  	// Image descriptor: 2x1, no local palette, and 2-bit LZW literals.
	 249  	b.WriteString("\x2c\x00\x00\x00\x00\x02\x00\x01\x00\x00\x02")
	 250  
	 251  	// Encode the pixels: neither is in range, because there is no palette.
	 252  	enc := lzwEncode([]byte{0x00, 0x03})
	 253  	b.WriteByte(byte(len(enc)))
	 254  	b.Write(enc)
	 255  	b.WriteByte(0x00) // An empty block signifies the end of the image data.
	 256  
	 257  	b.WriteString(trailerStr)
	 258  
	 259  	try(t, b.Bytes(), "gif: no color table")
	 260  }
	 261  
	 262  func TestPixelOutsidePaletteRange(t *testing.T) {
	 263  	for _, pval := range []byte{0, 1, 2, 3} {
	 264  		b := &bytes.Buffer{}
	 265  
	 266  		// Manufacture a GIF with a 2 color palette.
	 267  		b.WriteString(headerStr)
	 268  		b.WriteString(paletteStr)
	 269  
	 270  		// Image descriptor: 2x1, no local palette, and 2-bit LZW literals.
	 271  		b.WriteString("\x2c\x00\x00\x00\x00\x02\x00\x01\x00\x00\x02")
	 272  
	 273  		// Encode the pixels; some pvals trigger the expected error.
	 274  		enc := lzwEncode([]byte{pval, pval})
	 275  		b.WriteByte(byte(len(enc)))
	 276  		b.Write(enc)
	 277  		b.WriteByte(0x00) // An empty block signifies the end of the image data.
	 278  
	 279  		b.WriteString(trailerStr)
	 280  
	 281  		// No error expected, unless the pixels are beyond the 2 color palette.
	 282  		want := ""
	 283  		if pval >= 2 {
	 284  			want = "gif: invalid pixel value"
	 285  		}
	 286  		try(t, b.Bytes(), want)
	 287  	}
	 288  }
	 289  
	 290  func TestTransparentPixelOutsidePaletteRange(t *testing.T) {
	 291  	b := &bytes.Buffer{}
	 292  
	 293  	// Manufacture a GIF with a 2 color palette.
	 294  	b.WriteString(headerStr)
	 295  	b.WriteString(paletteStr)
	 296  
	 297  	// Graphic Control Extension: transparency, transparent color index = 3.
	 298  	//
	 299  	// This index, 3, is out of range of the global palette and there is no
	 300  	// local palette in the subsequent image descriptor. This is an error
	 301  	// according to the spec, but Firefox and Google Chrome seem OK with this.
	 302  	//
	 303  	// See golang.org/issue/15059.
	 304  	b.WriteString("\x21\xf9\x04\x01\x00\x00\x03\x00")
	 305  
	 306  	// Image descriptor: 2x1, no local palette, and 2-bit LZW literals.
	 307  	b.WriteString("\x2c\x00\x00\x00\x00\x02\x00\x01\x00\x00\x02")
	 308  
	 309  	// Encode the pixels.
	 310  	enc := lzwEncode([]byte{0x03, 0x03})
	 311  	b.WriteByte(byte(len(enc)))
	 312  	b.Write(enc)
	 313  	b.WriteByte(0x00) // An empty block signifies the end of the image data.
	 314  
	 315  	b.WriteString(trailerStr)
	 316  
	 317  	try(t, b.Bytes(), "")
	 318  }
	 319  
	 320  func TestLoopCount(t *testing.T) {
	 321  	testCases := []struct {
	 322  		name			string
	 323  		data			[]byte
	 324  		loopCount int
	 325  	}{
	 326  		{
	 327  			"loopcount-missing",
	 328  			[]byte("GIF89a000\x00000" +
	 329  				",0\x00\x00\x00\n\x00\n\x00\x80000000" + // image 0 descriptor & color table
	 330  				"\x02\b\xf01u\xb9\xfdal\x05\x00;"), // image 0 image data & trailer
	 331  			-1,
	 332  		},
	 333  		{
	 334  			"loopcount-0",
	 335  			[]byte("GIF89a000\x00000" +
	 336  				"!\xff\vNETSCAPE2.0\x03\x01\x00\x00\x00" + // loop count = 0
	 337  				",0\x00\x00\x00\n\x00\n\x00\x80000000" + // image 0 descriptor & color table
	 338  				"\x02\b\xf01u\xb9\xfdal\x05\x00" + // image 0 image data
	 339  				",0\x00\x00\x00\n\x00\n\x00\x80000000" + // image 1 descriptor & color table
	 340  				"\x02\b\xf01u\xb9\xfdal\x05\x00;"), // image 1 image data & trailer
	 341  			0,
	 342  		},
	 343  		{
	 344  			"loopcount-1",
	 345  			[]byte("GIF89a000\x00000" +
	 346  				"!\xff\vNETSCAPE2.0\x03\x01\x01\x00\x00" + // loop count = 1
	 347  				",0\x00\x00\x00\n\x00\n\x00\x80000000" + // image 0 descriptor & color table
	 348  				"\x02\b\xf01u\xb9\xfdal\x05\x00" + // image 0 image data
	 349  				",0\x00\x00\x00\n\x00\n\x00\x80000000" + // image 1 descriptor & color table
	 350  				"\x02\b\xf01u\xb9\xfdal\x05\x00;"), // image 1 image data & trailer
	 351  			1,
	 352  		},
	 353  	}
	 354  
	 355  	for _, tc := range testCases {
	 356  		t.Run(tc.name, func(t *testing.T) {
	 357  			img, err := DecodeAll(bytes.NewReader(tc.data))
	 358  			if err != nil {
	 359  				t.Fatal("DecodeAll:", err)
	 360  			}
	 361  			w := new(bytes.Buffer)
	 362  			err = EncodeAll(w, img)
	 363  			if err != nil {
	 364  				t.Fatal("EncodeAll:", err)
	 365  			}
	 366  			img1, err := DecodeAll(w)
	 367  			if err != nil {
	 368  				t.Fatal("DecodeAll:", err)
	 369  			}
	 370  			if img.LoopCount != tc.loopCount {
	 371  				t.Errorf("loop count mismatch: %d vs %d", img.LoopCount, tc.loopCount)
	 372  			}
	 373  			if img.LoopCount != img1.LoopCount {
	 374  				t.Errorf("loop count failed round-trip: %d vs %d", img.LoopCount, img1.LoopCount)
	 375  			}
	 376  		})
	 377  	}
	 378  }
	 379  
	 380  func TestUnexpectedEOF(t *testing.T) {
	 381  	for i := len(testGIF) - 1; i >= 0; i-- {
	 382  		_, err := Decode(bytes.NewReader(testGIF[:i]))
	 383  		if err == errNotEnough {
	 384  			continue
	 385  		}
	 386  		text := ""
	 387  		if err != nil {
	 388  			text = err.Error()
	 389  		}
	 390  		if !strings.HasPrefix(text, "gif:") || !strings.HasSuffix(text, ": unexpected EOF") {
	 391  			t.Errorf("Decode(testGIF[:%d]) = %v, want gif: ...: unexpected EOF", i, err)
	 392  		}
	 393  	}
	 394  }
	 395  
	 396  // See golang.org/issue/22237
	 397  func TestDecodeMemoryConsumption(t *testing.T) {
	 398  	const frames = 3000
	 399  	img := image.NewPaletted(image.Rectangle{Max: image.Point{1, 1}}, palette.WebSafe)
	 400  	hugeGIF := &GIF{
	 401  		Image:		make([]*image.Paletted, frames),
	 402  		Delay:		make([]int, frames),
	 403  		Disposal: make([]byte, frames),
	 404  	}
	 405  	for i := 0; i < frames; i++ {
	 406  		hugeGIF.Image[i] = img
	 407  		hugeGIF.Delay[i] = 60
	 408  	}
	 409  	buf := new(bytes.Buffer)
	 410  	if err := EncodeAll(buf, hugeGIF); err != nil {
	 411  		t.Fatal("EncodeAll:", err)
	 412  	}
	 413  	s0, s1 := new(runtime.MemStats), new(runtime.MemStats)
	 414  	runtime.GC()
	 415  	defer debug.SetGCPercent(debug.SetGCPercent(5))
	 416  	runtime.ReadMemStats(s0)
	 417  	if _, err := Decode(buf); err != nil {
	 418  		t.Fatal("Decode:", err)
	 419  	}
	 420  	runtime.ReadMemStats(s1)
	 421  	if heapDiff := int64(s1.HeapAlloc - s0.HeapAlloc); heapDiff > 30<<20 {
	 422  		t.Fatalf("Decode of %d frames increased heap by %dMB", frames, heapDiff>>20)
	 423  	}
	 424  }
	 425  
	 426  func BenchmarkDecode(b *testing.B) {
	 427  	data, err := os.ReadFile("../testdata/video-001.gif")
	 428  	if err != nil {
	 429  		b.Fatal(err)
	 430  	}
	 431  	cfg, err := DecodeConfig(bytes.NewReader(data))
	 432  	if err != nil {
	 433  		b.Fatal(err)
	 434  	}
	 435  	b.SetBytes(int64(cfg.Width * cfg.Height))
	 436  	b.ReportAllocs()
	 437  	b.ResetTimer()
	 438  	for i := 0; i < b.N; i++ {
	 439  		Decode(bytes.NewReader(data))
	 440  	}
	 441  }
	 442  

View as plain text