ifd_builder_encode: Added tests for encoding child IBs to EXIF.

- Big step. This is the most complicated thing we can do.
- ifd_builder_encode: Need to debug encoding linked IFDs to EXIF.
- ifd_builder: Renamed `SetNextIfd()` to `SetNextIb()`.
- ifd_builder: Bugfix to size assertion on return of
  (IfdBuilderEncode).encodeIfdToBytes().
- ifd_enumerate: Rename PrintNode() to PrintTree().
- ifd_enumerate: Added DumpTree() (to return a list of strings).
pull/3/head
Dustin Oprea 2018-05-03 12:26:23 -04:00
parent b75f980bc4
commit 75b2c75c5a
5 changed files with 265 additions and 13 deletions

View File

@ -268,7 +268,7 @@ func NewIfdBuilderFromExistingChain(rootIfd *Ifd, exifData []byte) (rootIb *IfdB
newIb = NewIfdBuilder(ii, binary.BigEndian)
if lastIb != nil {
lastIb.SetNextIfd(newIb)
lastIb.SetNextIb(newIb)
}
if rootIb == nil {
@ -410,7 +410,7 @@ func (ib *IfdBuilder) DumpToStrings() (lines []string) {
return ib.dumpToStrings(ib, "", lines)
}
func (ib *IfdBuilder) SetNextIfd(nextIb *IfdBuilder) (err error) {
func (ib *IfdBuilder) SetNextIb(nextIb *IfdBuilder) (err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))

View File

@ -346,11 +346,16 @@ func (ibe *IfdByteEncoder) encodeAndAttachIfd(ib *IfdBuilder, ifdAddressableOffs
setNextIb := thisIb.nextIb != nil
tableAndAllocated, tableSize, allocatedDataSize, _, err := ibe.encodeIfdToBytes(ib, addressableOffset, nextIfdOffsetToWrite, setNextIb)
tableAndAllocated, tableSize, allocatedDataSize, childIfdSizes, err := ibe.encodeIfdToBytes(ib, addressableOffset, nextIfdOffsetToWrite, setNextIb)
log.PanicIf(err)
if len(tableAndAllocated) != int(tableSize + allocatedDataSize) {
log.Panicf("IFD table and data is not a consistent size: (%d) != (%d)", len(tableAndAllocated), tableSize + allocatedDataSize)
totalChildIfdSize := uint32(0)
for _, childIfdSize := range childIfdSizes {
totalChildIfdSize += childIfdSize
}
if len(tableAndAllocated) != int(tableSize + allocatedDataSize + totalChildIfdSize) {
log.Panicf("IFD table and data is not a consistent size: (%d) != (%d)", len(tableAndAllocated), tableSize + allocatedDataSize + totalChildIfdSize)
}
_, err = b.Write(tableAndAllocated)

View File

@ -742,6 +742,94 @@ func Test_IfdByteEncoder_EncodeToExif(t *testing.T) {
validateExifSimpleTestIb(exifData, t)
}
func Test_IfdByteEncoder_EncodeToExif_WithChild(t *testing.T) {
defer func() {
if state := recover(); state != nil {
err := log.Wrap(state.(error))
log.PrintErrorf(err, "Test failure.")
}
}()
ib := NewIfdBuilder(RootIi, TestDefaultByteOrder)
err := ib.AddFromConfig(0x000b, "asciivalue")
log.PanicIf(err)
err = ib.AddFromConfig(0x00ff, []uint16 { 0x1122 })
log.PanicIf(err)
// Add a child IB right in the middle.
childIb := NewIfdBuilder(ExifIi, EncodeDefaultByteOrder)
err = childIb.AddFromConfigWithName("ISOSpeedRatings", []uint16 { 0x1122 })
log.PanicIf(err)
err = childIb.AddFromConfigWithName("ISOSpeed", []uint32 { 0x33445566 })
log.PanicIf(err)
err = ib.AddChildIb(childIb)
log.PanicIf(err)
err = ib.AddFromConfig(0x0100, []uint32 { 0x33445566 })
log.PanicIf(err)
err = ib.AddFromConfig(0x013e, []Rational { { Numerator: 0x11112222, Denominator: 0x33334444 } })
log.PanicIf(err)
// TODO(dustin): !! Finish this.
// // Link to another IB (sibling relationship). The root IFD may occur twice
// // in some JPEGs (for thumbnail or FlashPix images).
// nextIb := NewIfdBuilder(RootIi, TestDefaultByteOrder)
// err = nextIb.AddFromConfig(0x0101, []uint32 { 0x11223344 })
// log.PanicIf(err)
// err = nextIb.AddFromConfig(0x0102, []uint16 { 0x5566 })
// log.PanicIf(err)
// ib.SetNextIb(nextIb)
// Encode.
ibe := NewIfdByteEncoder()
exifData, err := ibe.EncodeToExif(ib)
log.PanicIf(err)
// Parse.
e := NewExif()
_, index, err := e.Collect(exifData)
log.PanicIf(err)
// index.RootIfd.PrintTree(true)
tagsDump := index.RootIfd.DumpTree()
expected := []string {
"[ROOT]->[IFD] (0x0b)",
"[ROOT]->[IFD] (0xff)",
"[ROOT]->[IFD] (0x8769)",
"[IFD]->[Exif] (0x8827)",
"[IFD]->[Exif] (0x8833)",
"[ROOT]->[IFD] (0x100)",
"[ROOT]->[IFD] (0x13e)",
}
if reflect.DeepEqual(tagsDump, expected) != true {
t.Fatalf("IFD hierarchy not correct")
}
}
func ExampleIfdByteEncoder_EncodeToExif() {
// Construct an IFD.
@ -802,4 +890,79 @@ func ExampleIfdByteEncoder_EncodeToExif() {
// 5: IfdTagEntry<TAG-IFD=[] TAG-ID=(0x9201) TAG-TYPE=[SRATIONAL] UNIT-COUNT=(1)> [{286335522 858997828}]
}
// func ExampleIfdByteEncoder_EncodeToExif_WithChild() {
// // Construct an IFD.
// ib := NewIfdBuilder(RootIi, EncodeDefaultByteOrder)
// err := ib.AddFromConfigWithName("ProcessingSoftware", "asciivalue")
// log.PanicIf(err)
// err = ib.AddFromConfigWithName("DotRange", []uint8 { 0x11 })
// log.PanicIf(err)
// err = ib.AddFromConfigWithName("SubfileType", []uint16 { 0x2233 })
// log.PanicIf(err)
// // Add a child IB right in the middle.
// childIb := NewIfdBuilder(ExifIi, EncodeDefaultByteOrder)
// err = childIb.AddFromConfigWithName("ISOSpeedRatings", []uint16 { 0x1122 })
// log.PanicIf(err)
// err = childIb.AddFromConfigWithName("ISOSpeed", []uint32 { 0x33445566 })
// log.PanicIf(err)
// err = ib.AddChildIb(childIb)
// log.PanicIf(err)
// err = ib.AddFromConfigWithName("ImageWidth", []uint32 { 0x44556677 })
// log.PanicIf(err)
// err = ib.AddFromConfigWithName("WhitePoint", []Rational { { Numerator: 0x11112222, Denominator: 0x33334444 } })
// log.PanicIf(err)
// err = ib.AddFromConfigWithName("ShutterSpeedValue", []SignedRational { { Numerator: 0x11112222, Denominator: 0x33334444 } })
// log.PanicIf(err)
// // Encode it.
// ibe := NewIfdByteEncoder()
// exifData, err := ibe.EncodeToExif(ib)
// log.PanicIf(err)
// // Parse it so we can see it.
// e := NewExif()
// _, index, err := e.Collect(exifData)
// log.PanicIf(err)
// // addressableData is the byte-slice where the allocated data can be
// // resolved (where position 0x0 will correlate with offset 0x0).
// addressableData := exifData[ExifAddressableAreaStart:]
// for i, e := range index.RootIfd.Entries {
// value, err := e.Value(EncodeDefaultByteOrder, addressableData)
// log.PanicIf(err)
// fmt.Printf("%d: %s %v\n", i, e, value)
// }
// // Output:
// //
// // 0: IfdTagEntry<TAG-IFD=[] TAG-ID=(0x0b) TAG-TYPE=[ASCII] UNIT-COUNT=(11)> asciivalue
// // 1: IfdTagEntry<TAG-IFD=[] TAG-ID=(0x150) TAG-TYPE=[BYTE] UNIT-COUNT=(1)> [17]
// // 2: IfdTagEntry<TAG-IFD=[] TAG-ID=(0xff) TAG-TYPE=[SHORT] UNIT-COUNT=(1)> [8755]
// // 3: IfdTagEntry<TAG-IFD=[] TAG-ID=(0x100) TAG-TYPE=[LONG] UNIT-COUNT=(1)> [1146447479]
// // 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}]
// }
// TODO(dustin): !! Write test with both chained and child IFDs

View File

@ -87,7 +87,7 @@ func TestAdd(t *testing.T) {
}
}
func TestSetNextIfd(t *testing.T) {
func TestSetNextIb(t *testing.T) {
ib1 := NewIfdBuilder(RootIi, TestDefaultByteOrder)
ib2 := NewIfdBuilder(RootIi, TestDefaultByteOrder)
@ -95,7 +95,7 @@ func TestSetNextIfd(t *testing.T) {
t.Fatalf("Next-IFD for IB1 not initially terminal.")
}
err := ib1.SetNextIfd(ib2)
err := ib1.SetNextIb(ib2)
log.PanicIf(err)
if ib1.nextIb != ib2 {

View File

@ -352,7 +352,7 @@ func (ifd Ifd) Identity() IfdIdentity {
return ifd.Ii
}
func (ifd Ifd) printNode(level int, nextLink bool) {
func (ifd Ifd) printTree(level int, printTags bool, nextLink bool) {
indent := strings.Repeat(" ", level * 2)
prefix := " "
@ -360,21 +360,105 @@ func (ifd Ifd) printNode(level int, nextLink bool) {
prefix = ">"
}
fmt.Printf("%s%s%s\n", indent, prefix, ifd)
fmt.Printf("%s%sIFD: %s\n", indent, prefix, ifd)
// Quickly create an index of the child-IFDs.
childIfdIndex := make(map[string]*Ifd)
for _, childIfd := range ifd.Children {
childIfd.printNode(level + 1, false)
childIfdIndex[childIfd.Ii.IfdName] = childIfd
}
// Now, print the tags while also descending to child-IFDS as we encounter them.
ifdsFoundCount := 0
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)
tagName := ""
if err == nil {
tagName = it.Name
}
fmt.Printf("%s - TAG: %s NAME=[%s]\n", indent, tag, tagName)
}
}
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.printTree(level + 1, printTags, 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.printNode(level, true)
ifd.NextIfd.printTree(level, printTags, true)
}
}
func (ifd Ifd) PrintTree() {
ifd.printNode(0, false)
// PrintTree prints the IFD hierarchy.
func (ifd Ifd) PrintTree(printTags bool) {
ifd.printTree(0, printTags, false)
}
func (ifd Ifd) dumpTree(tagsDump []string) []string {
if tagsDump == nil {
tagsDump = make([]string, 0)
}
// Quickly create an index of the child-IFDs.
childIfdIndex := make(map[string]*Ifd)
for _, childIfd := range ifd.Children {
childIfdIndex[childIfd.Ii.IfdName] = childIfd
}
ifdsFoundCount := 0
for _, tag := range ifd.Entries {
if ifd.ParentIfd != nil {
tagsDump = append(tagsDump, fmt.Sprintf("[%s]->[%s] (0x%02x)", ifd.ParentIfd.Ii.IfdName, tag.Ii.IfdName, tag.TagId))
} else {
tagsDump = append(tagsDump, fmt.Sprintf("[ROOT]->[%s] (0x%02x)", tag.Ii.IfdName, tag.TagId))
}
if tag.ChildIfdName != "" {
ifdsFoundCount++
childIfd, found := childIfdIndex[tag.ChildIfdName]
if found != true {
log.Panicf("alien child IFD referenced by a tag: [%s]", tag.ChildIfdName)
}
tagsDump = childIfd.dumpTree(tagsDump)
}
}
if len(ifd.Children) != ifdsFoundCount {
log.Panicf("have one or more dangling child IFDs: (%d) != (%d)", len(ifd.Children), ifdsFoundCount)
}
return tagsDump
}
// DumpTree returns a list of strings describing the IFD hierarchy.
func (ifd Ifd) DumpTree() []string {
return ifd.dumpTree(nil)
}
type QueuedIfd struct {
Ii IfdIdentity