Black Lives Matter. Support the Equal Justice Initiative.

Source file src/image/png/reader_test.go

Documentation: image/png

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

View as plain text