Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

x/image/tiff: issue 20742, read XReslution, YResolution and page count tags #4

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added testdata/gopherhat.tiff
Binary file not shown.
1 change: 1 addition & 0 deletions tiff/consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ const (
tXResolution = 282
tYResolution = 283
tResolutionUnit = 296
tPageNumber = 297

tPredictor = 317
tColorMap = 320
Expand Down
77 changes: 73 additions & 4 deletions tiff/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,32 @@ import (
"golang.org/x/image/tiff/lzw"
)

// MeasurementResolutionUnit is the units of measure for resolution
type MeasurementResolutionUnit int

const (
// None is no absolute unit of measurement.
None MeasurementResolutionUnit = 1
// Inch unit of measurement.
Inch MeasurementResolutionUnit = 2
// Centimeter unit of measurement.
Centimeter MeasurementResolutionUnit = 3
)

// An Info is container for image.Config, a TIFF iamge resolution and the number of the page a TIFF image.
type Info struct {
image.Config
// XResolution is the number of pixels per ResolutionUnit in the image.Config.Width direction.
XResolution int
// YResolution is the number of pixels per ResolutionUnit in the image.Config.Width direction.
YResolution int
// UnitResolution is the unit of measurement for XResolution and YResolution.
UnitResolution MeasurementResolutionUnit
//PageCount is the number of the page from which a TIFF image was scanned.
// If PageCount is 0, the total number of pages in the document is not available.
PageCount int
}

// A FormatError reports that the input is not a valid TIFF image.
type FormatError string

Expand All @@ -39,9 +65,14 @@ func (e UnsupportedError) Error() string {
var errNoPixels = FormatError("not enough pixel data")

type decoder struct {
r io.ReaderAt
byteOrder binary.ByteOrder
config image.Config
r io.ReaderAt
byteOrder binary.ByteOrder
config image.Config
resolution struct {
x, y int
unit MeasurementResolutionUnit
}
pageCount int
mode imageMode
bpp uint
features map[int][]uint
Expand All @@ -63,6 +94,15 @@ func (d *decoder) firstVal(tag int) uint {
return f[0]
}

func (d *decoder) secondVal(tag int) uint {
f := d.features[tag]
if len(f) < 2 {
return 0
}

return f[1]
}

// ifdUint decodes the IFD entry in p, which must be of the Byte, Short
// or Long type, and returns the decoded uint values.
func (d *decoder) ifdUint(p []byte) (u []uint, err error) {
Expand Down Expand Up @@ -101,7 +141,7 @@ func (d *decoder) ifdUint(p []byte) (u []uint, err error) {
for i := uint32(0); i < count; i++ {
u[i] = uint(d.byteOrder.Uint16(raw[2*i : 2*(i+1)]))
}
case dtLong:
case dtLong, dtRational:
for i := uint32(0); i < count; i++ {
u[i] = uint(d.byteOrder.Uint32(raw[4*i : 4*(i+1)]))
}
Expand Down Expand Up @@ -131,6 +171,10 @@ func (d *decoder) parseIFD(p []byte) (int, error) {
tTileByteCounts,
tImageLength,
tImageWidth,
tXResolution,
tYResolution,
tResolutionUnit,
tPageNumber,
tFillOrder,
tT4Options,
tT6Options:
Expand Down Expand Up @@ -447,6 +491,20 @@ func newDecoder(r io.Reader) (*decoder, error) {
d.config.Width = int(d.firstVal(tImageWidth))
d.config.Height = int(d.firstVal(tImageLength))

d.resolution.x = int(d.firstVal(tXResolution))
d.resolution.y = int(d.firstVal(tYResolution))

switch int(d.firstVal(tResolutionUnit)) {
case 2:
d.resolution.unit = Inch
case 3:
d.resolution.unit = Centimeter
default:
d.resolution.unit = None
}

d.pageCount = int(d.secondVal(tPageNumber))

if _, ok := d.features[tBitsPerSample]; !ok {
// Default is 1 per specification.
d.features[tBitsPerSample] = []uint{1}
Expand Down Expand Up @@ -547,6 +605,17 @@ func DecodeConfig(r io.Reader) (image.Config, error) {
return d.config, nil
}

// DecodeTIFFInfo returns the page count and resolution of a TIFF image without
// decoding the entire image
func DecodeTIFFInfo(r io.Reader) (Info, error) {
d, err := newDecoder(r)
if err != nil {
return Info{}, err
}

return Info{d.config, d.resolution.x, d.resolution.y, d.resolution.unit, d.pageCount}, nil
}

func ccittFillOrder(tiffFillOrder uint) ccitt.Order {
if tiffFillOrder == 2 {
return ccitt.LSB
Expand Down
30 changes: 30 additions & 0 deletions tiff/reader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,36 @@ func TestLargeIFDEntry(t *testing.T) {
}
}

// TestGetResolution tests decoding an image that has XResolution, YResolution and PageNumber tags.
// Issue 20742
func TestGetResolution(t *testing.T) {
r, err := ioutil.ReadFile(testdataDir + "gopherhat.tiff")
if err != nil {
t.Fatal(err)
}

info, err := DecodeTIFFInfo(bytes.NewReader(r))
if err != nil {
t.Fatal(err)
}

if info.XResolution != 204 {
t.Fatalf("X resolution must be 204 but %d", info.XResolution)
}

if info.YResolution != 192 {
t.Fatalf("Y resolution must be 192 but %d", info.YResolution)
}

if info.PageCount != 1 {
t.Fatalf("PageCount must be 1 but %d", info.PageCount)
}

if info.UnitResolution != Inch {
t.Fatalf("UnitResolution must be Inch but %d", info.UnitResolution)
}
}

// benchmarkDecode benchmarks the decoding of an image.
func benchmarkDecode(b *testing.B, filename string) {
b.Helper()
Expand Down