From: Nigel Tao Date: Fri, 11 Sep 2009 04:33:44 +0000 (-0700) Subject: PNG decoder for go. X-Git-Tag: weekly.2009-11-06~595 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=70eef675d5aa4f4fb6f2a5f59777b826526d3205;p=gostls13.git PNG decoder for go. R=rsc APPROVED=r DELTA=694 (675 added, 3 deleted, 16 changed) OCL=34427 CL=34554 --- diff --git a/src/pkg/Make.deps b/src/pkg/Make.deps index bae5765645..aed887c0dc 100644 --- a/src/pkg/Make.deps +++ b/src/pkg/Make.deps @@ -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 diff --git a/src/pkg/Makefile b/src/pkg/Makefile index 73dde239b9..6495f5705a 100644 --- a/src/pkg/Makefile +++ b/src/pkg/Makefile @@ -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\ diff --git a/src/pkg/compress/gzip/gunzip.go b/src/pkg/compress/gzip/gunzip.go index 7a55528965..9a27d0d9b2 100644 --- a/src/pkg/compress/gzip/gunzip.go +++ b/src/pkg/compress/gzip/gunzip.go @@ -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(); +} + diff --git a/src/pkg/compress/gzip/gunzip_test.go b/src/pkg/compress/gzip/gunzip_test.go index d881c9875b..699a1b5fef 100644 --- a/src/pkg/compress/gzip/gunzip_test.go +++ b/src/pkg/compress/gzip/gunzip_test.go @@ -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); } diff --git a/src/pkg/compress/zlib/reader.go b/src/pkg/compress/zlib/reader.go index a777408038..6d4182df5b 100644 --- a/src/pkg/compress/zlib/reader.go +++ b/src/pkg/compress/zlib/reader.go @@ -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(); +} + diff --git a/src/pkg/compress/zlib/reader_test.go b/src/pkg/compress/zlib/reader_test.go index 6bec35b969..59aa0e7211 100644 --- a/src/pkg/compress/zlib/reader_test.go +++ b/src/pkg/compress/zlib/reader_test.go @@ -86,6 +86,7 @@ func TestInflater(t *testing.T) { } continue; } + defer zlib.Close(); b.Reset(); n, err := io.Copy(zlib, b); if err != nil { diff --git a/src/pkg/image/color.go b/src/pkg/image/color.go index 44b27643c9..6f2abc6d0e 100644 --- a/src/pkg/image/color.go +++ b/src/pkg/image/color.go @@ -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); + diff --git a/src/pkg/image/image.go b/src/pkg/image/image.go index f63df1bd3a..9062415d9d 100644 --- a/src/pkg/image/image.go +++ b/src/pkg/image/image.go @@ -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 index 0000000000..a99e2dc9e0 --- /dev/null +++ b/src/pkg/image/png/Makefile @@ -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 index 0000000000..d410b6e53b --- /dev/null +++ b/src/pkg/image/png/reader.go @@ -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; +} +