thumbnails: Reimplemented to use normal decode flow.

- We added a trick to overcome the fact that the tag is a slice of longs
  rather than bytes (and would usually cause check errors and alignment
  problems), but this allows us to use the normal allocation and
  decoding flows that are used by normal tags.
  - Made things simpler, but we're still mis-encoding the
    thumbnail, somehow/somewhere.
pull/3/head
Dustin Oprea 2018-05-24 15:29:18 -04:00
parent c93f37a85d
commit 75ff008365
4 changed files with 209 additions and 73 deletions

View File

@ -1,5 +1,10 @@
package exif
// NOTES:
//
// The thumbnail offset and length tags shouldn't be set directly. Use the
// (*IfdBuilder).SetThumbnail() method instead.
import (
"errors"
"fmt"
@ -246,6 +251,10 @@ type IfdBuilder struct {
// nextIb represents the next link if we're chaining to another.
nextIb *IfdBuilder
// thumbnailData is populated with thumbnail data if there was thumbnail
// data. Otherwise, it's nil.
thumbnailData []byte
}
func NewIfdBuilder(ii IfdIdentity, byteOrder binary.ByteOrder) (ib *IfdBuilder) {
@ -339,6 +348,66 @@ func (ib *IfdBuilder) Tags() (tags []builderTag) {
return ib.tags
}
// SetThumbnail sets thumbnail data.
//
// NOTES:
//
// - We don't manage any facet of the thumbnail data. This is the
// responsibility of the user/developer.
// - This method will fail unless the thumbnail is set on a the root IFD.
// However, in order to be valid, it must be set on the second one, linked to
// by the first, as per the EXIF/TIFF specification.
// - We set the offset to (0) now but will allocate the data and properly assign
// the offset when the IB is encoded (later).
func (ib *IfdBuilder) SetThumbnail(data []byte) (err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
if ib.ii != RootIi {
log.Panicf("thumbnails can only go into a root Ifd (and only the second one)")
}
// TODO(dustin): !! Add a test for this.
if data == nil || len(data) == 0 {
// TODO(dustin): !! Debugging.
fmt.Printf("Thumbnail empty.\n")
log.Panic("thumbnail is empty")
}
ib.thumbnailData = data
fmt.Printf("SETTING THUMBNAIL.\n")
ibtvfb := NewIfdBuilderTagValueFromBytes(ib.thumbnailData)
offsetBt := NewBuilderTag(ib.ii, ThumbnailOffsetTagId, TypeLong, ibtvfb)
// offsetBt := NewStandardBuilderTagFromConfig(ib.ii, ThumbnailOffsetTagId, ib.byteOrder, []uint32 { 0 })
err = ib.Set(offsetBt)
log.PanicIf(err)
sizeBt := NewStandardBuilderTagFromConfig(ib.ii, ThumbnailSizeTagId, ib.byteOrder, []uint32 { uint32(len(ib.thumbnailData)) })
err = ib.Set(sizeBt)
log.PanicIf(err)
// TODO(dustin): !! Debugging.
fmt.Printf("Set thumbnail into IB.\n")
return nil
}
func (ib *IfdBuilder) Thumbnail() []byte {
return ib.thumbnailData
}
func (ib *IfdBuilder) printTagTree(levels int) {
indent := strings.Repeat(" ", levels * 2)
@ -583,6 +652,27 @@ func (ib *IfdBuilder) Replace(tagId uint16, bt builderTag) (err error) {
return nil
}
// Set will add a new entry or update an existing entry.
func (ib *IfdBuilder) Set(bt builderTag) (err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
position, err := ib.Find(bt.tagId)
if err == nil {
ib.tags[position] = bt
} else if log.Is(err, ErrTagEntryNotFound) == true {
err = ib.Add(bt)
log.PanicIf(err)
} else {
log.Panic(err)
}
return nil
}
func (ib *IfdBuilder) FindN(tagId uint16, maxFound int) (found []int, err error) {
defer func() {
if state := recover(); state != nil {
@ -689,10 +779,32 @@ func (ib *IfdBuilder) AddTagsFromExisting(ifd *Ifd, itevr *IfdTagEntryValueResol
}
}()
thumbnailData, err := ifd.Thumbnail()
if err == nil {
// TODO(dustin): !! Debugging.
fmt.Printf("Importing thumbnail: %s\n", ifd.Identity())
err = ib.SetThumbnail(thumbnailData)
log.PanicIf(err)
} else if log.Is(err, ErrNoThumbnail) == false {
log.Panic(err)
} else {
// TODO(dustin): !! Debugging.
fmt.Printf("NO THUMBNAIL FOUND: %s\n", ifd.Identity())
}
for _, ite := range ifd.Entries {
// If we want to add an IFD tag, we'll have to build it first and *then*
// add it via a different method.
if ite.ChildIfdName != "" {
// If we want to add an IFD tag, we'll have to build it first and
// *then* add it via a different method.
continue
} else if ite.TagId == ThumbnailOffsetTagId || ite.TagId == ThumbnailSizeTagId {
// These will be added on-the-fly when we encode.
continue
}

View File

@ -234,10 +234,13 @@ func (ibe *IfdByteEncoder) encodeTagToBytes(ib *IfdBuilder, bt *builderTag, bw *
len_ := len(valueBytes)
unitCount := uint32(len_) / typeSize
remainder := uint32(len_) % typeSize
if remainder > 0 {
log.Panicf("tag value of (%d) bytes not evenly divisible by type-size (%d)", len_, typeSize)
if _, found := tagsWithoutAlignment[bt.tagId]; found == false {
remainder := uint32(len_) % typeSize
if remainder > 0 {
log.Panicf("tag (0x%02x) value of (%d) bytes not evenly divisible by type-size (%d)", bt.tagId, len_, typeSize)
}
}
err = bw.WriteUint32(unitCount)
@ -337,6 +340,8 @@ func (ibe *IfdByteEncoder) encodeIfdToBytes(ib *IfdBuilder, ifdAddressableOffset
log.PanicIf(err)
if childIfdBlock != nil {
// We aren't allowed to have non-nil child IFDs if we're just
// sizing things up.
if nextIfdOffsetToWrite == 0 {
log.Panicf("no IFD offset provided for child-IFDs; no new child-IFDs permitted")
}
@ -384,6 +389,23 @@ func (ibe *IfdByteEncoder) encodeIfdToBytes(ib *IfdBuilder, ifdAddressableOffset
_, err = b.Write(dataBytes)
log.PanicIf(err)
// TODO(dustin): !! Debugging.
// if thumbnailOffset != uint32(0) {
// currentRelativeOffset := thumbnailOffset - ifdAddressableOffset
// currentBuffer := b.Bytes()
// len_ := len(thumbnailData)
// extractedThumbnailData := currentBuffer[int(currentRelativeOffset):int(currentRelativeOffset) + len_]
// // We didn't have enough data available, which would be queer.
// if len(extractedThumbnailData) != len_ {
// log.Panicf("extracted thumbnail data was truncated; not enough data")
// }
// // fmt.Printf("Re-extracted (%d) bytes of thumbnail data.\n", len(extractedThumbnailData))
// // DumpBytes(extractedThumbnailData[:50])
// }
// Append any child IFD blocks after our table and data blocks. These IFDs
// were equipped with the appropriate offset information so it's expected
// that all offsets referred to by these will be correct.
@ -400,6 +422,13 @@ func (ibe *IfdByteEncoder) encodeIfdToBytes(ib *IfdBuilder, ifdAddressableOffset
ibe.pushToJournal("encodeIfdToBytes", "<", "%s", ib)
// if nextIfdOffsetToWrite > uint32(0) {
// fmt.Printf("Encoded IB: %s ADDRESSABLE-OFFSET=(0x%08x; %d):\n", ib.ii, ifdAddressableOffset, ifdAddressableOffset)
// fmt.Printf("\n")
// DumpBytes(b.Bytes())
// fmt.Printf("\n")
// }
return b.Bytes(), tableSize, dataSize, childIfdSizes, nil
}

View File

@ -176,7 +176,7 @@ type TagVisitor func(ii IfdIdentity, ifdIndex int, tagId uint16, tagType TagType
// ParseIfd decodes the IFD block that we're currently sitting on the first
// byte of.
func (ie *IfdEnumerate) ParseIfd(ii IfdIdentity, ifdIndex int, ite *IfdTagEnumerator, visitor TagVisitor, doDescend bool) (nextIfdOffset uint32, entries []*IfdTagEntry, err error) {
func (ie *IfdEnumerate) ParseIfd(ii IfdIdentity, ifdIndex int, ite *IfdTagEnumerator, visitor TagVisitor, doDescend bool) (nextIfdOffset uint32, entries []*IfdTagEntry, thumbnailData []byte, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
@ -188,12 +188,24 @@ func (ie *IfdEnumerate) ParseIfd(ii IfdIdentity, ifdIndex int, ite *IfdTagEnumer
ifdEnumerateLogger.Debugf(nil, "Current IFD tag-count: (%d)", tagCount)
entries = make([]*IfdTagEntry, tagCount)
entries = make([]*IfdTagEntry, 0)
var iteThumbnailOffset *IfdTagEntry
var iteThumbnailSize *IfdTagEntry
for i := 0; i < int(tagCount); i++ {
tag, err := ie.parseTag(ii, i, ite)
log.PanicIf(err)
if tag.TagId == ThumbnailOffsetTagId {
iteThumbnailOffset = tag
continue
} else if tag.TagId == ThumbnailSizeTagId {
iteThumbnailSize = tag
continue
}
if visitor != nil {
tt := NewTagType(tag.TagType, ie.byteOrder)
@ -220,7 +232,12 @@ func (ie *IfdEnumerate) ParseIfd(ii IfdIdentity, ifdIndex int, ite *IfdTagEnumer
log.PanicIf(err)
}
entries[i] = tag
entries = append(entries, tag)
}
if iteThumbnailOffset != nil && iteThumbnailSize != nil {
thumbnailData, err = ie.parseThumbnail(iteThumbnailOffset, iteThumbnailSize)
log.PanicIf(err)
}
nextIfdOffset, _, err = ite.getUint32()
@ -228,7 +245,37 @@ func (ie *IfdEnumerate) ParseIfd(ii IfdIdentity, ifdIndex int, ite *IfdTagEnumer
ifdEnumerateLogger.Debugf(nil, "Next IFD at offset: (%08x)", nextIfdOffset)
return nextIfdOffset, entries, nil
return nextIfdOffset, entries, thumbnailData, nil
}
func (ie *IfdEnumerate) parseThumbnail(offsetIte, lengthIte *IfdTagEntry) (thumbnailData []byte, err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
addressableData := ie.exifData[ExifAddressableAreaStart:]
vRaw, err := lengthIte.Value(addressableData, ie.byteOrder)
log.PanicIf(err)
vList := vRaw.([]uint32)
if len(vList) != 1 {
log.Panicf("not exactly one long: (%d)", len(vList))
}
length := vList[0]
// The tag is official a LONG type, but it's actually an offset to a blob of bytes.
offsetIte.TagType = TypeByte
offsetIte.UnitCount = length
thumbnailData, err = offsetIte.ValueBytes(addressableData, ie.byteOrder)
log.PanicIf(err)
return thumbnailData, nil
}
// Scan enumerates the different EXIF's IFD blocks.
@ -243,7 +290,7 @@ func (ie *IfdEnumerate) scan(ii IfdIdentity, ifdOffset uint32, visitor TagVisito
ifdEnumerateLogger.Debugf(nil, "Parsing IFD [%s] (%d) at offset (%04x).", ii.IfdName, ifdIndex, ifdOffset)
ite := ie.getTagEnumerator(ifdOffset)
nextIfdOffset, _, err := ie.ParseIfd(ii, ifdIndex, ite, visitor, true)
nextIfdOffset, _, _, err := ie.ParseIfd(ii, ifdIndex, ite, visitor, true)
log.PanicIf(err)
if nextIfdOffset == 0 {
@ -385,64 +432,6 @@ func (ifd Ifd) String() string {
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) parseThumbnail() (err error) {
defer func() {
if state := recover(); state != nil {
err = log.Wrap(state.(error))
}
}()
results, err := ifd.FindTagWithId(ThumbnailOffsetTagId)
if err != nil {
if log.Is(err, ErrTagNotFound) == true {
return nil
} else {
log.Panic(err)
}
}
offsetIte := results[0]
vRaw, err := ifd.TagValue(offsetIte)
log.PanicIf(err)
vList := vRaw.([]uint32)
if len(vList) != 1 {
log.Panicf("not exactly one long: (%d)", len(vList))
}
offset := vList[0]
results, err = ifd.FindTagWithId(ThumbnailSizeTagId)
if err != nil {
if log.Is(err, ErrTagNotFound) == true {
log.Panic(ErrNoThumbnail)
} else {
log.Panic(err)
}
}
lengthIte := results[0]
vRaw, err = ifd.TagValue(lengthIte)
log.PanicIf(err)
vList = vRaw.([]uint32)
if len(vList) != 1 {
log.Panicf("not exactly one long: (%d)", len(vList))
}
length := vList[0]
if len(ifd.addressableData) < int(offset) + int(length) {
log.Panicf("thumbnail size not valid: (%d) > (%d)", length, len(ifd.addressableData) - int(offset))
}
ifd.thumbnailData = ifd.addressableData[offset:offset + length]
return nil
}
func (ifd *Ifd) Thumbnail() (data []byte, err error) {
defer func() {
if state := recover(); state != nil {
@ -737,7 +726,7 @@ func (ie *IfdEnumerate) Collect(rootIfdOffset uint32) (index IfdIndex, err error
ifdEnumerateLogger.Debugf(nil, "Parsing IFD [%s] (%d) at offset (%04x).", ii.IfdName, index, offset)
ite := ie.getTagEnumerator(offset)
nextIfdOffset, entries, err := ie.ParseIfd(ii, index, ite, nil, false)
nextIfdOffset, entries, thumbnailData, err := ie.ParseIfd(ii, index, ite, nil, false)
log.PanicIf(err)
id := len(ifds)
@ -766,11 +755,9 @@ func (ie *IfdEnumerate) Collect(rootIfdOffset uint32) (index IfdIndex, err error
EntriesByTagId: entriesByTagId,
Children: make([]*Ifd, 0),
NextIfdOffset: nextIfdOffset,
thumbnailData: thumbnailData,
}
err = ifd.parseThumbnail()
log.PanicIf(err)
// Add ourselves to a big list of IFDs.
ifds = append(ifds, &ifd)
@ -858,7 +845,7 @@ func ParseOneIfd(ii IfdIdentity, byteOrder binary.ByteOrder, ifdBlock []byte, vi
ite := NewIfdTagEnumerator(ifdBlock, byteOrder, 0)
nextIfdOffset, entries, err = ie.ParseIfd(ii, 0, ite, visitor, true)
nextIfdOffset, entries, _, err = ie.ParseIfd(ii, 0, ite, visitor, true)
log.PanicIf(err)
return nextIfdOffset, entries, nil

View File

@ -79,6 +79,14 @@ var (
GpsIi: 3,
ExifIopIi: 4,
}
// tagsWithoutAlignment is a tag-lookup for tags whose value size won't
// necessarily be a multiple of its tag-type.
tagsWithoutAlignment = map[uint16]struct{} {
// The thumbnail offset is stored as a long, but its data is a binary
// blob (not a slice of longs).
ThumbnailOffsetTagId: struct{}{},
}
)
var (