github.com/mh-cbon/go@v0.0.0-20160603070303-9e112a3fe4c0/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  }
    43  
    44  var filenamesPaletted = []string{
    45  	"basn3p01",
    46  	"basn3p02",
    47  	"basn3p04",
    48  	"basn3p08",
    49  	"basn3p08-trns",
    50  }
    51  
    52  var filenamesShort = []string{
    53  	"basn0g01",
    54  	"basn0g04-31",
    55  	"basn6a16",
    56  }
    57  
    58  func readPNG(filename string) (image.Image, error) {
    59  	f, err := os.Open(filename)
    60  	if err != nil {
    61  		return nil, err
    62  	}
    63  	defer f.Close()
    64  	return Decode(f)
    65  }
    66  
    67  // An approximation of the sng command-line tool.
    68  func sng(w io.WriteCloser, filename string, png image.Image) {
    69  	defer w.Close()
    70  	bounds := png.Bounds()
    71  	cm := png.ColorModel()
    72  	var bitdepth int
    73  	switch cm {
    74  	case color.RGBAModel, color.NRGBAModel, color.AlphaModel, color.GrayModel:
    75  		bitdepth = 8
    76  	default:
    77  		bitdepth = 16
    78  	}
    79  	cpm, _ := cm.(color.Palette)
    80  	var paletted *image.Paletted
    81  	if cpm != nil {
    82  		switch {
    83  		case len(cpm) <= 2:
    84  			bitdepth = 1
    85  		case len(cpm) <= 4:
    86  			bitdepth = 2
    87  		case len(cpm) <= 16:
    88  			bitdepth = 4
    89  		default:
    90  			bitdepth = 8
    91  		}
    92  		paletted = png.(*image.Paletted)
    93  	}
    94  
    95  	// Write the filename and IHDR.
    96  	io.WriteString(w, "#SNG: from "+filename+".png\nIHDR {\n")
    97  	fmt.Fprintf(w, "    width: %d; height: %d; bitdepth: %d;\n", bounds.Dx(), bounds.Dy(), bitdepth)
    98  	switch {
    99  	case cm == color.RGBAModel, cm == color.RGBA64Model:
   100  		io.WriteString(w, "    using color;\n")
   101  	case cm == color.NRGBAModel, cm == color.NRGBA64Model:
   102  		io.WriteString(w, "    using color alpha;\n")
   103  	case cm == color.GrayModel, cm == color.Gray16Model:
   104  		io.WriteString(w, "    using grayscale;\n")
   105  	case cpm != nil:
   106  		io.WriteString(w, "    using color palette;\n")
   107  	default:
   108  		io.WriteString(w, "unknown PNG decoder color model\n")
   109  	}
   110  	io.WriteString(w, "}\n")
   111  
   112  	// We fake a gAMA output. The test files have a gAMA chunk but the go PNG parser ignores it
   113  	// (the PNG spec section 11.3 says "Ancillary chunks may be ignored by a decoder").
   114  	io.WriteString(w, "gAMA {1.0000}\n")
   115  
   116  	// Write the PLTE and tRNS (if applicable).
   117  	if cpm != nil {
   118  		lastAlpha := -1
   119  		io.WriteString(w, "PLTE {\n")
   120  		for i, c := range cpm {
   121  			var r, g, b, a uint8
   122  			switch c := c.(type) {
   123  			case color.RGBA:
   124  				r, g, b, a = c.R, c.G, c.B, 0xff
   125  			case color.NRGBA:
   126  				r, g, b, a = c.R, c.G, c.B, c.A
   127  			default:
   128  				panic("unknown palette color type")
   129  			}
   130  			if a != 0xff {
   131  				lastAlpha = i
   132  			}
   133  			fmt.Fprintf(w, "    (%3d,%3d,%3d)     # rgb = (0x%02x,0x%02x,0x%02x)\n", r, g, b, r, g, b)
   134  		}
   135  		io.WriteString(w, "}\n")
   136  		if lastAlpha != -1 {
   137  			io.WriteString(w, "tRNS {\n")
   138  			for i := 0; i <= lastAlpha; i++ {
   139  				_, _, _, a := cpm[i].RGBA()
   140  				a >>= 8
   141  				fmt.Fprintf(w, " %d", a)
   142  			}
   143  			io.WriteString(w, "}\n")
   144  		}
   145  	}
   146  
   147  	// Write the IMAGE.
   148  	io.WriteString(w, "IMAGE {\n    pixels hex\n")
   149  	for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
   150  		switch {
   151  		case cm == color.GrayModel:
   152  			for x := bounds.Min.X; x < bounds.Max.X; x++ {
   153  				gray := png.At(x, y).(color.Gray)
   154  				fmt.Fprintf(w, "%02x", gray.Y)
   155  			}
   156  		case cm == color.Gray16Model:
   157  			for x := bounds.Min.X; x < bounds.Max.X; x++ {
   158  				gray16 := png.At(x, y).(color.Gray16)
   159  				fmt.Fprintf(w, "%04x ", gray16.Y)
   160  			}
   161  		case cm == color.RGBAModel:
   162  			for x := bounds.Min.X; x < bounds.Max.X; x++ {
   163  				rgba := png.At(x, y).(color.RGBA)
   164  				fmt.Fprintf(w, "%02x%02x%02x ", rgba.R, rgba.G, rgba.B)
   165  			}
   166  		case cm == color.RGBA64Model:
   167  			for x := bounds.Min.X; x < bounds.Max.X; x++ {
   168  				rgba64 := png.At(x, y).(color.RGBA64)
   169  				fmt.Fprintf(w, "%04x%04x%04x ", rgba64.R, rgba64.G, rgba64.B)
   170  			}
   171  		case cm == color.NRGBAModel:
   172  			for x := bounds.Min.X; x < bounds.Max.X; x++ {
   173  				nrgba := png.At(x, y).(color.NRGBA)
   174  				fmt.Fprintf(w, "%02x%02x%02x%02x ", nrgba.R, nrgba.G, nrgba.B, nrgba.A)
   175  			}
   176  		case cm == color.NRGBA64Model:
   177  			for x := bounds.Min.X; x < bounds.Max.X; x++ {
   178  				nrgba64 := png.At(x, y).(color.NRGBA64)
   179  				fmt.Fprintf(w, "%04x%04x%04x%04x ", nrgba64.R, nrgba64.G, nrgba64.B, nrgba64.A)
   180  			}
   181  		case cpm != nil:
   182  			var b, c int
   183  			for x := bounds.Min.X; x < bounds.Max.X; x++ {
   184  				b = b<<uint(bitdepth) | int(paletted.ColorIndexAt(x, y))
   185  				c++
   186  				if c == 8/bitdepth {
   187  					fmt.Fprintf(w, "%02x", b)
   188  					b = 0
   189  					c = 0
   190  				}
   191  			}
   192  			if c != 0 {
   193  				for c != 8/bitdepth {
   194  					b = b << uint(bitdepth)
   195  					c++
   196  				}
   197  				fmt.Fprintf(w, "%02x", b)
   198  			}
   199  		}
   200  		io.WriteString(w, "\n")
   201  	}
   202  	io.WriteString(w, "}\n")
   203  }
   204  
   205  func TestReader(t *testing.T) {
   206  	names := filenames
   207  	if testing.Short() {
   208  		names = filenamesShort
   209  	}
   210  	for _, fn := range names {
   211  		// Read the .png file.
   212  		img, err := readPNG("testdata/pngsuite/" + fn + ".png")
   213  		if err != nil {
   214  			t.Error(fn, err)
   215  			continue
   216  		}
   217  
   218  		if fn == "basn4a16" {
   219  			// basn4a16.sng is gray + alpha but sng() will produce true color + alpha
   220  			// so we just check a single random pixel.
   221  			c := img.At(2, 1).(color.NRGBA64)
   222  			if c.R != 0x11a7 || c.G != 0x11a7 || c.B != 0x11a7 || c.A != 0x1085 {
   223  				t.Error(fn, fmt.Errorf("wrong pixel value at (2, 1): %x", c))
   224  			}
   225  			continue
   226  		}
   227  
   228  		piper, pipew := io.Pipe()
   229  		pb := bufio.NewScanner(piper)
   230  		go sng(pipew, fn, img)
   231  		defer piper.Close()
   232  
   233  		// Read the .sng file.
   234  		sf, err := os.Open("testdata/pngsuite/" + fn + ".sng")
   235  		if err != nil {
   236  			t.Error(fn, err)
   237  			continue
   238  		}
   239  		defer sf.Close()
   240  		sb := bufio.NewScanner(sf)
   241  		if err != nil {
   242  			t.Error(fn, err)
   243  			continue
   244  		}
   245  
   246  		// Compare the two, in SNG format, line by line.
   247  		for {
   248  			pdone := !pb.Scan()
   249  			sdone := !sb.Scan()
   250  			if pdone && sdone {
   251  				break
   252  			}
   253  			if pdone || sdone {
   254  				t.Errorf("%s: Different sizes", fn)
   255  				break
   256  			}
   257  			ps := pb.Text()
   258  			ss := sb.Text()
   259  			if ps != ss {
   260  				t.Errorf("%s: Mismatch\n%sversus\n%s\n", fn, ps, ss)
   261  				break
   262  			}
   263  		}
   264  		if pb.Err() != nil {
   265  			t.Error(fn, pb.Err())
   266  		}
   267  		if sb.Err() != nil {
   268  			t.Error(fn, sb.Err())
   269  		}
   270  	}
   271  }
   272  
   273  var readerErrors = []struct {
   274  	file string
   275  	err  string
   276  }{
   277  	{"invalid-zlib.png", "zlib: invalid checksum"},
   278  	{"invalid-crc32.png", "invalid checksum"},
   279  	{"invalid-noend.png", "unexpected EOF"},
   280  	{"invalid-trunc.png", "unexpected EOF"},
   281  }
   282  
   283  func TestReaderError(t *testing.T) {
   284  	for _, tt := range readerErrors {
   285  		img, err := readPNG("testdata/" + tt.file)
   286  		if err == nil {
   287  			t.Errorf("decoding %s: missing error", tt.file)
   288  			continue
   289  		}
   290  		if !strings.Contains(err.Error(), tt.err) {
   291  			t.Errorf("decoding %s: %s, want %s", tt.file, err, tt.err)
   292  		}
   293  		if img != nil {
   294  			t.Errorf("decoding %s: have image + error", tt.file)
   295  		}
   296  	}
   297  }
   298  
   299  func TestPalettedDecodeConfig(t *testing.T) {
   300  	for _, fn := range filenamesPaletted {
   301  		f, err := os.Open("testdata/pngsuite/" + fn + ".png")
   302  		if err != nil {
   303  			t.Errorf("%s: open failed: %v", fn, err)
   304  			continue
   305  		}
   306  		defer f.Close()
   307  		cfg, err := DecodeConfig(f)
   308  		if err != nil {
   309  			t.Errorf("%s: %v", fn, err)
   310  			continue
   311  		}
   312  		pal, ok := cfg.ColorModel.(color.Palette)
   313  		if !ok {
   314  			t.Errorf("%s: expected paletted color model", fn)
   315  			continue
   316  		}
   317  		if pal == nil {
   318  			t.Errorf("%s: palette not initialized", fn)
   319  			continue
   320  		}
   321  	}
   322  }
   323  
   324  func TestInterlaced(t *testing.T) {
   325  	a, err := readPNG("testdata/gray-gradient.png")
   326  	if err != nil {
   327  		t.Fatal(err)
   328  	}
   329  	b, err := readPNG("testdata/gray-gradient.interlaced.png")
   330  	if err != nil {
   331  		t.Fatal(err)
   332  	}
   333  	if !reflect.DeepEqual(a, b) {
   334  		t.Fatalf("decodings differ:\nnon-interlaced:\n%#v\ninterlaced:\n%#v", a, b)
   335  	}
   336  }
   337  
   338  func TestIncompleteIDATOnRowBoundary(t *testing.T) {
   339  	// The following is an invalid 1x2 grayscale PNG image. The header is OK,
   340  	// but the zlib-compressed IDAT payload contains two bytes "\x02\x00",
   341  	// which is only one row of data (the leading "\x02" is a row filter).
   342  	const (
   343  		ihdr = "\x00\x00\x00\x0dIHDR\x00\x00\x00\x01\x00\x00\x00\x02\x08\x00\x00\x00\x00\xbc\xea\xe9\xfb"
   344  		idat = "\x00\x00\x00\x0eIDAT\x78\x9c\x62\x62\x00\x04\x00\x00\xff\xff\x00\x06\x00\x03\xfa\xd0\x59\xae"
   345  		iend = "\x00\x00\x00\x00IEND\xae\x42\x60\x82"
   346  	)
   347  	_, err := Decode(strings.NewReader(pngHeader + ihdr + idat + iend))
   348  	if err == nil {
   349  		t.Fatal("got nil error, want non-nil")
   350  	}
   351  }
   352  
   353  func TestTrailingIDATChunks(t *testing.T) {
   354  	// The following is a valid 1x1 PNG image containing color.Gray{255} and
   355  	// a trailing zero-length IDAT chunk (see PNG specification section 12.9):
   356  	const (
   357  		ihdr      = "\x00\x00\x00\x0dIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x00\x00\x00\x00\x3a\x7e\x9b\x55"
   358  		idatWhite = "\x00\x00\x00\x0eIDAT\x78\x9c\x62\xfa\x0f\x08\x00\x00\xff\xff\x01\x05\x01\x02\x5a\xdd\x39\xcd"
   359  		idatZero  = "\x00\x00\x00\x00IDAT\x35\xaf\x06\x1e"
   360  		iend      = "\x00\x00\x00\x00IEND\xae\x42\x60\x82"
   361  	)
   362  	_, err := Decode(strings.NewReader(pngHeader + ihdr + idatWhite + idatZero + iend))
   363  	if err != nil {
   364  		t.Fatalf("decoding valid image: %v", err)
   365  	}
   366  
   367  	// Non-zero-length trailing IDAT chunks should be ignored (recoverable error).
   368  	// The following chunk contains a single pixel with color.Gray{0}.
   369  	const idatBlack = "\x00\x00\x00\x0eIDAT\x78\x9c\x62\x62\x00\x04\x00\x00\xff\xff\x00\x06\x00\x03\xfa\xd0\x59\xae"
   370  
   371  	img, err := Decode(strings.NewReader(pngHeader + ihdr + idatWhite + idatBlack + iend))
   372  	if err != nil {
   373  		t.Fatalf("trailing IDAT not ignored: %v", err)
   374  	}
   375  	if img.At(0, 0) == (color.Gray{0}) {
   376  		t.Fatal("decoded image from trailing IDAT chunk")
   377  	}
   378  }
   379  
   380  func TestMultipletRNSChunks(t *testing.T) {
   381  	/*
   382  		The following is a valid 1x1 paletted PNG image with a 1-element palette
   383  		containing color.NRGBA{0xff, 0x00, 0x00, 0x7f}:
   384  			0000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452  .PNG........IHDR
   385  			0000010: 0000 0001 0000 0001 0803 0000 0028 cb34  .............(.4
   386  			0000020: bb00 0000 0350 4c54 45ff 0000 19e2 0937  .....PLTE......7
   387  			0000030: 0000 0001 7452 4e53 7f80 5cb4 cb00 0000  ....tRNS..\.....
   388  			0000040: 0e49 4441 5478 9c62 6200 0400 00ff ff00  .IDATx.bb.......
   389  			0000050: 0600 03fa d059 ae00 0000 0049 454e 44ae  .....Y.....IEND.
   390  			0000060: 4260 82                                  B`.
   391  		Dropping the tRNS chunk makes that color's alpha 0xff instead of 0x7f.
   392  	*/
   393  	const (
   394  		ihdr = "\x00\x00\x00\x0dIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x03\x00\x00\x00\x28\xcb\x34\xbb"
   395  		plte = "\x00\x00\x00\x03PLTE\xff\x00\x00\x19\xe2\x09\x37"
   396  		trns = "\x00\x00\x00\x01tRNS\x7f\x80\x5c\xb4\xcb"
   397  		idat = "\x00\x00\x00\x0eIDAT\x78\x9c\x62\x62\x00\x04\x00\x00\xff\xff\x00\x06\x00\x03\xfa\xd0\x59\xae"
   398  		iend = "\x00\x00\x00\x00IEND\xae\x42\x60\x82"
   399  	)
   400  	for i := 0; i < 4; i++ {
   401  		var b []byte
   402  		b = append(b, pngHeader...)
   403  		b = append(b, ihdr...)
   404  		b = append(b, plte...)
   405  		for j := 0; j < i; j++ {
   406  			b = append(b, trns...)
   407  		}
   408  		b = append(b, idat...)
   409  		b = append(b, iend...)
   410  
   411  		var want color.Color
   412  		m, err := Decode(bytes.NewReader(b))
   413  		switch i {
   414  		case 0:
   415  			if err != nil {
   416  				t.Errorf("%d tRNS chunks: %v", i, err)
   417  				continue
   418  			}
   419  			want = color.RGBA{0xff, 0x00, 0x00, 0xff}
   420  		case 1:
   421  			if err != nil {
   422  				t.Errorf("%d tRNS chunks: %v", i, err)
   423  				continue
   424  			}
   425  			want = color.NRGBA{0xff, 0x00, 0x00, 0x7f}
   426  		default:
   427  			if err == nil {
   428  				t.Errorf("%d tRNS chunks: got nil error, want non-nil", i)
   429  			}
   430  			continue
   431  		}
   432  		if got := m.At(0, 0); got != want {
   433  			t.Errorf("%d tRNS chunks: got %T %v, want %T %v", i, got, got, want, want)
   434  		}
   435  	}
   436  }
   437  
   438  func TestUnknownChunkLengthUnderflow(t *testing.T) {
   439  	data := []byte{0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0xff, 0xff,
   440  		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x06, 0xf4, 0x7c, 0x55, 0x04, 0x1a,
   441  		0xd3, 0x11, 0x9a, 0x73, 0x00, 0x00, 0xf8, 0x1e, 0xf3, 0x2e, 0x00, 0x00,
   442  		0x01, 0x00, 0xff, 0xff, 0xff, 0xff, 0x07, 0xf4, 0x7c, 0x55, 0x04, 0x1a,
   443  		0xd3}
   444  	_, err := Decode(bytes.NewReader(data))
   445  	if err == nil {
   446  		t.Errorf("Didn't fail reading an unknown chunk with length 0xffffffff")
   447  	}
   448  }
   449  
   450  func benchmarkDecode(b *testing.B, filename string, bytesPerPixel int) {
   451  	b.StopTimer()
   452  	data, err := ioutil.ReadFile(filename)
   453  	if err != nil {
   454  		b.Fatal(err)
   455  	}
   456  	s := string(data)
   457  	cfg, err := DecodeConfig(strings.NewReader(s))
   458  	if err != nil {
   459  		b.Fatal(err)
   460  	}
   461  	b.SetBytes(int64(cfg.Width * cfg.Height * bytesPerPixel))
   462  	b.StartTimer()
   463  	for i := 0; i < b.N; i++ {
   464  		Decode(strings.NewReader(s))
   465  	}
   466  }
   467  
   468  func BenchmarkDecodeGray(b *testing.B) {
   469  	benchmarkDecode(b, "testdata/benchGray.png", 1)
   470  }
   471  
   472  func BenchmarkDecodeNRGBAGradient(b *testing.B) {
   473  	benchmarkDecode(b, "testdata/benchNRGBA-gradient.png", 4)
   474  }
   475  
   476  func BenchmarkDecodeNRGBAOpaque(b *testing.B) {
   477  	benchmarkDecode(b, "testdata/benchNRGBA-opaque.png", 4)
   478  }
   479  
   480  func BenchmarkDecodePaletted(b *testing.B) {
   481  	benchmarkDecode(b, "testdata/benchPaletted.png", 1)
   482  }
   483  
   484  func BenchmarkDecodeRGB(b *testing.B) {
   485  	benchmarkDecode(b, "testdata/benchRGB.png", 4)
   486  }
   487  
   488  func BenchmarkDecodeInterlacing(b *testing.B) {
   489  	benchmarkDecode(b, "testdata/benchRGB-interlace.png", 4)
   490  }