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

commitde306d5329a578923266b6e9bd4b97f014e78124
parent822abc7ef1
authorChaiShushan <[email protected]>
date2013-09-06 20:07
go.image/tiff: decoder support tiled tiff format

Use these commands to generate testdata:
tiffcp -s -r 64 video-001.tiff video-001-strip-64.tiff
tiffcp -t -l 64 -w 64 video-001.tiff video-001-tile-64x64.tiff

R=golang-dev, nigeltao, bsiegert
CC=golang-dev
https://golang.org/cl/13453043

 testdata/video-001-strip-64.tiff   | Bin 0 -> 30916 bytes
 testdata/video-001-tile-64x64.tiff | Bin 0 -> 56404 bytes
 tiff/consts.go                     |   5 +
 tiff/reader.go                     | 184 ++++++++++++++++++++++++-------------
 tiff/reader_test.go                |  48 +++++-----
 5 files changed, 147 insertions(+), 90 deletions(-)

diff --git a/testdata/video-001-strip-64.tiff b/testdata/video-001-strip-64.tiff
new file mode 100644
index 0000000..9cf6c32
Binary files /dev/null and b/testdata/video-001-strip-64.tiff differ
diff --git a/testdata/video-001-tile-64x64.tiff b/testdata/video-001-tile-64x64.tiff
new file mode 100644
index 0000000..fa56713
Binary files /dev/null and b/testdata/video-001-tile-64x64.tiff differ
diff --git a/tiff/consts.go b/tiff/consts.go
index 772e98a..3c51a70 100644
--- a/tiff/consts.go
+++ b/tiff/consts.go
@@ -47,6 +47,11 @@ const (
 	tRowsPerStrip    = 278
 	tStripByteCounts = 279
 
+	tTileWidth      = 322
+	tTileLength     = 323
+	tTileOffsets    = 324
+	tTileByteCounts = 325
+
 	tXResolution    = 282
 	tYResolution    = 283
 	tResolutionUnit = 296
diff --git a/tiff/reader.go b/tiff/reader.go
index bd25c3c..bbfea3b 100644
--- a/tiff/reader.go
+++ b/tiff/reader.go
@@ -114,6 +114,10 @@ func (d *decoder) parseIFD(p []byte) error {
 		tStripOffsets,
 		tStripByteCounts,
 		tRowsPerStrip,
+		tTileWidth,
+		tTileLength,
+		tTileOffsets,
+		tTileByteCounts,
 		tImageLength,
 		tImageWidth:
 		val, err := d.ifdUint(p)
@@ -178,9 +182,17 @@ func (d *decoder) flushBits() {
 	d.nbits = 0
 }
 
+// minInt returns the smaller of x or y.
+func minInt(a, b int) int {
+	if a <= b {
+		return a
+	}
+	return b
+}
+
 // decode decodes the raw data of an image.
-// It reads from d.buf and writes the strip with ymin <= y < ymax into dst.
-func (d *decoder) decode(dst image.Image, ymin, ymax int) error {
+// It reads from d.buf and writes the strip or tile into dst.
+func (d *decoder) decode(dst image.Image, xmin, ymin, xmax, ymax int) error {
 	d.off = 0
 
 	// Apply horizontal predictor if necessary.
@@ -191,19 +203,21 @@ func (d *decoder) decode(dst image.Image, ymin, ymax int) error {
 		spp := len(d.features[tBitsPerSample]) // samples per pixel
 		for y := ymin; y < ymax; y++ {
 			off += spp
-			for x := 0; x < (dst.Bounds().Dx()-1)*spp; x++ {
+			for x := 0; x < (xmax-xmin-1)*spp; x++ {
 				d.buf[off] += d.buf[off-spp]
 				off++
 			}
 		}
 	}
 
+	rMaxX := minInt(xmax, dst.Bounds().Max.X)
+	rMaxY := minInt(ymax, dst.Bounds().Max.Y)
 	switch d.mode {
 	case mGray, mGrayInvert:
 		if d.bpp == 16 {
 			img := dst.(*image.Gray16)
-			for y := ymin; y < ymax; y++ {
-				for x := img.Rect.Min.X; x < img.Rect.Max.X; x++ {
+			for y := ymin; y < rMaxY; y++ {
+				for x := xmin; x < rMaxX; x++ {
 					v := d.byteOrder.Uint16(d.buf[d.off : d.off+2])
 					d.off += 2
 					if d.mode == mGrayInvert {
@@ -215,8 +229,8 @@ func (d *decoder) decode(dst image.Image, ymin, ymax int) error {
 		} else {
 			img := dst.(*image.Gray)
 			max := uint32((1 << d.bpp) - 1)
-			for y := ymin; y < ymax; y++ {
-				for x := img.Rect.Min.X; x < img.Rect.Max.X; x++ {
+			for y := ymin; y < rMaxY; y++ {
+				for x := xmin; x < rMaxX; x++ {
 					v := uint8(d.readBits(d.bpp) * 0xff / max)
 					if d.mode == mGrayInvert {
 						v = 0xff - v
@@ -228,40 +242,42 @@ func (d *decoder) decode(dst image.Image, ymin, ymax int) error {
 		}
 	case mPaletted:
 		img := dst.(*image.Paletted)
-		for y := ymin; y < ymax; y++ {
-			for x := img.Rect.Min.X; x < img.Rect.Max.X; x++ {
+		for y := ymin; y < rMaxY; y++ {
+			for x := xmin; x < rMaxX; x++ {
 				img.SetColorIndex(x, y, uint8(d.readBits(d.bpp)))
 			}
 			d.flushBits()
 		}
 	case mRGB:
 		img := dst.(*image.RGBA)
-		min := img.PixOffset(0, ymin)
-		max := img.PixOffset(0, ymax)
-		var off int
-		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
+		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)
-		min := img.PixOffset(0, ymin)
-		max := img.PixOffset(0, ymax)
-		if len(d.buf) != max-min {
-			return FormatError("short data strip")
+		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)
 		}
-		copy(img.Pix[min:max], d.buf)
 	case mRGBA:
 		img := dst.(*image.RGBA)
-		min := img.PixOffset(0, ymin)
-		max := img.PixOffset(0, ymax)
-		if len(d.buf) != max-min {
-			return FormatError("short data strip")
+		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)
 		}
-		copy(img.Pix[min:max], d.buf)
 	}
 
 	return nil
@@ -387,14 +403,39 @@ func Decode(r io.Reader) (img image.Image, err error) {
 		return
 	}
 
-	// Check if we have the right number of strips, offsets and counts.
-	rps := int(d.firstVal(tRowsPerStrip))
-	if rps == 0 {
-		// Assume only one strip.
-		rps = d.config.Height
+	blockPadding := false
+	blockWidth := d.config.Width
+	blockHeight := d.config.Height
+	blocksAcross := 1
+	blocksDown := 1
+
+	var blockOffsets, blockCounts []uint
+
+	if int(d.firstVal(tTileWidth)) != 0 {
+		blockPadding = true
+
+		blockWidth = int(d.firstVal(tTileWidth))
+		blockHeight = int(d.firstVal(tTileLength))
+
+		blocksAcross = (d.config.Width + blockWidth - 1) / blockWidth
+		blocksDown = (d.config.Height + blockHeight - 1) / blockHeight
+
+		blockCounts = d.features[tTileByteCounts]
+		blockOffsets = d.features[tTileOffsets]
+
+	} else {
+		if int(d.firstVal(tRowsPerStrip)) != 0 {
+			blockHeight = int(d.firstVal(tRowsPerStrip))
+		}
+
+		blocksDown = (d.config.Height + blockHeight - 1) / blockHeight
+
+		blockOffsets = d.features[tStripOffsets]
+		blockCounts = d.features[tStripByteCounts]
 	}
-	numStrips := (d.config.Height + rps - 1) / rps
-	if rps == 0 || len(d.features[tStripOffsets]) < numStrips || len(d.features[tStripByteCounts]) < numStrips {
+
+	// Check if we have the right number of strips/tiles, offsets and counts.
+	if n := blocksAcross * blocksDown; len(blockOffsets) < n || len(blockCounts) < n {
 		return nil, FormatError("inconsistent header")
 	}
 
@@ -414,42 +455,55 @@ func Decode(r io.Reader) (img image.Image, err error) {
 		img = image.NewRGBA(imgRect)
 	}
 
-	for i := 0; i < numStrips; i++ {
-		ymin := i * rps
-		// The last strip may be shorter.
-		if i == numStrips-1 && d.config.Height%rps != 0 {
-			rps = d.config.Height % rps
+	for i := 0; i < blocksAcross; i++ {
+		blkW := blockWidth
+		if !blockPadding && i == blocksAcross-1 && d.config.Width%blockWidth != 0 {
+			blkW = d.config.Width % blockWidth
 		}
-		offset := int64(d.features[tStripOffsets][i])
-		n := int64(d.features[tStripByteCounts][i])
-		switch d.firstVal(tCompression) {
-		case cNone:
-			if b, ok := d.r.(*buffer); ok {
-				d.buf, err = b.Slice(int(offset), int(n))
-			} else {
-				d.buf = make([]byte, n)
-				_, err = d.r.ReadAt(d.buf, offset)
+		for j := 0; j < blocksDown; j++ {
+			blkH := blockHeight
+			if !blockPadding && j == blocksDown-1 && d.config.Height%blockHeight != 0 {
+				blkH = d.config.Height % blockHeight
+			}
+			offset := int64(blockOffsets[j*blocksAcross+i])
+			n := int64(blockCounts[j*blocksAcross+i])
+			switch d.firstVal(tCompression) {
+			case cNone:
+				if b, ok := d.r.(*buffer); ok {
+					d.buf, err = b.Slice(int(offset), int(n))
+				} else {
+					d.buf = make([]byte, n)
+					_, err = d.r.ReadAt(d.buf, offset)
+				}
+			case cLZW:
+				r := lzw.NewReader(io.NewSectionReader(d.r, offset, n), lzw.MSB, 8)
+				d.buf, err = ioutil.ReadAll(r)
+				r.Close()
+			case cDeflate, cDeflateOld:
+				r, err := zlib.NewReader(io.NewSectionReader(d.r, offset, n))
+				if err != nil {
+					return nil, err
+				}
+				d.buf, err = ioutil.ReadAll(r)
+				r.Close()
+			case cPackBits:
+				d.buf, err = unpackBits(io.NewSectionReader(d.r, offset, n))
+			default:
+				err = UnsupportedError("compression")
 			}
-		case cLZW:
-			r := lzw.NewReader(io.NewSectionReader(d.r, offset, n), lzw.MSB, 8)
-			d.buf, err = ioutil.ReadAll(r)
-			r.Close()
-		case cDeflate, cDeflateOld:
-			r, err := zlib.NewReader(io.NewSectionReader(d.r, offset, n))
 			if err != nil {
 				return nil, err
 			}
-			d.buf, err = ioutil.ReadAll(r)
-			r.Close()
-		case cPackBits:
-			d.buf, err = unpackBits(io.NewSectionReader(d.r, offset, n))
-		default:
-			err = UnsupportedError("compression")
-		}
-		if err != nil {
-			return
+
+			xmin := i * blockWidth
+			ymin := j * blockHeight
+			xmax := xmin + blkW
+			ymax := ymin + blkH
+			err = d.decode(img, xmin, ymin, xmax, ymax)
+			if err != nil {
+				return nil, err
+			}
 		}
-		err = d.decode(img, ymin, ymin+rps)
 	}
 	return
 }
diff --git a/tiff/reader_test.go b/tiff/reader_test.go
index 0af9e5c..59637c5 100644
--- a/tiff/reader_test.go
+++ b/tiff/reader_test.go
@@ -21,16 +21,24 @@ func (*buffer) Read([]byte) (int, error) {
 	panic("unimplemented")
 }
 
+func load(name string) (image.Image, error) {
+	f, err := os.Open(testdataDir + name)
+	if err != nil {
+		return nil, err
+	}
+	defer f.Close()
+	img, _, err := image.Decode(f)
+	if err != nil {
+		return nil, err
+	}
+	return img, nil
+}
+
 // TestNoRPS tries to decode an image that has no RowsPerStrip tag.
 // The tag is mandatory according to the spec but some software omits
 // it in the case of a single strip.
 func TestNoRPS(t *testing.T) {
-	f, err := os.Open(testdataDir + "no_rps.tiff")
-	if err != nil {
-		t.Fatal(err)
-	}
-	defer f.Close()
-	_, err = Decode(f)
+	_, err := load("no_rps.tiff")
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -81,27 +89,26 @@ func compare(t *testing.T, img0, img1 image.Image) {
 // TestDecode tests that decoding a PNG image and a TIFF image result in the
 // same pixel data.
 func TestDecode(t *testing.T) {
-	f0, err := os.Open(testdataDir + "video-001.png")
+	img0, err := load("video-001.png")
 	if err != nil {
 		t.Fatal(err)
 	}
-	defer f0.Close()
-	img0, _, err := image.Decode(f0)
+	img1, err := load("video-001.tiff")
 	if err != nil {
 		t.Fatal(err)
 	}
-
-	f1, err := os.Open(testdataDir + "video-001.tiff")
+	img2, err := load("video-001-strip-64.tiff")
 	if err != nil {
 		t.Fatal(err)
 	}
-	defer f1.Close()
-	img1, _, err := image.Decode(f1)
+	img3, err := load("video-001-tile-64x64.tiff")
 	if err != nil {
 		t.Fatal(err)
 	}
 
 	compare(t, img0, img1)
+	compare(t, img0, img2)
+	compare(t, img0, img3)
 }
 
 // TestDecompress tests that decoding some TIFF images that use different
@@ -114,23 +121,14 @@ func TestDecompress(t *testing.T) {
 	}
 	var img0 image.Image
 	for _, name := range decompressTests {
-		f, err := os.Open(testdataDir + name)
+		img1, err := load(name)
 		if err != nil {
-			t.Fatal(err)
+			t.Fatalf("decoding %s: %v", name, err)
 		}
-		defer f.Close()
 		if img0 == nil {
-			img0, err = Decode(f)
-			if err != nil {
-				t.Fatalf("decoding %s: %v", name, err)
-			}
+			img0 = img1
 			continue
 		}
-
-		img1, err := Decode(f)
-		if err != nil {
-			t.Fatalf("decoding %s: %v", name, err)
-		}
 		compare(t, img0, img1)
 	}
 }