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 }
     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 benchmarkDecode(b *testing.B, filename string, bytesPerPixel int) {
    412 	b.StopTimer()
    413 	data, err := ioutil.ReadFile(filename)
    414 	if err != nil {
    415 		b.Fatal(err)
    416 	}
    417 	s := string(data)
    418 	cfg, err := DecodeConfig(strings.NewReader(s))
    419 	if err != nil {
    420 		b.Fatal(err)
    421 	}
    422 	b.SetBytes(int64(cfg.Width * cfg.Height * bytesPerPixel))
    423 	b.StartTimer()
    424 	for i := 0; i < b.N; i++ {
    425 		Decode(strings.NewReader(s))
    426 	}
    427 }
    428 
    429 func BenchmarkDecodeGray(b *testing.B) {
    430 	benchmarkDecode(b, "testdata/benchGray.png", 1)
    431 }
    432 
    433 func BenchmarkDecodeNRGBAGradient(b *testing.B) {
    434 	benchmarkDecode(b, "testdata/benchNRGBA-gradient.png", 4)
    435 }
    436 
    437 func BenchmarkDecodeNRGBAOpaque(b *testing.B) {
    438 	benchmarkDecode(b, "testdata/benchNRGBA-opaque.png", 4)
    439 }
    440 
    441 func BenchmarkDecodePaletted(b *testing.B) {
    442 	benchmarkDecode(b, "testdata/benchPaletted.png", 1)
    443 }
    444 
    445 func BenchmarkDecodeRGB(b *testing.B) {
    446 	benchmarkDecode(b, "testdata/benchRGB.png", 4)
    447 }
    448 
    449 func BenchmarkDecodeInterlacing(b *testing.B) {
    450 	benchmarkDecode(b, "testdata/benchRGB-interlace.png", 4)
    451 }
    452