ifd_builder_encoder: Fixed subtle but detrimental bugs.

- ifd_builder_encode
  - Bugfix: We were assigning the "next" IFD in correctly (so, chaining
    was broken).
  - Bugfix: We weren't using the right value when *writing* the "next"
    IFD offset (so, chaining was, again, broken).
    - The same IFD was showing as both a child and a sibling, so we were
      seeing the EXIF IFD also being chained-to by the first root IFD
      instead of the *actual* sibling, but only after encoding and then
      decoding (so, this had to be tracked down in the encoding
      semantics, which was non-trivial).

  - Added a test for the stuff that we fixed.
  - Implemented journaling, where we can dump a hierarchically-formatted
    list of operations involved in a completed encode.

- ifd_enumerate: Added (Ifd).TagValueBytes() to return the raw tag-
  value's bytes (rather than just having (Ifd).TagValue(), which returns
  the value marshaled into a string). Good for comparing raw encoded
  and decoded values without imposing interpretation (which gets us into
  trouble with unknown-type tags whose values are non-standard and
  undocumentedand, therefore, not actually parseable).

- Rewired a lot of print and dump functions.
  - For naming-specificity as well as functionality.

- Removed from debugging.
pull/3/head
Dustin Oprea 2018-05-21 13:14:42 -04:00
parent 6207bd6200
commit 3cee9b956b
4 changed files with 452 additions and 108 deletions

View File

