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:
parent
822abc7ef1
commit
de306d5329
BIN
testdata/video-001-strip-64.tiff
vendored
Normal file
BIN
testdata/video-001-strip-64.tiff
vendored
Normal file
Binary file not shown.
BIN
testdata/video-001-tile-64x64.tiff
vendored
Normal file
BIN
testdata/video-001-tile-64x64.tiff
vendored
Normal file
Binary file not shown.
|
@ -47,6 +47,11 @@ const (
|
|||
tRowsPerStrip = 278
|
||||
tStripByteCounts = 279
|
||||
|
||||
tTileWidth = 322
|
||||
tTileLength = 323
|
||||
tTileOffsets = 324
|
||||
tTileByteCounts = 325
|
||||
|
||||
tXResolution = 282
|
||||
tYResolution = 283
|
||||
tResolutionUnit = 296
|
||||
|
|
130
tiff/reader.go
130
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,17 +242,18 @@ 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 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]
|
||||
|
@ -246,22 +261,23 @@ func (d *decoder) decode(dst image.Image, ymin, ymax int) error {
|
|||
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))
|
||||
}
|
||||
numStrips := (d.config.Height + rps - 1) / rps
|
||||
if rps == 0 || len(d.features[tStripOffsets]) < numStrips || len(d.features[tStripByteCounts]) < numStrips {
|
||||
|
||||
blocksDown = (d.config.Height + blockHeight - 1) / blockHeight
|
||||
|
||||
blockOffsets = d.features[tStripOffsets]
|
||||
blockCounts = d.features[tStripByteCounts]
|
||||
}
|
||||
|
||||
// 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,14 +455,18 @@ 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])
|
||||
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 {
|
||||
|
@ -447,9 +492,18 @@ func Decode(r io.Reader) (img image.Image, err error) {
|
|||
err = UnsupportedError("compression")
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user