Home | History | Annotate | Download | only in 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 	"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://golang.org/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 TestDimensionOverflow(t *testing.T) {
    653 	// These bytes come from https://golang.org/issues/22304
    654 	//
    655 	// It encodes a 2147483646  2147483646 (i.e. 0x7ffffffe  0x7ffffffe)
    656 	// NRGBA image. The (width  height) per se doesn't overflow an int64, but
    657 	// (width  height  bytesPerPixel) will.
    658 	_, err := Decode(bytes.NewReader([]byte{
    659 		0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
    660 		0x7f, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xfe, 0x08, 0x06, 0x00, 0x00, 0x00, 0x30, 0x57, 0xb3,
    661 		0xfd, 0x00, 0x00, 0x00, 0x15, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9c, 0x62, 0x62, 0x20, 0x12, 0x8c,
    662 		0x2a, 0xa4, 0xb3, 0x42, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0x13, 0x38, 0x00, 0x15, 0x2d, 0xef,
    663 		0x5f, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82,
    664 	}))
    665 	if _, ok := err.(UnsupportedError); !ok {
    666 		t.Fatalf("Decode: got %v (of type %T), want non-nil error (of type png.UnsupportedError)", err, err)
    667 	}
    668 }
    669 
    670 func benchmarkDecode(b *testing.B, filename string, bytesPerPixel int) {
    671 	data, err := ioutil.ReadFile(filename)
    672 	if err != nil {
    673 		b.Fatal(err)
    674 	}
    675 	cfg, err := DecodeConfig(bytes.NewReader(data))
    676 	if err != nil {
    677 		b.Fatal(err)
    678 	}
    679 	b.SetBytes(int64(cfg.Width * cfg.Height * bytesPerPixel))
    680 	b.ReportAllocs()
    681 	b.ResetTimer()
    682 	for i := 0; i < b.N; i++ {
    683 		Decode(bytes.NewReader(data))
    684 	}
    685 }
    686 
    687 func BenchmarkDecodeGray(b *testing.B) {
    688 	benchmarkDecode(b, "testdata/benchGray.png", 1)
    689 }
    690 
    691 func BenchmarkDecodeNRGBAGradient(b *testing.B) {
    692 	benchmarkDecode(b, "testdata/benchNRGBA-gradient.png", 4)
    693 }
    694 
    695 func BenchmarkDecodeNRGBAOpaque(b *testing.B) {
    696 	benchmarkDecode(b, "testdata/benchNRGBA-opaque.png", 4)
    697 }
    698 
    699 func BenchmarkDecodePaletted(b *testing.B) {
    700 	benchmarkDecode(b, "testdata/benchPaletted.png", 1)
    701 }
    702 
    703 func BenchmarkDecodeRGB(b *testing.B) {
    704 	benchmarkDecode(b, "testdata/benchRGB.png", 4)
    705 }
    706 
    707 func BenchmarkDecodeInterlacing(b *testing.B) {
    708 	benchmarkDecode(b, "testdata/benchRGB-interlace.png", 4)
    709 }
    710