go.image/tiff: support 16bit RGB

R=nigeltao, bsiegert
CC=golang-dev
https://golang.org/cl/13736044
This commit is contained in:
ChaiShushan 2013-09-18 17:16:05 +10:00 committed by Nigel Tao
parent e39b2394e5
commit b2d744f611
4 changed files with 179 additions and 38 deletions

View File

@ -198,7 +198,21 @@ func (d *decoder) decode(dst image.Image, xmin, ymin, xmax, ymax int) error {
// Apply horizontal predictor if necessary.
// In this case, p contains the color difference to the preceding pixel.
// See page 64-65 of the spec.
if d.firstVal(tPredictor) == prHorizontal && d.bpp == 8 {
if d.firstVal(tPredictor) == prHorizontal {
if d.bpp == 16 {
var off int
spp := len(d.features[tBitsPerSample]) // samples per pixel
bpp := spp * 2 // bytes per pixel
for y := ymin; y < ymax; y++ {
off += spp * 2
for x := 0; x < (xmax-xmin-1)*bpp; x += 2 {
v0 := d.byteOrder.Uint16(d.buf[off-bpp : off-bpp+2])
v1 := d.byteOrder.Uint16(d.buf[off : off+2])
d.byteOrder.PutUint16(d.buf[off:off+2], v1+v0)
off += 2
}
}
} else if d.bpp == 8 {
var off int
spp := len(d.features[tBitsPerSample]) // samples per pixel
for y := ymin; y < ymax; y++ {
@ -209,6 +223,7 @@ func (d *decoder) decode(dst image.Image, xmin, ymin, xmax, ymax int) error {
}
}
}
}
rMaxX := minInt(xmax, dst.Bounds().Max.X)
rMaxY := minInt(ymax, dst.Bounds().Max.Y)
@ -249,6 +264,18 @@ func (d *decoder) decode(dst image.Image, xmin, ymin, xmax, ymax int) error {
d.flushBits()
}
case mRGB:
if d.bpp == 16 {
img := dst.(*image.RGBA64)
for y := ymin; y < rMaxY; y++ {
for x := xmin; x < rMaxX; x++ {
r := d.byteOrder.Uint16(d.buf[d.off+0 : d.off+2])
g := d.byteOrder.Uint16(d.buf[d.off+2 : d.off+4])
b := d.byteOrder.Uint16(d.buf[d.off+4 : d.off+6])
d.off += 6
img.SetRGBA64(x, y, color.RGBA64{r, g, b, 0xffff})
}
}
} else {
img := dst.(*image.RGBA)
for y := ymin; y < rMaxY; y++ {
min := img.PixOffset(xmin, y)
@ -262,7 +289,21 @@ func (d *decoder) decode(dst image.Image, xmin, ymin, xmax, ymax int) error {
off += 3
}
}
}
case mNRGBA:
if d.bpp == 16 {
img := dst.(*image.NRGBA64)
for y := ymin; y < rMaxY; y++ {
for x := xmin; x < rMaxX; x++ {
r := d.byteOrder.Uint16(d.buf[d.off+0 : d.off+2])
g := d.byteOrder.Uint16(d.buf[d.off+2 : d.off+4])
b := d.byteOrder.Uint16(d.buf[d.off+4 : d.off+6])
a := d.byteOrder.Uint16(d.buf[d.off+6 : d.off+8])
d.off += 8
img.SetNRGBA64(x, y, color.NRGBA64{r, g, b, a})
}
}
} else {
img := dst.(*image.NRGBA)
for y := ymin; y < rMaxY; y++ {
min := img.PixOffset(xmin, y)
@ -270,7 +311,21 @@ func (d *decoder) decode(dst image.Image, xmin, ymin, xmax, ymax int) error {
buf := d.buf[(y-ymin)*(xmax-xmin)*4 : (y-ymin+1)*(xmax-xmin)*4]
copy(img.Pix[min:max], buf)
}
}
case mRGBA:
if d.bpp == 16 {
img := dst.(*image.RGBA64)
for y := ymin; y < rMaxY; y++ {
for x := xmin; x < rMaxX; x++ {
r := d.byteOrder.Uint16(d.buf[d.off+0 : d.off+2])
g := d.byteOrder.Uint16(d.buf[d.off+2 : d.off+4])
b := d.byteOrder.Uint16(d.buf[d.off+4 : d.off+6])
a := d.byteOrder.Uint16(d.buf[d.off+6 : d.off+8])
d.off += 8
img.SetRGBA64(x, y, color.RGBA64{r, g, b, a})
}
}
} else {
img := dst.(*image.RGBA)
for y := ymin; y < rMaxY; y++ {
min := img.PixOffset(xmin, y)
@ -279,6 +334,7 @@ func (d *decoder) decode(dst image.Image, xmin, ymin, xmax, ymax int) error {
copy(img.Pix[min:max], buf)
}
}
}
return nil
}
@ -333,12 +389,19 @@ func newDecoder(r io.Reader) (*decoder, error) {
// Determine the image mode.
switch d.firstVal(tPhotometricInterpretation) {
case pRGB:
if d.bpp == 16 {
for _, b := range d.features[tBitsPerSample] {
if b != 16 {
return nil, FormatError("wrong number of samples for 16bit RGB")
}
}
} else {
for _, b := range d.features[tBitsPerSample] {
if b != 8 {
return nil, UnsupportedError("non-8-bit RGB image")
return nil, FormatError("wrong number of samples for 8bit RGB")
}
}
}
d.config.ColorModel = color.RGBAModel
// RGB images normally have 3 samples per pixel.
// If there are more, ExtraSamples (p. 31-32 of the spec)
// gives their meaning (usually an alpha channel).
@ -348,13 +411,27 @@ func newDecoder(r io.Reader) (*decoder, error) {
switch len(d.features[tBitsPerSample]) {
case 3:
d.mode = mRGB
if d.bpp == 16 {
d.config.ColorModel = color.RGBA64Model
} else {
d.config.ColorModel = color.RGBAModel
}
case 4:
switch d.firstVal(tExtraSamples) {
case 1:
d.mode = mRGBA
if d.bpp == 16 {
d.config.ColorModel = color.RGBA64Model
} else {
d.config.ColorModel = color.RGBAModel
}
case 2:
d.mode = mNRGBA
if d.bpp == 16 {
d.config.ColorModel = color.NRGBA64Model
} else {
d.config.ColorModel = color.NRGBAModel
}
default:
return nil, FormatError("wrong number of samples for RGB")
}
@ -450,10 +527,18 @@ func Decode(r io.Reader) (img image.Image, err error) {
case mPaletted:
img = image.NewPaletted(imgRect, d.palette)
case mNRGBA:
if d.bpp == 16 {
img = image.NewNRGBA64(imgRect)
} else {
img = image.NewNRGBA(imgRect)
}
case mRGB, mRGBA:
if d.bpp == 16 {
img = image.NewRGBA64(imgRect)
} else {
img = image.NewRGBA(imgRect)
}
}
for i := 0; i < blocksAcross; i++ {
blkW := blockWidth

View File

@ -105,10 +105,15 @@ func TestDecode(t *testing.T) {
if err != nil {
t.Fatal(err)
}
img4, err := load("video-001-16bit.tiff")
if err != nil {
t.Fatal(err)
}
compare(t, img0, img1)
compare(t, img0, img2)
compare(t, img0, img3)
compare(t, img0, img4)
}
// TestDecompress tests that decoding some TIFF images that use different

View File

@ -129,6 +129,43 @@ func encodeRGBA(w io.Writer, pix []uint8, dx, dy, stride int, predictor bool) er
return nil
}
func encodeRGBA64(w io.Writer, pix []uint8, dx, dy, stride int, predictor bool) error {
buf := make([]byte, dx*8)
for y := 0; y < dy; y++ {
min := y*stride + 0
max := y*stride + dx*8
off := 0
var r0, g0, b0, a0 uint16
for i := min; i < max; i += 8 {
// An image.RGBA64's Pix is in big-endian order.
r1 := uint16(pix[i+0])<<8 | uint16(pix[i+1])
g1 := uint16(pix[i+2])<<8 | uint16(pix[i+3])
b1 := uint16(pix[i+4])<<8 | uint16(pix[i+5])
a1 := uint16(pix[i+6])<<8 | uint16(pix[i+7])
if predictor {
r0, r1 = r1, r1-r0
g0, g1 = g1, g1-g0
b0, b1 = b1, b1-b0
a0, a1 = a1, a1-a0
}
// We only write little-endian TIFF files.
buf[off+0] = byte(r1)
buf[off+1] = byte(r1 >> 8)
buf[off+2] = byte(g1)
buf[off+3] = byte(g1 >> 8)
buf[off+4] = byte(b1)
buf[off+5] = byte(b1 >> 8)
buf[off+6] = byte(a1)
buf[off+7] = byte(a1 >> 8)
off += 8
}
if _, err := w.Write(buf); err != nil {
return err
}
}
return nil
}
func encode(w io.Writer, m image.Image, predictor bool) error {
bounds := m.Bounds()
buf := make([]byte, 4*bounds.Dx())
@ -287,6 +324,10 @@ func Encode(w io.Writer, m image.Image, opt *Options) error {
imageLen = d.X * d.Y * 1
case *image.Gray16:
imageLen = d.X * d.Y * 2
case *image.RGBA64:
imageLen = d.X * d.Y * 8
case *image.NRGBA64:
imageLen = d.X * d.Y * 8
default:
imageLen = d.X * d.Y * 4
}
@ -334,8 +375,15 @@ func Encode(w io.Writer, m image.Image, opt *Options) error {
case *image.NRGBA:
extrasamples = 2 // Unassociated alpha.
err = encodeRGBA(dst, m.Pix, d.X, d.Y, m.Stride, predictor)
case *image.NRGBA64:
extrasamples = 2 // Unassociated alpha.
bitsPerSample = []uint32{16, 16, 16, 16}
err = encodeRGBA64(dst, m.Pix, d.X, d.Y, m.Stride, predictor)
case *image.RGBA:
err = encodeRGBA(dst, m.Pix, d.X, d.Y, m.Stride, predictor)
case *image.RGBA64:
bitsPerSample = []uint32{16, 16, 16, 16}
err = encodeRGBA64(dst, m.Pix, d.X, d.Y, m.Stride, predictor)
default:
err = encode(dst, m, predictor)
}

View File

@ -17,6 +17,7 @@ var roundtripTests = []struct {
opts *Options
}{
{"video-001.tiff", nil},
{"video-001-16bit.tiff", nil},
{"video-001-gray.tiff", nil},
{"video-001-gray-16bit.tiff", nil},
{"video-001-paletted.tiff", nil},
@ -90,3 +91,5 @@ func BenchmarkEncode(b *testing.B) { benchmarkEncode(b, "video-001.tiff"
func BenchmarkEncodePaletted(b *testing.B) { benchmarkEncode(b, "video-001-paletted.tiff", 1) }
func BenchmarkEncodeGray(b *testing.B) { benchmarkEncode(b, "video-001-gray.tiff", 1) }
func BenchmarkEncodeGray16(b *testing.B) { benchmarkEncode(b, "video-001-gray-16bit.tiff", 2) }
func BenchmarkEncodeRGBA(b *testing.B) { benchmarkEncode(b, "video-001.tiff", 4) }
func BenchmarkEncodeRGBA64(b *testing.B) { benchmarkEncode(b, "video-001-16bit.tiff", 8) }