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

View as plain text