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 }