git clone https://git.lucas.co/go_mono.git
go.image/tiff: support 16bit RGB
R=nigeltao, bsiegert
CC=golang-dev
https://golang.org/cl/13736044
tiff/reader.go | 161 +++++++++++++++++++++++++++++++++++++++-------------
tiff/reader_test.go | 5 ++
tiff/writer.go | 48 ++++++++++++++++
tiff/writer_test.go | 3 +
4 files changed, 179 insertions(+), 38 deletions(-)
diff --git a/tiff/reader.go b/tiff/reader.go
index bbfea3b..fcb2b56 100644
--- a/tiff/reader.go
+++ b/tiff/reader.go
@@ -198,14 +198,29 @@ 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 {
- var off int
- spp := len(d.features[tBitsPerSample]) // samples per pixel
- for y := ymin; y < ymax; y++ {
- off += spp
- for x := 0; x < (xmax-xmin-1)*spp; x++ {
- d.buf[off] += d.buf[off-spp]
- off++
+ 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++ {
+ off += spp
+ for x := 0; x < (xmax-xmin-1)*spp; x++ {
+ d.buf[off] += d.buf[off-spp]
+ off++
+ }
}
}
}
@@ -249,34 +264,75 @@ func (d *decoder) decode(dst image.Image, xmin, ymin, xmax, ymax int) error {
d.flushBits()
}
case mRGB:
- img := dst.(*image.RGBA)
- for y := ymin; y < rMaxY; y++ {
- min := img.PixOffset(xmin, y)
- max := img.PixOffset(rMaxX, y)
- off := (y - ymin) * (xmax - xmin) * 3
- for i := min; i < max; i += 4 {
- img.Pix[i+0] = d.buf[off+0]
- img.Pix[i+1] = d.buf[off+1]
- img.Pix[i+2] = d.buf[off+2]
- img.Pix[i+3] = 0xff
- off += 3
+ 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)
+ max := img.PixOffset(rMaxX, y)
+ off := (y - ymin) * (xmax - xmin) * 3
+ for i := min; i < max; i += 4 {
+ img.Pix[i+0] = d.buf[off+0]
+ img.Pix[i+1] = d.buf[off+1]
+ img.Pix[i+2] = d.buf[off+2]
+ img.Pix[i+3] = 0xff
+ off += 3
+ }
}
}
case mNRGBA:
- img := dst.(*image.NRGBA)
- for y := ymin; y < rMaxY; y++ {
- min := img.PixOffset(xmin, y)
- max := img.PixOffset(rMaxX, y)
- buf := d.buf[(y-ymin)*(xmax-xmin)*4 : (y-ymin+1)*(xmax-xmin)*4]
- copy(img.Pix[min:max], buf)
+ 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)
+ max := img.PixOffset(rMaxX, y)
+ buf := d.buf[(y-ymin)*(xmax-xmin)*4 : (y-ymin+1)*(xmax-xmin)*4]
+ copy(img.Pix[min:max], buf)
+ }
}
case mRGBA:
- img := dst.(*image.RGBA)
- for y := ymin; y < rMaxY; y++ {
- min := img.PixOffset(xmin, y)
- max := img.PixOffset(rMaxX, y)
- buf := d.buf[(y-ymin)*(xmax-xmin)*4 : (y-ymin+1)*(xmax-xmin)*4]
- copy(img.Pix[min:max], buf)
+ 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)
+ max := img.PixOffset(rMaxX, y)
+ buf := d.buf[(y-ymin)*(xmax-xmin)*4 : (y-ymin+1)*(xmax-xmin)*4]
+ copy(img.Pix[min:max], buf)
+ }
}
}
@@ -333,12 +389,19 @@ func newDecoder(r io.Reader) (*decoder, error) {
// Determine the image mode.
switch d.firstVal(tPhotometricInterpretation) {
case pRGB:
- for _, b := range d.features[tBitsPerSample] {
- if b != 8 {
- return nil, UnsupportedError("non-8-bit RGB image")
+ 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, 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
- d.config.ColorModel = color.NRGBAModel
+ 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,9 +527,17 @@ func Decode(r io.Reader) (img image.Image, err error) {
case mPaletted:
img = image.NewPaletted(imgRect, d.palette)
case mNRGBA:
- img = image.NewNRGBA(imgRect)
+ if d.bpp == 16 {
+ img = image.NewNRGBA64(imgRect)
+ } else {
+ img = image.NewNRGBA(imgRect)
+ }
case mRGB, mRGBA:
- img = image.NewRGBA(imgRect)
+ if d.bpp == 16 {
+ img = image.NewRGBA64(imgRect)
+ } else {
+ img = image.NewRGBA(imgRect)
+ }
}
for i := 0; i < blocksAcross; i++ {
diff --git a/tiff/reader_test.go b/tiff/reader_test.go
index 59637c5..8539e9c 100644
--- a/tiff/reader_test.go
+++ b/tiff/reader_test.go
@@ -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
diff --git a/tiff/writer.go b/tiff/writer.go
index 2cdd74e..531dbc4 100644
--- a/tiff/writer.go
+++ b/tiff/writer.go
@@ -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)
}
diff --git a/tiff/writer_test.go b/tiff/writer_test.go
index 644ac49..c8fb7bf 100644
--- a/tiff/writer_test.go
+++ b/tiff/writer_test.go
@@ -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) }