]> Cypherpunks repositories - gostls13.git/commitdiff
PNG decoder for go.
authorNigel Tao <nigeltao@golang.org>
Fri, 11 Sep 2009 04:33:44 +0000 (21:33 -0700)
committerNigel Tao <nigeltao@golang.org>
Fri, 11 Sep 2009 04:33:44 +0000 (21:33 -0700)
R=rsc
APPROVED=r
DELTA=694  (675 added, 3 deleted, 16 changed)
OCL=34427
CL=34554

src/pkg/Make.deps
src/pkg/Makefile
src/pkg/compress/gzip/gunzip.go
src/pkg/compress/gzip/gunzip_test.go
src/pkg/compress/zlib/reader.go
src/pkg/compress/zlib/reader_test.go
src/pkg/image/color.go
src/pkg/image/image.go
src/pkg/image/png/Makefile [new file with mode: 0644]
src/pkg/image/png/reader.go [new file with mode: 0644]

index bae57656458d6f95ee7d49efc3dd4d7522d5deda..aed887c0dc0af0a5259623649ff3860a20e30be3 100644 (file)
@@ -38,6 +38,7 @@ hash/adler32.install: hash.install os.install
 hash/crc32.install: hash.install os.install
 http.install: bufio.install bytes.install container/vector.install fmt.install io.install log.install net.install os.install path.install strconv.install strings.install utf8.install
 image.install:
+image/png.install: compress/zlib.install hash.install hash/crc32.install image.install io.install os.install
 io.install: bytes.install os.install strings.install sync.install
 json.install: bytes.install container/vector.install fmt.install math.install reflect.install strconv.install strings.install utf8.install
 log.install: fmt.install io.install os.install runtime.install time.install
index 73dde239b916050d076be4b9e051c2923d479030..6495f5705a34b0641db77f0d8ba7f69d2ce63507 100644 (file)
@@ -52,6 +52,7 @@ DIRS=\
        hash/crc32\
        http\
        image\
+       image/png\
        io\
        json\
        log\
@@ -86,6 +87,7 @@ NOTEST=\
        go/token\
        hash\
        image\
+       image/png\
        malloc\
        rand\
        runtime\
