github.com/peggyl/go@v0.0.0-20151008231540-ae315999c2d5/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 TestMultipletRNSChunks(t *testing.T) {
   354  	/*
   355  		The following is a valid 1x1 paletted PNG image with a 1-element palette
   356  		containing color.NRGBA{0xff, 0x00, 0x00, 0x7f}:
   357  			0000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452  .PNG........IHDR
   358  			0000010: 0000 0001 0000 0001 0803 0000 0028 cb34  .............(.4
   359  			0000020: bb00 0000 0350 4c54 45ff 0000 19e2 0937  .....PLTE......7
   360  			0000030: 0000 0001 7452 4e53 7f80 5cb4 cb00 0000  ....tRNS..\.....
   361  			0000040: 0e49 4441 5478 9c62 6200 0400 00ff ff00  .IDATx.bb.......
   362  			0000050: 0600 03fa d059 ae00 0000 0049 454e 44ae  .....Y.....IEND.
   363  			0000060: 4260 82                                  B`.
   364  		Dropping the tRNS chunk makes that color's alpha 0xff instead of 0x7f.
   365  	*/
   366  	const (
   367  		ihdr = "\x00\x00\x00\x0dIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x03\x00\x00\x00\x28\xcb\x34\xbb"
   368  		plte = "\x00\x00\x00\x03PLTE\xff\x00\x00\x19\xe2\x09\x37"
   369  		trns = "\x00\x00\x00\x01tRNS\x7f\x80\x5c\xb4\xcb"
   370  		idat = "\x00\x00\x00\x0eIDAT\x78\x9c\x62\x62\x00\x04\x00\x00\xff\xff\x00\x06\x00\x03\xfa\xd0\x59\xae"
   371  		iend = "\x00\x00\x00\x00IEND\xae\x42\x60\x82"
   372  	)
   373  	for i := 0; i < 4; i++ {
   374  		var b []byte
   375  		b = append(b, pngHeader...)
   376  		b = append(b, ihdr...)
   377  		b = append(b, plte...)
   378  		for j := 0; j < i; j++ {
   379  			b = append(b, trns...)
   380  		}
   381  		b = append(b, idat...)
   382  		b = append(b, iend...)
   383  
   384  		var want color.Color
   385  		m, err := Decode(bytes.NewReader(b))
   386  		switch i {
   387  		case 0:
   388  			if err != nil {
   389  				t.Errorf("%d tRNS chunks: %v", i, err)
   390  				continue
   391  			}
   392  			want = color.RGBA{0xff, 0x00, 0x00, 0xff}
   393  		case 1:
   394  			if err != nil {
   395  				t.Errorf("%d tRNS chunks: %v", i, err)
   396  				continue
   397  			}
   398  			want = color.NRGBA{0xff, 0x00, 0x00, 0x7f}
   399  		default:
   400  			if err == nil {
   401  				t.Errorf("%d tRNS chunks: got nil error, want non-nil", i)
   402  			}
   403  			continue
   404  		}
   405  		if got := m.At(0, 0); got != want {
   406  			t.Errorf("%d tRNS chunks: got %T %v, want %T %v", i, got, got, want, want)
   407  		}
   408  	}
   409  }
   410  
   411  func TestUnknownChunkLengthUnderflow(t *testing.T) {
   412  	data := []byte{0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0xff, 0xff,
   413  		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x06, 0xf4, 0x7c, 0x55, 0x04, 0x1a,
   414  		0xd3, 0x11, 0x9a, 0x73, 0x00, 0x00, 0xf8, 0x1e, 0xf3, 0x2e, 0x00, 0x00,
   415  		0x01, 0x00, 0xff, 0xff, 0xff, 0xff, 0x07, 0xf4, 0x7c, 0x55, 0x04, 0x1a,
   416  		0xd3}
   417  	_, err := Decode(bytes.NewReader(data))
   418  	if err == nil {
   419  		t.Errorf("Didn't fail reading an unknown chunk with length 0xffffffff")
   420  	}
   421  }
   422  
   423  func benchmarkDecode(b *testing.B, filename string, bytesPerPixel int) {
   424  	b.StopTimer()
   425  	data, err := ioutil.ReadFile(filename)
   426  	if err != nil {
   427  		b.Fatal(err)
   428  	}
   429  	s := string(data)
   430  	cfg, err := DecodeConfig(strings.NewReader(s))
   431  	if err != nil {
   432  		b.Fatal(err)
   433  	}
   434  	b.SetBytes(int64(cfg.Width * cfg.Height * bytesPerPixel))
   435  	b.StartTimer()
   436  	for i := 0; i < b.N; i++ {
   437  		Decode(strings.NewReader(s))
   438  	}
   439  }
   440  
   441  func BenchmarkDecodeGray(b *testing.B) {
   442  	benchmarkDecode(b, "testdata/benchGray.png", 1)
   443  }
   444  
   445  func BenchmarkDecodeNRGBAGradient(b *testing.B) {
   446  	benchmarkDecode(b, "testdata/benchNRGBA-gradient.png", 4)
   447  }
   448  
   449  func BenchmarkDecodeNRGBAOpaque(b *testing.B) {
   450  	benchmarkDecode(b, "testdata/benchNRGBA-opaque.png", 4)
   451  }
   452  
   453  func BenchmarkDecodePaletted(b *testing.B) {
   454  	benchmarkDecode(b, "testdata/benchPaletted.png", 1)
   455  }
   456  
   457  func BenchmarkDecodeRGB(b *testing.B) {
   458  	benchmarkDecode(b, "testdata/benchRGB.png", 4)
   459  }
   460  
   461  func BenchmarkDecodeInterlacing(b *testing.B) {
   462  	benchmarkDecode(b, "testdata/benchRGB-interlace.png", 4)
   463  }