git.lucas.co / go_mono
git clone https://git.lucas.co/go_mono.git

commitb2d744f611613af0685e7875e23ef45bbe724565
parente39b2394e5
authorChaiShushan <[email protected]>
date2013-09-18 17:16
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) }