import (
"compress/zlib"
+ "fmt"
"hash"
"hash/crc32"
"image"
ctTrueColorAlpha = 6
)
+// A cb is a combination of color type and bit depth.
+const (
+ cbG8 = iota
+ cbTC8
+ cbP8
+ cbTCA8
+ cbG16
+ cbTC16
+ cbTCA16
+)
+
// Filter type, as per the PNG spec.
const (
ftNone = 0
type decoder struct {
width, height int
image image.Image
- colorType uint8
+ cb int
stage int
idatWriter io.WriteCloser
idatDone chan os.Error
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")
}
if nPixels != int64(int(nPixels)) {
return UnsupportedError("dimension overflow")
}
- d.colorType = d.tmp[9]
- switch d.colorType {
- case ctGrayscale:
- d.image = image.NewGray(int(w), int(h))
- 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")
+ 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 {
+ 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
return err
}
crc.Write(d.tmp[0:n])
- switch d.colorType {
- case ctPaletted:
+ switch d.cb {
+ case cbP8:
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 ctGrayscale, ctTrueColor, ctTrueColorAlpha:
+ 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).
- return nil
default:
return FormatError("PLTE, color type mismatch")
}
return err
}
crc.Write(d.tmp[0:n])
- switch d.colorType {
- case ctGrayscale:
+ switch d.cb {
+ case cbG8, cbG16:
return UnsupportedError("grayscale transparency")
- case ctTrueColor:
+ case cbTC8, cbTC16:
return UnsupportedError("truecolor transparency")
- case ctPaletted:
+ case cbP8:
p := d.image.(*image.Paletted).Palette
if n > len(p) {
return FormatError("bad tRNS length")
rgba := p[i].(image.RGBAColor)
p[i] = image.RGBAColor{rgba.R, rgba.G, rgba.B, d.tmp[i]}
}
- case ctTrueColorAlpha:
+ case cbTCA8, cbTCA16:
return FormatError("tRNS, color type mismatch")
}
return nil
rgba *image.RGBA
paletted *image.Paletted
nrgba *image.NRGBA
+ gray16 *image.Gray16
+ rgba64 *image.RGBA64
+ nrgba64 *image.NRGBA64
)
- switch d.colorType {
- case ctGrayscale:
+ switch d.cb {
+ case cbG8:
bpp = 1
gray = d.image.(*image.Gray)
- case ctTrueColor:
+ case cbTC8:
bpp = 3
rgba = d.image.(*image.RGBA)
- case ctPaletted:
+ case cbP8:
bpp = 1
paletted = d.image.(*image.Paletted)
maxPalette = uint8(len(paletted.Palette) - 1)
- case ctTrueColorAlpha:
+ case cbTCA8:
bpp = 4
nrgba = d.image.(*image.NRGBA)
+ case cbG16:
+ bpp = 2
+ gray16 = d.image.(*image.Gray16)
+ case cbTC16:
+ bpp = 6
+ rgba64 = d.image.(*image.RGBA64)
+ case cbTCA16:
+ bpp = 8
+ nrgba64 = d.image.(*image.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].
}
// Convert from bytes to colors.
- switch d.colorType {
- case ctGrayscale:
+ switch d.cb {
+ case cbG8:
for x := 0; x < d.width; x++ {
gray.Set(x, y, image.GrayColor{cdat[x]})
}
- case ctTrueColor:
+ 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 ctPaletted:
+ case cbP8:
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:
+ 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 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.
}
err = d.parsetRNS(r, crc, length)
case "IDAT":
- if d.stage < dsSeenIHDR || d.stage > dsSeenIDAT || (d.colorType == ctPaletted && d.stage == dsSeenIHDR) {
+ if d.stage < dsSeenIHDR || d.stage > dsSeenIDAT || (d.cb == cbP8 && d.stage == dsSeenIHDR) {
return chunkOrderError
}
d.stage = dsSeenIDAT
)
// The go PNG library currently supports only a subset of the full PNG specification.
-// In particular, bit depths other than 8 are not supported, and neither are grayscale images.
+// In particular, bit depths other than 8 or 16 are not supported, nor are grayscale-
+// alpha images.
var filenames = []string{
- //"basn0g01", // bit depth is not 8
- //"basn0g02", // bit depth is not 8
- //"basn0g04", // bit depth is not 8
+ //"basn0g01", // bit depth is not 8 or 16
+ //"basn0g02", // bit depth is not 8 or 16
+ //"basn0g04", // bit depth is not 8 or 16
"basn0g08",
- //"basn0g16", // bit depth is not 8
+ "basn0g16",
"basn2c08",
- //"basn2c16", // bit depth is not 8
- //"basn3p01", // bit depth is not 8
- //"basn3p02", // bit depth is not 8
- //"basn3p04", // bit depth is not 8
+ "basn2c16",
+ //"basn3p01", // bit depth is not 8 or 16
+ //"basn3p02", // bit depth is not 8 or 16
+ //"basn3p04", // bit depth is not 8 or 16
"basn3p08",
- //"basn4a08", // grayscale color model
- //"basn4a16", // bit depth is not 8
+ //"basn4a08", // grayscale-alpha color model
+ //"basn4a16", // grayscale-alpha color model
"basn6a08",
- //"basn6a16", // bit depth is not 8
+ "basn6a16",
}
func readPng(filename string) (image.Image, os.Error) {
func sng(w io.WriteCloser, filename string, png image.Image) {
defer w.Close()
bounds := png.Bounds()
- // For now, the go PNG parser only reads bitdepths of 8.
- bitdepth := 8
+ cm := png.ColorModel()
+ var bitdepth int
+ switch cm {
+ case image.RGBAColorModel, image.NRGBAColorModel, image.AlphaColorModel, image.GrayColorModel:
+ bitdepth = 8
+ default:
+ bitdepth = 16
+ }
+ cpm, _ := cm.(image.PalettedColorModel)
+ var paletted *image.Paletted
+ if cpm != nil {
+ bitdepth = 8
+ paletted = png.(*image.Paletted)
+ }
// Write the filename and IHDR.
io.WriteString(w, "#SNG: from "+filename+".png\nIHDR {\n")
fmt.Fprintf(w, " width: %d; height: %d; bitdepth: %d;\n", bounds.Dx(), bounds.Dy(), bitdepth)
- cm := png.ColorModel()
- var paletted *image.Paletted
- cpm, _ := cm.(image.PalettedColorModel)
switch {
- case cm == image.GrayColorModel:
- io.WriteString(w, " using grayscale;\n")
- case cm == image.RGBAColorModel:
+ case cm == image.RGBAColorModel, cm == image.RGBA64ColorModel:
io.WriteString(w, " using color;\n")
- case cm == image.NRGBAColorModel:
+ case cm == image.NRGBAColorModel, cm == image.NRGBA64ColorModel:
io.WriteString(w, " using color alpha;\n")
+ case cm == image.GrayColorModel, cm == image.Gray16ColorModel:
+ io.WriteString(w, " using grayscale;\n")
case cpm != nil:
io.WriteString(w, " using color palette;\n")
- paletted = png.(*image.Paletted)
default:
io.WriteString(w, "unknown PNG decoder color model\n")
}
gray := png.At(x, y).(image.GrayColor)
fmt.Fprintf(w, "%02x", gray.Y)
}
+ case cm == image.Gray16ColorModel:
+ for x := bounds.Min.X; x < bounds.Max.X; x++ {
+ gray16 := png.At(x, y).(image.Gray16Color)
+ fmt.Fprintf(w, "%04x ", gray16.Y)
+ }
case cm == image.RGBAColorModel:
for x := bounds.Min.X; x < bounds.Max.X; x++ {
rgba := png.At(x, y).(image.RGBAColor)
fmt.Fprintf(w, "%02x%02x%02x ", rgba.R, rgba.G, rgba.B)
}
+ case cm == image.RGBA64ColorModel:
+ for x := bounds.Min.X; x < bounds.Max.X; x++ {
+ rgba64 := png.At(x, y).(image.RGBA64Color)
+ fmt.Fprintf(w, "%04x%04x%04x ", rgba64.R, rgba64.G, rgba64.B)
+ }
case cm == image.NRGBAColorModel:
for x := bounds.Min.X; x < bounds.Max.X; x++ {
nrgba := png.At(x, y).(image.NRGBAColor)
fmt.Fprintf(w, "%02x%02x%02x%02x ", nrgba.R, nrgba.G, nrgba.B, nrgba.A)
}
+ case cm == image.NRGBA64ColorModel:
+ for x := bounds.Min.X; x < bounds.Max.X; x++ {
+ nrgba64 := png.At(x, y).(image.NRGBA64Color)
+ fmt.Fprintf(w, "%04x%04x%04x%04x ", nrgba64.R, nrgba64.G, nrgba64.B, nrgba64.A)
+ }
case cpm != nil:
for x := bounds.Min.X; x < bounds.Max.X; x++ {
fmt.Fprintf(w, "%02x", paletted.ColorIndexAt(x, y))
)
type encoder struct {
- w io.Writer
- m image.Image
- colorType uint8
- err os.Error
- header [8]byte
- footer [4]byte
- tmp [3 * 256]byte
+ w io.Writer
+ m image.Image
+ cb int
+ err os.Error
+ header [8]byte
+ footer [4]byte
+ tmp [3 * 256]byte
}
// Big-endian.
b := e.m.Bounds()
writeUint32(e.tmp[0:4], uint32(b.Dx()))
writeUint32(e.tmp[4:8], uint32(b.Dy()))
- e.tmp[8] = 8 // bit depth
- e.tmp[9] = e.colorType
+ // Set bit depth and color type.
+ switch e.cb {
+ case cbG8:
+ e.tmp[8] = 8
+ e.tmp[9] = ctGrayscale
+ case cbTC8:
+ e.tmp[8] = 8
+ e.tmp[9] = ctTrueColor
+ case cbP8:
+ e.tmp[8] = 8
+ e.tmp[9] = ctPaletted
+ case cbTCA8:
+ e.tmp[8] = 8
+ e.tmp[9] = ctTrueColorAlpha
+ case cbG16:
+ e.tmp[8] = 16
+ e.tmp[9] = ctGrayscale
+ case cbTC16:
+ e.tmp[8] = 16
+ e.tmp[9] = ctTrueColor
+ case cbTCA16:
+ e.tmp[8] = 16
+ e.tmp[9] = ctTrueColorAlpha
+ }
e.tmp[10] = 0 // default compression method
e.tmp[11] = 0 // default filter method
e.tmp[12] = 0 // non-interlaced
return filter
}
-func writeImage(w io.Writer, m image.Image, ct uint8) os.Error {
+func writeImage(w io.Writer, m image.Image, cb int) os.Error {
zw, err := zlib.NewWriter(w)
if err != nil {
return err
bpp := 0 // Bytes per pixel.
var paletted *image.Paletted
- switch ct {
- case ctGrayscale:
+ switch cb {
+ case cbG8:
bpp = 1
- case ctTrueColor:
+ case cbTC8:
bpp = 3
- case ctPaletted:
+ case cbP8:
bpp = 1
paletted = m.(*image.Paletted)
- case ctTrueColorAlpha:
+ case cbTCA8:
bpp = 4
+ case cbTC16:
+ bpp = 6
+ case cbTCA16:
+ bpp = 8
+ case cbG16:
+ bpp = 2
}
// cr[*] and pr are the bytes for the current and previous row.
// cr[0] is unfiltered (or equivalently, filtered with the ftNone filter).
for y := b.Min.Y; y < b.Max.Y; y++ {
// Convert from colors to bytes.
- switch ct {
- case ctGrayscale:
+ switch cb {
+ case cbG8:
for x := b.Min.X; x < b.Max.X; x++ {
c := image.GrayColorModel.Convert(m.At(x, y)).(image.GrayColor)
cr[0][x+1] = c.Y
}
- case ctTrueColor:
+ case cbTC8:
for x := b.Min.X; x < b.Max.X; x++ {
// We have previously verified that the alpha value is fully opaque.
r, g, b, _ := m.At(x, y).RGBA()
cr[0][3*x+2] = uint8(g >> 8)
cr[0][3*x+3] = uint8(b >> 8)
}
- case ctPaletted:
+ case cbP8:
for x := b.Min.X; x < b.Max.X; x++ {
cr[0][x+1] = paletted.ColorIndexAt(x, y)
}
- case ctTrueColorAlpha:
+ case cbTCA8:
// Convert from image.Image (which is alpha-premultiplied) to PNG's non-alpha-premultiplied.
for x := b.Min.X; x < b.Max.X; x++ {
c := image.NRGBAColorModel.Convert(m.At(x, y)).(image.NRGBAColor)
cr[0][4*x+3] = c.B
cr[0][4*x+4] = c.A
}
+ case cbG16:
+ for x := b.Min.X; x < b.Max.X; x++ {
+ c := image.Gray16ColorModel.Convert(m.At(x, y)).(image.Gray16Color)
+ cr[0][2*x+1] = uint8(c.Y >> 8)
+ cr[0][2*x+2] = uint8(c.Y)
+ }
+ case cbTC16:
+ for x := b.Min.X; x < b.Max.X; x++ {
+ // We have previously verified that the alpha value is fully opaque.
+ r, g, b, _ := m.At(x, y).RGBA()
+ cr[0][6*x+1] = uint8(r >> 8)
+ cr[0][6*x+2] = uint8(r)
+ cr[0][6*x+3] = uint8(g >> 8)
+ cr[0][6*x+4] = uint8(g)
+ cr[0][6*x+5] = uint8(b >> 8)
+ cr[0][6*x+6] = uint8(b)
+ }
+ case cbTCA16:
+ // Convert from image.Image (which is alpha-premultiplied) to PNG's non-alpha-premultiplied.
+ for x := b.Min.X; x < b.Max.X; x++ {
+ c := image.NRGBA64ColorModel.Convert(m.At(x, y)).(image.NRGBA64Color)
+ cr[0][8*x+1] = uint8(c.R >> 8)
+ cr[0][8*x+2] = uint8(c.R)
+ cr[0][8*x+3] = uint8(c.G >> 8)
+ cr[0][8*x+4] = uint8(c.G)
+ cr[0][8*x+5] = uint8(c.B >> 8)
+ cr[0][8*x+6] = uint8(c.B)
+ cr[0][8*x+7] = uint8(c.A >> 8)
+ cr[0][8*x+8] = uint8(c.A)
+ }
}
// Apply the filter.
if e.err != nil {
return
}
- e.err = writeImage(bw, e.m, e.colorType)
+ e.err = writeImage(bw, e.m, e.cb)
if e.err != nil {
return
}
var e encoder
e.w = w
e.m = m
- e.colorType = ctTrueColorAlpha
pal, _ := m.(*image.Paletted)
if pal != nil {
- e.colorType = ctPaletted
+ e.cb = cbP8
} else {
switch m.ColorModel() {
case image.GrayColorModel:
- e.colorType = ctGrayscale
+ e.cb = cbG8
+ case image.Gray16ColorModel:
+ e.cb = cbG16
+ case image.RGBAColorModel, image.NRGBAColorModel, image.AlphaColorModel:
+ if opaque(m) {
+ e.cb = cbTC8
+ } else {
+ e.cb = cbTCA8
+ }
default:
if opaque(m) {
- e.colorType = ctTrueColor
+ e.cb = cbTC16
+ } else {
+ e.cb = cbTCA16
}
}
}