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
This commit is contained in:
ChaiShushan 2013-09-06 20:07:58 +10:00 committed by Nigel Tao
parent 822abc7ef1
commit de306d5329
5 changed files with 149 additions and 92 deletions

BIN
testdata/video-001-strip-64.tiff vendored Normal file

Binary file not shown.

BIN
testdata/video-001-tile-64x64.tiff vendored Normal file

Binary file not shown.

View File

@ -47,6 +47,11 @@ const (
tRowsPerStrip = 278 tRowsPerStrip = 278
tStripByteCounts = 279 tStripByteCounts = 279
tTileWidth = 322
tTileLength = 323
tTileOffsets = 324
tTileByteCounts = 325
tXResolution = 282 tXResolution = 282
tYResolution = 283 tYResolution = 283
tResolutionUnit = 296 tResolutionUnit = 296

View File

@ -114,6 +114,10 @@ func (d *decoder) parseIFD(p []byte) error {
tStripOffsets, tStripOffsets,
tStripByteCounts, tStripByteCounts,
tRowsPerStrip, tRowsPerStrip,
tTileWidth,
tTileLength,
tTileOffsets,
tTileByteCounts,
tImageLength, tImageLength,
tImageWidth: tImageWidth:
val, err := d.ifdUint(p) val, err := d.ifdUint(p)
@ -178,9 +182,17 @@ func (d *decoder) flushBits() {
d.nbits = 0 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. // decode decodes the raw data of an image.
// It reads from d.buf and writes the strip with ymin <= y < ymax into dst. // It reads from d.buf and writes the strip or tile into dst.
func (d *decoder) decode(dst image.Image, ymin, ymax int) error { func (d *decoder) decode(dst image.Image, xmin, ymin, xmax, ymax int) error {
d.off = 0 d.off = 0
// Apply horizontal predictor if necessary. // 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 spp := len(d.features[tBitsPerSample]) // samples per pixel
for y := ymin; y < ymax; y++ { for y := ymin; y < ymax; y++ {
off += spp 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] d.buf[off] += d.buf[off-spp]
off++ off++
} }
} }
} }
rMaxX := minInt(xmax, dst.Bounds().Max.X)
rMaxY := minInt(ymax, dst.Bounds().Max.Y)
switch d.mode { switch d.mode {
case mGray, mGrayInvert: case mGray, mGrayInvert:
if d.bpp == 16 { if d.bpp == 16 {
img := dst.(*image.Gray16) img := dst.(*image.Gray16)
for y := ymin; y < ymax; y++ { for y := ymin; y < rMaxY; y++ {
for x := img.Rect.Min.X; x < img.Rect.Max.X; x++ { for x := xmin; x < rMaxX; x++ {
v := d.byteOrder.Uint16(d.buf[d.off : d.off+2]) v := d.byteOrder.Uint16(d.buf[d.off : d.off+2])
d.off += 2 d.off += 2
if d.mode == mGrayInvert { if d.mode == mGrayInvert {
@ -215,8 +229,8 @@ func (d *decoder) decode(dst image.Image, ymin, ymax int) error {
} else { } else {
img := dst.(*image.Gray) img := dst.(*image.Gray)
max := uint32((1 << d.bpp) - 1) max := uint32((1 << d.bpp) - 1)
for y := ymin; y < ymax; y++ { for y := ymin; y < rMaxY; y++ {
for x := img.Rect.Min.X; x < img.Rect.Max.X; x++ { for x := xmin; x < rMaxX; x++ {
v := uint8(d.readBits(d.bpp) * 0xff / max) v := uint8(d.readBits(d.bpp) * 0xff / max)
if d.mode == mGrayInvert { if d.mode == mGrayInvert {
v = 0xff - v v = 0xff - v
@ -228,40 +242,42 @@ func (d *decoder) decode(dst image.Image, ymin, ymax int) error {
} }
case mPaletted: case mPaletted:
img := dst.(*image.Paletted) img := dst.(*image.Paletted)
for y := ymin; y < ymax; y++ { for y := ymin; y < rMaxY; y++ {
for x := img.Rect.Min.X; x < img.Rect.Max.X; x++ { for x := xmin; x < rMaxX; x++ {
img.SetColorIndex(x, y, uint8(d.readBits(d.bpp))) img.SetColorIndex(x, y, uint8(d.readBits(d.bpp)))
} }
d.flushBits() d.flushBits()
} }
case mRGB: case mRGB:
img := dst.(*image.RGBA) img := dst.(*image.RGBA)
min := img.PixOffset(0, ymin) for y := ymin; y < rMaxY; y++ {
max := img.PixOffset(0, ymax) min := img.PixOffset(xmin, y)
var off int max := img.PixOffset(rMaxX, y)
for i := min; i < max; i += 4 { off := (y - ymin) * (xmax - xmin) * 3
img.Pix[i+0] = d.buf[off+0] for i := min; i < max; i += 4 {
img.Pix[i+1] = d.buf[off+1] img.Pix[i+0] = d.buf[off+0]
img.Pix[i+2] = d.buf[off+2] img.Pix[i+1] = d.buf[off+1]
img.Pix[i+3] = 0xff img.Pix[i+2] = d.buf[off+2]
off += 3 img.Pix[i+3] = 0xff
off += 3
}
} }
case mNRGBA: case mNRGBA:
img := dst.(*image.NRGBA) img := dst.(*image.NRGBA)
min := img.PixOffset(0, ymin) for y := ymin; y < rMaxY; y++ {
max := img.PixOffset(0, ymax) min := img.PixOffset(xmin, y)
if len(d.buf) != max-min { max := img.PixOffset(rMaxX, y)
return FormatError("short data strip") 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: case mRGBA:
img := dst.(*image.RGBA) img := dst.(*image.RGBA)
min := img.PixOffset(0, ymin) for y := ymin; y < rMaxY; y++ {
max := img.PixOffset(0, ymax) min := img.PixOffset(xmin, y)
if len(d.buf) != max-min { max := img.PixOffset(rMaxX, y)
return FormatError("short data strip") 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 return nil
@ -387,14 +403,39 @@ func Decode(r io.Reader) (img image.Image, err error) {
return return
} }
// Check if we have the right number of strips, offsets and counts. blockPadding := false
rps := int(d.firstVal(tRowsPerStrip)) blockWidth := d.config.Width
if rps == 0 { blockHeight := d.config.Height
// Assume only one strip. blocksAcross := 1
rps = d.config.Height 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") return nil, FormatError("inconsistent header")
} }
@ -414,42 +455,55 @@ func Decode(r io.Reader) (img image.Image, err error) {
img = image.NewRGBA(imgRect) img = image.NewRGBA(imgRect)
} }
for i := 0; i < numStrips; i++ { for i := 0; i < blocksAcross; i++ {
ymin := i * rps blkW := blockWidth
// The last strip may be shorter. if !blockPadding && i == blocksAcross-1 && d.config.Width%blockWidth != 0 {
if i == numStrips-1 && d.config.Height%rps != 0 { blkW = d.config.Width % blockWidth
rps = d.config.Height % rps
} }
offset := int64(d.features[tStripOffsets][i]) for j := 0; j < blocksDown; j++ {
n := int64(d.features[tStripByteCounts][i]) blkH := blockHeight
switch d.firstVal(tCompression) { if !blockPadding && j == blocksDown-1 && d.config.Height%blockHeight != 0 {
case cNone: blkH = d.config.Height % blockHeight
if b, ok := d.r.(*buffer); ok { }
d.buf, err = b.Slice(int(offset), int(n)) offset := int64(blockOffsets[j*blocksAcross+i])
} else { n := int64(blockCounts[j*blocksAcross+i])
d.buf = make([]byte, n) switch d.firstVal(tCompression) {
_, err = d.r.ReadAt(d.buf, offset) 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 { if err != nil {
return nil, err return nil, err
} }
d.buf, err = ioutil.ReadAll(r)
r.Close() xmin := i * blockWidth
case cPackBits: ymin := j * blockHeight
d.buf, err = unpackBits(io.NewSectionReader(d.r, offset, n)) xmax := xmin + blkW
default: ymax := ymin + blkH
err = UnsupportedError("compression") err = d.decode(img, xmin, ymin, xmax, ymax)
if err != nil {
return nil, err
}
} }
if err != nil {
return
}
err = d.decode(img, ymin, ymin+rps)
} }
return return
} }

View File

@ -21,16 +21,24 @@ func (*buffer) Read([]byte) (int, error) {
panic("unimplemented") 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. // TestNoRPS tries to decode an image that has no RowsPerStrip tag.
// The tag is mandatory according to the spec but some software omits // The tag is mandatory according to the spec but some software omits
// it in the case of a single strip. // it in the case of a single strip.
func TestNoRPS(t *testing.T) { func TestNoRPS(t *testing.T) {
f, err := os.Open(testdataDir + "no_rps.tiff") _, err := load("no_rps.tiff")
if err != nil {
t.Fatal(err)
}
defer f.Close()
_, err = Decode(f)
if err != nil { if err != nil {
t.Fatal(err) 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 // TestDecode tests that decoding a PNG image and a TIFF image result in the
// same pixel data. // same pixel data.
func TestDecode(t *testing.T) { func TestDecode(t *testing.T) {
f0, err := os.Open(testdataDir + "video-001.png") img0, err := load("video-001.png")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer f0.Close() img1, err := load("video-001.tiff")
img0, _, err := image.Decode(f0)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
img2, err := load("video-001-strip-64.tiff")
f1, err := os.Open(testdataDir + "video-001.tiff")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer f1.Close() img3, err := load("video-001-tile-64x64.tiff")
img1, _, err := image.Decode(f1)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
compare(t, img0, img1) compare(t, img0, img1)
compare(t, img0, img2)
compare(t, img0, img3)
} }
// TestDecompress tests that decoding some TIFF images that use different // TestDecompress tests that decoding some TIFF images that use different
@ -114,23 +121,14 @@ func TestDecompress(t *testing.T) {
} }
var img0 image.Image var img0 image.Image
for _, name := range decompressTests { for _, name := range decompressTests {
f, err := os.Open(testdataDir + name) img1, err := load(name)
if err != nil {
t.Fatal(err)
}
defer f.Close()
if img0 == nil {
img0, err = Decode(f)
if err != nil {
t.Fatalf("decoding %s: %v", name, err)
}
continue
}
img1, err := Decode(f)
if err != nil { if err != nil {
t.Fatalf("decoding %s: %v", name, err) t.Fatalf("decoding %s: %v", name, err)
} }
if img0 == nil {
img0 = img1
continue
}
compare(t, img0, img1) compare(t, img0, img1)
} }
} }