439 lines
10 KiB
Go
439 lines
10 KiB
Go
|
package tiff
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"encoding/binary"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"math/big"
|
||
|
"strings"
|
||
|
"unicode"
|
||
|
"unicode/utf8"
|
||
|
)
|
||
|
|
||
|
// Format specifies the Go type equivalent used to represent the basic
|
||
|
// tiff data types.
|
||
|
type Format int
|
||
|
|
||
|
const (
|
||
|
IntVal Format = iota
|
||
|
FloatVal
|
||
|
RatVal
|
||
|
StringVal
|
||
|
UndefVal
|
||
|
OtherVal
|
||
|
)
|
||
|
|
||
|
var ErrShortReadTagValue = errors.New("tiff: short read of tag value")
|
||
|
|
||
|
var formatNames = map[Format]string{
|
||
|
IntVal: "int",
|
||
|
FloatVal: "float",
|
||
|
RatVal: "rational",
|
||
|
StringVal: "string",
|
||
|
UndefVal: "undefined",
|
||
|
OtherVal: "other",
|
||
|
}
|
||
|
|
||
|
// DataType represents the basic tiff tag data types.
|
||
|
type DataType uint16
|
||
|
|
||
|
const (
|
||
|
DTByte DataType = 1
|
||
|
DTAscii = 2
|
||
|
DTShort = 3
|
||
|
DTLong = 4
|
||
|
DTRational = 5
|
||
|
DTSByte = 6
|
||
|
DTUndefined = 7
|
||
|
DTSShort = 8
|
||
|
DTSLong = 9
|
||
|
DTSRational = 10
|
||
|
DTFloat = 11
|
||
|
DTDouble = 12
|
||
|
)
|
||
|
|
||
|
var typeNames = map[DataType]string{
|
||
|
DTByte: "byte",
|
||
|
DTAscii: "ascii",
|
||
|
DTShort: "short",
|
||
|
DTLong: "long",
|
||
|
DTRational: "rational",
|
||
|
DTSByte: "signed byte",
|
||
|
DTUndefined: "undefined",
|
||
|
DTSShort: "signed short",
|
||
|
DTSLong: "signed long",
|
||
|
DTSRational: "signed rational",
|
||
|
DTFloat: "float",
|
||
|
DTDouble: "double",
|
||
|
}
|
||
|
|
||
|
// typeSize specifies the size in bytes of each type.
|
||
|
var typeSize = map[DataType]uint32{
|
||
|
DTByte: 1,
|
||
|
DTAscii: 1,
|
||
|
DTShort: 2,
|
||
|
DTLong: 4,
|
||
|
DTRational: 8,
|
||
|
DTSByte: 1,
|
||
|
DTUndefined: 1,
|
||
|
DTSShort: 2,
|
||
|
DTSLong: 4,
|
||
|
DTSRational: 8,
|
||
|
DTFloat: 4,
|
||
|
DTDouble: 8,
|
||
|
}
|
||
|
|
||
|
// Tag reflects the parsed content of a tiff IFD tag.
|
||
|
type Tag struct {
|
||
|
// Id is the 2-byte tiff tag identifier.
|
||
|
Id uint16
|
||
|
// Type is an integer (1 through 12) indicating the tag value's data type.
|
||
|
Type DataType
|
||
|
// Count is the number of type Type stored in the tag's value (i.e. the
|
||
|
// tag's value is an array of type Type and length Count).
|
||
|
Count uint32
|
||
|
// Val holds the bytes that represent the tag's value.
|
||
|
Val []byte
|
||
|
// ValOffset holds byte offset of the tag value w.r.t. the beginning of the
|
||
|
// reader it was decoded from. Zero if the tag value fit inside the offset
|
||
|
// field.
|
||
|
ValOffset uint32
|
||
|
|
||
|
order binary.ByteOrder
|
||
|
intVals []int64
|
||
|
floatVals []float64
|
||
|
ratVals [][]int64
|
||
|
strVal string
|
||
|
format Format
|
||
|
}
|
||
|
|
||
|
// DecodeTag parses a tiff-encoded IFD tag from r and returns a Tag object. The
|
||
|
// first read from r should be the first byte of the tag. ReadAt offsets should
|
||
|
// generally be relative to the beginning of the tiff structure (not relative
|
||
|
// to the beginning of the tag).
|
||
|
func DecodeTag(r ReadAtReader, order binary.ByteOrder) (*Tag, error) {
|
||
|
t := new(Tag)
|
||
|
t.order = order
|
||
|
|
||
|
err := binary.Read(r, order, &t.Id)
|
||
|
if err != nil {
|
||
|
return nil, errors.New("tiff: tag id read failed: " + err.Error())
|
||
|
}
|
||
|
|
||
|
err = binary.Read(r, order, &t.Type)
|
||
|
if err != nil {
|
||
|
return nil, errors.New("tiff: tag type read failed: " + err.Error())
|
||
|
}
|
||
|
|
||
|
err = binary.Read(r, order, &t.Count)
|
||
|
if err != nil {
|
||
|
return nil, errors.New("tiff: tag component count read failed: " + err.Error())
|
||
|
}
|
||
|
|
||
|
// There seems to be a relatively common corrupt tag which has a Count of
|
||
|
// MaxUint32. This is probably not a valid value, so return early.
|
||
|
if t.Count == 1<<32-1 {
|
||
|
return t, errors.New("invalid Count offset in tag")
|
||
|
}
|
||
|
|
||
|
valLen := typeSize[t.Type] * t.Count
|
||
|
if valLen == 0 {
|
||
|
return t, errors.New("zero length tag value")
|
||
|
}
|
||
|
|
||
|
if valLen > 4 {
|
||
|
binary.Read(r, order, &t.ValOffset)
|
||
|
|
||
|
// Use a bytes.Buffer so we don't allocate a huge slice if the tag
|
||
|
// is corrupt.
|
||
|
var buff bytes.Buffer
|
||
|
sr := io.NewSectionReader(r, int64(t.ValOffset), int64(valLen))
|
||
|
n, err := io.Copy(&buff, sr)
|
||
|
if err != nil {
|
||
|
return t, errors.New("tiff: tag value read failed: " + err.Error())
|
||
|
} else if n != int64(valLen) {
|
||
|
return t, ErrShortReadTagValue
|
||
|
}
|
||
|
t.Val = buff.Bytes()
|
||
|
|
||
|
} else {
|
||
|
val := make([]byte, valLen)
|
||
|
if _, err = io.ReadFull(r, val); err != nil {
|
||
|
return t, errors.New("tiff: tag offset read failed: " + err.Error())
|
||
|
}
|
||
|
// ignore padding.
|
||
|
if _, err = io.ReadFull(r, make([]byte, 4-valLen)); err != nil {
|
||
|
return t, errors.New("tiff: tag offset read failed: " + err.Error())
|
||
|
}
|
||
|
|
||
|
t.Val = val
|
||
|
}
|
||
|
|
||
|
return t, t.convertVals()
|
||
|
}
|
||
|
|
||
|
func (t *Tag) convertVals() error {
|
||
|
r := bytes.NewReader(t.Val)
|
||
|
|
||
|
switch t.Type {
|
||
|
case DTAscii:
|
||
|
if len(t.Val) > 0 {
|
||
|
t.strVal = string(t.Val[:len(t.Val)-1]) // ignore the last byte (NULL).
|
||
|
}
|
||
|
case DTByte:
|
||
|
var v uint8
|
||
|
t.intVals = make([]int64, int(t.Count))
|
||
|
for i := range t.intVals {
|
||
|
err := binary.Read(r, t.order, &v)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
t.intVals[i] = int64(v)
|
||
|
}
|
||
|
case DTShort:
|
||
|
var v uint16
|
||
|
t.intVals = make([]int64, int(t.Count))
|
||
|
for i := range t.intVals {
|
||
|
err := binary.Read(r, t.order, &v)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
t.intVals[i] = int64(v)
|
||
|
}
|
||
|
case DTLong:
|
||
|
var v uint32
|
||
|
t.intVals = make([]int64, int(t.Count))
|
||
|
for i := range t.intVals {
|
||
|
err := binary.Read(r, t.order, &v)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
t.intVals[i] = int64(v)
|
||
|
}
|
||
|
case DTSByte:
|
||
|
var v int8
|
||
|
t.intVals = make([]int64, int(t.Count))
|
||
|
for i := range t.intVals {
|
||
|
err := binary.Read(r, t.order, &v)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
t.intVals[i] = int64(v)
|
||
|
}
|
||
|
case DTSShort:
|
||
|
var v int16
|
||
|
t.intVals = make([]int64, int(t.Count))
|
||
|
for i := range t.intVals {
|
||
|
err := binary.Read(r, t.order, &v)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
t.intVals[i] = int64(v)
|
||
|
}
|
||
|
case DTSLong:
|
||
|
var v int32
|
||
|
t.intVals = make([]int64, int(t.Count))
|
||
|
for i := range t.intVals {
|
||
|
err := binary.Read(r, t.order, &v)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
t.intVals[i] = int64(v)
|
||
|
}
|
||
|
case DTRational:
|
||
|
t.ratVals = make([][]int64, int(t.Count))
|
||
|
for i := range t.ratVals {
|
||
|
var n, d uint32
|
||
|
err := binary.Read(r, t.order, &n)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
err = binary.Read(r, t.order, &d)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
t.ratVals[i] = []int64{int64(n), int64(d)}
|
||
|
}
|
||
|
case DTSRational:
|
||
|
t.ratVals = make([][]int64, int(t.Count))
|
||
|
for i := range t.ratVals {
|
||
|
var n, d int32
|
||
|
err := binary.Read(r, t.order, &n)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
err = binary.Read(r, t.order, &d)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
t.ratVals[i] = []int64{int64(n), int64(d)}
|
||
|
}
|
||
|
case DTFloat: // float32
|
||
|
t.floatVals = make([]float64, int(t.Count))
|
||
|
for i := range t.floatVals {
|
||
|
var v float32
|
||
|
err := binary.Read(r, t.order, &v)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
t.floatVals[i] = float64(v)
|
||
|
}
|
||
|
case DTDouble:
|
||
|
t.floatVals = make([]float64, int(t.Count))
|
||
|
for i := range t.floatVals {
|
||
|
var u float64
|
||
|
err := binary.Read(r, t.order, &u)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
t.floatVals[i] = u
|
||
|
}
|
||
|
}
|
||
|
|
||
|
switch t.Type {
|
||
|
case DTByte, DTShort, DTLong, DTSByte, DTSShort, DTSLong:
|
||
|
t.format = IntVal
|
||
|
case DTRational, DTSRational:
|
||
|
t.format = RatVal
|
||
|
case DTFloat, DTDouble:
|
||
|
t.format = FloatVal
|
||
|
case DTAscii:
|
||
|
t.format = StringVal
|
||
|
case DTUndefined:
|
||
|
t.format = UndefVal
|
||
|
default:
|
||
|
t.format = OtherVal
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Format returns a value indicating which method can be called to retrieve the
|
||
|
// tag's value properly typed (e.g. integer, rational, etc.).
|
||
|
func (t *Tag) Format() Format { return t.format }
|
||
|
|
||
|
func (t *Tag) typeErr(to Format) error {
|
||
|
return &wrongFmtErr{typeNames[t.Type], formatNames[to]}
|
||
|
}
|
||
|
|
||
|
// Rat returns the tag's i'th value as a rational number. It returns a nil and
|
||
|
// an error if this tag's Format is not RatVal. It panics for zero deminators
|
||
|
// or if i is out of range.
|
||
|
func (t *Tag) Rat(i int) (*big.Rat, error) {
|
||
|
n, d, err := t.Rat2(i)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return big.NewRat(n, d), nil
|
||
|
}
|
||
|
|
||
|
// Rat2 returns the tag's i'th value as a rational number represented by a
|
||
|
// numerator-denominator pair. It returns an error if the tag's Format is not
|
||
|
// RatVal. It panics if i is out of range.
|
||
|
func (t *Tag) Rat2(i int) (num, den int64, err error) {
|
||
|
if t.format != RatVal {
|
||
|
return 0, 0, t.typeErr(RatVal)
|
||
|
}
|
||
|
return t.ratVals[i][0], t.ratVals[i][1], nil
|
||
|
}
|
||
|
|
||
|
// Int64 returns the tag's i'th value as an integer. It returns an error if the
|
||
|
// tag's Format is not IntVal. It panics if i is out of range.
|
||
|
func (t *Tag) Int64(i int) (int64, error) {
|
||
|
if t.format != IntVal {
|
||
|
return 0, t.typeErr(IntVal)
|
||
|
}
|
||
|
return t.intVals[i], nil
|
||
|
}
|
||
|
|
||
|
// Int returns the tag's i'th value as an integer. It returns an error if the
|
||
|
// tag's Format is not IntVal. It panics if i is out of range.
|
||
|
func (t *Tag) Int(i int) (int, error) {
|
||
|
if t.format != IntVal {
|
||
|
return 0, t.typeErr(IntVal)
|
||
|
}
|
||
|
return int(t.intVals[i]), nil
|
||
|
}
|
||
|
|
||
|
// Float returns the tag's i'th value as a float. It returns an error if the
|
||
|
// tag's Format is not IntVal. It panics if i is out of range.
|
||
|
func (t *Tag) Float(i int) (float64, error) {
|
||
|
if t.format != FloatVal {
|
||
|
return 0, t.typeErr(FloatVal)
|
||
|
}
|
||
|
return t.floatVals[i], nil
|
||
|
}
|
||
|
|
||
|
// StringVal returns the tag's value as a string. It returns an error if the
|
||
|
// tag's Format is not StringVal. It panics if i is out of range.
|
||
|
func (t *Tag) StringVal() (string, error) {
|
||
|
if t.format != StringVal {
|
||
|
return "", t.typeErr(StringVal)
|
||
|
}
|
||
|
return t.strVal, nil
|
||
|
}
|
||
|
|
||
|
// String returns a nicely formatted version of the tag.
|
||
|
func (t *Tag) String() string {
|
||
|
data, err := t.MarshalJSON()
|
||
|
if err != nil {
|
||
|
return "ERROR: " + err.Error()
|
||
|
}
|
||
|
|
||
|
if t.Count == 1 {
|
||
|
return strings.Trim(fmt.Sprintf("%s", data), "[]")
|
||
|
}
|
||
|
return fmt.Sprintf("%s", data)
|
||
|
}
|
||
|
|
||
|
func (t *Tag) MarshalJSON() ([]byte, error) {
|
||
|
switch t.format {
|
||
|
case StringVal, UndefVal:
|
||
|
return nullString(t.Val), nil
|
||
|
case OtherVal:
|
||
|
return []byte(fmt.Sprintf("unknown tag type '%v'", t.Type)), nil
|
||
|
}
|
||
|
|
||
|
rv := []string{}
|
||
|
for i := 0; i < int(t.Count); i++ {
|
||
|
switch t.format {
|
||
|
case RatVal:
|
||
|
n, d, _ := t.Rat2(i)
|
||
|
rv = append(rv, fmt.Sprintf(`"%v/%v"`, n, d))
|
||
|
case FloatVal:
|
||
|
v, _ := t.Float(i)
|
||
|
rv = append(rv, fmt.Sprintf("%v", v))
|
||
|
case IntVal:
|
||
|
v, _ := t.Int(i)
|
||
|
rv = append(rv, fmt.Sprintf("%v", v))
|
||
|
}
|
||
|
}
|
||
|
return []byte(fmt.Sprintf(`[%s]`, strings.Join(rv, ","))), nil
|
||
|
}
|
||
|
|
||
|
func nullString(in []byte) []byte {
|
||
|
rv := bytes.Buffer{}
|
||
|
rv.WriteByte('"')
|
||
|
for _, b := range in {
|
||
|
if unicode.IsPrint(rune(b)) {
|
||
|
rv.WriteByte(b)
|
||
|
}
|
||
|
}
|
||
|
rv.WriteByte('"')
|
||
|
rvb := rv.Bytes()
|
||
|
if utf8.Valid(rvb) {
|
||
|
return rvb
|
||
|
}
|
||
|
return []byte(`""`)
|
||
|
}
|
||
|
|
||
|
type wrongFmtErr struct {
|
||
|
From, To string
|
||
|
}
|
||
|
|
||
|
func (e *wrongFmtErr) Error() string {
|
||
|
return fmt.Sprintf("cannot convert tag type '%v' into '%v'", e.From, e.To)
|
||
|
}
|