image: introduce Config type and DecodeConfig function, to decode an

image's color model and dimensions without allocating and decoding its
actual pixels.

Fixes #695.

R=r
CC=golang-dev
https://golang.org/cl/2151042
This commit is contained in:
Nigel Tao 2010-09-03 18:03:08 +10:00
parent 1a5d3c224d
commit 2b4218f321
4 changed files with 158 additions and 67 deletions

View file

@ -15,8 +15,9 @@ var UnknownFormatErr = os.NewError("image: unknown format")
// A format holds an image format's name, magic header and how to decode it.
type format struct {
name, magic string
decode func(io.Reader) (Image, os.Error)
name, magic string
decode func(io.Reader) (Image, os.Error)
decodeConfig func(io.Reader) (Config, os.Error)
}
// Formats is the list of registered formats.
@ -26,7 +27,8 @@ var formats []format
// Name is the name of the format, like "jpeg" or "png".
// Magic is the magic prefix that identifies the format's encoding.
// Decode is the function that decodes the encoded image.
func RegisterFormat(name, magic string, decode func(io.Reader) (Image, os.Error)) {
// DecodeConfig is the function that decodes just its configuration.
func RegisterFormat(name, magic string, decode func(io.Reader) (Image, os.Error), decodeConfig func(io.Reader) (Config, os.Error)) {
n := len(formats)
if n == cap(formats) {
x := make([]format, n+1, 2*n+4)
@ -35,7 +37,7 @@ func RegisterFormat(name, magic string, decode func(io.Reader) (Image, os.Error)
} else {
formats = formats[0 : n+1]
}
formats[n] = format{name, magic, decode}
formats[n] = format{name, magic, decode, decodeConfig}
}
// A reader is an io.Reader that can also peek ahead.
@ -52,22 +54,41 @@ func asReader(r io.Reader) reader {
return bufio.NewReader(r)
}
// Decode decodes an image that has been encoded in a registered format.
// Format registration is typically done by the init method of the codec-
// specific package.
func Decode(r io.Reader) (m Image, formatName string, err os.Error) {
var f format
rr := asReader(r)
for _, g := range formats {
s, err := rr.Peek(len(g.magic))
if err == nil && string(s) == g.magic {
f = g
break
// sniff determines the format of r's data.
func sniff(r reader) format {
for _, f := range formats {
s, err := r.Peek(len(f.magic))
if err == nil && string(s) == f.magic {
return f
}
}
return format{}
}
// Decode decodes an image that has been encoded in a registered format.
// The string returned is the format name used during format registration.
// Format registration is typically done by the init method of the codec-
// specific package.
func Decode(r io.Reader) (Image, string, os.Error) {
rr := asReader(r)
f := sniff(rr)
if f.decode == nil {
return nil, "", UnknownFormatErr
}
m, err = f.decode(rr)
m, err := f.decode(rr)
return m, f.name, err
}
// DecodeConfig decodes the color model and dimensions of an image that has
// been encoded in a registered format. The string returned is the format name
// used during format registration. Format registration is typically done by
// the init method of the codec-specific package.
func DecodeConfig(r io.Reader) (Config, string, os.Error) {
rr := asReader(r)
f := sniff(rr)
if f.decodeConfig == nil {
return Config{}, "", UnknownFormatErr
}
c, err := f.decodeConfig(rr)
return c, f.name, err
}

View file

@ -5,6 +5,12 @@
// The image package implements a basic 2-D image library.
package image
// A Config consists of an image's color model and dimensions.
type Config struct {
ColorModel ColorModel
Width, Height int
}
// An Image is a finite rectangular grid of Colors drawn from a ColorModel.
type Image interface {
// ColorModel returns the Image's ColorModel.

View file

@ -147,7 +147,6 @@ func (d *decoder) processSOF(n int) os.Error {
}
}
}
d.image = image.NewRGBA(d.width, d.height)
return nil
}
@ -240,7 +239,7 @@ func (d *decoder) convertMCU(mx, my, h0, v0 int) {
// Specified in section B.2.3.
func (d *decoder) processSOS(n int) os.Error {
if d.image == nil {
return FormatError("missing SOF segment")
d.image = image.NewRGBA(d.width, d.height)
}
if n != 4+2*nComponent {
return UnsupportedError("SOS has wrong length")
@ -365,9 +364,8 @@ func (d *decoder) processDRI(n int) os.Error {
return nil
}
// Decode reads a JPEG formatted image from r and returns it as an image.Image.
func Decode(r io.Reader) (image.Image, os.Error) {
var d decoder
// decode reads a JPEG image from r and returns it as an image.Image.
func (d *decoder) decode(r io.Reader, configOnly bool) (image.Image, os.Error) {
if rr, ok := r.(Reader); ok {
d.r = rr
} else {
@ -411,6 +409,9 @@ func Decode(r io.Reader) (image.Image, os.Error) {
switch {
case marker == sof0Marker: // Start Of Frame (Baseline).
err = d.processSOF(n)
if configOnly {
return nil, err
}
case marker == sof2Marker: // Start Of Frame (Progressive).
err = UnsupportedError("progressive mode")
case marker == dhtMarker: // Define Huffman Table.
@ -433,6 +434,22 @@ func Decode(r io.Reader) (image.Image, os.Error) {
return d.image, nil
}
func init() {
image.RegisterFormat("jpeg", "\xff\xd8", Decode)
// Decode reads a JPEG image from r and returns it as an image.Image.
func Decode(r io.Reader) (image.Image, os.Error) {
var d decoder
return d.decode(r, false)
}
// DecodeConfig returns the color model and dimensions of a JPEG image without
// decoding the entire image.
func DecodeConfig(r io.Reader) (image.Config, os.Error) {
var d decoder
if _, err := d.decode(r, true); err != nil {
return image.Config{}, err
}
return image.Config{image.RGBAColorModel, d.width, d.height}, nil
}
func init() {
image.RegisterFormat("jpeg", "\xff\xd8", Decode, DecodeConfig)
}

View file

@ -28,7 +28,8 @@ const (
// A cb is a combination of color type and bit depth.
const (
cbG8 = iota
cbInvalid = iota
cbG8
cbTC8
cbP8
cbTCA8
@ -62,20 +63,25 @@ const (
const pngHeader = "\x89PNG\r\n\x1a\n"
type imgOrErr struct {
img image.Image
err os.Error
}
type decoder struct {
width, height int
image image.Image
palette image.PalettedColorModel
cb int
stage int
idatWriter io.WriteCloser
idatDone chan os.Error
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 "invalid PNG format: " + string(e) }
func (e FormatError) String() string { return "png: invalid format: " + string(e) }
var chunkOrderError = FormatError("chunk out of order")
@ -84,12 +90,12 @@ type IDATDecodingError struct {
Err os.Error
}
func (e IDATDecodingError) String() string { return "IDAT decoding error: " + e.Err.String() }
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 "unsupported PNG feature: " + string(e) }
func (e UnsupportedError) String() string { return "png: unsupported feature: " + string(e) }
// Big-endian.
func parseUint32(b []uint8) uint32 {
@ -131,36 +137,30 @@ func (d *decoder) parseIHDR(r io.Reader, crc hash.Hash32, length uint32) os.Erro
if nPixels != int64(int(nPixels)) {
return UnsupportedError("dimension overflow")
}
d.cb = cbInvalid
switch d.tmp[8] {
case 8:
switch d.tmp[9] {
case ctGrayscale:
d.image = image.NewGray(int(w), int(h))
d.cb = cbG8
case ctTrueColor:
d.image = image.NewRGBA(int(w), int(h))
d.cb = cbTC8
case ctPaletted:
d.image = image.NewPaletted(int(w), int(h), nil)
d.cb = cbP8
case ctTrueColorAlpha:
d.image = image.NewNRGBA(int(w), int(h))
d.cb = cbTCA8
}
case 16:
switch d.tmp[9] {
case ctGrayscale:
d.image = image.NewGray16(int(w), int(h))
d.cb = cbG16
case ctTrueColor:
d.image = image.NewRGBA64(int(w), int(h))
d.cb = cbTC16
case ctTrueColorAlpha:
d.image = image.NewNRGBA64(int(w), int(h))
d.cb = cbTCA16
}
}
if d.image == nil {
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)
@ -179,11 +179,10 @@ func (d *decoder) parsePLTE(r io.Reader, crc hash.Hash32, length uint32) os.Erro
crc.Write(d.tmp[0:n])
switch d.cb {
case cbP8:
palette := make([]image.Color, np)
d.palette = image.PalettedColorModel(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.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 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).
@ -208,13 +207,12 @@ func (d *decoder) parsetRNS(r io.Reader, crc hash.Hash32, length uint32) os.Erro
case cbTC8, cbTC16:
return UnsupportedError("truecolor transparency")
case cbP8:
p := d.image.(*image.Paletted).Palette
if n > len(p) {
if n > len(d.palette) {
return FormatError("bad tRNS length")
}
for i := 0; i < n; i++ {
rgba := p[i].(image.RGBAColor)
p[i] = image.RGBAColor{rgba.R, rgba.G, rgba.B, d.tmp[i]}
rgba := d.palette[i].(image.RGBAColor)
d.palette[i] = image.RGBAColor{rgba.R, rgba.G, rgba.B, d.tmp[i]}
}
case cbTCA8, cbTCA16:
return FormatError("tRNS, color type mismatch")
@ -236,10 +234,10 @@ func paeth(a, b, c uint8) uint8 {
return c
}
func (d *decoder) idatReader(idat io.Reader) os.Error {
func (d *decoder) idatReader(idat io.Reader) (image.Image, os.Error) {
r, err := zlib.NewReader(idat)
if err != nil {
return err
return nil, err
}
defer r.Close()
bpp := 0 // Bytes per pixel.
@ -252,30 +250,38 @@ func (d *decoder) idatReader(idat io.Reader) os.Error {
gray16 *image.Gray16
rgba64 *image.RGBA64
nrgba64 *image.NRGBA64
img image.Image
)
switch d.cb {
case cbG8:
bpp = 1
gray = d.image.(*image.Gray)
gray = image.NewGray(d.width, d.height)
img = gray
case cbTC8:
bpp = 3
rgba = d.image.(*image.RGBA)
rgba = image.NewRGBA(d.width, d.height)
img = rgba
case cbP8:
bpp = 1
paletted = d.image.(*image.Paletted)
maxPalette = uint8(len(paletted.Palette) - 1)
paletted = image.NewPaletted(d.width, d.height, d.palette)
img = paletted
maxPalette = uint8(len(d.palette) - 1)
case cbTCA8:
bpp = 4
nrgba = d.image.(*image.NRGBA)
nrgba = image.NewNRGBA(d.width, d.height)
img = nrgba
case cbG16:
bpp = 2
gray16 = d.image.(*image.Gray16)
gray16 = image.NewGray16(d.width, d.height)
img = gray16
case cbTC16:
bpp = 6
rgba64 = d.image.(*image.RGBA64)
rgba64 = image.NewRGBA64(d.width, d.height)
img = rgba64
case cbTCA16:
bpp = 8
nrgba64 = d.image.(*image.NRGBA64)
nrgba64 = image.NewNRGBA64(d.width, d.height)
img = nrgba64
}
// 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].
@ -286,7 +292,7 @@ func (d *decoder) idatReader(idat io.Reader) os.Error {
// Read the decompressed bytes.
_, err := io.ReadFull(r, cr)
if err != nil {
return err
return nil, err
}
// Apply the filter.
@ -318,7 +324,7 @@ func (d *decoder) idatReader(idat io.Reader) os.Error {
cdat[i] += paeth(cdat[i-bpp], pdat[i], pdat[i-bpp])
}
default:
return FormatError("bad filter type")
return nil, FormatError("bad filter type")
}
// Convert from bytes to colors.
@ -334,7 +340,7 @@ func (d *decoder) idatReader(idat io.Reader) os.Error {
case cbP8:
for x := 0; x < d.width; x++ {
if cdat[x] > maxPalette {
return FormatError("palette index out of range")
return nil, FormatError("palette index out of range")
}
paletted.SetColorIndex(x, y, cdat[x])
}
@ -367,7 +373,7 @@ func (d *decoder) idatReader(idat io.Reader) os.Error {
// The current row for y is the previous row for y+1.
pr, cr = cr, pr
}
return nil
return img, nil
}
func (d *decoder) parseIDAT(r io.Reader, crc hash.Hash32, length uint32) os.Error {
@ -379,14 +385,14 @@ func (d *decoder) parseIDAT(r io.Reader, crc hash.Hash32, length uint32) os.Erro
if d.idatWriter == nil {
pr, pw := io.Pipe()
d.idatWriter = pw
d.idatDone = make(chan os.Error)
d.idatDone = make(chan imgOrErr)
go func() {
err := d.idatReader(pr)
img, err := d.idatReader(pr)
if err == os.EOF {
err = FormatError("too little IDAT")
}
pr.CloseWithError(FormatError("too much IDAT"))
d.idatDone <- err
d.idatDone <- imgOrErr{img, err}
}()
}
var buf [4096]byte
@ -509,7 +515,7 @@ func (d *decoder) checkHeader(r io.Reader) os.Error {
return nil
}
// Decode reads a PNG formatted image from r and returns it as an image.Image.
// 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
@ -517,25 +523,66 @@ func Decode(r io.Reader) (image.Image, os.Error) {
if err != nil {
return nil, err
}
for d.stage = dsStart; d.stage != dsSeenIEND; {
for d.stage != dsSeenIEND {
err = d.parseChunk(r)
if err != nil {
break
}
}
var img image.Image
if d.idatWriter != nil {
d.idatWriter.Close()
err1 := <-d.idatDone
ie := <-d.idatDone
if err == nil {
err = err1
img, err = ie.img, ie.err
}
}
if err != nil {
return nil, err
}
return d.image, nil
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 cbG8:
cm = image.GrayColorModel
case cbTC8:
cm = image.RGBAColorModel
case cbP8:
cm = d.palette
case cbTCA8:
cm = image.NRGBAColorModel
case cbG16:
cm = image.Gray16ColorModel
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)
image.RegisterFormat("png", pngHeader, Decode, DecodeConfig)
}