github.com/zebozhuang/go@v0.0.0-20200207033046-f8a98f6f5c5d/src/image/png/reader_test.go (about)

     1  // Copyright 2009 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 png
     6  
     7  import (
     8  	"bufio"
     9  	"bytes"
    10  	"fmt"
    11  	"image"
    12  	"image/color"
    13  	"io"
    14  	"io/ioutil"
    15  	"os"
    16  	"reflect"
    17  	"strings"
    18  	"testing"
    19  )
    20  
    21  var filenames = []string{
    22  	"basn0g01",
    23  	"basn0g01-30",
    24  	"basn0g02",
    25  	"basn0g02-29",
    26  	"basn0g04",
    27  	"basn0g04-31",
    28  	"basn0g08",
    29  	"basn0g16",
    30  	"basn2c08",
    31  	"basn2c16",
    32  	"basn3p01",
    33  	"basn3p02",
    34  	"basn3p04",
    35  	"basn3p04-31i",
    36  	"basn3p08",
    37  	"basn3p08-trns",
    38  	"basn4a08",
    39  	"basn4a16",
    40  	"basn6a08",
    41  	"basn6a16",
    42  	"ftbbn0g01",
    43  	"ftbbn0g02",
    44  	"ftbbn0g04",
    45  	"ftbbn2c16",
    46  	"ftbbn3p08",
    47  	"ftbgn2c16",
    48  	"ftbgn3p08",
    49  	"ftbrn2c08",
    50  	"ftbwn0g16",
    51  	"ftbwn3p08",
    52  	"ftbyn3p08",
    53  	"ftp0n0g08",
    54  	"ftp0n2c08",
    55  	"ftp0n3p08",
    56  	"ftp1n3p08",
    57  }
    58  
    59  var filenamesPaletted = []string{
    60  	"basn3p01",
    61  	"basn3p02",
    62  	"basn3p04",
    63  	"basn3p08",
    64  	"basn3p08-trns",
    65  }
    66  
    67  var filenamesShort = []string{
    68  	"basn0g01",
    69  	"basn0g04-31",
    70  	"basn6a16",
    71  }
    72  
    73  func readPNG(filename string) (image.Image, error) {
    74  	f, err := os.Open(filename)
    75  	if err != nil {
    76  		return nil, err
    77  	}
    78  	defer f.Close()
    79  	return Decode(f)
    80  }
    81  
    82  // fakebKGDs maps from filenames to fake bKGD chunks for our approximation to
    83  // the sng command-line tool. Package png doesn't keep that metadata when
    84  // png.Decode returns an image.Image.
    85  var fakebKGDs = map[string]string{
    86  	"ftbbn0g01": "bKGD {gray: 0;}\n",
    87  	"ftbbn0g02": "bKGD {gray: 0;}\n",
    88  	"ftbbn0g04": "bKGD {gray: 0;}\n",
    89  	"ftbbn2c16": "bKGD {red: 0;  green: 0;  blue: 65535;}\n",
    90  	"ftbbn3p08": "bKGD {index: 245}\n",
    91  	"ftbgn2c16": "bKGD {red: 0;  green: 65535;  blue: 0;}\n",
    92  	"ftbgn3p08": "bKGD {index: 245}\n",
    93  	"ftbrn2c08": "bKGD {red: 255;  green: 0;  blue: 0;}\n",
    94  	"ftbwn0g16": "bKGD {gray: 65535;}\n",
    95  	"ftbwn3p08": "bKGD {index: 0}\n",
    96  	"ftbyn3p08": "bKGD {index: 245}\n",
    97  }
    98  
    99  // fakegAMAs maps from filenames to fake gAMA chunks for our approximation to
   100  // the sng command-line tool. Package png doesn't keep that metadata when
   101  // png.Decode returns an image.Image.
   102  var fakegAMAs = map[string]string{
   103  	"ftbbn0g01": "",
   104  	"ftbbn0g02": "gAMA {0.45455}\n",
   105  }
   106  
   107  // fakeIHDRUsings maps from filenames to fake IHDR "using" lines for our
   108  // approximation to the sng command-line tool. The PNG model is that
   109  // transparency (in the tRNS chunk) is separate to the color/grayscale/palette
   110  // color model (in the IHDR chunk). The Go model is that the concrete
   111  // image.Image type returned by png.Decode, such as image.RGBA (with all pixels
   112  // having 100% alpha) or image.NRGBA, encapsulates whether or not the image has
   113  // transparency. This map is a hack to work around the fact that the Go model
   114  // can't otherwise discriminate PNG's "IHDR says color (with no alpha) but tRNS
   115  // says alpha" and "IHDR says color with alpha".
   116  var fakeIHDRUsings = map[string]string{
   117  	"ftbbn0g01": "    using grayscale;\n",
   118  	"ftbbn0g02": "    using grayscale;\n",
   119  	"ftbbn0g04": "    using grayscale;\n",
   120  	"ftbbn2c16": "    using color;\n",
   121  	"ftbgn2c16": "    using color;\n",
   122  	"ftbrn2c08": "    using color;\n",
   123  	"ftbwn0g16": "    using grayscale;\n",
   124  }
   125  
   126  // An approximation of the sng command-line tool.
   127  func sng(w io.WriteCloser, filename string, png image.Image) {
   128  	defer w.Close()
   129  	bounds := png.Bounds()
   130  	cm := png.ColorModel()
   131  	var bitdepth int
   132  	switch cm {
   133  	case color.RGBAModel, color.NRGBAModel, color.AlphaModel, color.GrayModel:
   134  		bitdepth = 8
   135  	default:
   136  		bitdepth = 16
   137  	}
   138  	cpm, _ := cm.(color.Palette)
   139  	var paletted *image.Paletted
   140  	if cpm != nil {
   141  		switch {
   142  		case len(cpm) <= 2:
   143  			bitdepth = 1
   144  		case len(cpm) <= 4:
   145  			bitdepth = 2
   146  		case len(cpm) <= 16:
   147  			bitdepth = 4
   148  		default:
   149  			bitdepth = 8
   150  		}
   151  		paletted = png.(*image.Paletted)
   152  	}
   153  
   154  	// Write the filename and IHDR.
   155  	io.WriteString(w, "#SNG: from "+filename+".png\nIHDR {\n")
   156  	fmt.Fprintf(w, "    width: %d; height: %d; bitdepth: %d;\n", bounds.Dx(), bounds.Dy(), bitdepth)
   157  	if s, ok := fakeIHDRUsings[filename]; ok {
   158  		io.WriteString(w, s)
   159  	} else {
   160  		switch {
   161  		case cm == color.RGBAModel, cm == color.RGBA64Model:
   162  			io.WriteString(w, "    using color;\n")
   163  		case cm == color.NRGBAModel, cm == color.NRGBA64Model:
   164  			io.WriteString(w, "    using color alpha;\n")
   165  		case cm == color.GrayModel, cm == color.Gray16Model:
   166  			io.WriteString(w, "    using grayscale;\n")
   167  		case cpm != nil:
   168  			io.WriteString(w, "    using color palette;\n")
   169  		default:
   170  			io.WriteString(w, "unknown PNG decoder color model\n")
   171  		}
   172  	}
   173  	io.WriteString(w, "}\n")
   174  
   175  	// We fake a gAMA chunk. The test files have a gAMA chunk but the go PNG
   176  	// parser ignores it (the PNG spec section 11.3 says "Ancillary chunks may
   177  	// be ignored by a decoder").
   178  	if s, ok := fakegAMAs[filename]; ok {
   179  		io.WriteString(w, s)
   180  	} else {
   181  		io.WriteString(w, "gAMA {1.0000}\n")
   182  	}
   183  
   184  	// Write the PLTE and tRNS (if applicable).
   185  	useTransparent := false
   186  	if cpm != nil {
   187  		lastAlpha := -1
   188  		io.WriteString(w, "PLTE {\n")
   189  		for i, c := range cpm {
   190  			var r, g, b, a uint8
   191  			switch c := c.(type) {
   192  			case color.RGBA:
   193  				r, g, b, a = c.R, c.G, c.B, 0xff
   194  			case color.NRGBA:
   195  				r, g, b, a = c.R, c.G, c.B, c.A
   196  			default:
   197  				panic("unknown palette color type")
   198  			}
   199  			if a != 0xff {
   200  				lastAlpha = i
   201  			}
   202  			fmt.Fprintf(w, "    (%3d,%3d,%3d)     # rgb = (0x%02x,0x%02x,0x%02x)\n", r, g, b, r, g, b)
   203  		}
   204  		io.WriteString(w, "}\n")
   205  		if s, ok := fakebKGDs[filename]; ok {
   206  			io.WriteString(w, s)
   207  		}
   208  		if lastAlpha != -1 {
   209  			io.WriteString(w, "tRNS {\n")
   210  			for i := 0; i <= lastAlpha; i++ {
   211  				_, _, _, a := cpm[i].RGBA()
   212  				a >>= 8
   213  				fmt.Fprintf(w, " %d", a)
   214  			}
   215  			io.WriteString(w, "}\n")
   216  		}
   217  	} else if strings.HasPrefix(filename, "ft") {
   218  		if s, ok := fakebKGDs[filename]; ok {
   219  			io.WriteString(w, s)
   220  		}
   221  		// We fake a tRNS chunk. The test files' grayscale and truecolor
   222  		// transparent images all have their top left corner transparent.
   223  		switch c := png.At(0, 0).(type) {
   224  		case color.NRGBA:
   225  			if c.A == 0 {
   226  				useTransparent = true
   227  				io.WriteString(w, "tRNS {\n")
   228  				switch filename {
   229  				case "ftbbn0g01", "ftbbn0g02", "ftbbn0g04":
   230  					// The standard image package doesn't have a "gray with
   231  					// alpha" type. Instead, we use an image.NRGBA.
   232  					fmt.Fprintf(w, "    gray: %d;\n", c.R)
   233  				default:
   234  					fmt.Fprintf(w, "    red: %d; green: %d; blue: %d;\n", c.R, c.G, c.B)
   235  				}
   236  				io.WriteString(w, "}\n")
   237  			}
   238  		case color.NRGBA64:
   239  			if c.A == 0 {
   240  				useTransparent = true
   241  				io.WriteString(w, "tRNS {\n")
   242  				switch filename {
   243  				case "ftbwn0g16":
   244  					// The standard image package doesn't have a "gray16 with
   245  					// alpha" type. Instead, we use an image.NRGBA64.
   246  					fmt.Fprintf(w, "    gray: %d;\n", c.R)
   247  				default:
   248  					fmt.Fprintf(w, "    red: %d; green: %d; blue: %d;\n", c.R, c.G, c.B)
   249  				}
   250  				io.WriteString(w, "}\n")
   251  			}
   252  		}
   253  	}
   254  
   255  	// Write the IMAGE.
   256  	io.WriteString(w, "IMAGE {\n    pixels hex\n")
   257  	for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
   258  		switch {
   259  		case cm == color.GrayModel:
   260  			for x := bounds.Min.X; x < bounds.Max.X; x++ {
   261  				gray := png.At(x, y).(color.Gray)
   262  				fmt.Fprintf(w, "%02x", gray.Y)
   263  			}
   264  		case cm == color.Gray16Model:
   265  			for x := bounds.Min.X; x < bounds.Max.X; x++ {
   266  				gray16 := png.At(x, y).(color.Gray16)
   267  				fmt.Fprintf(w, "%04x ", gray16.Y)
   268  			}
   269  		case cm == color.RGBAModel:
   270  			for x := bounds.Min.X; x < bounds.Max.X; x++ {
   271  				rgba := png.At(x, y).(color.RGBA)
   272  				fmt.Fprintf(w, "%02x%02x%02x ", rgba.R, rgba.G, rgba.B)
   273  			}
   274  		case cm == color.RGBA64Model:
   275  			for x := bounds.Min.X; x < bounds.Max.X; x++ {
   276  				rgba64 := png.At(x, y).(color.RGBA64)
   277  				fmt.Fprintf(w, "%04x%04x%04x ", rgba64.R, rgba64.G, rgba64.B)
   278  			}
   279  		case cm == color.NRGBAModel:
   280  			for x := bounds.Min.X; x < bounds.Max.X; x++ {
   281  				nrgba := png.At(x, y).(color.NRGBA)
   282  				switch filename {
   283  				case "ftbbn0g01", "ftbbn0g02", "ftbbn0g04":
   284  					fmt.Fprintf(w, "%02x", nrgba.R)
   285  				default:
   286  					if useTransparent {
   287  						fmt.Fprintf(w, "%02x%02x%02x ", nrgba.R, nrgba.G, nrgba.B)
   288  					} else {
   289  						fmt.Fprintf(w, "%02x%02x%02x%02x ", nrgba.R, nrgba.G, nrgba.B, nrgba.A)
   290  					}
   291  				}
   292  			}
   293  		case cm == color.NRGBA64Model:
   294  			for x := bounds.Min.X; x < bounds.Max.X; x++ {
   295  				nrgba64 := png.At(x, y).(color.NRGBA64)
   296  				switch filename {
   297  				case "ftbwn0g16":
   298  					fmt.Fprintf(w, "%04x ", nrgba64.R)
   299  				default:
   300  					if useTransparent {
   301  						fmt.Fprintf(w, "%04x%04x%04x ", nrgba64.R, nrgba64.G, nrgba64.B)
   302  					} else {
   303  						fmt.Fprintf(w, "%04x%04x%04x%04x ", nrgba64.R, nrgba64.G, nrgba64.B, nrgba64.A)
   304  					}
   305  				}
   306  			}
   307  		case cpm != nil:
   308  			var b, c int
   309  			for x := bounds.Min.X; x < bounds.Max.X; x++ {
   310  				b = b<<uint(bitdepth) | int(paletted.ColorIndexAt(x, y))
   311  				c++
   312  				if c == 8/bitdepth {
   313  					fmt.Fprintf(w, "%02x", b)
   314  					b = 0
   315  					c = 0
   316  				}
   317  			}
   318  			if c != 0 {
   319  				for c != 8/bitdepth {
   320  					b = b << uint(bitdepth)
   321  					c++
   322  				}
   323  				fmt.Fprintf(w, "%02x", b)
   324  			}
   325  		}
   326  		io.WriteString(w, "\n")
   327  	}
   328  	io.WriteString(w, "}\n")
   329  }
   330  
   331  func TestReader(t *testing.T) {
   332  	names := filenames
   333  	if testing.Short() {
   334  		names = filenamesShort
   335  	}
   336  	for _, fn := range names {
   337  		// Read the .png file.
   338  		img, err := readPNG("testdata/pngsuite/" + fn + ".png")
   339  		if err != nil {
   340  			t.Error(fn, err)
   341  			continue
   342  		}
   343  
   344  		if fn == "basn4a16" {
   345  			// basn4a16.sng is gray + alpha but sng() will produce true color + alpha
   346  			// so we just check a single random pixel.
   347  			c := img.At(2, 1).(color.NRGBA64)
   348  			if c.R != 0x11a7 || c.G != 0x11a7 || c.B != 0x11a7 || c.A != 0x1085 {
   349  				t.Error(fn, fmt.Errorf("wrong pixel value at (2, 1): %x", c))
   350  			}
   351  			continue
   352  		}
   353  
   354  		piper, pipew := io.Pipe()
   355  		pb := bufio.NewScanner(piper)
   356  		go sng(pipew, fn, img)
   357  		defer piper.Close()
   358  
   359  		// Read the .sng file.
   360  		sf, err := os.Open("testdata/pngsuite/" + fn + ".sng")
   361  		if err != nil {
   362  			t.Error(fn, err)
   363  			continue
   364  		}
   365  		defer sf.Close()
   366  		sb := bufio.NewScanner(sf)
   367  		if err != nil {
   368  			t.Error(fn, err)
   369  			continue
   370  		}
   371  
   372  		// Compare the two, in SNG format, line by line.
   373  		for {
   374  			pdone := !pb.Scan()
   375  			sdone := !sb.Scan()
   376  			if pdone && sdone {
   377  				break
   378  			}
   379  			if pdone || sdone {
   380  				t.Errorf("%s: Different sizes", fn)
   381  				break
   382  			}
   383  			ps := pb.Text()
   384  			ss := sb.Text()
   385  
   386  			// Newer versions of the sng command line tool append an optional
   387  			// color name to the RGB tuple. For example:
   388  			//	# rgb = (0xff,0xff,0xff) grey100
   389  			//	# rgb = (0x00,0x00,0xff) blue1
   390  			// instead of the older version's plainer:
   391  			//	# rgb = (0xff,0xff,0xff)
   392  			//	# rgb = (0x00,0x00,0xff)
   393  			// We strip any such name.
   394  			if strings.Contains(ss, "# rgb = (") && !strings.HasSuffix(ss, ")") {
   395  				if i := strings.LastIndex(ss, ") "); i >= 0 {
   396  					ss = ss[:i+1]
   397  				}
   398  			}
   399  
   400  			if ps != ss {
   401  				t.Errorf("%s: Mismatch\n%s\nversus\n%s\n", fn, ps, ss)
   402  				break
   403  			}
   404  		}
   405  		if pb.Err() != nil {
   406  			t.Error(fn, pb.Err())
   407  		}
   408  		if sb.Err() != nil {
   409  			t.Error(fn, sb.Err())
   410  		}
   411  	}
   412  }
   413  
   414  var readerErrors = []struct {
   415  	file string
   416  	err  string
   417  }{
   418  	{"invalid-zlib.png", "zlib: invalid checksum"},
   419  	{"invalid-crc32.png", "invalid checksum"},
   420  	{"invalid-noend.png", "unexpected EOF"},
   421  	{"invalid-trunc.png", "unexpected EOF"},
   422  }
   423  
   424  func TestReaderError(t *testing.T) {
   425  	for _, tt := range readerErrors {
   426  		img, err := readPNG("testdata/" + tt.file)
   427  		if err == nil {
   428  			t.Errorf("decoding %s: missing error", tt.file)
   429  			continue
   430  		}
   431  		if !strings.Contains(err.Error(), tt.err) {
   432  			t.Errorf("decoding %s: %s, want %s", tt.file, err, tt.err)
   433  		}
   434  		if img != nil {
   435  			t.Errorf("decoding %s: have image + error", tt.file)
   436  		}
   437  	}
   438  }
   439  
   440  func TestPalettedDecodeConfig(t *testing.T) {
   441  	for _, fn := range filenamesPaletted {
   442  		f, err := os.Open("testdata/pngsuite/" + fn + ".png")
   443  		if err != nil {
   444  			t.Errorf("%s: open failed: %v", fn, err)
   445  			continue
   446  		}
   447  		defer f.Close()
   448  		cfg, err := DecodeConfig(f)
   449  		if err != nil {
   450  			t.Errorf("%s: %v", fn, err)
   451  			continue
   452  		}
   453  		pal, ok := cfg.ColorModel.(color.Palette)
   454  		if !ok {
   455  			t.Errorf("%s: expected paletted color model", fn)
   456  			continue
   457  		}
   458  		if pal == nil {
   459  			t.Errorf("%s: palette not initialized", fn)
   460  			continue
   461  		}
   462  	}
   463  }
   464  
   465  func TestInterlaced(t *testing.T) {
   466  	a, err := readPNG("testdata/gray-gradient.png")
   467  	if err != nil {
   468  		t.Fatal(err)
   469  	}
   470  	b, err := readPNG("testdata/gray-gradient.interlaced.png")
   471  	if err != nil {
   472  		t.Fatal(err)
   473  	}
   474  	if !reflect.DeepEqual(a, b) {
   475  		t.Fatalf("decodings differ:\nnon-interlaced:\n%#v\ninterlaced:\n%#v", a, b)
   476  	}
   477  }
   478  
   479  func TestIncompleteIDATOnRowBoundary(t *testing.T) {
   480  	// The following is an invalid 1x2 grayscale PNG image. The header is OK,
   481  	// but the zlib-compressed IDAT payload contains two bytes "\x02\x00",
   482  	// which is only one row of data (the leading "\x02" is a row filter).
   483  	const (
   484  		ihdr = "\x00\x00\x00\x0dIHDR\x00\x00\x00\x01\x00\x00\x00\x02\x08\x00\x00\x00\x00\xbc\xea\xe9\xfb"
   485  		idat = "\x00\x00\x00\x0eIDAT\x78\x9c\x62\x62\x00\x04\x00\x00\xff\xff\x00\x06\x00\x03\xfa\xd0\x59\xae"
   486  		iend = "\x00\x00\x00\x00IEND\xae\x42\x60\x82"
   487  	)
   488  	_, err := Decode(strings.NewReader(pngHeader + ihdr + idat + iend))
   489  	if err == nil {
   490  		t.Fatal("got nil error, want non-nil")
   491  	}
   492  }
   493  
   494  func TestTrailingIDATChunks(t *testing.T) {
   495  	// The following is a valid 1x1 PNG image containing color.Gray{255} and
   496  	// a trailing zero-length IDAT chunk (see PNG specification section 12.9):
   497  	const (
   498  		ihdr      = "\x00\x00\x00\x0dIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x00\x00\x00\x00\x3a\x7e\x9b\x55"
   499  		idatWhite = "\x00\x00\x00\x0eIDAT\x78\x9c\x62\xfa\x0f\x08\x00\x00\xff\xff\x01\x05\x01\x02\x5a\xdd\x39\xcd"
   500  		idatZero  = "\x00\x00\x00\x00IDAT\x35\xaf\x06\x1e"
   501  		iend      = "\x00\x00\x00\x00IEND\xae\x42\x60\x82"
   502  	)
   503  	_, err := Decode(strings.NewReader(pngHeader + ihdr + idatWhite + idatZero + iend))
   504  	if err != nil {
   505  		t.Fatalf("decoding valid image: %v", err)
   506  	}
   507  
   508  	// Non-zero-length trailing IDAT chunks should be ignored (recoverable error).
   509  	// The following chunk contains a single pixel with color.Gray{0}.
   510  	const idatBlack = "\x00\x00\x00\x0eIDAT\x78\x9c\x62\x62\x00\x04\x00\x00\xff\xff\x00\x06\x00\x03\xfa\xd0\x59\xae"
   511  
   512  	img, err := Decode(strings.NewReader(pngHeader + ihdr + idatWhite + idatBlack + iend))
   513  	if err != nil {
   514  		t.Fatalf("trailing IDAT not ignored: %v", err)
   515  	}
   516  	if img.At(0, 0) == (color.Gray{0}) {
   517  		t.Fatal("decoded image from trailing IDAT chunk")
   518  	}
   519  }
   520  
   521  func TestMultipletRNSChunks(t *testing.T) {
   522  	/*
   523  		The following is a valid 1x1 paletted PNG image with a 1-element palette
   524  		containing color.NRGBA{0xff, 0x00, 0x00, 0x7f}:
   525  			0000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452  .PNG........IHDR
   526  			0000010: 0000 0001 0000 0001 0803 0000 0028 cb34  .............(.4
   527  			0000020: bb00 0000 0350 4c54 45ff 0000 19e2 0937  .....PLTE......7
   528  			0000030: 0000 0001 7452 4e53 7f80 5cb4 cb00 0000  ....tRNS..\.....
   529  			0000040: 0e49 4441 5478 9c62 6200 0400 00ff ff00  .IDATx.bb.......
   530  			0000050: 0600 03fa d059 ae00 0000 0049 454e 44ae  .....Y.....IEND.
   531  			0000060: 4260 82                                  B`.
   532  		Dropping the tRNS chunk makes that color's alpha 0xff instead of 0x7f.
   533  	*/
   534  	const (
   535  		ihdr = "\x00\x00\x00\x0dIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x03\x00\x00\x00\x28\xcb\x34\xbb"
   536  		plte = "\x00\x00\x00\x03PLTE\xff\x00\x00\x19\xe2\x09\x37"
   537  		trns = "\x00\x00\x00\x01tRNS\x7f\x80\x5c\xb4\xcb"
   538  		idat = "\x00\x00\x00\x0eIDAT\x78\x9c\x62\x62\x00\x04\x00\x00\xff\xff\x00\x06\x00\x03\xfa\xd0\x59\xae"
   539  		iend = "\x00\x00\x00\x00IEND\xae\x42\x60\x82"
   540  	)
   541  	for i := 0; i < 4; i++ {
   542  		var b []byte
   543  		b = append(b, pngHeader...)
   544  		b = append(b, ihdr...)
   545  		b = append(b, plte...)
   546  		for j := 0; j < i; j++ {
   547  			b = append(b, trns...)
   548  		}
   549  		b = append(b, idat...)
   550  		b = append(b, iend...)
   551  
   552  		var want color.Color
   553  		m, err := Decode(bytes.NewReader(b))
   554  		switch i {
   555  		case 0:
   556  			if err != nil {
   557  				t.Errorf("%d tRNS chunks: %v", i, err)
   558  				continue
   559  			}
   560  			want = color.RGBA{0xff, 0x00, 0x00, 0xff}
   561  		case 1:
   562  			if err != nil {
   563  				t.Errorf("%d tRNS chunks: %v", i, err)
   564  				continue
   565  			}
   566  			want = color.NRGBA{0xff, 0x00, 0x00, 0x7f}
   567  		default:
   568  			if err == nil {
   569  				t.Errorf("%d tRNS chunks: got nil error, want non-nil", i)
   570  			}
   571  			continue
   572  		}
   573  		if got := m.At(0, 0); got != want {
   574  			t.Errorf("%d tRNS chunks: got %T %v, want %T %v", i, got, got, want, want)
   575  		}
   576  	}
   577  }
   578  
   579  func TestUnknownChunkLengthUnderflow(t *testing.T) {
   580  	data := []byte{0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0xff, 0xff,
   581  		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x06, 0xf4, 0x7c, 0x55, 0x04, 0x1a,
   582  		0xd3, 0x11, 0x9a, 0x73, 0x00, 0x00, 0xf8, 0x1e, 0xf3, 0x2e, 0x00, 0x00,
   583  		0x01, 0x00, 0xff, 0xff, 0xff, 0xff, 0x07, 0xf4, 0x7c, 0x55, 0x04, 0x1a,
   584  		0xd3}
   585  	_, err := Decode(bytes.NewReader(data))
   586  	if err == nil {
   587  		t.Errorf("Didn't fail reading an unknown chunk with length 0xffffffff")
   588  	}
   589  }
   590  
   591  func TestGray8Transparent(t *testing.T) {
   592  	// These bytes come from https://github.com/golang/go/issues/19553
   593  	m, err := Decode(bytes.NewReader([]byte{
   594  		0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
   595  		0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x0b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x85, 0x2c, 0x88,
   596  		0x80, 0x00, 0x00, 0x00, 0x02, 0x74, 0x52, 0x4e, 0x53, 0x00, 0xff, 0x5b, 0x91, 0x22, 0xb5, 0x00,
   597  		0x00, 0x00, 0x02, 0x62, 0x4b, 0x47, 0x44, 0x00, 0xff, 0x87, 0x8f, 0xcc, 0xbf, 0x00, 0x00, 0x00,
   598  		0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0a, 0xf0, 0x00, 0x00, 0x0a, 0xf0, 0x01, 0x42, 0xac,
   599  		0x34, 0x98, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4d, 0x45, 0x07, 0xd5, 0x04, 0x02, 0x12, 0x11,
   600  		0x11, 0xf7, 0x65, 0x3d, 0x8b, 0x00, 0x00, 0x00, 0x4f, 0x49, 0x44, 0x41, 0x54, 0x08, 0xd7, 0x63,
   601  		0xf8, 0xff, 0xff, 0xff, 0xb9, 0xbd, 0x70, 0xf0, 0x8c, 0x01, 0xc8, 0xaf, 0x6e, 0x99, 0x02, 0x05,
   602  		0xd9, 0x7b, 0xc1, 0xfc, 0x6b, 0xff, 0xa1, 0xa0, 0x87, 0x30, 0xff, 0xd9, 0xde, 0xbd, 0xd5, 0x4b,
   603  		0xf7, 0xee, 0xfd, 0x0e, 0xe3, 0xef, 0xcd, 0x06, 0x19, 0x14, 0xf5, 0x1e, 0xce, 0xef, 0x01, 0x31,
   604  		0x92, 0xd7, 0x82, 0x41, 0x31, 0x9c, 0x3f, 0x07, 0x02, 0xee, 0xa1, 0xaa, 0xff, 0xff, 0x9f, 0xe1,
   605  		0xd9, 0x56, 0x30, 0xf8, 0x0e, 0xe5, 0x03, 0x00, 0xa9, 0x42, 0x84, 0x3d, 0xdf, 0x8f, 0xa6, 0x8f,
   606  		0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82,
   607  	}))
   608  	if err != nil {
   609  		t.Fatalf("Decode: %v", err)
   610  	}
   611  
   612  	const hex = "0123456789abcdef"
   613  	var got []byte
   614  	bounds := m.Bounds()
   615  	for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
   616  		for x := bounds.Min.X; x < bounds.Max.X; x++ {
   617  			if r, _, _, a := m.At(x, y).RGBA(); a != 0 {
   618  				got = append(got,
   619  					hex[0x0f&(r>>12)],
   620  					hex[0x0f&(r>>8)],
   621  					' ',
   622  				)
   623  			} else {
   624  				got = append(got,
   625  					'.',
   626  					'.',
   627  					' ',
   628  				)
   629  			}
   630  		}
   631  		got = append(got, '\n')
   632  	}
   633  
   634  	const want = "" +
   635  		".. .. .. ce bd bd bd bd bd bd bd bd bd bd e6 \n" +
   636  		".. .. .. 7b 84 94 94 94 94 94 94 94 94 6b bd \n" +
   637  		".. .. .. 7b d6 .. .. .. .. .. .. .. .. 8c bd \n" +
   638  		".. .. .. 7b d6 .. .. .. .. .. .. .. .. 8c bd \n" +
   639  		".. .. .. 7b d6 .. .. .. .. .. .. .. .. 8c bd \n" +
   640  		"e6 bd bd 7b a5 bd bd f7 .. .. .. .. .. 8c bd \n" +
   641  		"bd 6b 94 94 94 94 5a ef .. .. .. .. .. 8c bd \n" +
   642  		"bd 8c .. .. .. .. 63 ad ad ad ad ad ad 73 bd \n" +
   643  		"bd 8c .. .. .. .. 63 9c 9c 9c 9c 9c 9c 9c de \n" +
   644  		"bd 6b 94 94 94 94 5a ef .. .. .. .. .. .. .. \n" +
   645  		"e6 b5 b5 b5 b5 b5 b5 f7 .. .. .. .. .. .. .. \n"
   646  
   647  	if string(got) != want {
   648  		t.Errorf("got:\n%swant:\n%s", got, want)
   649  	}
   650  }
   651  
   652  func benchmarkDecode(b *testing.B, filename string, bytesPerPixel int) {
   653  	b.StopTimer()
   654  	data, err := ioutil.ReadFile(filename)
   655  	if err != nil {
   656  		b.Fatal(err)
   657  	}
   658  	s := string(data)
   659  	cfg, err := DecodeConfig(strings.NewReader(s))
   660  	if err != nil {
   661  		b.Fatal(err)
   662  	}
   663  	b.SetBytes(int64(cfg.Width * cfg.Height * bytesPerPixel))
   664  	b.StartTimer()
   665  	for i := 0; i < b.N; i++ {
   666  		Decode(strings.NewReader(s))
   667  	}
   668  }
   669  
   670  func BenchmarkDecodeGray(b *testing.B) {
   671  	benchmarkDecode(b, "testdata/benchGray.png", 1)
   672  }
   673  
   674  func BenchmarkDecodeNRGBAGradient(b *testing.B) {
   675  	benchmarkDecode(b, "testdata/benchNRGBA-gradient.png", 4)
   676  }
   677  
   678  func BenchmarkDecodeNRGBAOpaque(b *testing.B) {
   679  	benchmarkDecode(b, "testdata/benchNRGBA-opaque.png", 4)
   680  }
   681  
   682  func BenchmarkDecodePaletted(b *testing.B) {
   683  	benchmarkDecode(b, "testdata/benchPaletted.png", 1)
   684  }
   685  
   686  func BenchmarkDecodeRGB(b *testing.B) {
   687  	benchmarkDecode(b, "testdata/benchRGB.png", 4)
   688  }
   689  
   690  func BenchmarkDecodeInterlacing(b *testing.B) {
   691  	benchmarkDecode(b, "testdata/benchRGB-interlace.png", 4)
   692  }