// 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.
// 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)
} 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.
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.
+// 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) (m Image, formatName string, err os.Error) {
- var f format
+func Decode(r io.Reader) (Image, string, os.Error) {
rr := asReader(r)
- for _, g := range formats {
- s, err := rr.Peek(len(g.magic))
- if err == nil && string(s) == g.magic {
- f = g
- break
- }
- }
+ 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
+}
}
}
}
- d.image = image.NewRGBA(d.width, d.height)
return nil
}
// 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")
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 {
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.
return d.image, nil
}
+// 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)
+ image.RegisterFormat("jpeg", "\xff\xd8", Decode, DecodeConfig)
}
// A cb is a combination of color type and bit depth.
const (
- cbG8 = iota
+ cbInvalid = iota
+ cbG8
cbTC8
cbP8
cbTCA8
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")
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 {
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)
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).
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")
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.
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].
// Read the decompressed bytes.
_, err := io.ReadFull(r, cr)
if err != nil {
- return err
+ return nil, err
}
// Apply the filter.
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.
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])
}
// 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 {
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
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
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)
}