diff options
Diffstat (limited to 'src/image/png/reader_test.go')
-rw-r--r-- | src/image/png/reader_test.go | 362 |
1 files changed, 362 insertions, 0 deletions
diff --git a/src/image/png/reader_test.go b/src/image/png/reader_test.go new file mode 100644 index 000000000..ce772eb6f --- /dev/null +++ b/src/image/png/reader_test.go @@ -0,0 +1,362 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package png + +import ( + "bufio" + "fmt" + "image" + "image/color" + "io" + "io/ioutil" + "os" + "strings" + "testing" +) + +var filenames = []string{ + "basn0g01", + "basn0g01-30", + "basn0g02", + "basn0g02-29", + "basn0g04", + "basn0g04-31", + "basn0g08", + "basn0g16", + "basn2c08", + "basn2c16", + "basn3p01", + "basn3p02", + "basn3p04", + "basn3p04-31i", + "basn3p08", + "basn3p08-trns", + "basn4a08", + "basn4a16", + "basn6a08", + "basn6a16", +} + +var filenamesPaletted = []string{ + "basn3p01", + "basn3p02", + "basn3p04", + "basn3p08", + "basn3p08-trns", +} + +var filenamesShort = []string{ + "basn0g01", + "basn0g04-31", + "basn6a16", +} + +func readPNG(filename string) (image.Image, error) { + f, err := os.Open(filename) + if err != nil { + return nil, err + } + defer f.Close() + return Decode(f) +} + +// An approximation of the sng command-line tool. +func sng(w io.WriteCloser, filename string, png image.Image) { + defer w.Close() + bounds := png.Bounds() + cm := png.ColorModel() + var bitdepth int + switch cm { + case color.RGBAModel, color.NRGBAModel, color.AlphaModel, color.GrayModel: + bitdepth = 8 + default: + bitdepth = 16 + } + cpm, _ := cm.(color.Palette) + var paletted *image.Paletted + if cpm != nil { + switch { + case len(cpm) <= 2: + bitdepth = 1 + case len(cpm) <= 4: + bitdepth = 2 + case len(cpm) <= 16: + bitdepth = 4 + default: + bitdepth = 8 + } + paletted = png.(*image.Paletted) + } + + // Write the filename and IHDR. + io.WriteString(w, "#SNG: from "+filename+".png\nIHDR {\n") + fmt.Fprintf(w, " width: %d; height: %d; bitdepth: %d;\n", bounds.Dx(), bounds.Dy(), bitdepth) + switch { + case cm == color.RGBAModel, cm == color.RGBA64Model: + io.WriteString(w, " using color;\n") + case cm == color.NRGBAModel, cm == color.NRGBA64Model: + io.WriteString(w, " using color alpha;\n") + case cm == color.GrayModel, cm == color.Gray16Model: + io.WriteString(w, " using grayscale;\n") + case cpm != nil: + io.WriteString(w, " using color palette;\n") + default: + io.WriteString(w, "unknown PNG decoder color model\n") + } + io.WriteString(w, "}\n") + + // We fake a gAMA output. The test files have a gAMA chunk but the go PNG parser ignores it + // (the PNG spec section 11.3 says "Ancillary chunks may be ignored by a decoder"). + io.WriteString(w, "gAMA {1.0000}\n") + + // Write the PLTE and tRNS (if applicable). + if cpm != nil { + lastAlpha := -1 + io.WriteString(w, "PLTE {\n") + for i, c := range cpm { + var r, g, b, a uint8 + switch c := c.(type) { + case color.RGBA: + r, g, b, a = c.R, c.G, c.B, 0xff + case color.NRGBA: + r, g, b, a = c.R, c.G, c.B, c.A + default: + panic("unknown palette color type") + } + if a != 0xff { + lastAlpha = i + } + fmt.Fprintf(w, " (%3d,%3d,%3d) # rgb = (0x%02x,0x%02x,0x%02x)\n", r, g, b, r, g, b) + } + io.WriteString(w, "}\n") + if lastAlpha != -1 { + io.WriteString(w, "tRNS {\n") + for i := 0; i <= lastAlpha; i++ { + _, _, _, a := cpm[i].RGBA() + a >>= 8 + fmt.Fprintf(w, " %d", a) + } + io.WriteString(w, "}\n") + } + } + + // Write the IMAGE. + io.WriteString(w, "IMAGE {\n pixels hex\n") + for y := bounds.Min.Y; y < bounds.Max.Y; y++ { + switch { + case cm == color.GrayModel: + for x := bounds.Min.X; x < bounds.Max.X; x++ { + gray := png.At(x, y).(color.Gray) + fmt.Fprintf(w, "%02x", gray.Y) + } + case cm == color.Gray16Model: + for x := bounds.Min.X; x < bounds.Max.X; x++ { + gray16 := png.At(x, y).(color.Gray16) + fmt.Fprintf(w, "%04x ", gray16.Y) + } + case cm == color.RGBAModel: + for x := bounds.Min.X; x < bounds.Max.X; x++ { + rgba := png.At(x, y).(color.RGBA) + fmt.Fprintf(w, "%02x%02x%02x ", rgba.R, rgba.G, rgba.B) + } + case cm == color.RGBA64Model: + for x := bounds.Min.X; x < bounds.Max.X; x++ { + rgba64 := png.At(x, y).(color.RGBA64) + fmt.Fprintf(w, "%04x%04x%04x ", rgba64.R, rgba64.G, rgba64.B) + } + case cm == color.NRGBAModel: + for x := bounds.Min.X; x < bounds.Max.X; x++ { + nrgba := png.At(x, y).(color.NRGBA) + fmt.Fprintf(w, "%02x%02x%02x%02x ", nrgba.R, nrgba.G, nrgba.B, nrgba.A) + } + case cm == color.NRGBA64Model: + for x := bounds.Min.X; x < bounds.Max.X; x++ { + nrgba64 := png.At(x, y).(color.NRGBA64) + fmt.Fprintf(w, "%04x%04x%04x%04x ", nrgba64.R, nrgba64.G, nrgba64.B, nrgba64.A) + } + case cpm != nil: + var b, c int + for x := bounds.Min.X; x < bounds.Max.X; x++ { + b = b<<uint(bitdepth) | int(paletted.ColorIndexAt(x, y)) + c++ + if c == 8/bitdepth { + fmt.Fprintf(w, "%02x", b) + b = 0 + c = 0 + } + } + if c != 0 { + for c != 8/bitdepth { + b = b << uint(bitdepth) + c++ + } + fmt.Fprintf(w, "%02x", b) + } + } + io.WriteString(w, "\n") + } + io.WriteString(w, "}\n") +} + +func TestReader(t *testing.T) { + names := filenames + if testing.Short() { + names = filenamesShort + } + for _, fn := range names { + // Read the .png file. + img, err := readPNG("testdata/pngsuite/" + fn + ".png") + if err != nil { + t.Error(fn, err) + continue + } + + if fn == "basn4a16" { + // basn4a16.sng is gray + alpha but sng() will produce true color + alpha + // so we just check a single random pixel. + c := img.At(2, 1).(color.NRGBA64) + if c.R != 0x11a7 || c.G != 0x11a7 || c.B != 0x11a7 || c.A != 0x1085 { + t.Error(fn, fmt.Errorf("wrong pixel value at (2, 1): %x", c)) + } + continue + } + + piper, pipew := io.Pipe() + pb := bufio.NewScanner(piper) + go sng(pipew, fn, img) + defer piper.Close() + + // Read the .sng file. + sf, err := os.Open("testdata/pngsuite/" + fn + ".sng") + if err != nil { + t.Error(fn, err) + continue + } + defer sf.Close() + sb := bufio.NewScanner(sf) + if err != nil { + t.Error(fn, err) + continue + } + + // Compare the two, in SNG format, line by line. + for { + pdone := !pb.Scan() + sdone := !sb.Scan() + if pdone && sdone { + break + } + if pdone || sdone { + t.Errorf("%s: Different sizes", fn) + break + } + ps := pb.Text() + ss := sb.Text() + if ps != ss { + t.Errorf("%s: Mismatch\n%sversus\n%s\n", fn, ps, ss) + break + } + } + if pb.Err() != nil { + t.Error(fn, pb.Err()) + } + if sb.Err() != nil { + t.Error(fn, sb.Err()) + } + } +} + +var readerErrors = []struct { + file string + err string +}{ + {"invalid-zlib.png", "zlib: invalid checksum"}, + {"invalid-crc32.png", "invalid checksum"}, + {"invalid-noend.png", "unexpected EOF"}, + {"invalid-trunc.png", "unexpected EOF"}, +} + +func TestReaderError(t *testing.T) { + for _, tt := range readerErrors { + img, err := readPNG("testdata/" + tt.file) + if err == nil { + t.Errorf("decoding %s: missing error", tt.file) + continue + } + if !strings.Contains(err.Error(), tt.err) { + t.Errorf("decoding %s: %s, want %s", tt.file, err, tt.err) + } + if img != nil { + t.Errorf("decoding %s: have image + error", tt.file) + } + } +} + +func TestPalettedDecodeConfig(t *testing.T) { + for _, fn := range filenamesPaletted { + f, err := os.Open("testdata/pngsuite/" + fn + ".png") + if err != nil { + t.Errorf("%s: open failed: %v", fn, err) + continue + } + defer f.Close() + cfg, err := DecodeConfig(f) + if err != nil { + t.Errorf("%s: %v", fn, err) + continue + } + pal, ok := cfg.ColorModel.(color.Palette) + if !ok { + t.Errorf("%s: expected paletted color model", fn) + continue + } + if pal == nil { + t.Errorf("%s: palette not initialized", fn) + continue + } + } +} + +func benchmarkDecode(b *testing.B, filename string, bytesPerPixel int) { + b.StopTimer() + data, err := ioutil.ReadFile(filename) + if err != nil { + b.Fatal(err) + } + s := string(data) + cfg, err := DecodeConfig(strings.NewReader(s)) + if err != nil { + b.Fatal(err) + } + b.SetBytes(int64(cfg.Width * cfg.Height * bytesPerPixel)) + b.StartTimer() + for i := 0; i < b.N; i++ { + Decode(strings.NewReader(s)) + } +} + +func BenchmarkDecodeGray(b *testing.B) { + benchmarkDecode(b, "testdata/benchGray.png", 1) +} + +func BenchmarkDecodeNRGBAGradient(b *testing.B) { + benchmarkDecode(b, "testdata/benchNRGBA-gradient.png", 4) +} + +func BenchmarkDecodeNRGBAOpaque(b *testing.B) { + benchmarkDecode(b, "testdata/benchNRGBA-opaque.png", 4) +} + +func BenchmarkDecodePaletted(b *testing.B) { + benchmarkDecode(b, "testdata/benchPaletted.png", 1) +} + +func BenchmarkDecodeRGB(b *testing.B) { + benchmarkDecode(b, "testdata/benchRGB.png", 4) +} + +func BenchmarkDecodeInterlacing(b *testing.B) { + benchmarkDecode(b, "testdata/benchRGB-interlace.png", 4) +} |