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
|
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
|
||||||
|
|
130
tiff/reader.go
130
tiff/reader.go
|
@ -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,17 +242,18 @@ 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)
|
||||||
|
off := (y - ymin) * (xmax - xmin) * 3
|
||||||
for i := min; i < max; i += 4 {
|
for i := min; i < max; i += 4 {
|
||||||
img.Pix[i+0] = d.buf[off+0]
|
img.Pix[i+0] = d.buf[off+0]
|
||||||
img.Pix[i+1] = d.buf[off+1]
|
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
|
img.Pix[i+3] = 0xff
|
||||||
off += 3
|
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))
|
||||||
}
|
}
|
||||||
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")
|
return nil, FormatError("inconsistent header")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -414,14 +455,18 @@ 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
|
||||||
|
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) {
|
switch d.firstVal(tCompression) {
|
||||||
case cNone:
|
case cNone:
|
||||||
if b, ok := d.r.(*buffer); ok {
|
if b, ok := d.r.(*buffer); ok {
|
||||||
|
@ -447,9 +492,18 @@ func Decode(r io.Reader) (img image.Image, err error) {
|
||||||
err = UnsupportedError("compression")
|
err = UnsupportedError("compression")
|
||||||
}
|
}
|
||||||
if err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatalf("decoding %s: %v", name, err)
|
||||||
}
|
}
|
||||||
defer f.Close()
|
|
||||||
if img0 == nil {
|
if img0 == nil {
|
||||||
img0, err = Decode(f)
|
img0 = img1
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("decoding %s: %v", name, err)
|
|
||||||
}
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
img1, err := Decode(f)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("decoding %s: %v", name, err)
|
|
||||||
}
|
|
||||||
compare(t, img0, img1)
|
compare(t, img0, img1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user