@ -289,27 +289,23 @@ func NewIfdBuilderWithExistingIfd(ifd *Ifd) (ib *IfdBuilder) {
// NewIfdBuilderFromExistingChain creates a chain of IB instances from an
// IFD chain generated from real data.
func NewIfdBuilderFromExistingChain(rootIfd *Ifd, exifData []byte) (rootIb *IfdBuilder) {
func NewIfdBuilderFromExistingChain(rootIfd *Ifd, exifData []byte) (firstIb *IfdBuilder) {
itevr := NewIfdTagEntryValueResolver(exifData, rootIfd.ByteOrder)
// TODO(dustin): !! When we actually write the code to flatten the IB to bytes, make sure to skip the tags that have a nil value (which will happen when we add-from-exsting without a resolver instance).
var newIb *IfdBuilder
var lastIb *IfdBuilder
i := 0
for thisExistingIfd := rootIfd; thisExistingIfd != nil; thisExistingIfd = thisExistingIfd.NextIfd {
lastIb := newIb
ii := thisExistingIfd.Identity()
newIb = NewIfdBuilder(ii, rootIfd.ByteOrder)
if lastIb != nil {
fmt.Printf("SETTING NEXT-IB: %s\n", newIb)
newIb := NewIfdBuilder(ii, thisExistingIfd.ByteOrder)
if firstIb == nil {
firstIb = newIb
} else {
lastIb.SetNextIb(newIb)
}
if rootIb == nil {
rootIb = newIb
}
err := newIb.AddTagsFromExisting(thisExistingIfd, itevr, nil, nil)
log.PanicIf(err)
@ -321,9 +317,12 @@ func NewIfdBuilderFromExistingChain(rootIfd *Ifd, exifData []byte) (rootIb *IfdB
err = newIb.AddChildIb(childIb)
log.PanicIf(err)
}
lastIb = newIb
i++
}
return rootIb
return firstIb
}
func (ib *IfdBuilder) String() string {
@ -333,66 +332,110 @@ func (ib *IfdBuilder) String() string {
nextIfdPhrase = ib.nextIb.ii.IfdName
}
return fmt.Sprintf("IfdBuilder<NAME=[%s] TAG-ID=(0x%02x) BO=[%s] COUNT=(%d) OFFSET=(0x%04x) NEXT-IFD=(0x%04x)>", ib.ii, ib.ifdTagId, ib.byteOrder, len(ib.tags), ib.existingOffset, nextIfdPhrase)
return fmt.Sprintf("IfdBuilder<PARENT-IFD=[%s] IFD=[%s] TAG-ID=(0x%02x) COUNT=(%d) OFF=(0x%04x) NEXT-IFD=(0x%04x)>", ib.ii.ParentIfdName, ib.ii.IfdName, ib.ifdTagId, len(ib.tags), ib.existingOffset, nextIfdPhrase)
}
func (ib *IfdBuilder) Tags() (tags []builderTag) {
return ib.tags
}
func (ib *IfdBuilder) dump(levels int) {
indent := strings.Repeat(" ", levels * 4)
if levels == 0 {
fmt.Printf("%sIFD: %s\n", indent, ib)
} else {
fmt.Printf("%sChild IFD: %s\n", indent, ib)
}
func (ib *IfdBuilder) printTagTree(levels int) {
indent := strings.Repeat(" ", levels * 2)
ti := NewTagIndex()
if len(ib.tags) > 0 {
fmt.Printf("\n")
for i, tag := range ib.tags {
_, isChildIb := IfdTagNameWithId(ib.ii.IfdName, tag.tagId)
tagName := ""
// If a normal tag (not a child IFD) get the name.
if isChildIb == true {
tagName = "<Child IFD>"
} else {
it, err := ti.Get(tag.ii, tag.tagId)
if log.Is(err, ErrTagNotFound) == true {
tagName = "<UNKNOWN>"
} else if err != nil {
log.Panic(err)
} else {
tagName = it.Name
}
}
fmt.Printf("%s (%d): [%s] %s\n", indent, i, tagName, tag)
if isChildIb == true {
if tag.value.IsIb() == false {
log.Panicf("tag-ID (0x%02x) is an IFD but the tag value is not an IB instance: %v", tag.tagId, tag)
}
fmt.Printf("\n")
childIb := tag.value.Ib()
childIb.dump(levels + 1)
}
i := 0
for currentIb := ib; currentIb != nil; currentIb = currentIb.nextIb {
prefix := " "
if i > 0 {
prefix = ">"
}
fmt.Printf("\n")
if levels == 0 {
fmt.Printf("%s%sIFD: %s\n", indent, prefix, currentIb)
} else {
fmt.Printf("%s%sChild IFD: %s\n", indent, prefix, currentIb)
}
if len(currentIb.tags) > 0 {
fmt.Printf("\n")
for i, tag := range currentIb.tags {
_, isChildIb := IfdTagNameWithId(currentIb.ii.IfdName, tag.tagId)
tagName := ""
// If a normal tag (not a child IFD) get the name.
if isChildIb == true {
tagName = "<Child IFD>"
} else {
it, err := ti.Get(tag.ii, tag.tagId)
if log.Is(err, ErrTagNotFound) == true {
tagName = "<UNKNOWN>"
} else if err != nil {
log.Panic(err)
} else {
tagName = it.Name
}
}
fmt.Printf("%s (%d): [%s] %s\n", indent, i, tagName, tag)
if isChildIb == true {
if tag.value.IsIb() == false {
log.Panicf("tag-ID (0x%02x) is an IFD but the tag value is not an IB instance: %v", tag.tagId, tag)
}
fmt.Printf("\n")
childIb := tag.value.Ib()
childIb.printTagTree(levels + 1)
}
}
fmt.Printf("\n")
}
i++
}
}
func (ib *IfdBuilder) Dump() {
ib.dump(0)
func (ib *IfdBuilder) PrintTagTree() {
ib.printTagTree(0)
}
func (ib *IfdBuilder) printIfdTree(levels int) {
indent := strings.Repeat(" ", levels * 2)
i := 0
for currentIb := ib; currentIb != nil; currentIb = currentIb.nextIb {
prefix := " "
if i > 0 {
prefix = ">"
}
fmt.Printf("%s%s%s\n", indent, prefix,currentIb)
if len(currentIb.tags) > 0 {
for _, tag := range currentIb.tags {
_, isChildIb := IfdTagNameWithId(currentIb.ii.IfdName, tag.tagId)
if isChildIb == true {
if tag.value.IsIb() == false {
log.Panicf("tag-ID (0x%02x) is an IFD but the tag value is not an IB instance: %v", tag.tagId, tag)
}
childIb := tag.value.Ib()
childIb.printIfdTree(levels + 1)
}
}
}
i++
}
}
func (ib *IfdBuilder) PrintIfdTree() {
ib.printIfdTree(0)
}
func (ib *IfdBuilder) dumpToStrings(thisIb *IfdBuilder, prefix string, lines []string) (linesOutput []string) {
@ -789,11 +832,6 @@ func (ib *IfdBuilder) SetFromConfigWithName(tagName string, value interface{}) (
i, err := ib.Find(bt.tagId)
log.PanicIf(err)
fmt.Printf("FOUND [%s] AT POSITION (%d) OF (%d)\n", tagName, i + 1, len(ib.tags))
fmt.Printf("ORIGINAL TAG: %s\n", ib.tags[i])
fmt.Printf("UPDATED TAG: %s\n", bt)
ib.tags[i] = bt
return nil

View File

@ -3,6 +3,7 @@ package exif
import (
"bytes"
"fmt"
"strings"
"encoding/binary"
@ -121,10 +122,18 @@ func (ida *ifdDataAllocator) Bytes() []byte {
// out all of the allocations and indirection that is required for extended
// data.
type IfdByteEncoder struct {
// journal holds a list of actions taken while encoding.
journal [][3]string
}
func NewIfdByteEncoder() (ibe *IfdByteEncoder) {
return new(IfdByteEncoder)
return &IfdByteEncoder{
journal: make([][3]string, 0),
}
}
func (ibe *IfdByteEncoder) Journal() [][3]string {
return ibe.journal
}
func (ibe *IfdByteEncoder) TableSize(entryCount int) uint32 {
@ -132,6 +141,61 @@ func (ibe *IfdByteEncoder) TableSize(entryCount int) uint32 {
return uint32(2) + (IfdTagEntrySize * uint32(entryCount)) + uint32(4)
}
func (ibe *IfdByteEncoder) pushToJournal(where, direction, format string, args ...interface{}) {
event := [3]string {
direction,
where,
fmt.Sprintf(format, args...),
}
ibe.journal = append(ibe.journal, event)
}
// PrintJournal prints a hierarchical representation of the steps taken during
// encoding.
func (ibe *IfdByteEncoder) PrintJournal() {
maxWhereLength := 0
for _, event := range ibe.journal {
where := event[1]
len_ := len(where)
if len_ > maxWhereLength {
maxWhereLength = len_
}
}
level := 0
for i, event := range ibe.journal {
direction := event[0]
where := event[1]
message := event[2]
if direction != ">" && direction != "<" && direction != "-" {
log.Panicf("journal operation not valid: [%s]", direction)
}
if direction == "<" {
if level <= 0 {
log.Panicf("journal operations unbalanced (too many closes)")
}
level--
}
indent := strings.Repeat(" ", level)
fmt.Printf("%3d %s%s %s: %s\n", i, indent, direction, where, message)
if direction == ">" {
level++
}
}
if level != 0 {
log.Panicf("journal operations unbalanced (too many opens)")
}
}
// encodeTagToBytes encodes the given tag to a byte stream. If
// `nextIfdOffsetToWrite` is more than (0), recurse into child IFDs
// (`nextIfdOffsetToWrite` is required in order for them to know where the its
@ -144,12 +208,6 @@ func (ibe *IfdByteEncoder) encodeTagToBytes(ib *IfdBuilder, bt *builderTag, bw *
}
}()
// TODO(dustin): !! This function is running more times than it should.
if bt.tagId == 0x9286 {
fmt.Printf("ENCODING/WRITING: %s\n", bt)
}
// Write tag-ID.
err = bw.WriteUint16(bt.tagId)
log.PanicIf(err)
@ -188,24 +246,12 @@ func (ibe *IfdByteEncoder) encodeTagToBytes(ib *IfdBuilder, bt *builderTag, bw *
// Write four-byte value/offset.
if len_ > 4 {
if bt.tagId == 0x9286 {
fmt.Printf("USERCOMMENTS: Allocating: [%s]\n", string(valueBytes[:5]))
}
offset, err := ida.Allocate(valueBytes)
log.PanicIf(err)
if bt.tagId == 0x9286 {
fmt.Printf("USERCOMMENTS: Offset: (%d) [0x%02x]\n", offset, offset)
}
err = bw.WriteUint32(offset)
log.PanicIf(err)
} else {
if bt.tagId == 0x9286 {
fmt.Printf("USERCOMMENTS: Not allocating: [%v]\n", valueBytes)
}
fourBytes := make([]byte, 4)
copy(fourBytes, valueBytes)
@ -224,10 +270,14 @@ func (ibe *IfdByteEncoder) encodeTagToBytes(ib *IfdBuilder, bt *builderTag, bw *
if nextIfdOffsetToWrite > 0 {
var err error
ibe.pushToJournal("encodeTagToBytes", ">", "[%s]->[%s]", ib.ii.IfdName, bt.value.Ib().ii.IfdName)
// Create the block of IFD data and everything it requires.
childIfdBlock, err = ibe.encodeAndAttachIfd(bt.value.Ib(), nextIfdOffsetToWrite)
log.PanicIf(err)
ibe.pushToJournal("encodeTagToBytes", "<", "[%s]->[%s]", bt.value.Ib().ii.IfdName, ib.ii.IfdName)
// Use the next-IFD offset for it. The IFD will actually get
// attached after we return.
err = bw.WriteUint32(nextIfdOffsetToWrite)
@ -237,6 +287,8 @@ func (ibe *IfdByteEncoder) encodeTagToBytes(ib *IfdBuilder, bt *builderTag, bw *
// No child-IFDs are to be allocated. Finish the entry with a NULL
// pointer.
ibe.pushToJournal("encodeTagToBytes", "-", "*Not* descending to child: [%s]", bt.value.Ib().ii.IfdName)
err = bw.WriteUint32(0)
log.PanicIf(err)
}
@ -261,6 +313,8 @@ func (ibe *IfdByteEncoder) encodeIfdToBytes(ib *IfdBuilder, ifdAddressableOffset
}
}()
ibe.pushToJournal("encodeIfdToBytes", ">", "%s", ib)
tableSize = ibe.TableSize(len(ib.tags))
b := new(bytes.Buffer)
@ -309,10 +363,18 @@ func (ibe *IfdByteEncoder) encodeIfdToBytes(ib *IfdBuilder, ifdAddressableOffset
// Write address of next IFD in chain. This will be the original
// allocation offset plus the size of everything we have allocated for
// this IFD and its child-IFDs.
//
// It is critical that this number is stepped properly. We experienced
// an issue whereby it first looked like we were duplicating the IFD and
// then that we were duplicating the tags in the wrong IFD, and then
// finally we determined that the next-IFD offset for the first IFD was
// accidentally pointing back to the EXIF IFD, so we were visiting it
// twice when visiting through the tags after decoding. It was an
// expensive bug to find.
offset := ifdAddressableOffset + dataSize
ibe.pushToJournal("encodeIfdToBytes", "-", "Setting 'next' IFD to (0x%08x).", nextIfdOffsetToWrite)
err := bw.WriteUint32(offset)
err := bw.WriteUint32(nextIfdOffsetToWrite)
log.PanicIf(err)
} else {
err := bw.WriteUint32(0)
@ -336,6 +398,8 @@ func (ibe *IfdByteEncoder) encodeIfdToBytes(ib *IfdBuilder, ifdAddressableOffset
log.PanicIf(err)
}
ibe.pushToJournal("encodeIfdToBytes", "<", "%s", ib)
return b.Bytes(), tableSize, dataSize, childIfdSizes, nil
}
@ -347,6 +411,8 @@ func (ibe *IfdByteEncoder) encodeAndAttachIfd(ib *IfdBuilder, ifdAddressableOffs
}
}()
ibe.pushToJournal("encodeAndAttachIfd", ">", "%s", ib)
if len(ib.tags) == 0 {
log.Panicf("trying to encode an IfdBuilder that doesn't have any tags")
}
@ -354,12 +420,19 @@ func (ibe *IfdByteEncoder) encodeAndAttachIfd(ib *IfdBuilder, ifdAddressableOffs
b := new(bytes.Buffer)
nextIfdOffsetToWrite := uint32(0)
i := 0
for thisIb := ib; thisIb != nil; thisIb = thisIb.nextIb {
// Do a dry-run in order to pre-determine its size requirement.
_, tableSize, allocatedDataSize, _, err := ibe.encodeIfdToBytes(ib, ifdAddressableOffset, 0, false)
ibe.pushToJournal("encodeAndAttachIfd", ">", "Beginning encoding process: (%d) [%s] NEXT-IFD-OFFSET-TO-WRITE=(0x%08x)", i, thisIb.ii.IfdName, nextIfdOffsetToWrite)
ibe.pushToJournal("encodeAndAttachIfd", ">", "Calculating size: (%d) [%s]", i, thisIb.ii.IfdName)
_, tableSize, allocatedDataSize, _, err := ibe.encodeIfdToBytes(thisIb, ifdAddressableOffset, 0, false)
log.PanicIf(err)
ibe.pushToJournal("encodeAndAttachIfd", "<", "Finished calculating size: (%d) [%s]", i, thisIb.ii.IfdName)
ifdAddressableOffset += tableSize
nextIfdOffsetToWrite = ifdAddressableOffset + allocatedDataSize
@ -368,9 +441,15 @@ func (ibe *IfdByteEncoder) encodeAndAttachIfd(ib *IfdBuilder, ifdAddressableOffs
setNextIb := thisIb.nextIb != nil
tableAndAllocated, tableSize, allocatedDataSize, childIfdSizes, err := ibe.encodeIfdToBytes(ib, ifdAddressableOffset, nextIfdOffsetToWrite, setNextIb)
ibe.pushToJournal("encodeAndAttachIfd", ">", "Encoding starting: (%d) [%s] NEXT-IFD-OFFSET-TO-WRITE=(0x%08x)", i, thisIb.ii.IfdName, nextIfdOffsetToWrite)
tableAndAllocated, tableSize, allocatedDataSize, childIfdSizes, err :=
ibe.encodeIfdToBytes(thisIb, ifdAddressableOffset, nextIfdOffsetToWrite, setNextIb)
log.PanicIf(err)
ibe.pushToJournal("encodeAndAttachIfd", "<", "Encoding done: (%d) [%s]", i, thisIb.ii.IfdName)
totalChildIfdSize := uint32(0)
for _, childIfdSize := range childIfdSizes {
totalChildIfdSize += childIfdSize
@ -383,10 +462,17 @@ func (ibe *IfdByteEncoder) encodeAndAttachIfd(ib *IfdBuilder, ifdAddressableOffs
_, err = b.Write(tableAndAllocated)
log.PanicIf(err)
// TODO(dustin): !! The core problem is the offset being the same for two IFDs.
// Advance past what we've allocated, thus far.
ifdAddressableOffset = nextIfdOffsetToWrite
ibe.pushToJournal("encodeAndAttachIfd", "<", "Finishing encoding process: (%d) [%s] [FINAL:] NEXT-IFD-OFFSET-TO-WRITE=(0x%08x)", i, ib.ii.IfdName, nextIfdOffsetToWrite)
i++
}
ibe.pushToJournal("encodeAndAttachIfd", "<", "%s", ib)
return b.Bytes(), nil
}

View File

@ -6,6 +6,8 @@ import (
"reflect"
"fmt"
"strings"
"path"
"sort"
"github.com/dsoprea/go-logging"
)
@ -925,3 +927,113 @@ func ExampleIfdByteEncoder_EncodeToExif() {
// 4: IfdTagEntry<TAG-IFD=[] TAG-ID=(0x13e) TAG-TYPE=[RATIONAL] UNIT-COUNT=(1)> [[{286335522 858997828}]]
// 5: IfdTagEntry<TAG-IFD=[] TAG-ID=(0x9201) TAG-TYPE=[SRATIONAL] UNIT-COUNT=(1)> [[{286335522 858997828}]]
}
func TestNewIfdBuilderFromExistingChain_RealData(t *testing.T) {
filepath := path.Join(assetsPath, "NDM_8901.jpg")
e := NewExif()
rawExif, err := e.SearchAndExtractExif(filepath)
log.PanicIf(err)
// Decode from binary.
_, index, err := e.Collect(rawExif)
log.PanicIf(err)
originalTags := index.RootIfd.DumpTags()
// Encode back to binary.
ibe := NewIfdByteEncoder()
rootIb := NewIfdBuilderFromExistingChain(index.RootIfd, rawExif)
updatedExif, err := ibe.EncodeToExif(rootIb)
log.PanicIf(err)
// Parse again.
_, index, err = e.Collect(updatedExif)
log.PanicIf(err)
recoveredTags := index.RootIfd.DumpTags()
// Validate that all of the same IFDs were presented.
originalIfdTags := make([][2]interface{}, 0)
for _, ite := range originalTags {
if ite.ChildIfdName != "" {
originalIfdTags = append(originalIfdTags, [2]interface{} { ite.Ii, ite.TagId })
}
}
recoveredIfdTags := make([][2]interface{}, 0)
for _, ite := range recoveredTags {
if ite.ChildIfdName != "" {
recoveredIfdTags = append(recoveredIfdTags, [2]interface{} { ite.Ii, ite.TagId })
}
}
if reflect.DeepEqual(recoveredIfdTags, originalIfdTags) != true {
fmt.Printf("Original IFD tags:\n\n")
for i, x := range originalIfdTags {
fmt.Printf(" %02d %v\n", i, x)
}
fmt.Printf("\nRecovered IFD tags:\n\n")
for i, x := range recoveredIfdTags {
fmt.Printf(" %02d %v\n", i, x)
}
fmt.Printf("\n")
t.Fatalf("Recovered IFD tags are not correct.")
}
// Validate that all of the tags owned by the IFDs were presented. The tags
// might not be in the same order since the IFD tags are allocated after
// the non-IFD ones.
originalNonIfdTags := make([]string, 0)
for _, ite := range originalTags {
if ite.ChildIfdName == "" {
originalNonIfdTags = append(originalNonIfdTags, fmt.Sprintf("%s 0x%02x", ite.Ii, ite.TagId))
}
}
sort.StringSlice(originalNonIfdTags).Sort()
recoveredNonIfdTags := make([]string, 0)
for _, ite := range recoveredTags {
if ite.ChildIfdName == "" {
recoveredNonIfdTags = append(recoveredNonIfdTags, fmt.Sprintf("%s 0x%02x", ite.Ii, ite.TagId))
}
}
sort.StringSlice(recoveredNonIfdTags).Sort()
if reflect.DeepEqual(recoveredNonIfdTags, originalNonIfdTags) != true {
fmt.Printf("Original non-IFD tags:\n\n")
for i, x := range originalNonIfdTags {
fmt.Printf(" %02d %v\n", i, x)
}
fmt.Printf("\nRecovered non-IFD tags:\n\n")
for i, x := range recoveredNonIfdTags {
fmt.Printf(" %02d %v\n", i, x)
}
fmt.Printf("\n")
t.Fatalf("Recovered non-IFD tags are not correct.")
}
}

View File

@ -228,7 +228,7 @@ func (ie *IfdEnumerate) ParseIfd(ii IfdIdentity, ifdIndex int, ite *IfdTagEnumer
return nextIfdOffset, entries, nil
}
// Scan enumerates the different EXIF blocks (called IFDs).
// Scan enumerates the different EXIF's IFD blocks.
func (ie *IfdEnumerate) scan(ii IfdIdentity, ifdOffset uint32, visitor TagVisitor) (err error) {
defer func() {
if state := recover(); state != nil {
@ -270,6 +270,7 @@ func (ie *IfdEnumerate) Scan(ifdOffset uint32, visitor TagVisitor) (err error) {
}
// Ifd represents a single parsed IFD.
type Ifd struct {
// This is just for convenience, just so that we can easily get the values
// and not involve other projects in semantics that they won't otherwise
@ -307,6 +308,19 @@ func (ifd *Ifd) TagValue(ite *IfdTagEntry) (value interface{}, err error) {
return value, nil
}
func (ifd *Ifd) TagValueBytes(ite *IfdTagEntry) (value []byte, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
value, err = ite.ValueBytes(ifd.addressableData, ifd.ByteOrder)
log.PanicIf(err)
return value, nil
}
// FindTagWithId returns a list of tags (usually just zero or one) that match
// the given tag ID. This is efficient.
func (ifd Ifd) FindTagWithId(tagId uint16) (results []*IfdTagEntry, err error) {
@ -363,14 +377,61 @@ func (ifd Ifd) String() string {
parentOffset = ifd.ParentIfd.Offset
}
return fmt.Sprintf("IFD<ID=(%d) N=[%s] IDX=(%d) OFF=(0x%04x) COUNT=(%d) CHILDREN=(%d) PARENT=(0x%04x) NEXT-IFD=(0x%04x)", ifd.Id, ifd.Name, ifd.Index, ifd.Offset, len(ifd.Entries), len(ifd.Children), parentOffset, ifd.NextIfdOffset)
return fmt.Sprintf("Ifd<ID=(%d) PARENT-IFD=[%s] IFD=[%s] IDX=(%d) COUNT=(%d) OFF=(0x%04x) CHILDREN=(%d) PARENT=(0x%04x) NEXT-IFD=(0x%04x)", ifd.Id, ifd.Ii.ParentIfdName, ifd.Ii.IfdName, ifd.Index, len(ifd.Entries), ifd.Offset, len(ifd.Children), parentOffset, ifd.NextIfdOffset)
}
func (ifd Ifd) Identity() IfdIdentity {
return ifd.Ii
}
func (ifd Ifd) printTree(level int, printTags bool, nextLink bool) {
func (ifd Ifd) dumpTags(tags []*IfdTagEntry) []*IfdTagEntry {
if tags == nil {
tags = make([]*IfdTagEntry, 0)
}
// Quickly create an index of the child-IFDs.
childIfdIndex := make(map[string]*Ifd)
for _, childIfd := range ifd.Children {
childIfdIndex[childIfd.Ii.IfdName] = childIfd
}
// Now, print the tags while also descending to child-IFDS as we encounter them.
ifdsFoundCount := 0
for _, tag := range ifd.Entries {
tags = append(tags, tag)
if tag.ChildIfdName != "" {
ifdsFoundCount++
childIfd, found := childIfdIndex[tag.ChildIfdName]
if found != true {
log.Panicf("alien child IFD referenced by a tag: [%s]", tag.ChildIfdName)
}
tags = childIfd.dumpTags(tags)
}
}
if len(ifd.Children) != ifdsFoundCount {
log.Panicf("have one or more dangling child IFDs: (%d) != (%d)", len(ifd.Children), ifdsFoundCount)
}
if ifd.NextIfd != nil {
tags = ifd.NextIfd.dumpTags(tags)
}
return tags
}
// PrintTagTree prints the IFD hierarchy.
func (ifd Ifd) DumpTags() []*IfdTagEntry {
return ifd.dumpTags(nil)
}
func (ifd Ifd) printTagTree(level int, nextLink bool) {
indent := strings.Repeat(" ", level * 2)
prefix := " "
@ -393,19 +454,17 @@ func (ifd Ifd) printTree(level int, printTags bool, nextLink bool) {
ti := NewTagIndex()
for _, tag := range ifd.Entries {
if printTags == true {
if tag.ChildIfdName != "" {
fmt.Printf("%s - TAG: %s\n", indent, tag)
} else {
it, err := ti.Get(ifd.Identity(), tag.TagId)
if tag.ChildIfdName != "" {
fmt.Printf("%s - TAG: %s\n", indent, tag)
} else {
it, err := ti.Get(ifd.Identity(), tag.TagId)
tagName := ""
if err == nil {
tagName = it.Name
}
fmt.Printf("%s - TAG: %s NAME=[%s]\n", indent, tag, tagName)
tagName := ""
if err == nil {
tagName = it.Name
}
fmt.Printf("%s - TAG: %s NAME=[%s]\n", indent, tag, tagName)
}
if tag.ChildIfdName != "" {
@ -416,7 +475,7 @@ func (ifd Ifd) printTree(level int, printTags bool, nextLink bool) {
log.Panicf("alien child IFD referenced by a tag: [%s]", tag.ChildIfdName)
}
childIfd.printTree(level + 1, printTags, false)
childIfd.printTagTree(level + 1, false)
}
}
@ -425,13 +484,61 @@ func (ifd Ifd) printTree(level int, printTags bool, nextLink bool) {
}
if ifd.NextIfd != nil {
ifd.NextIfd.printTree(level, printTags, true)
ifd.NextIfd.printTagTree(level, true)
}
}
// PrintTree prints the IFD hierarchy.
func (ifd Ifd) PrintTree(printTags bool) {
ifd.printTree(0, printTags, false)
// PrintTagTree prints the IFD hierarchy.
func (ifd Ifd) PrintTagTree() {
ifd.printTagTree(0, false)
}
func (ifd Ifd) printIfdTree(level int, nextLink bool) {
indent := strings.Repeat(" ", level * 2)
prefix := " "
if nextLink {
prefix = ">"
}
fmt.Printf("%s%s%s\n", indent, prefix, ifd)
// Quickly create an index of the child-IFDs.
childIfdIndex := make(map[string]*Ifd)
for _, childIfd := range ifd.Children {
childIfdIndex[childIfd.Ii.IfdName] = childIfd
}
// Now, print the tags while also descending to child-IFDS as we encounter them.
ifdsFoundCount := 0
for _, tag := range ifd.Entries {
if tag.ChildIfdName != "" {
ifdsFoundCount++
childIfd, found := childIfdIndex[tag.ChildIfdName]
if found != true {
log.Panicf("alien child IFD referenced by a tag: [%s]", tag.ChildIfdName)
}
childIfd.printIfdTree(level + 1, false)
}
}
if len(ifd.Children) != ifdsFoundCount {
log.Panicf("have one or more dangling child IFDs: (%d) != (%d)", len(ifd.Children), ifdsFoundCount)
}
if ifd.NextIfd != nil {
ifd.NextIfd.printIfdTree(level, true)
}
}
// PrintIfdTree prints the IFD hierarchy.
func (ifd Ifd) PrintIfdTree() {
ifd.printIfdTree(0, false)
}
func (ifd Ifd) dumpTree(tagsDump []string, level int) []string {
@ -545,6 +652,7 @@ func (ie *IfdEnumerate) Collect(rootIfdOffset uint32) (index IfdIndex, err error
name := ii.IfdName
index := queue[0].Index
offset := queue[0].Offset
parentIfd := queue[0].Parent
queue = queue[1:]
@ -617,13 +725,13 @@ func (ie *IfdEnumerate) Collect(rootIfdOffset uint32) (index IfdIndex, err error
continue
}
childId := IfdIdentity{
childIi := IfdIdentity{
ParentIfdName: name,
IfdName: entry.ChildIfdName,
}
qi := QueuedIfd{
Ii: childId,
Ii: childIi,
Index: 0,
Offset: entry.ValueOffset,
Parent: &ifd,