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) }