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
5package png
6
7import (
8	"bufio"
9	"bytes"
10	"fmt"
11	"image"
12	"image/color"
13	"io"
14	"os"
15	"reflect"
16	"strings"
17	"testing"
18)
19
20var 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
58var filenamesPaletted = []string{
59	"basn3p01",
60	"basn3p02",
61	"basn3p04",
62	"basn3p08",
63	"basn3p08-trns",
64}
65
66var filenamesShort = []string{
67	"basn0g01",
68	"basn0g04-31",
69	"basn6a16",
70}
71
72func 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.
84var 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.
101var 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".
115var 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.
126func 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
330func 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
409var 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
419func 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
435func 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
460func 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
474func 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
489func 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
516func 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
574func 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
586func 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
601func 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
662func 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
786func TestDecodePalettedWithTransparency(t *testing.T) {
787	// These bytes come from https://go.dev/issue/54325
788	//
789	// Per the PNG spec, a PLTE chunk contains 3 (not 4) bytes per palette
790	// entry: RGB (not RGBA). The alpha value comes from the optional tRNS
791	// chunk. Here, the PLTE chunk (0x50, 0x4c, 0x54, 0x45, etc) has 16 entries
792	// (0x30 = 48 bytes) and the tRNS chunk (0x74, 0x52, 0x4e, 0x53, etc) has 1
793	// entry (0x01 = 1 byte) that sets the first palette entry's alpha to zero.
794	//
795	// Both Decode and DecodeConfig should pick up that the first palette
796	// entry's alpha is zero.
797	src := []byte{
798		0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
799		0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 0x04, 0x03, 0x00, 0x00, 0x00, 0x81, 0x54, 0x67,
800		0xc7, 0x00, 0x00, 0x00, 0x30, 0x50, 0x4c, 0x54, 0x45, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x0e,
801		0x00, 0x23, 0x27, 0x7b, 0xb1, 0x2d, 0x0a, 0x49, 0x3f, 0x19, 0x78, 0x5f, 0xcd, 0xe4, 0x69, 0x69,
802		0xe4, 0x71, 0x59, 0x53, 0x80, 0x11, 0x14, 0x8b, 0x00, 0xa9, 0x8d, 0x95, 0xcb, 0x99, 0x2f, 0x6b,
803		0xd7, 0x29, 0x91, 0xd7, 0x7b, 0xba, 0xff, 0xe3, 0xd7, 0x13, 0xc6, 0xd3, 0x58, 0x00, 0x00, 0x00,
804		0x01, 0x74, 0x52, 0x4e, 0x53, 0x00, 0x40, 0xe6, 0xd8, 0x66, 0x00, 0x00, 0x00, 0xfd, 0x49, 0x44,
805		0x41, 0x54, 0x28, 0xcf, 0x63, 0x60, 0x00, 0x83, 0x55, 0x0c, 0x68, 0x60, 0x9d, 0x02, 0x9a, 0x80,
806		0xde, 0x23, 0x74, 0x15, 0xef, 0x50, 0x94, 0x70, 0x2d, 0xd2, 0x7b, 0x87, 0xa2, 0x84, 0xeb, 0xee,
807		0xbb, 0x77, 0x6f, 0x51, 0x94, 0xe8, 0xbd, 0x7d, 0xf7, 0xee, 0x12, 0xb2, 0x80, 0xd2, 0x3d, 0x54,
808		0x01, 0x26, 0x10, 0x1f, 0x59, 0x40, 0x0f, 0xc8, 0xd7, 0x7e, 0x84, 0x70, 0x1c, 0xd7, 0xba, 0xb7,
809		0x4a, 0xda, 0xda, 0x77, 0x11, 0xf6, 0xac, 0x5a, 0xa5, 0xf4, 0xf9, 0xbf, 0xfd, 0x3d, 0x24, 0x6b,
810		0x98, 0x94, 0xf4, 0xff, 0x7f, 0x52, 0x42, 0x16, 0x30, 0x0e, 0xd9, 0xed, 0x6a, 0x8c, 0xec, 0x10,
811		0x65, 0x53, 0x97, 0x60, 0x23, 0x64, 0x1d, 0x8a, 0x2e, 0xc6, 0x2e, 0x42, 0x08, 0x3d, 0x4c, 0xca,
812		0x81, 0xc1, 0x82, 0xa6, 0xa2, 0x46, 0x08, 0x3d, 0x4a, 0xa1, 0x82, 0xc6, 0x82, 0xa1, 0x4a, 0x08,
813		0x3d, 0xfa, 0xa6, 0x81, 0xa1, 0xa2, 0xc1, 0x9f, 0x10, 0x66, 0xd4, 0x2b, 0x87, 0x0a, 0x86, 0x1a,
814		0x7d, 0x57, 0x80, 0x9b, 0x99, 0xaf, 0x62, 0x1a, 0x1a, 0xec, 0xf0, 0x0d, 0x66, 0x2a, 0x7b, 0x5a,
815		0xba, 0xd2, 0x64, 0x63, 0x4b, 0xa6, 0xb2, 0xb4, 0x02, 0xa8, 0x12, 0xb5, 0x24, 0xa5, 0x99, 0x2e,
816		0x33, 0x95, 0xd4, 0x92, 0x10, 0xee, 0xd0, 0x59, 0xb9, 0x6a, 0xd6, 0x21, 0x24, 0xb7, 0x33, 0x9d,
817		0x01, 0x01, 0x64, 0xbf, 0xac, 0x59, 0xb2, 0xca, 0xeb, 0x14, 0x92, 0x80, 0xd6, 0x9a, 0x53, 0x4a,
818		0x6b, 0x4e, 0x2d, 0x42, 0x52, 0xa1, 0x73, 0x28, 0x54, 0xe7, 0x90, 0x6a, 0x00, 0x92, 0x92, 0x45,
819		0xa1, 0x40, 0x84, 0x2c, 0xe0, 0xc4, 0xa0, 0xb2, 0x28, 0x14, 0xc1, 0x67, 0xe9, 0x50, 0x60, 0x60,
820		0xea, 0x70, 0x40, 0x12, 0x00, 0x79, 0x54, 0x09, 0x22, 0x00, 0x00, 0x30, 0xf3, 0x52, 0x87, 0xc6,
821		0xe4, 0xbd, 0x70, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82,
822	}
823
824	cfg, err := DecodeConfig(bytes.NewReader(src))
825	if err != nil {
826		t.Fatalf("DecodeConfig: %v", err)
827	} else if _, _, _, alpha := cfg.ColorModel.(color.Palette)[0].RGBA(); alpha != 0 {
828		t.Errorf("DecodeConfig: got %d, want 0", alpha)
829	}
830
831	img, err := Decode(bytes.NewReader(src))
832	if err != nil {
833		t.Fatalf("Decode: %v", err)
834	} else if _, _, _, alpha := img.ColorModel().(color.Palette)[0].RGBA(); alpha != 0 {
835		t.Errorf("Decode: got %d, want 0", alpha)
836	}
837}
838
839func benchmarkDecode(b *testing.B, filename string, bytesPerPixel int) {
840	data, err := os.ReadFile(filename)
841	if err != nil {
842		b.Fatal(err)
843	}
844	cfg, err := DecodeConfig(bytes.NewReader(data))
845	if err != nil {
846		b.Fatal(err)
847	}
848	b.SetBytes(int64(cfg.Width * cfg.Height * bytesPerPixel))
849	b.ReportAllocs()
850	b.ResetTimer()
851	for i := 0; i < b.N; i++ {
852		Decode(bytes.NewReader(data))
853	}
854}
855
856func BenchmarkDecodeGray(b *testing.B) {
857	benchmarkDecode(b, "testdata/benchGray.png", 1)
858}
859
860func BenchmarkDecodeNRGBAGradient(b *testing.B) {
861	benchmarkDecode(b, "testdata/benchNRGBA-gradient.png", 4)
862}
863
864func BenchmarkDecodeNRGBAOpaque(b *testing.B) {
865	benchmarkDecode(b, "testdata/benchNRGBA-opaque.png", 4)
866}
867
868func BenchmarkDecodePaletted(b *testing.B) {
869	benchmarkDecode(b, "testdata/benchPaletted.png", 1)
870}
871
872func BenchmarkDecodeRGB(b *testing.B) {
873	benchmarkDecode(b, "testdata/benchRGB.png", 4)
874}
875
876func BenchmarkDecodeInterlacing(b *testing.B) {
877	benchmarkDecode(b, "testdata/benchRGB-interlace.png", 4)
878}
879