2020-06-08 03:29:51 +03:00
|
|
|
package tiff
|
|
|
|
|
|
|
|
// https://www.adobe.io/content/dam/udp/en/open/standards/tiff/TIFF6.pdf
|
|
|
|
|
|
|
|
import (
|
2021-08-17 13:06:32 +03:00
|
|
|
"github.com/wader/fq/format"
|
|
|
|
"github.com/wader/fq/format/registry"
|
|
|
|
"github.com/wader/fq/pkg/decode"
|
2021-12-02 00:48:25 +03:00
|
|
|
"github.com/wader/fq/pkg/scalar"
|
2020-06-08 03:29:51 +03:00
|
|
|
)
|
|
|
|
|
2021-11-17 18:46:10 +03:00
|
|
|
var tiffIccProfile decode.Group
|
2020-06-08 03:29:51 +03:00
|
|
|
|
|
|
|
func init() {
|
2021-11-17 18:46:10 +03:00
|
|
|
registry.MustRegister(decode.Format{
|
2020-06-08 03:29:51 +03:00
|
|
|
Name: format.TIFF,
|
|
|
|
Description: "Tag Image File Format",
|
|
|
|
Groups: []string{format.PROBE, format.IMAGE},
|
|
|
|
DecodeFn: tiffDecode,
|
|
|
|
Dependencies: []decode.Dependency{
|
2021-11-17 18:46:10 +03:00
|
|
|
{Names: []string{format.ICC_PROFILE}, Group: &tiffIccProfile},
|
2020-06-08 03:29:51 +03:00
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
const littleEndian = 0x49492a00 // "II*\0"
|
|
|
|
const bigEndian = 0x4d4d002a // "MM\0*"
|
|
|
|
|
2021-12-02 00:48:25 +03:00
|
|
|
var endianNames = scalar.UToSymStr{
|
2021-12-12 13:26:00 +03:00
|
|
|
littleEndian: "little-endian",
|
2021-11-05 17:04:26 +03:00
|
|
|
bigEndian: "big-endian",
|
|
|
|
}
|
|
|
|
|
2020-06-08 03:29:51 +03:00
|
|
|
const (
|
|
|
|
BYTE = 1
|
|
|
|
ASCII = 2
|
|
|
|
SHORT = 3
|
|
|
|
LONG = 4
|
|
|
|
RATIONAL = 5
|
|
|
|
UNDEFINED = 7
|
|
|
|
SLONG = 9
|
|
|
|
SRATIONAL = 10
|
|
|
|
)
|
|
|
|
|
2021-12-02 00:48:25 +03:00
|
|
|
var typeNames = scalar.UToSymStr{
|
2020-06-08 03:29:51 +03:00
|
|
|
BYTE: "BYTE",
|
|
|
|
ASCII: "ASCII",
|
|
|
|
SHORT: "SHORT",
|
|
|
|
LONG: "LONG",
|
|
|
|
RATIONAL: "RATIONAL",
|
|
|
|
UNDEFINED: "UNDEFINED",
|
|
|
|
SLONG: "SLONG",
|
|
|
|
SRATIONAL: "SRATIONAL",
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: tiff 6.0 types
|
|
|
|
var typeByteSize = map[uint64]uint64{
|
|
|
|
BYTE: 1,
|
|
|
|
ASCII: 1,
|
|
|
|
SHORT: 2,
|
|
|
|
LONG: 4,
|
|
|
|
RATIONAL: 4 + 4,
|
|
|
|
UNDEFINED: 1,
|
|
|
|
SLONG: 4,
|
|
|
|
SRATIONAL: 4 + 4,
|
|
|
|
}
|
|
|
|
|
|
|
|
func fieldRational(d *decode.D, name string) float64 {
|
|
|
|
var v float64
|
2021-11-05 17:04:26 +03:00
|
|
|
d.FieldStruct(name, func(d *decode.D) {
|
2020-06-08 03:29:51 +03:00
|
|
|
numerator := d.FieldU32("numerator")
|
|
|
|
denominator := d.FieldU32("denominator")
|
|
|
|
v := float64(numerator) / float64(denominator)
|
2021-11-05 17:04:26 +03:00
|
|
|
d.FieldValueFloat("float", v)
|
2020-06-08 03:29:51 +03:00
|
|
|
})
|
|
|
|
return v
|
|
|
|
}
|
|
|
|
|
|
|
|
func fieldSRational(d *decode.D, name string) float64 {
|
|
|
|
var v float64
|
2021-11-05 17:04:26 +03:00
|
|
|
d.FieldStruct(name, func(d *decode.D) {
|
2020-06-08 03:29:51 +03:00
|
|
|
numerator := d.FieldS32("numerator")
|
|
|
|
denominator := d.FieldS32("denominator")
|
|
|
|
v := float64(numerator) / float64(denominator)
|
2021-11-05 17:04:26 +03:00
|
|
|
d.FieldValueFloat("float", v)
|
2020-06-08 03:29:51 +03:00
|
|
|
})
|
|
|
|
return v
|
|
|
|
}
|
|
|
|
|
|
|
|
type strips struct {
|
|
|
|
offsets []int64
|
|
|
|
byteCounts []int64
|
|
|
|
}
|
|
|
|
|
2021-12-02 00:48:25 +03:00
|
|
|
func decodeIfd(d *decode.D, s *strips, tagNames scalar.UToSymStr) int64 {
|
2020-06-08 03:29:51 +03:00
|
|
|
var nextIfdOffset int64
|
|
|
|
|
2021-11-05 17:04:26 +03:00
|
|
|
d.FieldStruct("ifd", func(d *decode.D) {
|
2020-06-08 03:29:51 +03:00
|
|
|
numberOfFields := d.FieldU16("number_of_field")
|
2021-11-05 17:04:26 +03:00
|
|
|
d.FieldArray("entries", func(d *decode.D) {
|
2020-06-08 03:29:51 +03:00
|
|
|
for i := uint64(0); i < numberOfFields; i++ {
|
2021-11-05 17:04:26 +03:00
|
|
|
d.FieldStruct("entry", func(d *decode.D) {
|
2022-05-07 13:46:34 +03:00
|
|
|
tag := d.FieldU16("tag", tagNames, scalar.ActualHex)
|
2021-12-02 00:48:25 +03:00
|
|
|
typ := d.FieldU16("type", typeNames)
|
2020-06-08 03:29:51 +03:00
|
|
|
count := d.FieldU32("count")
|
|
|
|
valueOrByteOffset := d.FieldU32("value_offset")
|
|
|
|
|
2021-11-05 17:04:26 +03:00
|
|
|
if _, ok := typeNames[typ]; !ok {
|
2020-06-08 03:29:51 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
valueByteOffset := valueOrByteOffset
|
|
|
|
valueByteSize := typeByteSize[typ] * count
|
|
|
|
if valueByteSize <= 4 {
|
|
|
|
// if value fits in offset itself use offset to value_offset
|
|
|
|
valueByteOffset = uint64(d.Pos()/8) - 4
|
|
|
|
}
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case typ == LONG && (tag == ExifIFD || tag == GPSInfo):
|
|
|
|
ifdPos := valueOrByteOffset
|
|
|
|
pos := d.Pos()
|
|
|
|
d.SeekAbs(int64(ifdPos * 8))
|
|
|
|
|
|
|
|
switch tag {
|
|
|
|
case ExifIFD:
|
|
|
|
// TODO: exif tag names?
|
|
|
|
decodeIfd(d, &strips{}, tiffTagNames)
|
|
|
|
case GPSInfo:
|
|
|
|
decodeIfd(d, &strips{}, gpsInfoTagNames)
|
|
|
|
}
|
|
|
|
|
|
|
|
d.SeekAbs(pos)
|
|
|
|
default:
|
|
|
|
|
2021-11-05 17:04:26 +03:00
|
|
|
d.FieldArray("values", func(d *decode.D) {
|
2020-06-08 03:29:51 +03:00
|
|
|
switch {
|
|
|
|
case typ == UNDEFINED:
|
|
|
|
switch tag {
|
|
|
|
case InterColorProfile:
|
2021-09-16 16:29:11 +03:00
|
|
|
d.FieldFormatRange("icc", int64(valueByteOffset)*8, int64(valueByteSize)*8, tiffIccProfile, nil)
|
2020-06-08 03:29:51 +03:00
|
|
|
default:
|
2021-11-05 17:04:26 +03:00
|
|
|
d.RangeFn(int64(valueByteOffset*8), int64(valueByteSize*8), func(d *decode.D) {
|
|
|
|
d.FieldRawLen("value", d.BitsLeft())
|
|
|
|
})
|
2020-06-08 03:29:51 +03:00
|
|
|
}
|
|
|
|
case typ == ASCII:
|
2021-11-05 17:04:26 +03:00
|
|
|
d.RangeFn(int64(valueByteOffset*8), int64(valueByteSize*8), func(d *decode.D) {
|
2021-11-17 18:13:10 +03:00
|
|
|
d.FieldUTF8NullFixedLen("value", int(valueByteSize))
|
2020-06-08 03:29:51 +03:00
|
|
|
})
|
|
|
|
case typ == BYTE:
|
2021-11-05 17:04:26 +03:00
|
|
|
d.RangeFn(int64(valueByteOffset*8), int64(valueByteSize*8), func(d *decode.D) {
|
|
|
|
d.FieldRawLen("value", d.BitsLeft())
|
|
|
|
})
|
2020-06-08 03:29:51 +03:00
|
|
|
default:
|
2021-11-05 17:04:26 +03:00
|
|
|
d.RangeFn(int64(valueByteOffset*8), int64(valueByteSize*8), func(d *decode.D) {
|
2020-06-08 03:29:51 +03:00
|
|
|
for i := uint64(0); i < count; i++ {
|
|
|
|
switch typ {
|
|
|
|
// TODO: only some typ?
|
|
|
|
case BYTE:
|
|
|
|
d.FieldU8("value")
|
|
|
|
case SHORT:
|
|
|
|
v := d.FieldU16("value")
|
|
|
|
_ = v
|
|
|
|
switch tag {
|
|
|
|
case StripOffsets:
|
|
|
|
s.offsets = append(s.offsets, int64(v*8))
|
|
|
|
case StripByteCounts:
|
|
|
|
s.byteCounts = append(s.byteCounts, int64(v*8))
|
|
|
|
}
|
|
|
|
case LONG:
|
|
|
|
v := d.FieldU32("value")
|
|
|
|
_ = v
|
|
|
|
switch tag {
|
|
|
|
case StripOffsets:
|
|
|
|
s.offsets = append(s.offsets, int64(v*8))
|
|
|
|
case StripByteCounts:
|
|
|
|
s.byteCounts = append(s.byteCounts, int64(v*8))
|
|
|
|
}
|
|
|
|
case RATIONAL:
|
|
|
|
fieldRational(d, "value")
|
|
|
|
case SLONG:
|
|
|
|
d.FieldS32("value")
|
|
|
|
case SRATIONAL:
|
|
|
|
fieldSRational(d, "value")
|
|
|
|
default:
|
2021-11-17 18:26:13 +03:00
|
|
|
d.Errorf("unknown type")
|
2020-06-08 03:29:51 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
nextIfdOffset = int64(d.FieldU32("next_ifd"))
|
|
|
|
})
|
|
|
|
|
|
|
|
return nextIfdOffset
|
|
|
|
}
|
|
|
|
|
|
|
|
func tiffDecode(d *decode.D, in interface{}) interface{} {
|
2022-05-07 13:46:34 +03:00
|
|
|
endian := d.FieldU32("endian", endianNames, scalar.ActualHex)
|
2020-06-08 03:29:51 +03:00
|
|
|
|
|
|
|
switch endian {
|
|
|
|
case littleEndian:
|
|
|
|
d.Endian = decode.LittleEndian
|
|
|
|
case bigEndian:
|
|
|
|
d.Endian = decode.BigEndian
|
2021-11-05 17:04:26 +03:00
|
|
|
default:
|
2021-11-17 18:26:13 +03:00
|
|
|
d.Fatalf("unknown endian")
|
2020-06-08 03:29:51 +03:00
|
|
|
}
|
|
|
|
|
2021-11-05 17:04:26 +03:00
|
|
|
d.SeekRel(-4 * 8)
|
|
|
|
|
2021-12-12 13:18:29 +03:00
|
|
|
d.FieldUTF8("order", 2, d.AssertStr("II", "MM"))
|
|
|
|
d.FieldU16("integer_42", d.AssertU(42))
|
2021-11-05 17:04:26 +03:00
|
|
|
|
2020-06-08 03:29:51 +03:00
|
|
|
ifdOffset := int64(d.FieldU32("first_ifd"))
|
|
|
|
s := &strips{}
|
|
|
|
|
2021-12-12 13:18:29 +03:00
|
|
|
// to catch infinite loops
|
|
|
|
ifdSeen := map[int64]struct{}{}
|
|
|
|
|
2021-11-05 17:04:26 +03:00
|
|
|
d.FieldArray("ifds", func(d *decode.D) {
|
2020-06-08 03:29:51 +03:00
|
|
|
for ifdOffset != 0 {
|
2021-12-12 13:18:29 +03:00
|
|
|
if _, ok := ifdSeen[ifdOffset]; ok {
|
|
|
|
d.Fatalf("ifd loop detected for %d", ifdOffset)
|
|
|
|
}
|
|
|
|
ifdSeen[ifdOffset] = struct{}{}
|
2020-06-08 03:29:51 +03:00
|
|
|
d.SeekAbs(ifdOffset * 8)
|
|
|
|
ifdOffset = decodeIfd(d, s, tiffTagNames)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
if len(s.offsets) != len(s.byteCounts) {
|
|
|
|
// TODO: warning
|
|
|
|
} else {
|
2021-11-05 17:04:26 +03:00
|
|
|
d.FieldArray("strips", func(d *decode.D) {
|
2020-06-08 03:29:51 +03:00
|
|
|
for i := 0; i < len(s.offsets); i++ {
|
2021-11-05 17:04:26 +03:00
|
|
|
d.RangeFn(s.offsets[i], s.byteCounts[i], func(d *decode.D) {
|
|
|
|
d.FieldRawLen("strip", d.BitsLeft())
|
|
|
|
})
|
2020-06-08 03:29:51 +03:00
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|