709 lines
16 KiB
Go
709 lines
16 KiB
Go
// 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"
|
|
"fmt"
|
|
"hash"
|
|
"hash/crc32"
|
|
"image"
|
|
"io"
|
|
"os"
|
|
)
|
|
|
|
// Color type, as per the PNG spec.
|
|
const (
|
|
ctGrayscale = 0
|
|
ctTrueColor = 2
|
|
ctPaletted = 3
|
|
ctGrayscaleAlpha = 4
|
|
ctTrueColorAlpha = 6
|
|
)
|
|
|
|
// A cb is a combination of color type and bit depth.
|
|
const (
|
|
cbInvalid = iota
|
|
cbG1
|
|
cbG2
|
|
cbG4
|
|
cbG8
|
|
cbGA8
|
|
cbTC8
|
|
cbP1
|
|
cbP2
|
|
cbP4
|
|
cbP8
|
|
cbTCA8
|
|
cbG16
|
|
cbGA16
|
|
cbTC16
|
|
cbTCA16
|
|
)
|
|
|
|
// 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 imgOrErr struct {
|
|
img image.Image
|
|
err os.Error
|
|
}
|
|
|
|
type decoder struct {
|
|
width, height int
|
|
depth int
|
|
palette image.PalettedColorModel
|
|
cb int
|
|
stage int
|
|
idatWriter io.WriteCloser
|
|
idatDone chan imgOrErr
|
|
tmp [3 * 256]byte
|
|
}
|
|
|
|
// A FormatError reports that the input is not a valid PNG.
|
|
type FormatError string
|
|
|
|
func (e FormatError) String() string { return "png: invalid 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 "png: 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 "png: unsupported 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[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.cb = cbInvalid
|
|
d.depth = int(d.tmp[8])
|
|
switch d.depth {
|
|
case 1:
|
|
switch d.tmp[9] {
|
|
case ctGrayscale:
|
|
d.cb = cbG1
|
|
case ctPaletted:
|
|
d.cb = cbP1
|
|
}
|
|
case 2:
|
|
switch d.tmp[9] {
|
|
case ctGrayscale:
|
|
d.cb = cbG2
|
|
case ctPaletted:
|
|
d.cb = cbP2
|
|
}
|
|
case 4:
|
|
switch d.tmp[9] {
|
|
case ctGrayscale:
|
|
d.cb = cbG4
|
|
case ctPaletted:
|
|
d.cb = cbP4
|
|
}
|
|
case 8:
|
|
switch d.tmp[9] {
|
|
case ctGrayscale:
|
|
d.cb = cbG8
|
|
case ctTrueColor:
|
|
d.cb = cbTC8
|
|
case ctPaletted:
|
|
d.cb = cbP8
|
|
case ctGrayscaleAlpha:
|
|
d.cb = cbGA8
|
|
case ctTrueColorAlpha:
|
|
d.cb = cbTCA8
|
|
}
|
|
case 16:
|
|
switch d.tmp[9] {
|
|
case ctGrayscale:
|
|
d.cb = cbG16
|
|
case ctTrueColor:
|
|
d.cb = cbTC16
|
|
case ctGrayscaleAlpha:
|
|
d.cb = cbGA16
|
|
case ctTrueColorAlpha:
|
|
d.cb = cbTCA16
|
|
}
|
|
}
|
|
if d.cb == cbInvalid {
|
|
return UnsupportedError(fmt.Sprintf("bit depth %d, color type %d", d.tmp[8], d.tmp[9]))
|
|
}
|
|
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 || np > 1<<uint(d.depth) {
|
|
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.cb {
|
|
case cbP1, cbP2, cbP4, cbP8:
|
|
d.palette = image.PalettedColorModel(make([]image.Color, np))
|
|
for i := 0; i < np; i++ {
|
|
d.palette[i] = image.RGBAColor{d.tmp[3*i+0], d.tmp[3*i+1], d.tmp[3*i+2], 0xff}
|
|
}
|
|
case cbTC8, cbTCA8, cbTC16, cbTCA16:
|
|
// 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).
|
|
default:
|
|
return FormatError("PLTE, color type mismatch")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (d *decoder) parsetRNS(r io.Reader, crc hash.Hash32, length uint32) os.Error {
|
|
if length > 256 {
|
|
return FormatError("bad tRNS length")
|
|
}
|
|
n, err := io.ReadFull(r, d.tmp[0:length])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
crc.Write(d.tmp[0:n])
|
|
switch d.cb {
|
|
case cbG8, cbG16:
|
|
return UnsupportedError("grayscale transparency")
|
|
case cbTC8, cbTC16:
|
|
return UnsupportedError("truecolor transparency")
|
|
case cbP1, cbP2, cbP4, cbP8:
|
|
if n > len(d.palette) {
|
|
return FormatError("bad tRNS length")
|
|
}
|
|
for i := 0; i < n; i++ {
|
|
rgba := d.palette[i].(image.RGBAColor)
|
|
d.palette[i] = image.RGBAColor{rgba.R, rgba.G, rgba.B, d.tmp[i]}
|
|
}
|
|
case cbGA8, cbGA16, cbTCA8, cbTCA16:
|
|
return FormatError("tRNS, 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) (image.Image, os.Error) {
|
|
r, err := zlib.NewReader(idat)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer r.Close()
|
|
bitsPerPixel := 0
|
|
maxPalette := uint8(0)
|
|
var (
|
|
gray *image.Gray
|
|
rgba *image.RGBA
|
|
paletted *image.Paletted
|
|
nrgba *image.NRGBA
|
|
gray16 *image.Gray16
|
|
rgba64 *image.RGBA64
|
|
nrgba64 *image.NRGBA64
|
|
img image.Image
|
|
)
|
|
switch d.cb {
|
|
case cbG1, cbG2, cbG4, cbG8:
|
|
bitsPerPixel = d.depth
|
|
gray = image.NewGray(d.width, d.height)
|
|
img = gray
|
|
case cbGA8:
|
|
bitsPerPixel = 16
|
|
nrgba = image.NewNRGBA(d.width, d.height)
|
|
img = nrgba
|
|
case cbTC8:
|
|
bitsPerPixel = 24
|
|
rgba = image.NewRGBA(d.width, d.height)
|
|
img = rgba
|
|
case cbP1, cbP2, cbP4, cbP8:
|
|
bitsPerPixel = d.depth
|
|
paletted = image.NewPaletted(d.width, d.height, d.palette)
|
|
img = paletted
|
|
maxPalette = uint8(len(d.palette) - 1)
|
|
case cbTCA8:
|
|
bitsPerPixel = 32
|
|
nrgba = image.NewNRGBA(d.width, d.height)
|
|
img = nrgba
|
|
case cbG16:
|
|
bitsPerPixel = 16
|
|
gray16 = image.NewGray16(d.width, d.height)
|
|
img = gray16
|
|
case cbGA16:
|
|
bitsPerPixel = 32
|
|
nrgba64 = image.NewNRGBA64(d.width, d.height)
|
|
img = nrgba64
|
|
case cbTC16:
|
|
bitsPerPixel = 48
|
|
rgba64 = image.NewRGBA64(d.width, d.height)
|
|
img = rgba64
|
|
case cbTCA16:
|
|
bitsPerPixel = 64
|
|
nrgba64 = image.NewNRGBA64(d.width, d.height)
|
|
img = nrgba64
|
|
}
|
|
bytesPerPixel := (bitsPerPixel + 7) / 8
|
|
|
|
// 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+(bitsPerPixel*d.width+7)/8)
|
|
pr := make([]uint8, 1+(bitsPerPixel*d.width+7)/8)
|
|
|
|
for y := 0; y < d.height; y++ {
|
|
// Read the decompressed bytes.
|
|
_, err := io.ReadFull(r, cr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Apply the filter.
|
|
cdat := cr[1:]
|
|
pdat := pr[1:]
|
|
switch cr[0] {
|
|
case ftNone:
|
|
// No-op.
|
|
case ftSub:
|
|
for i := bytesPerPixel; i < len(cdat); i++ {
|
|
cdat[i] += cdat[i-bytesPerPixel]
|
|
}
|
|
case ftUp:
|
|
for i := 0; i < len(cdat); i++ {
|
|
cdat[i] += pdat[i]
|
|
}
|
|
case ftAverage:
|
|
for i := 0; i < bytesPerPixel; i++ {
|
|
cdat[i] += pdat[i] / 2
|
|
}
|
|
for i := bytesPerPixel; i < len(cdat); i++ {
|
|
cdat[i] += uint8((int(cdat[i-bytesPerPixel]) + int(pdat[i])) / 2)
|
|
}
|
|
case ftPaeth:
|
|
for i := 0; i < bytesPerPixel; i++ {
|
|
cdat[i] += paeth(0, pdat[i], 0)
|
|
}
|
|
for i := bytesPerPixel; i < len(cdat); i++ {
|
|
cdat[i] += paeth(cdat[i-bytesPerPixel], pdat[i], pdat[i-bytesPerPixel])
|
|
}
|
|
default:
|
|
return nil, FormatError("bad filter type")
|
|
}
|
|
|
|
// Convert from bytes to colors.
|
|
switch d.cb {
|
|
case cbG1:
|
|
for x := 0; x < d.width; x += 8 {
|
|
b := cdat[x/8]
|
|
for x2 := 0; x2 < 8 && x+x2 < d.width; x2++ {
|
|
gray.Set(x+x2, y, image.GrayColor{(b >> 7) * 0xff})
|
|
b <<= 1
|
|
}
|
|
}
|
|
case cbG2:
|
|
for x := 0; x < d.width; x += 4 {
|
|
b := cdat[x/4]
|
|
for x2 := 0; x2 < 4 && x+x2 < d.width; x2++ {
|
|
gray.Set(x+x2, y, image.GrayColor{(b >> 6) * 0x55})
|
|
b <<= 2
|
|
}
|
|
}
|
|
case cbG4:
|
|
for x := 0; x < d.width; x += 2 {
|
|
b := cdat[x/2]
|
|
for x2 := 0; x2 < 2 && x+x2 < d.width; x2++ {
|
|
gray.Set(x+x2, y, image.GrayColor{(b >> 4) * 0x11})
|
|
b <<= 4
|
|
}
|
|
}
|
|
case cbG8:
|
|
for x := 0; x < d.width; x++ {
|
|
gray.Set(x, y, image.GrayColor{cdat[x]})
|
|
}
|
|
case cbGA8:
|
|
for x := 0; x < d.width; x++ {
|
|
ycol := cdat[2*x+0]
|
|
nrgba.Set(x, y, image.NRGBAColor{ycol, ycol, ycol, cdat[2*x+1]})
|
|
}
|
|
case cbTC8:
|
|
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 cbP1:
|
|
for x := 0; x < d.width; x += 8 {
|
|
b := cdat[x/8]
|
|
for x2 := 0; x2 < 8 && x+x2 < d.width; x2++ {
|
|
idx := b >> 7
|
|
if idx > maxPalette {
|
|
return nil, FormatError("palette index out of range")
|
|
}
|
|
paletted.SetColorIndex(x+x2, y, idx)
|
|
b <<= 1
|
|
}
|
|
}
|
|
case cbP2:
|
|
for x := 0; x < d.width; x += 4 {
|
|
b := cdat[x/4]
|
|
for x2 := 0; x2 < 4 && x+x2 < d.width; x2++ {
|
|
idx := b >> 6
|
|
if idx > maxPalette {
|
|
return nil, FormatError("palette index out of range")
|
|
}
|
|
paletted.SetColorIndex(x+x2, y, idx)
|
|
b <<= 2
|
|
}
|
|
}
|
|
case cbP4:
|
|
for x := 0; x < d.width; x += 2 {
|
|
b := cdat[x/2]
|
|
for x2 := 0; x2 < 2 && x+x2 < d.width; x2++ {
|
|
idx := b >> 4
|
|
if idx > maxPalette {
|
|
return nil, FormatError("palette index out of range")
|
|
}
|
|
paletted.SetColorIndex(x+x2, y, idx)
|
|
b <<= 4
|
|
}
|
|
}
|
|
case cbP8:
|
|
for x := 0; x < d.width; x++ {
|
|
if cdat[x] > maxPalette {
|
|
return nil, FormatError("palette index out of range")
|
|
}
|
|
paletted.SetColorIndex(x, y, cdat[x])
|
|
}
|
|
case cbTCA8:
|
|
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]})
|
|
}
|
|
case cbG16:
|
|
for x := 0; x < d.width; x++ {
|
|
ycol := uint16(cdat[2*x+0])<<8 | uint16(cdat[2*x+1])
|
|
gray16.Set(x, y, image.Gray16Color{ycol})
|
|
}
|
|
case cbGA16:
|
|
for x := 0; x < d.width; x++ {
|
|
ycol := uint16(cdat[4*x+0])<<8 | uint16(cdat[4*x+1])
|
|
acol := uint16(cdat[4*x+2])<<8 | uint16(cdat[4*x+3])
|
|
nrgba64.Set(x, y, image.NRGBA64Color{ycol, ycol, ycol, acol})
|
|
}
|
|
case cbTC16:
|
|
for x := 0; x < d.width; x++ {
|
|
rcol := uint16(cdat[6*x+0])<<8 | uint16(cdat[6*x+1])
|
|
gcol := uint16(cdat[6*x+2])<<8 | uint16(cdat[6*x+3])
|
|
bcol := uint16(cdat[6*x+4])<<8 | uint16(cdat[6*x+5])
|
|
rgba64.Set(x, y, image.RGBA64Color{rcol, gcol, bcol, 0xffff})
|
|
}
|
|
case cbTCA16:
|
|
for x := 0; x < d.width; x++ {
|
|
rcol := uint16(cdat[8*x+0])<<8 | uint16(cdat[8*x+1])
|
|
gcol := uint16(cdat[8*x+2])<<8 | uint16(cdat[8*x+3])
|
|
bcol := uint16(cdat[8*x+4])<<8 | uint16(cdat[8*x+5])
|
|
acol := uint16(cdat[8*x+6])<<8 | uint16(cdat[8*x+7])
|
|
nrgba64.Set(x, y, image.NRGBA64Color{rcol, gcol, bcol, acol})
|
|
}
|
|
}
|
|
|
|
// The current row for y is the previous row for y+1.
|
|
pr, cr = cr, pr
|
|
}
|
|
return img, 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 imgOrErr)
|
|
go func() {
|
|
img, err := d.idatReader(pr)
|
|
if err == os.EOF {
|
|
err = FormatError("too little IDAT")
|
|
}
|
|
pr.CloseWithError(FormatError("too much IDAT"))
|
|
d.idatDone <- imgOrErr{img, 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 "tRNS":
|
|
if d.stage != dsSeenPLTE {
|
|
return chunkOrderError
|
|
}
|
|
err = d.parsetRNS(r, crc, length)
|
|
case "IDAT":
|
|
if d.stage < dsSeenIHDR || d.stage > dsSeenIDAT || (d.cb == cbP8 && 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 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 != dsSeenIEND {
|
|
err = d.parseChunk(r)
|
|
if err != nil {
|
|
break
|
|
}
|
|
}
|
|
var img image.Image
|
|
if d.idatWriter != nil {
|
|
d.idatWriter.Close()
|
|
ie := <-d.idatDone
|
|
if err == nil {
|
|
img, err = ie.img, ie.err
|
|
}
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return img, nil
|
|
}
|
|
|
|
// DecodeConfig returns the color model and dimensions of a PNG image without
|
|
// decoding the entire image.
|
|
func DecodeConfig(r io.Reader) (image.Config, os.Error) {
|
|
var d decoder
|
|
err := d.checkHeader(r)
|
|
if err != nil {
|
|
return image.Config{}, err
|
|
}
|
|
for {
|
|
err = d.parseChunk(r)
|
|
if err != nil {
|
|
return image.Config{}, err
|
|
}
|
|
if d.stage == dsSeenIHDR && d.cb != cbP8 {
|
|
break
|
|
}
|
|
if d.stage == dsSeenPLTE && d.cb == cbP8 {
|
|
break
|
|
}
|
|
}
|
|
var cm image.ColorModel
|
|
switch d.cb {
|
|
case cbG1, cbG2, cbG4, cbG8:
|
|
cm = image.GrayColorModel
|
|
case cbGA8:
|
|
cm = image.NRGBAColorModel
|
|
case cbTC8:
|
|
cm = image.RGBAColorModel
|
|
case cbP1, cbP2, cbP4, cbP8:
|
|
cm = d.palette
|
|
case cbTCA8:
|
|
cm = image.NRGBAColorModel
|
|
case cbG16:
|
|
cm = image.Gray16ColorModel
|
|
case cbGA16:
|
|
cm = image.NRGBA64ColorModel
|
|
case cbTC16:
|
|
cm = image.RGBA64ColorModel
|
|
case cbTCA16:
|
|
cm = image.NRGBA64ColorModel
|
|
}
|
|
return image.Config{cm, d.width, d.height}, nil
|
|
}
|
|
|
|
func init() {
|
|
image.RegisterFormat("png", pngHeader, Decode, DecodeConfig)
|
|
}
|