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
tStripByteCounts = 279
tTileWidth = 322
tTileLength = 323
tTileOffsets = 324
tTileByteCounts = 325
tXResolution = 282
tYResolution = 283
tResolutionUnit = 296

View File

@ -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")
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
}
}
if err != nil {
return
}
err = d.decode(img, ymin, ymin+rps)
}
return
}

View File

@ -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)
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)
img1, err := load(name)
if err != nil {
t.Fatalf("decoding %s: %v", name, err)
}
if img0 == nil {
img0 = img1
continue
}
compare(t, img0, img1)
}
}