index 7a55528965a29e7190a74e82f47edc0b529dccc4..9a27d0d9b2647a99c01e57c8ddda9a0f682edba6 100644 (file)
@@ -62,7 +62,7 @@ type Inflater struct {
        OS byte;                        // operating system type
 
        r flate.Reader;
-       inflater io.Reader;
+       inflater io.ReadCloser;
        digest hash.Hash32;
        size uint32;
        flg byte;
@@ -73,6 +73,7 @@ type Inflater struct {
 
 // NewInflater creates a new Inflater reading the given reader.
 // The implementation buffers input and may read more data than necessary from r.
+// It is the caller's responsibility to call Close on the Inflater when done.
 func NewInflater(r io.Reader) (*Inflater, os.Error) {
        z := new(Inflater);
        z.r = makeReader(r);
@@ -221,3 +222,8 @@ func (z *Inflater) Read(p []byte) (n int, err os.Error) {
        return z.Read(p);
 }
 
+// Calling Close does not close the wrapped io.Reader originally passed to NewInflater.
+func (z *Inflater) Close() os.Error {
+       return z.inflater.Close();
+}
+
index d881c9875b4dafe0bb8798793ed9d42fbff82a28..699a1b5fef3f5b247a58e18dc6b306ba8839f4fd 100644 (file)
@@ -289,6 +289,7 @@ func TestInflater(t *testing.T) {
                        t.Errorf("%s: NewInflater: %s", tt.name, err);
                        continue;
                }
+               defer gzip.Close();
                if tt.name != gzip.Name {
                        t.Errorf("%s: got name %s", tt.name, gzip.Name);
                }
index a77740803837ee4888da51bf3293a8f105d988ba..6d4182df5bddf5186a2d6746c92c9e64c9fd40f5 100644 (file)
@@ -23,30 +23,31 @@ var UnsupportedError os.Error = os.ErrorString("unsupported zlib format")
 
 type reader struct {
        r flate.Reader;
-       inflater io.Reader;
+       inflater io.ReadCloser;
        digest hash.Hash32;
        err os.Error;
+       scratch [4]byte;
 }
 
-// NewInflater creates a new io.Reader that satisfies reads by decompressing data read from r.
+// NewInflater creates a new io.ReadCloser that satisfies reads by decompressing data read from r.
 // The implementation buffers input and may read more data than necessary from r.
-func NewInflater(r io.Reader) (io.Reader, os.Error) {
+// It is the caller's responsibility to call Close on the ReadCloser when done.
+func NewInflater(r io.Reader) (io.ReadCloser, os.Error) {
        z := new(reader);
        if fr, ok := r.(flate.Reader); ok {
                z.r = fr;
        } else {
                z.r = bufio.NewReader(r);
        }
-       var buf [2]byte;
-       n, err := io.ReadFull(z.r, buf[0:2]);
+       n, err := io.ReadFull(z.r, z.scratch[0:2]);
        if err != nil {
                return nil, err;
        }
-       h := uint(buf[0])<<8 | uint(buf[1]);
-       if (buf[0] & 0x0f != zlibDeflate) || (h % 31 != 0) {
+       h := uint(z.scratch[0])<<8 | uint(z.scratch[1]);
+       if (z.scratch[0] & 0x0f != zlibDeflate) || (h % 31 != 0) {
                return nil, HeaderError;
        }
-       if buf[1] & 0x20 != 0 {
+       if z.scratch[1] & 0x20 != 0 {
                // BUG(nigeltao): The zlib package does not implement the FDICT flag.
                return nil, UnsupportedError;
        }
@@ -71,13 +72,12 @@ func (z *reader) Read(p []byte) (n int, err os.Error) {
        }
 
        // Finished file; check checksum.
-       var buf [4]byte;
-       if _, err := io.ReadFull(z.r, buf[0:4]); err != nil {
+       if _, err := io.ReadFull(z.r, z.scratch[0:4]); err != nil {
                z.err = err;
                return 0, err;
        }
        // ZLIB (RFC 1950) is big-endian, unlike GZIP (RFC 1952).
-       checksum := uint32(buf[0])<<24 | uint32(buf[1])<<16 | uint32(buf[2])<<8 | uint32(buf[3]);
+       checksum := uint32(z.scratch[0])<<24 | uint32(z.scratch[1])<<16 | uint32(z.scratch[2])<<8 | uint32(z.scratch[3]);
        if checksum != z.digest.Sum32() {
                z.err = ChecksumError;
                return 0, z.err;
@@ -85,3 +85,8 @@ func (z *reader) Read(p []byte) (n int, err os.Error) {
        return;
 }
 
+// Calling Close does not close the wrapped io.Reader originally passed to NewInflater.
+func (z *reader) Close() os.Error {
+       return z.inflater.Close();
+}
+
index 6bec35b969ab33cd39bf4d3da8ff194059a6abef..59aa0e7211e62965be9704bfd4dcd139a3a2a196 100644 (file)
@@ -86,6 +86,7 @@ func TestInflater(t *testing.T) {
                        }
                        continue;
                }
+               defer zlib.Close();
                b.Reset();
                n, err := io.Copy(zlib, b);
                if err != nil {
index 44b27643c9c286733bdae539efc28e3f651e1261..6f2abc6d0ebf8ace8965f9e11a872f0d04e7ce8b 100644 (file)
@@ -4,15 +4,14 @@
 
 package image
 
-// TODO(nigeltao): Clarify semantics wrt premultiplied vs unpremultiplied colors.
-// It's probably also worth thinking about floating-point color models.
+// TODO(nigeltao): Think about how floating-point color models work.
 
-// All Colors can convert themselves, with a possible loss of precision, to 128-bit RGBA.
+// All Colors can convert themselves, with a possible loss of precision, to 128-bit alpha-premultiplied RGBA.
 type Color interface {
        RGBA() (r, g, b, a uint32);
 }
 
-// An RGBAColor represents a traditional 32-bit color, having 8 bits for each of red, green, blue and alpha.
+// An RGBAColor represents a traditional 32-bit alpha-premultiplied color, having 8 bits for each of red, green, blue and alpha.
 type RGBAColor struct {
        R, G, B, A uint8;
 }
@@ -33,7 +32,7 @@ func (c RGBAColor) RGBA() (r, g, b, a uint32) {
        return;
 }
 
-// An RGBA64Color represents a 64-bit color, having 16 bits for each of red, green, blue and alpha.
+// An RGBA64Color represents a 64-bit alpha-premultiplied color, having 16 bits for each of red, green, blue and alpha.
 type RGBA64Color struct {
        R, G, B, A uint16;
 }
@@ -50,6 +49,57 @@ func (c RGBA64Color) RGBA() (r, g, b, a uint32) {
        return;
 }
 
+// An NRGBAColor represents a non-alpha-premultiplied 32-bit color.
+type NRGBAColor struct {
+       R, G, B, A uint8;
+}
+
+func (c NRGBAColor) RGBA() (r, g, b, a uint32) {
+       r = uint32(c.R);
+       r |= r<<8;
+       r *= uint32(c.A);
+       r /= 0xff;
+       r |= r<<16;
+       g = uint32(c.G);
+       g |= g<<8;
+       g *= uint32(c.A);
+       g /= 0xff;
+       g |= g<<16;
+       b = uint32(c.B);
+       b |= b<<8;
+       b *= uint32(c.A);
+       b /= 0xff;
+       b |= b<<16;
+       a = uint32(c.A);
+       a |= a<<8;
+       a |= a<<16;
+       return;
+}
+
+// An NRGBA64Color represents a non-alpha-premultiplied 64-bit color, having 16 bits for each of red, green, blue and alpha.
+type NRGBA64Color struct {
+       R, G, B, A uint16;
+}
+
+func (c NRGBA64Color) RGBA() (r, g, b, a uint32) {
+       r = uint32(c.R);
+       r *= uint32(c.A);
+       r /= 0xffff;
+       r |= r<<16;
+       g = uint32(c.G);
+       g *= uint32(c.A);
+       g /= 0xffff;
+       g |= g<<16;
+       b = uint32(c.B);
+       b *= uint32(c.A);
+       b /= 0xffff;
+       b |= b<<16;
+       a = uint32(c.A);
+       a |= a<<8;
+       a |= a<<16;
+       return;
+}
+
 // A ColorModel can convert foreign Colors, with a possible loss of precision, to a Color
 // from its own color model.
 type ColorModel interface {
@@ -82,9 +132,59 @@ func toRGBA64Color(c Color) Color {
        return RGBA64Color{ uint16(r>>16), uint16(g>>16), uint16(b>>16), uint16(a>>16) };
 }
 
+func toNRGBAColor(c Color) Color {
+       if _, ok := c.(NRGBAColor); ok {        // no-op conversion
+               return c;
+       }
+       r, g, b, a := c.RGBA();
+       a >>= 16;
+       if a == 0xffff {
+               return NRGBAColor{ uint8(r>>24), uint8(g>>24), uint8(b>>24), 0xff };
+       }
+       if a == 0 {
+               return NRGBAColor{ 0, 0, 0, 0 };
+       }
+       r >>= 16;
+       g >>= 16;
+       b >>= 16;
+       // Since Color.RGBA returns a alpha-premultiplied color, we should have r <= a && g <= a && b <= a.
+       r = (r * 0xffff) / a;
+       g = (g * 0xffff) / a;
+       b = (b * 0xffff) / a;
+       return NRGBAColor{ uint8(r>>8), uint8(g>>8), uint8(b>>8), uint8(a>>8) };
+}
+
+func toNRGBA64Color(c Color) Color {
+       if _, ok := c.(NRGBA64Color); ok {      // no-op conversion
+               return c;
+       }
+       r, g, b, a := c.RGBA();
+       a >>= 16;
+       r >>= 16;
+       g >>= 16;
+       b >>= 16;
+       if a == 0xffff {
+               return NRGBA64Color{ uint16(r), uint16(g), uint16(b), 0xffff };
+       }
+       if a == 0 {
+               return NRGBA64Color{ 0, 0, 0, 0 };
+       }
+       // Since Color.RGBA returns a alpha-premultiplied color, we should have r <= a && g <= a && b <= a.
+       r = (r * 0xffff) / a;
+       g = (g * 0xffff) / a;
+       b = (b * 0xffff) / a;
+       return NRGBA64Color{ uint16(r), uint16(g), uint16(b), uint16(a) };
+}
+
 // The ColorModel associated with RGBAColor.
 var RGBAColorModel ColorModel = ColorModelFunc(toRGBAColor);
 
 // The ColorModel associated with RGBA64Color.
 var RGBA64ColorModel ColorModel = ColorModelFunc(toRGBA64Color);
 
+// The ColorModel associated with NRGBAColor.
+var NRGBAColorModel ColorModel = ColorModelFunc(toNRGBAColor);
+
+// The ColorModel associated with NRGBA64Color.
+var NRGBA64ColorModel ColorModel = ColorModelFunc(toNRGBA64Color);
+
index f63df1bd3a7cef9bd65ef9db1fd89600d37c60db..9062415d9d29d48bdbecd48dc33dcf142c18a983 100644 (file)
@@ -44,6 +44,15 @@ func (p *RGBA) Set(x, y int, c Color) {
        p.Pixel[y][x] = toRGBAColor(c).(RGBAColor);
 }
 
+// NewRGBA returns a new RGBA with the given width and height.
+func NewRGBA(w, h int) *RGBA {
+       pixel := make([][]RGBAColor, h);
+       for y := 0; y < int(h); y++ {
+               pixel[y] = make([]RGBAColor, w);
+       }
+       return &RGBA{ pixel };
+}
+
 // An RGBA64 is an in-memory image backed by a 2-D slice of RGBA64Color values.
 type RGBA64 struct {
        // The Pixel field's indices are y first, then x, so that At(x, y) == Pixel[y][x].
@@ -73,6 +82,91 @@ func (p *RGBA64) Set(x, y int, c Color) {
        p.Pixel[y][x] = toRGBA64Color(c).(RGBA64Color);
 }
 
+// NewRGBA64 returns a new RGBA64 with the given width and height.
+func NewRGBA64(w, h int) *RGBA64 {
+       pixel := make([][]RGBA64Color, h);
+       for y := 0; y < int(h); y++ {
+               pixel[y] = make([]RGBA64Color, w);
+       }
+       return &RGBA64{ pixel };
+}
+
+// A NRGBA is an in-memory image backed by a 2-D slice of NRGBAColor values.
+type NRGBA struct {
+       // The Pixel field's indices are y first, then x, so that At(x, y) == Pixel[y][x].
+       Pixel [][]NRGBAColor;
+}
+
+func (p *NRGBA) ColorModel() ColorModel {
+       return NRGBAColorModel;
+}
+
+func (p *NRGBA) Width() int {
+       if len(p.Pixel) == 0 {
+               return 0;
+       }
+       return len(p.Pixel[0]);
+}
+
+func (p *NRGBA) Height() int {
+       return len(p.Pixel);
+}
+
+func (p *NRGBA) At(x, y int) Color {
+       return p.Pixel[y][x];
+}
+
+func (p *NRGBA) Set(x, y int, c Color) {
+       p.Pixel[y][x] = toNRGBAColor(c).(NRGBAColor);
+}
+
+// NewNRGBA returns a new NRGBA with the given width and height.
+func NewNRGBA(w, h int) *NRGBA {
+       pixel := make([][]NRGBAColor, h);
+       for y := 0; y < int(h); y++ {
+               pixel[y] = make([]NRGBAColor, w);
+       }
+       return &NRGBA{ pixel };
+}
+
+// A NRGBA64 is an in-memory image backed by a 2-D slice of NRGBA64Color values.
+type NRGBA64 struct {
+       // The Pixel field's indices are y first, then x, so that At(x, y) == Pixel[y][x].
+       Pixel [][]NRGBA64Color;
+}
+
+func (p *NRGBA64) ColorModel() ColorModel {
+       return NRGBA64ColorModel;
+}
+
+func (p *NRGBA64) Width() int {
+       if len(p.Pixel) == 0 {
+               return 0;
+       }
+       return len(p.Pixel[0]);
+}
+
+func (p *NRGBA64) Height() int {
+       return len(p.Pixel);
+}
+
+func (p *NRGBA64) At(x, y int) Color {
+       return p.Pixel[y][x];
+}
+
+func (p *NRGBA64) Set(x, y int, c Color) {
+       p.Pixel[y][x] = toNRGBA64Color(c).(NRGBA64Color);
+}
+
+// NewNRGBA64 returns a new NRGBA64 with the given width and height.
+func NewNRGBA64(w, h int) *NRGBA64 {
+       pixel := make([][]NRGBA64Color, h);
+       for y := 0; y < int(h); y++ {
+               pixel[y] = make([]NRGBA64Color, w);
+       }
+       return &NRGBA64{ pixel };
+}
+
 // A PalettedColorModel represents a fixed palette of colors.
 type PalettedColorModel []Color;
 
@@ -113,10 +207,10 @@ func (p PalettedColorModel) Convert(c Color) Color {
        return result;
 }
 
-// A Paletted is an in-memory image backed by a 2-D slice of byte values and a PalettedColorModel.
+// A Paletted is an in-memory image backed by a 2-D slice of uint8 values and a PalettedColorModel.
 type Paletted struct {
        // The Pixel field's indices are y first, then x, so that At(x, y) == Palette[Pixel[y][x]].
-       Pixel [][]byte;
+       Pixel [][]uint8;
        Palette PalettedColorModel;
 }
 
@@ -138,3 +232,17 @@ func (p *Paletted) Height() int {
 func (p *Paletted) At(x, y int) Color {
        return p.Palette[p.Pixel[y][x]];
 }
+
+func (p *Paletted) SetColorIndex(x, y int, index uint8) {
+       p.Pixel[y][x] = index;
+}
+
+// NewPaletted returns a new Paletted with the given width, height and palette.
+func NewPaletted(w, h int, m PalettedColorModel) *Paletted {
+       pixel := make([][]uint8, h);
+       for y := 0; y < int(h); y++ {
+               pixel[y] = make([]uint8, w);
+       }
+       return &Paletted{ pixel, m };
+}
+
diff --git a/src/pkg/image/png/Makefile b/src/pkg/image/png/Makefile
new file mode 100644 (file)
index 0000000..a99e2dc
--- /dev/null
@@ -0,0 +1,11 @@
+# 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.
+
+include $(GOROOT)/src/Make.$(GOARCH)
+
+TARG=image/png
+GOFILES=\
+       reader.go\
+
+include $(GOROOT)/src/Make.pkg
diff --git a/src/pkg/image/png/reader.go b/src/pkg/image/png/reader.go
new file mode 100644 (file)
index 0000000..d410b6e
--- /dev/null
@@ -0,0 +1,437 @@
+// 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.
+
+// The png package implements a PNG image decoder (and eventually, an encoder).
+//
+// The PNG specification is at http://www.libpng.org/pub/png/spec/1.2/PNG-Contents.html
+package png
+
+// TODO(nigeltao): Add tests.
+import (
+       "compress/zlib";
+       "hash";
+       "hash/crc32";
+       "image";
+       "io";
+       "os";
+)
+
+// Color type, as per the PNG spec.
+const (
+       ctGrayscale = 0;
+       ctTrueColor = 2;
+       ctPaletted = 3;
+       ctGrayscaleAlpha = 4;
+       ctTrueColorAlpha = 6;
+)
+
+// Filter type, as per the PNG spec.
+const (
+       ftNone = 0;
+       ftSub = 1;
+       ftUp = 2;
+       ftAverage = 3;
+       ftPaeth = 4;
+)
+
+// Decoding stage.
+// The PNG specification says that the IHDR, PLTE (if present), IDAT and IEND
+// chunks must appear in that order. There may be multiple IDAT chunks, and
+// IDAT chunks must be sequential (i.e. they may not have any other chunks
+// between them).
+const (
+       dsStart = iota;
+       dsSeenIHDR;
+       dsSeenPLTE;
+       dsSeenIDAT;
+       dsSeenIEND;
+)
+
+type decoder struct {
+       width, height int;
+       image image.Image;
+       colorType uint8;
+       stage int;
+       idatWriter io.WriteCloser;
+       idatDone chan os.Error;
+       scratch [3 * 256]byte;
+}
+
+// A FormatError reports that the input is not a valid PNG.
+type FormatError string
+
+func (e FormatError) String() string {
+       return "invalid PNG format: " + e;
+}
+
+// An IDATDecodingError wraps an inner error (such as a ZLIB decoding error) encountered while processing an IDAT chunk.
+type IDATDecodingError struct {
+       Err os.Error;
+}
+
+func (e IDATDecodingError) String() string {
+       return "IDAT decoding error: " + e.Err.String();
+}
+
+// An UnsupportedError reports that the input uses a valid but unimplemented PNG feature.
+type UnsupportedError string
+
+func (e UnsupportedError) String() string {
+       return "unsupported PNG feature: " + e;
+}
+
+// Big-endian.
+func parseUint32(b []uint8) uint32 {
+       return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3]);
+}
+
+func abs(x int) int {
+       if x < 0 {
+               return -x;
+       }
+       return x;
+}
+
+func min(a, b int) int {
+       if a < b {
+               return a;
+       }
+       return b;
+}
+
+func (d *decoder) parseIHDR(r io.Reader, crc hash.Hash32, length uint32) os.Error {
+       if length != 13 {
+               return FormatError("bad IHDR length");
+       }
+       n, err := io.ReadFull(r, d.scratch[0:13]);
+       if err != nil {
+               return err;
+       }
+       crc.Write(d.scratch[0:13]);
+       if d.scratch[8] != 8 {
+               return UnsupportedError("bit depth");
+       }
+       if d.scratch[10] != 0 || d.scratch[11] != 0 || d.scratch[12] != 0 {
+               return UnsupportedError("compression, filter or interlace method");
+       }
+       w := int32(parseUint32(d.scratch[0:4]));
+       h := int32(parseUint32(d.scratch[4:8]));
+       if w < 0 || h < 0 {
+               return FormatError("negative dimension");
+       }
+       nPixels := int64(w) * int64(h);
+       if nPixels != int64(int(nPixels)) {
+               return UnsupportedError("dimension overflow");
+       }
+       d.colorType = d.scratch[9];
+       switch d.colorType {
+       case ctTrueColor:
+               d.image = image.NewRGBA(int(w), int(h));
+       case ctPaletted:
+               d.image = image.NewPaletted(int(w), int(h), nil);
+       case ctTrueColorAlpha:
+               d.image = image.NewNRGBA(int(w), int(h));
+       default:
+               return UnsupportedError("color type");
+       }
+       d.width, d.height = int(w), int(h);
+       return nil;
+}
+
+func (d *decoder) parsePLTE(r io.Reader, crc hash.Hash32, length uint32) os.Error {
+       np := int(length / 3);  // The number of palette entries.
+       if length % 3 != 0 || np <= 0 || np > 256 {
+               return FormatError("bad PLTE length");
+       }
+       n, err := io.ReadFull(r, d.scratch[0:3 * np]);
+       if err != nil {
+               return err;
+       }
+       crc.Write(d.scratch[0:n]);
+       switch d.colorType {
+       case ctPaletted:
+               palette := make([]image.Color, np);
+               for i := 0; i < np; i++ {
+                       palette[i] = image.RGBAColor{ d.scratch[3*i+0], d.scratch[3*i+1], d.scratch[3*i+2], 0xff };
+               }
+               d.image.(*image.Paletted).Palette = image.PalettedColorModel(palette);
+       case ctTrueColor, ctTrueColorAlpha:
+               // As per the PNG spec, a PLTE chunk is optional (and for practical purposes,
+               // ignorable) for the ctTrueColor and ctTrueColorAlpha color types (section 4.1.2).
+               return nil;
+       default:
+               return FormatError("PLTE, color type mismatch");
+       }
+       return nil;
+}
+
+// The Paeth filter function, as per the PNG specification.
+func paeth(a, b, c uint8) uint8 {
+       p := int(a) + int(b) - int(c);
+       pa := abs(p - int(a));
+       pb := abs(p - int(b));
+       pc := abs(p - int(c));
+       if pa <= pb && pa <= pc {
+               return a;
+       } else if pb <= pc {
+               return b;
+       }
+       return c;
+}
+
+func (d *decoder) idatReader(idat io.Reader) os.Error {
+       r, err := zlib.NewInflater(idat);
+       if err != nil {
+               return err;
+       }
+       defer r.Close();
+       bpp := 0;       // Bytes per pixel.
+       maxPalette := uint8(0);
+       var (
+               rgba *image.RGBA;
+               nrgba *image.NRGBA;
+               paletted *image.Paletted;
+       );
+       switch d.colorType {
+       case ctTrueColor:
+               bpp = 3;
+               rgba = d.image.(*image.RGBA);
+       case ctPaletted:
+               bpp = 1;
+               paletted = d.image.(*image.Paletted);
+               maxPalette = uint8(len(paletted.Palette) - 1);
+       case ctTrueColorAlpha:
+               bpp = 4;
+               nrgba = d.image.(*image.NRGBA);
+       }
+       // cr and pr are the bytes for the current and previous row.
+       cr := make([]uint8, bpp * d.width);
+       pr := make([]uint8, bpp * d.width);
+
+       var filter [1]uint8;
+       for y := 0; y < d.height; y++ {
+               // Read the decompressed bytes.
+               n, err := io.ReadFull(r, filter[0:1]);
+               if err != nil {
+                       return err;
+               }
+               n, err = io.ReadFull(r, cr);
+               if err != nil {
+                       return err;
+               }
+
+               // Apply the filter.
+               switch filter[0] {
+               case ftNone:
+                       // No-op.
+               case ftSub:
+                       for i := bpp; i < n; i++ {
+                               cr[i] += cr[i - bpp];
+                       }
+               case ftUp:
+                       for i := 0; i < n; i++ {
+                               cr[i] += pr[i];
+                       }
+               case ftAverage:
+                       for i := 0; i < bpp; i++ {
+                               cr[i] += pr[i] / 2;
+                       }
+                       for i := bpp; i < n; i++ {
+                               cr[i] += uint8((int(cr[i - bpp]) + int(pr[i])) / 2);
+                       }
+               case ftPaeth:
+                       for i := 0; i < bpp; i++ {
+                               cr[i] += paeth(0, pr[i], 0);
+                       }
+                       for i := bpp; i < n; i++ {
+                               cr[i] += paeth(cr[i - bpp], pr[i], pr[i - bpp]);
+                       }
+               default:
+                       return FormatError("bad filter type");
+               }
+
+               // Convert from bytes to colors.
+               switch d.colorType {
+               case ctTrueColor:
+                       for x := 0; x < d.width; x++ {
+                               rgba.Set(x, y, image.RGBAColor{ cr[3*x+0], cr[3*x+1], cr[3*x+2], 0xff });
+                       }
+               case ctPaletted:
+                       for x := 0; x < d.width; x++ {
+                               if cr[x] > maxPalette {
+                                       return FormatError("palette index out of range");
+                               }
+                               paletted.SetColorIndex(x, y, cr[x]);
+                       }
+               case ctTrueColorAlpha:
+                       for x := 0; x < d.width; x++ {
+                               nrgba.Set(x, y, image.NRGBAColor{ cr[4*x+0], cr[4*x+1], cr[4*x+2], cr[4*x+3] });
+                       }
+               }
+
+               // The current row for y is the previous row for y+1.
+               pr, cr = cr, pr;
+       }
+       return nil;
+}
+
+func (d *decoder) parseIDAT(r io.Reader, crc hash.Hash32, length uint32) os.Error {
+       // There may be more than one IDAT chunk, but their contents must be
+       // treated as if it was one continuous stream (to the zlib decoder).
+       // We bring up an io.Pipe and write the IDAT chunks into the pipe as
+       // we see them, and decode the stream in a separate go-routine, which
+       // signals its completion (successful or not) via a channel.
+       if d.idatWriter == nil {
+               pr, pw := io.Pipe();
+               d.idatWriter = pw;
+               d.idatDone = make(chan os.Error);
+               go func() {
+                       err := d.idatReader(pr);
+                       if err == os.EOF {
+                               err = FormatError("too little IDAT");
+                       }
+                       pr.CloseWithError(FormatError("too much IDAT"));
+                       d.idatDone <- err;
+               }();
+       }
+       var buf [4096]byte;
+       for length > 0 {
+               n, err1 := r.Read(buf[0:min(len(buf), int(length))]);
+               // We delay checking err1. It is possible to get n bytes and an error,
+               // but if the n bytes themselves contain a FormatError, for example, we
+               // want to report that error, and not the one that made the Read stop.
+               n, err2 := d.idatWriter.Write(buf[0:n]);
+               if err2 != nil {
+                       return err2;
+               }
+               if err1 != nil {
+                       return err1;
+               }
+               crc.Write(buf[0:n]);
+               length -= uint32(n);
+       }
+       return nil;
+}
+
+func (d *decoder) parseIEND(r io.Reader, crc hash.Hash32, length uint32) os.Error {
+       if length != 0 {
+               return FormatError("bad IEND length");
+       }
+       return nil;
+}
+
+func (d *decoder) parseChunk(r io.Reader) os.Error {
+       // Read the length.
+       n, err := io.ReadFull(r, d.scratch[0:4]);
+       if err == os.EOF {
+               return io.ErrUnexpectedEOF;
+       }
+       if err != nil {
+               return err;
+       }
+       length := parseUint32(d.scratch[0:4]);
+
+       // Read the chunk type.
+       n, err = io.ReadFull(r, d.scratch[0:4]);
+       if err == os.EOF {
+               return io.ErrUnexpectedEOF;
+       }
+       if err != nil {
+               return err;
+       }
+       crc := crc32.NewIEEE();
+       crc.Write(d.scratch[0:4]);
+
+       // Read the chunk data.
+       switch string(d.scratch[0:4]) {
+       case "IHDR":
+               if d.stage != dsStart {
+                       return FormatError("chunk out of order");
+               }
+               d.stage = dsSeenIHDR;
+               err = d.parseIHDR(r, crc, length);
+       case "PLTE":
+               if d.stage != dsSeenIHDR {
+                       return FormatError("chunk out of order");
+               }
+               d.stage = dsSeenPLTE;
+               err = d.parsePLTE(r, crc, length);
+       case "IDAT":
+               if d.stage < dsSeenIHDR || d.stage > dsSeenIDAT {
+                       return FormatError("chunk out of order");
+               }
+               d.stage = dsSeenIDAT;
+               err = d.parseIDAT(r, crc, length);
+       case "IEND":
+               if d.stage != dsSeenIDAT {
+                       return FormatError("chunk out of order");
+               }
+               d.stage = dsSeenIEND;
+               err = d.parseIEND(r, crc, length);
+       default:
+               // Ignore this chunk (of a known length).
+               var ignored [4096]byte;
+               for length > 0 {
+                       n, err = io.ReadFull(r, ignored[0:min(len(ignored), int(length))]);
+                       if err != nil {
+                               return err;
+                       }
+                       crc.Write(ignored[0:n]);
+                       length -= uint32(n);
+               }
+       }
+       if err != nil {
+               return err;
+       }
+
+       // Read the checksum.
+       n, err = io.ReadFull(r, d.scratch[0:4]);
+       if err == os.EOF {
+               return io.ErrUnexpectedEOF;
+       }
+       if err != nil {
+               return err;
+       }
+       if parseUint32(d.scratch[0:4]) != crc.Sum32() {
+               return FormatError("invalid checksum");
+       }
+       return nil;
+}
+
+func (d *decoder) checkHeader(r io.Reader) os.Error {
+       n, err := io.ReadFull(r, d.scratch[0:8]);
+       if err != nil {
+               return err;
+       }
+       if string(d.scratch[0:8]) != "\x89PNG\r\n\x1a\n" {
+               return FormatError("not a PNG file");
+       }
+       return nil;
+}
+
+func Decode(r io.Reader) (image.Image, os.Error) {
+       var d decoder;
+       err := d.checkHeader(r);
+       if err != nil {
+               return nil, err;
+       }
+       for d.stage = dsStart; d.stage != dsSeenIEND; {
+               err = d.parseChunk(r);
+               if err != nil {
+                       break;
+               }
+       }
+       if d.idatWriter != nil {
+               d.idatWriter.Close();
+               err1 := <-d.idatDone;
+               if err == nil {
+                       err = err1;
+               }
+       }
+       if err != nil {
+               return nil, err;
+       }
+       return d.image, nil;
+}
+