tinygo.org/x/drivers@v0.27.1-0.20240509133757-7dbca2a54349/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 "os" 15 "reflect" 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 "ftbbn0g01", 42 "ftbbn0g02", 43 "ftbbn0g04", 44 "ftbbn2c16", 45 "ftbbn3p08", 46 "ftbgn2c16", 47 "ftbgn3p08", 48 "ftbrn2c08", 49 "ftbwn0g16", 50 "ftbwn3p08", 51 "ftbyn3p08", 52 "ftp0n0g08", 53 "ftp0n2c08", 54 "ftp0n3p08", 55 "ftp1n3p08", 56 } 57 58 var filenamesPaletted = []string{ 59 "basn3p01", 60 "basn3p02", 61 "basn3p04", 62 "basn3p08", 63 "basn3p08-trns", 64 } 65 66 var filenamesShort = []string{ 67 "basn0g01", 68 "basn0g04-31", 69 "basn6a16", 70 } 71 72 func readPNG(filename string) (image.Image, error) { 73 f, err := os.Open(filename) 74 if err != nil { 75 return nil, err 76 } 77 defer f.Close() 78 return Decode(f) 79 } 80 81 // fakebKGDs maps from filenames to fake bKGD chunks for our approximation to 82 // the sng command-line tool. Package png doesn't keep that metadata when 83 // png.Decode returns an image.Image. 84 var fakebKGDs = map[string]string{ 85 "ftbbn0g01": "bKGD {gray: 0;}\n", 86 "ftbbn0g02": "bKGD {gray: 0;}\n", 87 "ftbbn0g04": "bKGD {gray: 0;}\n", 88 "ftbbn2c16": "bKGD {red: 0; green: 0; blue: 65535;}\n", 89 "ftbbn3p08": "bKGD {index: 245}\n", 90 "ftbgn2c16": "bKGD {red: 0; green: 65535; blue: 0;}\n", 91 "ftbgn3p08": "bKGD {index: 245}\n", 92 "ftbrn2c08": "bKGD {red: 255; green: 0; blue: 0;}\n", 93 "ftbwn0g16": "bKGD {gray: 65535;}\n", 94 "ftbwn3p08": "bKGD {index: 0}\n", 95 "ftbyn3p08": "bKGD {index: 245}\n", 96 } 97 98 // fakegAMAs maps from filenames to fake gAMA chunks for our approximation to 99 // the sng command-line tool. Package png doesn't keep that metadata when 100 // png.Decode returns an image.Image. 101 var fakegAMAs = map[string]string{ 102 "ftbbn0g01": "", 103 "ftbbn0g02": "gAMA {0.45455}\n", 104 } 105 106 // fakeIHDRUsings maps from filenames to fake IHDR "using" lines for our 107 // approximation to the sng command-line tool. The PNG model is that 108 // transparency (in the tRNS chunk) is separate to the color/grayscale/palette 109 // color model (in the IHDR chunk). The Go model is that the concrete 110 // image.Image type returned by png.Decode, such as image.RGBA (with all pixels 111 // having 100% alpha) or image.NRGBA, encapsulates whether or not the image has 112 // transparency. This map is a hack to work around the fact that the Go model 113 // can't otherwise discriminate PNG's "IHDR says color (with no alpha) but tRNS 114 // says alpha" and "IHDR says color with alpha". 115 var fakeIHDRUsings = map[string]string{ 116 "ftbbn0g01": " using grayscale;\n", 117 "ftbbn0g02": " using grayscale;\n", 118 "ftbbn0g04": " using grayscale;\n", 119 "ftbbn2c16": " using color;\n", 120 "ftbgn2c16": " using color;\n", 121 "ftbrn2c08": " using color;\n", 122 "ftbwn0g16": " using grayscale;\n", 123 } 124 125 // An approximation of the sng command-line tool. 126 func sng(w io.WriteCloser, filename string, png image.Image) { 127 defer w.Close() 128 bounds := png.Bounds() 129 cm := png.ColorModel() 130 var bitdepth int 131 switch cm { 132 case color.RGBAModel, color.NRGBAModel, color.AlphaModel, color.GrayModel: 133 bitdepth = 8 134 default: 135 bitdepth = 16 136 } 137 cpm, _ := cm.(color.Palette) 138 var paletted *image.Paletted 139 if cpm != nil { 140 switch { 141 case len(cpm) <= 2: 142 bitdepth = 1 143 case len(cpm) <= 4: 144 bitdepth = 2 145 case len(cpm) <= 16: 146 bitdepth = 4 147 default: 148 bitdepth = 8 149 } 150 paletted = png.(*image.Paletted) 151 } 152 153 // Write the filename and IHDR. 154 io.WriteString(w, "#SNG: from "+filename+".png\nIHDR {\n") 155 fmt.Fprintf(w, " width: %d; height: %d; bitdepth: %d;\n", bounds.Dx(), bounds.Dy(), bitdepth) 156 if s, ok := fakeIHDRUsings[filename]; ok { 157 io.WriteString(w, s) 158 } else { 159 switch { 160 case cm == color.RGBAModel, cm == color.RGBA64Model: 161 io.WriteString(w, " using color;\n") 162 case cm == color.NRGBAModel, cm == color.NRGBA64Model: 163 io.WriteString(w, " using color alpha;\n") 164 case cm == color.GrayModel, cm == color.Gray16Model: 165 io.WriteString(w, " using grayscale;\n") 166 case cpm != nil: 167 io.WriteString(w, " using color palette;\n") 168 default: 169 io.WriteString(w, "unknown PNG decoder color model\n") 170 } 171 } 172 io.WriteString(w, "}\n") 173 174 // We fake a gAMA chunk. The test files have a gAMA chunk but the go PNG 175 // parser ignores it (the PNG spec section 11.3 says "Ancillary chunks may 176 // be ignored by a decoder"). 177 if s, ok := fakegAMAs[filename]; ok { 178 io.WriteString(w, s) 179 } else { 180 io.WriteString(w, "gAMA {1.0000}\n") 181 } 182 183 // Write the PLTE and tRNS (if applicable). 184 useTransparent := false 185 if cpm != nil { 186 lastAlpha := -1 187 io.WriteString(w, "PLTE {\n") 188 for i, c := range cpm { 189 var r, g, b, a uint8 190 switch c := c.(type) { 191 case color.RGBA: 192 r, g, b, a = c.R, c.G, c.B, 0xff 193 case color.NRGBA: 194 r, g, b, a = c.R, c.G, c.B, c.A 195 default: 196 panic("unknown palette color type") 197 } 198 if a != 0xff { 199 lastAlpha = i 200 } 201 fmt.Fprintf(w, " (%3d,%3d,%3d) # rgb = (0x%02x,0x%02x,0x%02x)\n", r, g, b, r, g, b) 202 } 203 io.WriteString(w, "}\n") 204 if s, ok := fakebKGDs[filename]; ok { 205 io.WriteString(w, s) 206 } 207 if lastAlpha != -1 { 208 io.WriteString(w, "tRNS {\n") 209 for i := 0; i <= lastAlpha; i++ { 210 _, _, _, a := cpm[i].RGBA() 211 a >>= 8 212 fmt.Fprintf(w, " %d", a) 213 } 214 io.WriteString(w, "}\n") 215 } 216 } else if strings.HasPrefix(filename, "ft") { 217 if s, ok := fakebKGDs[filename]; ok { 218 io.WriteString(w, s) 219 } 220 // We fake a tRNS chunk. The test files' grayscale and truecolor 221 // transparent images all have their top left corner transparent. 222 switch c := png.At(0, 0).(type) { 223 case color.NRGBA: 224 if c.A == 0 { 225 useTransparent = true 226 io.WriteString(w, "tRNS {\n") 227 switch filename { 228 case "ftbbn0g01", "ftbbn0g02", "ftbbn0g04": 229 // The standard image package doesn't have a "gray with 230 // alpha" type. Instead, we use an image.NRGBA. 231 fmt.Fprintf(w, " gray: %d;\n", c.R) 232 default: 233 fmt.Fprintf(w, " red: %d; green: %d; blue: %d;\n", c.R, c.G, c.B) 234 } 235 io.WriteString(w, "}\n") 236 } 237 case color.NRGBA64: 238 if c.A == 0 { 239 useTransparent = true 240 io.WriteString(w, "tRNS {\n") 241 switch filename { 242 case "ftbwn0g16": 243 // The standard image package doesn't have a "gray16 with 244 // alpha" type. Instead, we use an image.NRGBA64. 245 fmt.Fprintf(w, " gray: %d;\n", c.R) 246 default: 247 fmt.Fprintf(w, " red: %d; green: %d; blue: %d;\n", c.R, c.G, c.B) 248 } 249 io.WriteString(w, "}\n") 250 } 251 } 252 } 253 254 // Write the IMAGE. 255 io.WriteString(w, "IMAGE {\n pixels hex\n") 256 for y := bounds.Min.Y; y < bounds.Max.Y; y++ { 257 switch { 258 case cm == color.GrayModel: 259 for x := bounds.Min.X; x < bounds.Max.X; x++ { 260 gray := png.At(x, y).(color.Gray) 261 fmt.Fprintf(w, "%02x", gray.Y) 262 } 263 case cm == color.Gray16Model: 264 for x := bounds.Min.X; x < bounds.Max.X; x++ { 265 gray16 := png.At(x, y).(color.Gray16) 266 fmt.Fprintf(w, "%04x ", gray16.Y) 267 } 268 case cm == color.RGBAModel: 269 for x := bounds.Min.X; x < bounds.Max.X; x++ { 270 rgba := png.At(x, y).(color.RGBA) 271 fmt.Fprintf(w, "%02x%02x%02x ", rgba.R, rgba.G, rgba.B) 272 } 273 case cm == color.RGBA64Model: 274 for x := bounds.Min.X; x < bounds.Max.X; x++ { 275 rgba64 := png.At(x, y).(color.RGBA64) 276 fmt.Fprintf(w, "%04x%04x%04x ", rgba64.R, rgba64.G, rgba64.B) 277 } 278 case cm == color.NRGBAModel: 279 for x := bounds.Min.X; x < bounds.Max.X; x++ { 280 nrgba := png.At(x, y).(color.NRGBA) 281 switch filename { 282 case "ftbbn0g01", "ftbbn0g02", "ftbbn0g04": 283 fmt.Fprintf(w, "%02x", nrgba.R) 284 default: 285 if useTransparent { 286 fmt.Fprintf(w, "%02x%02x%02x ", nrgba.R, nrgba.G, nrgba.B) 287 } else { 288 fmt.Fprintf(w, "%02x%02x%02x%02x ", nrgba.R, nrgba.G, nrgba.B, nrgba.A) 289 } 290 } 291 } 292 case cm == color.NRGBA64Model: 293 for x := bounds.Min.X; x < bounds.Max.X; x++ { 294 nrgba64 := png.At(x, y).(color.NRGBA64) 295 switch filename { 296 case "ftbwn0g16": 297 fmt.Fprintf(w, "%04x ", nrgba64.R) 298 default: 299 if useTransparent { 300 fmt.Fprintf(w, "%04x%04x%04x ", nrgba64.R, nrgba64.G, nrgba64.B) 301 } else { 302 fmt.Fprintf(w, "%04x%04x%04x%04x ", nrgba64.R, nrgba64.G, nrgba64.B, nrgba64.A) 303 } 304 } 305 } 306 case cpm != nil: 307 var b, c int 308 for x := bounds.Min.X; x < bounds.Max.X; x++ { 309 b = b<<uint(bitdepth) | int(paletted.ColorIndexAt(x, y)) 310 c++ 311 if c == 8/bitdepth { 312 fmt.Fprintf(w, "%02x", b) 313 b = 0 314 c = 0 315 } 316 } 317 if c != 0 { 318 for c != 8/bitdepth { 319 b = b << uint(bitdepth) 320 c++ 321 } 322 fmt.Fprintf(w, "%02x", b) 323 } 324 } 325 io.WriteString(w, "\n") 326 } 327 io.WriteString(w, "}\n") 328 } 329 330 func TestReader(t *testing.T) { 331 names := filenames 332 if testing.Short() { 333 names = filenamesShort 334 } 335 for _, fn := range names { 336 // Read the .png file. 337 img, err := readPNG("testdata/pngsuite/" + fn + ".png") 338 if err != nil { 339 t.Error(fn, err) 340 continue 341 } 342 343 if fn == "basn4a16" { 344 // basn4a16.sng is gray + alpha but sng() will produce true color + alpha 345 // so we just check a single random pixel. 346 c := img.At(2, 1).(color.NRGBA64) 347 if c.R != 0x11a7 || c.G != 0x11a7 || c.B != 0x11a7 || c.A != 0x1085 { 348 t.Error(fn, fmt.Errorf("wrong pixel value at (2, 1): %x", c)) 349 } 350 continue 351 } 352 353 piper, pipew := io.Pipe() 354 pb := bufio.NewScanner(piper) 355 go sng(pipew, fn, img) 356 defer piper.Close() 357 358 // Read the .sng file. 359 sf, err := os.Open("testdata/pngsuite/" + fn + ".sng") 360 if err != nil { 361 t.Error(fn, err) 362 continue 363 } 364 defer sf.Close() 365 sb := bufio.NewScanner(sf) 366 367 // Compare the two, in SNG format, line by line. 368 for { 369 pdone := !pb.Scan() 370 sdone := !sb.Scan() 371 if pdone && sdone { 372 break 373 } 374 if pdone || sdone { 375 t.Errorf("%s: Different sizes", fn) 376 break 377 } 378 ps := pb.Text() 379 ss := sb.Text() 380 381 // Newer versions of the sng command line tool append an optional 382 // color name to the RGB tuple. For example: 383 // # rgb = (0xff,0xff,0xff) grey100 384 // # rgb = (0x00,0x00,0xff) blue1 385 // instead of the older version's plainer: 386 // # rgb = (0xff,0xff,0xff) 387 // # rgb = (0x00,0x00,0xff) 388 // We strip any such name. 389 if strings.Contains(ss, "# rgb = (") && !strings.HasSuffix(ss, ")") { 390 if i := strings.LastIndex(ss, ") "); i >= 0 { 391 ss = ss[:i+1] 392 } 393 } 394 395 if ps != ss { 396 t.Errorf("%s: Mismatch\n%s\nversus\n%s\n", fn, ps, ss) 397 break 398 } 399 } 400 if pb.Err() != nil { 401 t.Error(fn, pb.Err()) 402 } 403 if sb.Err() != nil { 404 t.Error(fn, sb.Err()) 405 } 406 } 407 } 408 409 var readerErrors = []struct { 410 file string 411 err string 412 }{ 413 {"invalid-zlib.png", "zlib: invalid checksum"}, 414 {"invalid-crc32.png", "invalid checksum"}, 415 {"invalid-noend.png", "unexpected EOF"}, 416 {"invalid-trunc.png", "unexpected EOF"}, 417 } 418 419 func TestReaderError(t *testing.T) { 420 for _, tt := range readerErrors { 421 img, err := readPNG("testdata/" + tt.file) 422 if err == nil { 423 t.Errorf("decoding %s: missing error", tt.file) 424 continue 425 } 426 if !strings.Contains(err.Error(), tt.err) { 427 t.Errorf("decoding %s: %s, want %s", tt.file, err, tt.err) 428 } 429 if img != nil { 430 t.Errorf("decoding %s: have image + error", tt.file) 431 } 432 } 433 } 434 435 func TestPalettedDecodeConfig(t *testing.T) { 436 for _, fn := range filenamesPaletted { 437 f, err := os.Open("testdata/pngsuite/" + fn + ".png") 438 if err != nil { 439 t.Errorf("%s: open failed: %v", fn, err) 440 continue 441 } 442 defer f.Close() 443 cfg, err := DecodeConfig(f) 444 if err != nil { 445 t.Errorf("%s: %v", fn, err) 446 continue 447 } 448 pal, ok := cfg.ColorModel.(color.Palette) 449 if !ok { 450 t.Errorf("%s: expected paletted color model", fn) 451 continue 452 } 453 if pal == nil { 454 t.Errorf("%s: palette not initialized", fn) 455 continue 456 } 457 } 458 } 459 460 func TestInterlaced(t *testing.T) { 461 a, err := readPNG("testdata/gray-gradient.png") 462 if err != nil { 463 t.Fatal(err) 464 } 465 b, err := readPNG("testdata/gray-gradient.interlaced.png") 466 if err != nil { 467 t.Fatal(err) 468 } 469 if !reflect.DeepEqual(a, b) { 470 t.Fatalf("decodings differ:\nnon-interlaced:\n%#v\ninterlaced:\n%#v", a, b) 471 } 472 } 473 474 func TestIncompleteIDATOnRowBoundary(t *testing.T) { 475 // The following is an invalid 1x2 grayscale PNG image. The header is OK, 476 // but the zlib-compressed IDAT payload contains two bytes "\x02\x00", 477 // which is only one row of data (the leading "\x02" is a row filter). 478 const ( 479 ihdr = "\x00\x00\x00\x0dIHDR\x00\x00\x00\x01\x00\x00\x00\x02\x08\x00\x00\x00\x00\xbc\xea\xe9\xfb" 480 idat = "\x00\x00\x00\x0eIDAT\x78\x9c\x62\x62\x00\x04\x00\x00\xff\xff\x00\x06\x00\x03\xfa\xd0\x59\xae" 481 iend = "\x00\x00\x00\x00IEND\xae\x42\x60\x82" 482 ) 483 _, err := Decode(strings.NewReader(pngHeader + ihdr + idat + iend)) 484 if err == nil { 485 t.Fatal("got nil error, want non-nil") 486 } 487 } 488 489 func TestTrailingIDATChunks(t *testing.T) { 490 // The following is a valid 1x1 PNG image containing color.Gray{255} and 491 // a trailing zero-length IDAT chunk (see PNG specification section 12.9): 492 const ( 493 ihdr = "\x00\x00\x00\x0dIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x00\x00\x00\x00\x3a\x7e\x9b\x55" 494 idatWhite = "\x00\x00\x00\x0eIDAT\x78\x9c\x62\xfa\x0f\x08\x00\x00\xff\xff\x01\x05\x01\x02\x5a\xdd\x39\xcd" 495 idatZero = "\x00\x00\x00\x00IDAT\x35\xaf\x06\x1e" 496 iend = "\x00\x00\x00\x00IEND\xae\x42\x60\x82" 497 ) 498 _, err := Decode(strings.NewReader(pngHeader + ihdr + idatWhite + idatZero + iend)) 499 if err != nil { 500 t.Fatalf("decoding valid image: %v", err) 501 } 502 503 // Non-zero-length trailing IDAT chunks should be ignored (recoverable error). 504 // The following chunk contains a single pixel with color.Gray{0}. 505 const idatBlack = "\x00\x00\x00\x0eIDAT\x78\x9c\x62\x62\x00\x04\x00\x00\xff\xff\x00\x06\x00\x03\xfa\xd0\x59\xae" 506 507 img, err := Decode(strings.NewReader(pngHeader + ihdr + idatWhite + idatBlack + iend)) 508 if err != nil { 509 t.Fatalf("trailing IDAT not ignored: %v", err) 510 } 511 if img.At(0, 0) == (color.Gray{0}) { 512 t.Fatal("decoded image from trailing IDAT chunk") 513 } 514 } 515 516 func TestMultipletRNSChunks(t *testing.T) { 517 /* 518 The following is a valid 1x1 paletted PNG image with a 1-element palette 519 containing color.NRGBA{0xff, 0x00, 0x00, 0x7f}: 520 0000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452 .PNG........IHDR 521 0000010: 0000 0001 0000 0001 0803 0000 0028 cb34 .............(.4 522 0000020: bb00 0000 0350 4c54 45ff 0000 19e2 0937 .....PLTE......7 523 0000030: 0000 0001 7452 4e53 7f80 5cb4 cb00 0000 ....tRNS..\..... 524 0000040: 0e49 4441 5478 9c62 6200 0400 00ff ff00 .IDATx.bb....... 525 0000050: 0600 03fa d059 ae00 0000 0049 454e 44ae .....Y.....IEND. 526 0000060: 4260 82 B`. 527 Dropping the tRNS chunk makes that color's alpha 0xff instead of 0x7f. 528 */ 529 const ( 530 ihdr = "\x00\x00\x00\x0dIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x03\x00\x00\x00\x28\xcb\x34\xbb" 531 plte = "\x00\x00\x00\x03PLTE\xff\x00\x00\x19\xe2\x09\x37" 532 trns = "\x00\x00\x00\x01tRNS\x7f\x80\x5c\xb4\xcb" 533 idat = "\x00\x00\x00\x0eIDAT\x78\x9c\x62\x62\x00\x04\x00\x00\xff\xff\x00\x06\x00\x03\xfa\xd0\x59\xae" 534 iend = "\x00\x00\x00\x00IEND\xae\x42\x60\x82" 535 ) 536 for i := 0; i < 4; i++ { 537 var b []byte 538 b = append(b, pngHeader...) 539 b = append(b, ihdr...) 540 b = append(b, plte...) 541 for j := 0; j < i; j++ { 542 b = append(b, trns...) 543 } 544 b = append(b, idat...) 545 b = append(b, iend...) 546 547 var want color.Color 548 m, err := Decode(bytes.NewReader(b)) 549 switch i { 550 case 0: 551 if err != nil { 552 t.Errorf("%d tRNS chunks: %v", i, err) 553 continue 554 } 555 want = color.RGBA{0xff, 0x00, 0x00, 0xff} 556 case 1: 557 if err != nil { 558 t.Errorf("%d tRNS chunks: %v", i, err) 559 continue 560 } 561 want = color.NRGBA{0xff, 0x00, 0x00, 0x7f} 562 default: 563 if err == nil { 564 t.Errorf("%d tRNS chunks: got nil error, want non-nil", i) 565 } 566 continue 567 } 568 if got := m.At(0, 0); got != want { 569 t.Errorf("%d tRNS chunks: got %T %v, want %T %v", i, got, got, want, want) 570 } 571 } 572 } 573 574 func TestUnknownChunkLengthUnderflow(t *testing.T) { 575 data := []byte{0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0xff, 0xff, 576 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x06, 0xf4, 0x7c, 0x55, 0x04, 0x1a, 577 0xd3, 0x11, 0x9a, 0x73, 0x00, 0x00, 0xf8, 0x1e, 0xf3, 0x2e, 0x00, 0x00, 578 0x01, 0x00, 0xff, 0xff, 0xff, 0xff, 0x07, 0xf4, 0x7c, 0x55, 0x04, 0x1a, 579 0xd3} 580 _, err := Decode(bytes.NewReader(data)) 581 if err == nil { 582 t.Errorf("Didn't fail reading an unknown chunk with length 0xffffffff") 583 } 584 } 585 586 func TestPaletted8OutOfRangePixel(t *testing.T) { 587 // IDAT contains a reference to a palette index that does not exist in the file. 588 img, err := readPNG("testdata/invalid-palette.png") 589 if err != nil { 590 t.Errorf("decoding invalid-palette.png: unexpected error %v", err) 591 return 592 } 593 594 // Expect that the palette is extended with opaque black. 595 want := color.RGBA{0x00, 0x00, 0x00, 0xff} 596 if got := img.At(15, 15); got != want { 597 t.Errorf("got %F %v, expected %T %v", got, got, want, want) 598 } 599 } 600 601 func TestGray8Transparent(t *testing.T) { 602 // These bytes come from https://golang.org/issues/19553 603 m, err := Decode(bytes.NewReader([]byte{ 604 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 605 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x0b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x85, 0x2c, 0x88, 606 0x80, 0x00, 0x00, 0x00, 0x02, 0x74, 0x52, 0x4e, 0x53, 0x00, 0xff, 0x5b, 0x91, 0x22, 0xb5, 0x00, 607 0x00, 0x00, 0x02, 0x62, 0x4b, 0x47, 0x44, 0x00, 0xff, 0x87, 0x8f, 0xcc, 0xbf, 0x00, 0x00, 0x00, 608 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0a, 0xf0, 0x00, 0x00, 0x0a, 0xf0, 0x01, 0x42, 0xac, 609 0x34, 0x98, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4d, 0x45, 0x07, 0xd5, 0x04, 0x02, 0x12, 0x11, 610 0x11, 0xf7, 0x65, 0x3d, 0x8b, 0x00, 0x00, 0x00, 0x4f, 0x49, 0x44, 0x41, 0x54, 0x08, 0xd7, 0x63, 611 0xf8, 0xff, 0xff, 0xff, 0xb9, 0xbd, 0x70, 0xf0, 0x8c, 0x01, 0xc8, 0xaf, 0x6e, 0x99, 0x02, 0x05, 612 0xd9, 0x7b, 0xc1, 0xfc, 0x6b, 0xff, 0xa1, 0xa0, 0x87, 0x30, 0xff, 0xd9, 0xde, 0xbd, 0xd5, 0x4b, 613 0xf7, 0xee, 0xfd, 0x0e, 0xe3, 0xef, 0xcd, 0x06, 0x19, 0x14, 0xf5, 0x1e, 0xce, 0xef, 0x01, 0x31, 614 0x92, 0xd7, 0x82, 0x41, 0x31, 0x9c, 0x3f, 0x07, 0x02, 0xee, 0xa1, 0xaa, 0xff, 0xff, 0x9f, 0xe1, 615 0xd9, 0x56, 0x30, 0xf8, 0x0e, 0xe5, 0x03, 0x00, 0xa9, 0x42, 0x84, 0x3d, 0xdf, 0x8f, 0xa6, 0x8f, 616 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82, 617 })) 618 if err != nil { 619 t.Fatalf("Decode: %v", err) 620 } 621 622 const hex = "0123456789abcdef" 623 var got []byte 624 bounds := m.Bounds() 625 for y := bounds.Min.Y; y < bounds.Max.Y; y++ { 626 for x := bounds.Min.X; x < bounds.Max.X; x++ { 627 if r, _, _, a := m.At(x, y).RGBA(); a != 0 { 628 got = append(got, 629 hex[0x0f&(r>>12)], 630 hex[0x0f&(r>>8)], 631 ' ', 632 ) 633 } else { 634 got = append(got, 635 '.', 636 '.', 637 ' ', 638 ) 639 } 640 } 641 got = append(got, '\n') 642 } 643 644 const want = "" + 645 ".. .. .. ce bd bd bd bd bd bd bd bd bd bd e6 \n" + 646 ".. .. .. 7b 84 94 94 94 94 94 94 94 94 6b bd \n" + 647 ".. .. .. 7b d6 .. .. .. .. .. .. .. .. 8c bd \n" + 648 ".. .. .. 7b d6 .. .. .. .. .. .. .. .. 8c bd \n" + 649 ".. .. .. 7b d6 .. .. .. .. .. .. .. .. 8c bd \n" + 650 "e6 bd bd 7b a5 bd bd f7 .. .. .. .. .. 8c bd \n" + 651 "bd 6b 94 94 94 94 5a ef .. .. .. .. .. 8c bd \n" + 652 "bd 8c .. .. .. .. 63 ad ad ad ad ad ad 73 bd \n" + 653 "bd 8c .. .. .. .. 63 9c 9c 9c 9c 9c 9c 9c de \n" + 654 "bd 6b 94 94 94 94 5a ef .. .. .. .. .. .. .. \n" + 655 "e6 b5 b5 b5 b5 b5 b5 f7 .. .. .. .. .. .. .. \n" 656 657 if string(got) != want { 658 t.Errorf("got:\n%swant:\n%s", got, want) 659 } 660 } 661 662 func TestDimensionOverflow(t *testing.T) { 663 maxInt32AsInt := int((1 << 31) - 1) 664 have32BitInts := 0 > (1 + maxInt32AsInt) 665 666 testCases := []struct { 667 src []byte 668 unsupportedConfig bool 669 width int 670 height int 671 }{ 672 // These bytes come from https://golang.org/issues/22304 673 // 674 // It encodes a 2147483646 × 2147483646 (i.e. 0x7ffffffe × 0x7ffffffe) 675 // NRGBA image. The (width × height) per se doesn't overflow an int64, but 676 // (width × height × bytesPerPixel) will. 677 { 678 src: []byte{ 679 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 680 0x7f, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xfe, 0x08, 0x06, 0x00, 0x00, 0x00, 0x30, 0x57, 0xb3, 681 0xfd, 0x00, 0x00, 0x00, 0x15, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9c, 0x62, 0x62, 0x20, 0x12, 0x8c, 682 0x2a, 0xa4, 0xb3, 0x42, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0x13, 0x38, 0x00, 0x15, 0x2d, 0xef, 683 0x5f, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82, 684 }, 685 // It's debatable whether DecodeConfig (which does not allocate a 686 // pixel buffer, unlike Decode) should fail in this case. The Go 687 // standard library has made its choice, and the standard library 688 // has compatibility constraints. 689 unsupportedConfig: true, 690 width: 0x7ffffffe, 691 height: 0x7ffffffe, 692 }, 693 694 // The next three cases come from https://golang.org/issues/38435 695 696 { 697 src: []byte{ 698 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 699 0x00, 0x00, 0xb5, 0x04, 0x00, 0x00, 0xb5, 0x04, 0x08, 0x06, 0x00, 0x00, 0x00, 0xf5, 0x60, 0x2c, 700 0xb8, 0x00, 0x00, 0x00, 0x15, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9c, 0x62, 0x62, 0x20, 0x12, 0x8c, 701 0x2a, 0xa4, 0xb3, 0x42, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0x13, 0x38, 0x00, 0x15, 0x2d, 0xef, 702 0x5f, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82, 703 }, 704 // Here, width * height = 0x7ffea810, just under MaxInt32, but at 4 705 // bytes per pixel, the number of pixels overflows an int32. 706 unsupportedConfig: have32BitInts, 707 width: 0x0000b504, 708 height: 0x0000b504, 709 }, 710 711 { 712 src: []byte{ 713 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 714 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x06, 0x00, 0x00, 0x00, 0x30, 0x6e, 0xc5, 715 0x21, 0x00, 0x00, 0x00, 0x15, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9c, 0x62, 0x62, 0x20, 0x12, 0x8c, 716 0x2a, 0xa4, 0xb3, 0x42, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0x13, 0x38, 0x00, 0x15, 0x2d, 0xef, 717 0x5f, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82, 718 }, 719 unsupportedConfig: false, 720 width: 0x04000000, 721 height: 0x00000001, 722 }, 723 724 { 725 src: []byte{ 726 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 727 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x06, 0x00, 0x00, 0x00, 0xaa, 0xd4, 0x7c, 728 0xda, 0x00, 0x00, 0x00, 0x15, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9c, 0x62, 0x66, 0x20, 0x12, 0x30, 729 0x8d, 0x2a, 0xa4, 0xaf, 0x42, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0x14, 0xd2, 0x00, 0x16, 0x00, 730 0x00, 0x00, 731 }, 732 unsupportedConfig: false, 733 width: 0x08000000, 734 height: 0x00000001, 735 }, 736 } 737 738 for i, tc := range testCases { 739 cfg, err := DecodeConfig(bytes.NewReader(tc.src)) 740 if tc.unsupportedConfig { 741 if err == nil { 742 t.Errorf("i=%d: DecodeConfig: got nil error, want non-nil", i) 743 } else if _, ok := err.(UnsupportedError); !ok { 744 t.Fatalf("Decode: got %v (of type %T), want non-nil error (of type png.UnsupportedError)", err, err) 745 } 746 continue 747 } else if err != nil { 748 t.Errorf("i=%d: DecodeConfig: %v", i, err) 749 continue 750 } else if cfg.Width != tc.width { 751 t.Errorf("i=%d: width: got %d, want %d", i, cfg.Width, tc.width) 752 continue 753 } else if cfg.Height != tc.height { 754 t.Errorf("i=%d: height: got %d, want %d", i, cfg.Height, tc.height) 755 continue 756 } 757 758 if nPixels := int64(cfg.Width) * int64(cfg.Height); nPixels > 0x7f000000 { 759 // In theory, calling Decode would succeed, given several gigabytes 760 // of memory. In practice, trying to make a []uint8 big enough to 761 // hold all of the pixels can often result in OOM (out of memory). 762 // OOM is unrecoverable; we can't write a test that passes when OOM 763 // happens. Instead we skip the Decode call (and its tests). 764 continue 765 } else if testing.Short() { 766 // Even for smaller image dimensions, calling Decode might allocate 767 // 1 GiB or more of memory. This is usually feasible, and we want 768 // to check that calling Decode doesn't panic if there's enough 769 // memory, but we provide a runtime switch (testing.Short) to skip 770 // these if it would OOM. See also http://golang.org/issue/5050 771 // "decoding... images can cause huge memory allocations". 772 continue 773 } 774 775 // Even if we don't panic, these aren't valid PNG images. 776 if _, err := Decode(bytes.NewReader(tc.src)); err == nil { 777 t.Errorf("i=%d: Decode: got nil error, want non-nil", i) 778 } 779 } 780 781 if testing.Short() { 782 t.Skip("skipping tests which allocate large pixel buffers") 783 } 784 } 785 786 func benchmarkDecode(b *testing.B, filename string, bytesPerPixel int) { 787 data, err := os.ReadFile(filename) 788 if err != nil { 789 b.Fatal(err) 790 } 791 cfg, err := DecodeConfig(bytes.NewReader(data)) 792 if err != nil { 793 b.Fatal(err) 794 } 795 b.SetBytes(int64(cfg.Width * cfg.Height * bytesPerPixel)) 796 b.ReportAllocs() 797 b.ResetTimer() 798 for i := 0; i < b.N; i++ { 799 Decode(bytes.NewReader(data)) 800 } 801 } 802 803 func BenchmarkDecodeGray(b *testing.B) { 804 benchmarkDecode(b, "testdata/benchGray.png", 1) 805 } 806 807 func BenchmarkDecodeNRGBAGradient(b *testing.B) { 808 benchmarkDecode(b, "testdata/benchNRGBA-gradient.png", 4) 809 } 810 811 func BenchmarkDecodeNRGBAOpaque(b *testing.B) { 812 benchmarkDecode(b, "testdata/benchNRGBA-opaque.png", 4) 813 } 814 815 func BenchmarkDecodePaletted(b *testing.B) { 816 benchmarkDecode(b, "testdata/benchPaletted.png", 1) 817 } 818 819 func BenchmarkDecodeRGB(b *testing.B) { 820 benchmarkDecode(b, "testdata/benchRGB.png", 4) 821 } 822 823 func BenchmarkDecodeInterlacing(b *testing.B) { 824 benchmarkDecode(b, "testdata/benchRGB-interlace.png", 4) 825 }