1
0
mirror of https://github.com/golang/go synced 2024-11-22 02:44:39 -07:00

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

@ -17,6 +17,7 @@ var UnknownFormatErr = os.NewError("image: unknown format")
type format struct { type format struct {
name, magic string name, magic string
decode func(io.Reader) (Image, os.Error) decode func(io.Reader) (Image, os.Error)
decodeConfig func(io.Reader) (Config, os.Error)
} }
// Formats is the list of registered formats. // 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". // Name is the name of the format, like "jpeg" or "png".
// Magic is the magic prefix that identifies the format's encoding. // Magic is the magic prefix that identifies the format's encoding.
// Decode is the function that decodes the encoded image. // 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) n := len(formats)
if n == cap(formats) { if n == cap(formats) {
x := make([]format, n+1, 2*n+4) 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 { } else {
formats = formats[0 : n+1] 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. // 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) return bufio.NewReader(r)
} }
// 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. // 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- // Format registration is typically done by the init method of the codec-
// specific package. // specific package.
func Decode(r io.Reader) (m Image, formatName string, err os.Error) { func Decode(r io.Reader) (Image, string, os.Error) {
var f format
rr := asReader(r) rr := asReader(r)
for _, g := range formats { f := sniff(rr)
s, err := rr.Peek(len(g.magic))
if err == nil && string(s) == g.magic {
f = g
break
}
}
if f.decode == nil { if f.decode == nil {
return nil, "", UnknownFormatErr return nil, "", UnknownFormatErr
} }
m, err = f.decode(rr) m, err := f.decode(rr)
return m, f.name, err 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. // The image package implements a basic 2-D image library.
package image 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. // An Image is a finite rectangular grid of Colors drawn from a ColorModel.
type Image interface { type Image interface {
// ColorModel returns the Image's ColorModel. // 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 return nil
} }
@ -240,7 +239,7 @@ func (d *decoder) convertMCU(mx, my, h0, v0 int) {
// Specified in section B.2.3. // Specified in section B.2.3.
func (d *decoder) processSOS(n int) os.Error { func (d *decoder) processSOS(n int) os.Error {
if d.image == nil { if d.image == nil {
return FormatError("missing SOF segment") d.image = image.NewRGBA(d.width, d.height)
} }
if n != 4+2*nComponent { if n != 4+2*nComponent {
return UnsupportedError("SOS has wrong length") return UnsupportedError("SOS has wrong length")
@ -365,9 +364,8 @@ func (d *decoder) processDRI(n int) os.Error {
return nil return nil
} }
// Decode reads a JPEG formatted image from r and returns it as an image.Image. // decode reads a JPEG image from r and returns it as an image.Image.
func Decode(r io.Reader) (image.Image, os.Error) { func (d *decoder) decode(r io.Reader, configOnly bool) (image.Image, os.Error) {
var d decoder
if rr, ok := r.(Reader); ok { if rr, ok := r.(Reader); ok {
d.r = rr d.r = rr
} else { } else {
@ -411,6 +409,9 @@ func Decode(r io.Reader) (image.Image, os.Error) {
switch { switch {
case marker == sof0Marker: // Start Of Frame (Baseline). case marker == sof0Marker: // Start Of Frame (Baseline).
err = d.processSOF(n) err = d.processSOF(n)
if configOnly {
return nil, err
}
case marker == sof2Marker: // Start Of Frame (Progressive). case marker == sof2Marker: // Start Of Frame (Progressive).
err = UnsupportedError("progressive mode") err = UnsupportedError("progressive mode")
case marker == dhtMarker: // Define Huffman Table. case marker == dhtMarker: // Define Huffman Table.
@ -433,6 +434,22 @@ func Decode(r io.Reader) (image.Image, os.Error) {
return d.image, nil return d.image, nil
} }
func init() { // Decode reads a JPEG image from r and returns it as an image.Image.
image.RegisterFormat("jpeg", "\xff\xd8", Decode) 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. // A cb is a combination of color type and bit depth.
const ( const (
cbG8 = iota cbInvalid = iota
cbG8
cbTC8 cbTC8
cbP8 cbP8
cbTCA8 cbTCA8
@ -62,20 +63,25 @@ const (
const pngHeader = "\x89PNG\r\n\x1a\n" const pngHeader = "\x89PNG\r\n\x1a\n"
type imgOrErr struct {
img image.Image
err os.Error
}
type decoder struct { type decoder struct {
width, height int width, height int
image image.Image palette image.PalettedColorModel
cb int cb int
stage int stage int
idatWriter io.WriteCloser idatWriter io.WriteCloser
idatDone chan os.Error idatDone chan imgOrErr
tmp [3 * 256]byte tmp [3 * 256]byte
} }
// A FormatError reports that the input is not a valid PNG. // A FormatError reports that the input is not a valid PNG.
type FormatError string 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") var chunkOrderError = FormatError("chunk out of order")
@ -84,12 +90,12 @@ type IDATDecodingError struct {
Err os.Error 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. // An UnsupportedError reports that the input uses a valid but unimplemented PNG feature.
type UnsupportedError string 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. // Big-endian.
func parseUint32(b []uint8) uint32 { 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)) { if nPixels != int64(int(nPixels)) {
return UnsupportedError("dimension overflow") return UnsupportedError("dimension overflow")
} }
d.cb = cbInvalid
switch d.tmp[8] { switch d.tmp[8] {
case 8: case 8:
switch d.tmp[9] { switch d.tmp[9] {
case ctGrayscale: case ctGrayscale:
d.image = image.NewGray(int(w), int(h))
d.cb = cbG8 d.cb = cbG8
case ctTrueColor: case ctTrueColor:
d.image = image.NewRGBA(int(w), int(h))
d.cb = cbTC8 d.cb = cbTC8
case ctPaletted: case ctPaletted:
d.image = image.NewPaletted(int(w), int(h), nil)
d.cb = cbP8 d.cb = cbP8
case ctTrueColorAlpha: case ctTrueColorAlpha:
d.image = image.NewNRGBA(int(w), int(h))
d.cb = cbTCA8 d.cb = cbTCA8
} }
case 16: case 16:
switch d.tmp[9] { switch d.tmp[9] {
case ctGrayscale: case ctGrayscale:
d.image = image.NewGray16(int(w), int(h))
d.cb = cbG16 d.cb = cbG16
case ctTrueColor: case ctTrueColor:
d.image = image.NewRGBA64(int(w), int(h))
d.cb = cbTC16 d.cb = cbTC16
case ctTrueColorAlpha: case ctTrueColorAlpha:
d.image = image.NewNRGBA64(int(w), int(h))
d.cb = cbTCA16 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])) return UnsupportedError(fmt.Sprintf("bit depth %d, color type %d", d.tmp[8], d.tmp[9]))
} }
d.width, d.height = int(w), int(h) 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]) crc.Write(d.tmp[0:n])
switch d.cb { switch d.cb {
case cbP8: case cbP8:
palette := make([]image.Color, np) d.palette = image.PalettedColorModel(make([]image.Color, np))
for i := 0; i < np; i++ { 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: case cbTC8, cbTCA8, cbTC16, cbTCA16:
// As per the PNG spec, a PLTE chunk is optional (and for practical purposes, // 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). // 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: case cbTC8, cbTC16:
return UnsupportedError("truecolor transparency") return UnsupportedError("truecolor transparency")
case cbP8: case cbP8:
p := d.image.(*image.Paletted).Palette if n > len(d.palette) {
if n > len(p) {
return FormatError("bad tRNS length") return FormatError("bad tRNS length")
} }
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
rgba := p[i].(image.RGBAColor) rgba := d.palette[i].(image.RGBAColor)
p[i] = image.RGBAColor{rgba.R, rgba.G, rgba.B, d.tmp[i]} d.palette[i] = image.RGBAColor{rgba.R, rgba.G, rgba.B, d.tmp[i]}
} }
case cbTCA8, cbTCA16: case cbTCA8, cbTCA16:
return FormatError("tRNS, color type mismatch") return FormatError("tRNS, color type mismatch")
@ -236,10 +234,10 @@ func paeth(a, b, c uint8) uint8 {
return c 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) r, err := zlib.NewReader(idat)
if err != nil { if err != nil {
return err return nil, err
} }
defer r.Close() defer r.Close()
bpp := 0 // Bytes per pixel. bpp := 0 // Bytes per pixel.
@ -252,30 +250,38 @@ func (d *decoder) idatReader(idat io.Reader) os.Error {
gray16 *image.Gray16 gray16 *image.Gray16
rgba64 *image.RGBA64 rgba64 *image.RGBA64
nrgba64 *image.NRGBA64 nrgba64 *image.NRGBA64
img image.Image
) )
switch d.cb { switch d.cb {
case cbG8: case cbG8:
bpp = 1 bpp = 1
gray = d.image.(*image.Gray) gray = image.NewGray(d.width, d.height)
img = gray
case cbTC8: case cbTC8:
bpp = 3 bpp = 3
rgba = d.image.(*image.RGBA) rgba = image.NewRGBA(d.width, d.height)
img = rgba
case cbP8: case cbP8:
bpp = 1 bpp = 1
paletted = d.image.(*image.Paletted) paletted = image.NewPaletted(d.width, d.height, d.palette)
maxPalette = uint8(len(paletted.Palette) - 1) img = paletted
maxPalette = uint8(len(d.palette) - 1)
case cbTCA8: case cbTCA8:
bpp = 4 bpp = 4
nrgba = d.image.(*image.NRGBA) nrgba = image.NewNRGBA(d.width, d.height)
img = nrgba
case cbG16: case cbG16:
bpp = 2 bpp = 2
gray16 = d.image.(*image.Gray16) gray16 = image.NewGray16(d.width, d.height)
img = gray16
case cbTC16: case cbTC16:
bpp = 6 bpp = 6
rgba64 = d.image.(*image.RGBA64) rgba64 = image.NewRGBA64(d.width, d.height)
img = rgba64
case cbTCA16: case cbTCA16:
bpp = 8 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. // 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]. // 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. // Read the decompressed bytes.
_, err := io.ReadFull(r, cr) _, err := io.ReadFull(r, cr)
if err != nil { if err != nil {
return err return nil, err
} }
// Apply the filter. // 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]) cdat[i] += paeth(cdat[i-bpp], pdat[i], pdat[i-bpp])
} }
default: default:
return FormatError("bad filter type") return nil, FormatError("bad filter type")
} }
// Convert from bytes to colors. // Convert from bytes to colors.
@ -334,7 +340,7 @@ func (d *decoder) idatReader(idat io.Reader) os.Error {
case cbP8: case cbP8:
for x := 0; x < d.width; x++ { for x := 0; x < d.width; x++ {
if cdat[x] > maxPalette { 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]) 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. // The current row for y is the previous row for y+1.
pr, cr = cr, pr pr, cr = cr, pr
} }
return nil return img, nil
} }
func (d *decoder) parseIDAT(r io.Reader, crc hash.Hash32, length uint32) os.Error { 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 { if d.idatWriter == nil {
pr, pw := io.Pipe() pr, pw := io.Pipe()
d.idatWriter = pw d.idatWriter = pw
d.idatDone = make(chan os.Error) d.idatDone = make(chan imgOrErr)
go func() { go func() {
err := d.idatReader(pr) img, err := d.idatReader(pr)
if err == os.EOF { if err == os.EOF {
err = FormatError("too little IDAT") err = FormatError("too little IDAT")
} }
pr.CloseWithError(FormatError("too much IDAT")) pr.CloseWithError(FormatError("too much IDAT"))
d.idatDone <- err d.idatDone <- imgOrErr{img, err}
}() }()
} }
var buf [4096]byte var buf [4096]byte
@ -509,7 +515,7 @@ func (d *decoder) checkHeader(r io.Reader) os.Error {
return nil 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. // The type of Image returned depends on the PNG contents.
func Decode(r io.Reader) (image.Image, os.Error) { func Decode(r io.Reader) (image.Image, os.Error) {
var d decoder var d decoder
@ -517,25 +523,66 @@ func Decode(r io.Reader) (image.Image, os.Error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
for d.stage = dsStart; d.stage != dsSeenIEND; { for d.stage != dsSeenIEND {
err = d.parseChunk(r) err = d.parseChunk(r)
if err != nil { if err != nil {
break break
} }
} }
var img image.Image
if d.idatWriter != nil { if d.idatWriter != nil {
d.idatWriter.Close() d.idatWriter.Close()
err1 := <-d.idatDone ie := <-d.idatDone
if err == nil { if err == nil {
err = err1 img, err = ie.img, ie.err
} }
} }
if err != nil { if err != nil {
return nil, err 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() { func init() {
image.RegisterFormat("png", pngHeader, Decode) image.RegisterFormat("png", pngHeader, Decode, DecodeConfig)
} }