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