github.com/4ad/go@v0.0.0-20161219182952-69a12818b605/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 }