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