blob: ac35df8102d83124560ceffa3706df75299a29ac [file] [log] [blame]
// 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 encoder.
//
// The PNG specification is at http://www.libpng.org/pub/png/spec/1.2/PNG-Contents.html
package png
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;
nFilter = 5;
)
// 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;
)
const pngHeader = "\x89PNG\r\n\x1a\n"
type decoder struct {
width, height int;
image image.Image;
colorType uint8;
stage int;
idatWriter io.WriteCloser;
idatDone chan os.Error;
tmp [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: " + string(e) }
var chunkOrderError = FormatError("chunk out of order")
// 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: " + string(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")
}
_, err := io.ReadFull(r, d.tmp[0:13]);
if err != nil {
return err
}
crc.Write(d.tmp[0:13]);
if d.tmp[8] != 8 {
return UnsupportedError("bit depth")
}
if d.tmp[10] != 0 || d.tmp[11] != 0 || d.tmp[12] != 0 {
return UnsupportedError("compression, filter or interlace method")
}
w := int32(parseUint32(d.tmp[0:4]));
h := int32(parseUint32(d.tmp[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.tmp[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.tmp[0:3*np]);
if err != nil {
return err
}
crc.Write(d.tmp[0:n]);
switch d.colorType {
case ctPaletted:
palette := make([]image.Color, np);
for i := 0; i < np; i++ {
palette[i] = image.RGBAColor{d.tmp[3*i+0], d.tmp[3*i+1], d.tmp[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.
// The +1 is for the per-row filter type, which is at cr[0].
cr := make([]uint8, 1+bpp*d.width);
pr := make([]uint8, 1+bpp*d.width);
for y := 0; y < d.height; y++ {
// Read the decompressed bytes.
_, err := io.ReadFull(r, cr);
if err != nil {
return err
}
// Apply the filter.
cdat := cr[1:len(cr)];
pdat := pr[1:len(pr)];
switch cr[0] {
case ftNone:
// No-op.
case ftSub:
for i := bpp; i < len(cdat); i++ {
cdat[i] += cdat[i-bpp]
}
case ftUp:
for i := 0; i < len(cdat); i++ {
cdat[i] += pdat[i]
}
case ftAverage:
for i := 0; i < bpp; i++ {
cdat[i] += pdat[i] / 2
}
for i := bpp; i < len(cdat); i++ {
cdat[i] += uint8((int(cdat[i-bpp]) + int(pdat[i])) / 2)
}
case ftPaeth:
for i := 0; i < bpp; i++ {
cdat[i] += paeth(0, pdat[i], 0)
}
for i := bpp; i < len(cdat); i++ {
cdat[i] += paeth(cdat[i-bpp], pdat[i], pdat[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{cdat[3*x+0], cdat[3*x+1], cdat[3*x+2], 0xff})
}
case ctPaletted:
for x := 0; x < d.width; x++ {
if cdat[x] > maxPalette {
return FormatError("palette index out of range")
}
paletted.SetColorIndex(x, y, cdat[x]);
}
case ctTrueColorAlpha:
for x := 0; x < d.width; x++ {
nrgba.Set(x, y, image.NRGBAColor{cdat[4*x+0], cdat[4*x+1], cdat[4*x+2], cdat[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.tmp[0:4]);
if err == os.EOF {
return io.ErrUnexpectedEOF
}
if err != nil {
return err
}
length := parseUint32(d.tmp[0:4]);
// Read the chunk type.
n, err = io.ReadFull(r, d.tmp[0:4]);
if err == os.EOF {
return io.ErrUnexpectedEOF
}
if err != nil {
return err
}
crc := crc32.NewIEEE();
crc.Write(d.tmp[0:4]);
// Read the chunk data.
switch string(d.tmp[0:4]) {
case "IHDR":
if d.stage != dsStart {
return chunkOrderError
}
d.stage = dsSeenIHDR;
err = d.parseIHDR(r, crc, length);
case "PLTE":
if d.stage != dsSeenIHDR {
return chunkOrderError
}
d.stage = dsSeenPLTE;
err = d.parsePLTE(r, crc, length);
case "IDAT":
if d.stage < dsSeenIHDR || d.stage > dsSeenIDAT || (d.colorType == ctPaletted && d.stage == dsSeenIHDR) {
return chunkOrderError
}
d.stage = dsSeenIDAT;
err = d.parseIDAT(r, crc, length);
case "IEND":
if d.stage != dsSeenIDAT {
return chunkOrderError
}
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.tmp[0:4]);
if err == os.EOF {
return io.ErrUnexpectedEOF
}
if err != nil {
return err
}
if parseUint32(d.tmp[0:4]) != crc.Sum32() {
return FormatError("invalid checksum")
}
return nil;
}
func (d *decoder) checkHeader(r io.Reader) os.Error {
_, err := io.ReadFull(r, d.tmp[0:8]);
if err != nil {
return err
}
if string(d.tmp[0:8]) != pngHeader {
return FormatError("not a PNG file")
}
return nil;
}
// Decode reads a PNG formatted image from r and returns it as an image.Image.
// The type of Image returned depends on the PNG contents.
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;
}