// =================================================================================================
// ADOBE SYSTEMS INCORPORATED
// Copyright 2006 Adobe Systems Incorporated
// All Rights Reserved
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
// of the Adobe license agreement accompanying it.
// =================================================================================================
#include "public/include/XMP_Environment.h" // ! XMP_Environment.h must be the first included header.
#include "public/include/XMP_Const.h"
#include "XMPFiles/source/FormatSupport/TIFF_Support.hpp"
#include "source/XIO.hpp"
#include "source/EndianUtils.hpp"
// =================================================================================================
/// \file TIFF_FileWriter.cpp
/// \brief TIFF_FileWriter is used for memory-based read-write access and all file-based access.
///
/// \c TIFF_FileWriter is used for memory-based read-write access and all file-based access. The
/// main internal data structure is the InternalTagMap, a std::map that uses the tag number as the
/// key and InternalTagInfo as the value. There are 5 of these maps, one for each of the recognized
/// IFDs. The maps contain an entry for each tag in the IFD, whether we capture the data or not. The
/// dataPtr and dataLen fields in the InternalTagInfo are zero if the tag is not captured.
// =================================================================================================
// =================================================================================================
// TIFF_FileWriter::TIFF_FileWriter
// ================================
//
// Set big endian Get/Put routines so that routines are in place for creating TIFF without a parse.
// Parsing will reset them to the proper endianness for the stream. Big endian is a good default
// since JPEG and PSD files are big endian overall.
TIFF_FileWriter::TIFF_FileWriter() : changed(false), legacyDeleted(false), memParsed(false),
fileParsed(false), ownedStream(false), memStream(0), tiffLength(0)
{
XMP_Uns8 bogusTIFF [kEmptyTIFFLength];
bogusTIFF[0] = 0x4D;
bogusTIFF[1] = 0x4D;
bogusTIFF[2] = 0x00;
bogusTIFF[3] = 0x2A;
bogusTIFF[4] = bogusTIFF[5] = bogusTIFF[6] = bogusTIFF[7] = 0x00;
(void) this->CheckTIFFHeader ( bogusTIFF, sizeof ( bogusTIFF ) );
} // TIFF_FileWriter::TIFF_FileWriter
// =================================================================================================
// TIFF_FileWriter::~TIFF_FileWriter
// =================================
TIFF_FileWriter::~TIFF_FileWriter()
{
XMP_Assert ( ! (this->memParsed && this->fileParsed) );
if ( this->ownedStream ) {
XMP_Assert ( this->memStream != 0 );
free ( this->memStream );
}
} // TIFF_FileWriter::~TIFF_FileWriter
// =================================================================================================
// TIFF_FileWriter::DeleteExistingInfo
// ===================================
void TIFF_FileWriter::DeleteExistingInfo()
{
XMP_Assert ( ! (this->memParsed && this->fileParsed) );
if ( this->ownedStream ) free ( this->memStream ); // ! Current TIFF might be memory-parsed.
this->memStream = 0;
this->tiffLength = 0;
for ( int ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) this->containedIFDs[ifd].clear();
this->changed = false;
this->legacyDeleted = false;
this->memParsed = false;
this->fileParsed = false;
this->ownedStream = false;
} // TIFF_FileWriter::DeleteExistingInfo
// =================================================================================================
// TIFF_FileWriter::PickIFD
// ========================
XMP_Uns8 TIFF_FileWriter::PickIFD ( XMP_Uns8 ifd, XMP_Uns16 id )
{
if ( ifd > kTIFF_LastRealIFD ) {
if ( ifd != kTIFF_KnownIFD ) XMP_Throw ( "Invalid IFD number", kXMPErr_BadParam );
XMP_Throw ( "kTIFF_KnownIFD not yet implemented", kXMPErr_Unimplemented );
// *** Likely to stay unimplemented until there is a client need.
}
return ifd;
} // TIFF_FileWriter::PickIFD
// =================================================================================================
// TIFF_FileWriter::FindTagInIFD
// =============================
const TIFF_FileWriter::InternalTagInfo* TIFF_FileWriter::FindTagInIFD ( XMP_Uns8 ifd, XMP_Uns16 id ) const
{
ifd = PickIFD ( ifd, id );
const InternalTagMap& currIFD = this->containedIFDs[ifd].tagMap;
InternalTagMap::const_iterator tagPos = currIFD.find ( id );
if ( tagPos == currIFD.end() ) return 0;
return &tagPos->second;
} // TIFF_FileWriter::FindTagInIFD
// =================================================================================================
// TIFF_FileWriter::GetIFD
// =======================
bool TIFF_FileWriter::GetIFD ( XMP_Uns8 ifd, TagInfoMap* ifdMap ) const
{
if ( ifd > kTIFF_LastRealIFD ) XMP_Throw ( "Invalid IFD number", kXMPErr_BadParam );
const InternalTagMap& currIFD = this->containedIFDs[ifd].tagMap;
InternalTagMap::const_iterator tagPos = currIFD.begin();
InternalTagMap::const_iterator tagEnd = currIFD.end();
if ( ifdMap != 0 ) ifdMap->clear();
if ( tagPos == tagEnd ) return false; // Empty IFD.
if ( ifdMap != 0 ) {
for ( ; tagPos != tagEnd; ++tagPos ) {
const InternalTagInfo& intInfo = tagPos->second;
TagInfo extInfo ( intInfo.id, intInfo.type, intInfo.count, intInfo.dataPtr, intInfo.dataLen );
(*ifdMap)[intInfo.id] = extInfo;
}
}
return true;
} // TIFF_FileWriter::GetIFD
// =================================================================================================
// TIFF_FileWriter::GetValueOffset
// ===============================
XMP_Uns32 TIFF_FileWriter::GetValueOffset ( XMP_Uns8 ifd, XMP_Uns16 id ) const
{
const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id );
if ( (thisTag == 0) || (thisTag->origDataLen == 0) ) return 0;
return thisTag->origDataOffset;
} // TIFF_FileWriter::GetValueOffset
// =================================================================================================
// TIFF_FileWriter::GetTag
// =======================
bool TIFF_FileWriter::GetTag ( XMP_Uns8 ifd, XMP_Uns16 id, TagInfo* info ) const
{
const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id );
if ( thisTag == 0 ) return false;
if ( info != 0 ) {
info->id = thisTag->id;
info->type = thisTag->type;
info->count = thisTag->dataLen / (XMP_Uns32)kTIFF_TypeSizes[thisTag->type];
info->dataLen = thisTag->dataLen;
info->dataPtr = (const void*)(thisTag->dataPtr);
}
return true;
} // TIFF_FileWriter::GetTag
// =================================================================================================
// TIFF_FileWriter::SetTag
// =======================
void TIFF_FileWriter::SetTag ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns16 type, XMP_Uns32 count, const void* clientPtr )
{
if ( (type < kTIFF_ByteType) || (type > kTIFF_LastType) ) XMP_Throw ( "Invalid TIFF tag type", kXMPErr_BadParam );
size_t typeSize = kTIFF_TypeSizes[type];
size_t fullSize = count * typeSize;
ifd = PickIFD ( ifd, id );
InternalTagMap& currIFD = this->containedIFDs[ifd].tagMap;
InternalTagInfo* tagPtr = 0;
InternalTagMap::iterator tagPos = currIFD.find ( id );
if ( tagPos == currIFD.end() ) {
// The tag does not yet exist, add it.
InternalTagMap::value_type mapValue ( id, InternalTagInfo ( id, type, count, this->fileParsed ) );
tagPos = currIFD.insert ( tagPos, mapValue );
tagPtr = &tagPos->second;
} else {
tagPtr = &tagPos->second;
// The tag already exists, make sure the value is actually changing.
if ( (type == tagPtr->type) && (count == tagPtr->count) &&
(memcmp ( clientPtr, tagPtr->dataPtr, tagPtr->dataLen ) == 0) ) {
return; // ! The value is unchanged, exit.
}
tagPtr->FreeData(); // Release any existing data allocation.
tagPtr->type = type; // These might be changing also.
tagPtr->count = count;
}
tagPtr->changed = true;
tagPtr->dataLen = (XMP_Uns32)fullSize;
if ( fullSize <= 4 ) {
// The data is less than 4 bytes, store it in the smallValue field using native endianness.
tagPtr->dataPtr = (XMP_Uns8*) &tagPtr->smallValue;
} else {
// The data is more than 4 bytes, make a copy.
tagPtr->dataPtr = (XMP_Uns8*) malloc ( fullSize );
if ( tagPtr->dataPtr == 0 ) XMP_Throw ( "Out of memory", kXMPErr_NoMemory );
}
memcpy ( tagPtr->dataPtr, clientPtr, fullSize ); // AUDIT: Safe, space guaranteed to be fullSize.
if ( ! this->nativeEndian ) {
if ( typeSize == 2 ) {
XMP_Uns16* flipPtr = (XMP_Uns16*) tagPtr->dataPtr;
for ( XMP_Uns32 i = 0; i < count; ++i ) Flip2 ( flipPtr[i] );
} else if ( typeSize == 4 ) {
XMP_Uns32* flipPtr = (XMP_Uns32*) tagPtr->dataPtr;
for ( XMP_Uns32 i = 0; i < count; ++i ) Flip4 ( flipPtr[i] );
} else if ( typeSize == 8 ) {
XMP_Uns64* flipPtr = (XMP_Uns64*) tagPtr->dataPtr;
for ( XMP_Uns32 i = 0; i < count; ++i ) Flip8 ( flipPtr[i] );
}
}
this->containedIFDs[ifd].changed = true;
this->changed = true;
} // TIFF_FileWriter::SetTag
// =================================================================================================
// TIFF_FileWriter::DeleteTag
// ==========================
void TIFF_FileWriter::DeleteTag ( XMP_Uns8 ifd, XMP_Uns16 id )
{
ifd = PickIFD ( ifd, id );
InternalTagMap& currIFD = this->containedIFDs[ifd].tagMap;
InternalTagMap::iterator tagPos = currIFD.find ( id );
if ( tagPos == currIFD.end() ) return; // ! Don't set the changed flags if the tag didn't exist.
currIFD.erase ( tagPos );
this->containedIFDs[ifd].changed = true;
this->changed = true;
if ( (ifd != kTIFF_PrimaryIFD) || (id != kTIFF_XMP) ) this->legacyDeleted = true;
} // TIFF_FileWriter::DeleteTag
// =================================================================================================
static inline XMP_Uns8 GetUns8 ( const void* dataPtr )
{
return *((XMP_Uns8*)dataPtr);
}
// =================================================================================================
// TIFF_FileWriter::GetTag_Integer
// ===============================
bool TIFF_FileWriter::GetTag_Integer ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns32* data ) const
{
const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id );
if ( thisTag == 0 ) return false;
if ( thisTag->count != 1 ) return false;
XMP_Uns32 uns32;
XMP_Int32 int32;
switch ( thisTag->type ) {
case kTIFF_ByteType:
uns32 = GetUns8 ( thisTag->dataPtr );
break;
case kTIFF_ShortType:
uns32 = this->GetUns16 ( thisTag->dataPtr );
break;
case kTIFF_LongType:
uns32 = this->GetUns32 ( thisTag->dataPtr );
break;
case kTIFF_SByteType:
int32 = (XMP_Int8) GetUns8 ( thisTag->dataPtr );
uns32 = (XMP_Uns32) int32; // Make sure sign bits are properly set.
break;
case kTIFF_SShortType:
int32 = (XMP_Int16) this->GetUns16 ( thisTag->dataPtr );
uns32 = (XMP_Uns32) int32; // Make sure sign bits are properly set.
break;
case kTIFF_SLongType:
int32 = (XMP_Int32) this->GetUns32 ( thisTag->dataPtr );
uns32 = (XMP_Uns32) int32; // Make sure sign bits are properly set.
break;
default: return false;
}
if ( data != 0 ) *data = uns32;
return true;
} // TIFF_FileWriter::GetTag_Integer
// =================================================================================================
// TIFF_FileWriter::GetTag_Byte
// ============================
bool TIFF_FileWriter::GetTag_Byte ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns8* data ) const
{
const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id );
if ( thisTag == 0 ) return false;
if ( (thisTag->type != kTIFF_ByteType) || (thisTag->dataLen != 1) ) return false;
if ( data != 0 ) *data = *thisTag->dataPtr;
return true;
} // TIFF_FileWriter::GetTag_Byte
// =================================================================================================
// TIFF_FileWriter::GetTag_SByte
// =============================
bool TIFF_FileWriter::GetTag_SByte ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int8* data ) const
{
const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id );
if ( thisTag == 0 ) return false;
if ( (thisTag->type != kTIFF_SByteType) || (thisTag->dataLen != 1) ) return false;
if ( data != 0 ) *data = *thisTag->dataPtr;
return true;
} // TIFF_FileWriter::GetTag_SByte
// =================================================================================================
// TIFF_FileWriter::GetTag_Short
// =============================
bool TIFF_FileWriter::GetTag_Short ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns16* data ) const
{
const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id );
if ( thisTag == 0 ) return false;
if ( (thisTag->type != kTIFF_ShortType) || (thisTag->dataLen != 2) ) return false;
if ( data != 0 ) *data = this->GetUns16 ( thisTag->dataPtr );
return true;
} // TIFF_FileWriter::GetTag_Short
// =================================================================================================
// TIFF_FileWriter::GetTag_SShort
// ==============================
bool TIFF_FileWriter::GetTag_SShort ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int16* data ) const
{
const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id );
if ( thisTag == 0 ) return false;
if ( (thisTag->type != kTIFF_SShortType) || (thisTag->dataLen != 2) ) return false;
if ( data != 0 ) *data = (XMP_Int16) this->GetUns16 ( thisTag->dataPtr );
return true;
} // TIFF_FileWriter::GetTag_SShort
// =================================================================================================
// TIFF_FileWriter::GetTag_Long
// ============================
bool TIFF_FileWriter::GetTag_Long ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Uns32* data ) const
{
const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id );
if ( thisTag == 0 ) return false;
if ( (thisTag->type != kTIFF_LongType) || (thisTag->dataLen != 4) ) return false;
if ( data != 0 ) *data = this->GetUns32 ( thisTag->dataPtr );
return true;
} // TIFF_FileWriter::GetTag_Long
// =================================================================================================
// TIFF_FileWriter::GetTag_SLong
// =============================
bool TIFF_FileWriter::GetTag_SLong ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_Int32* data ) const
{
const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id );
if ( thisTag == 0 ) return false;
if ( (thisTag->type != kTIFF_SLongType) || (thisTag->dataLen != 4) ) return false;
if ( data != 0 ) *data = (XMP_Int32) this->GetUns32 ( thisTag->dataPtr );
return true;
} // TIFF_FileWriter::GetTag_SLong
// =================================================================================================
// TIFF_FileWriter::GetTag_Rational
// ================================
bool TIFF_FileWriter::GetTag_Rational ( XMP_Uns8 ifd, XMP_Uns16 id, Rational* data ) const
{
const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id );
if ( (thisTag == 0) || (thisTag->dataPtr == 0) ) return false;
if ( (thisTag->type != kTIFF_RationalType) || (thisTag->dataLen != 8) ) return false;
if ( data != 0 ) {
XMP_Uns32* dataPtr = (XMP_Uns32*)thisTag->dataPtr;
data->num = this->GetUns32 ( dataPtr );
data->denom = this->GetUns32 ( dataPtr+1 );
}
return true;
} // TIFF_FileWriter::GetTag_Rational
// =================================================================================================
// TIFF_FileWriter::GetTag_SRational
// =================================
bool TIFF_FileWriter::GetTag_SRational ( XMP_Uns8 ifd, XMP_Uns16 id, SRational* data ) const
{
const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id );
if ( (thisTag == 0) || (thisTag->dataPtr == 0) ) return false;
if ( (thisTag->type != kTIFF_SRationalType) || (thisTag->dataLen != 8) ) return false;
if ( data != 0 ) {
XMP_Uns32* dataPtr = (XMP_Uns32*)thisTag->dataPtr;
data->num = (XMP_Int32) this->GetUns32 ( dataPtr );
data->denom = (XMP_Int32) this->GetUns32 ( dataPtr+1 );
}
return true;
} // TIFF_FileWriter::GetTag_SRational
// =================================================================================================
// TIFF_FileWriter::GetTag_Float
// =============================
bool TIFF_FileWriter::GetTag_Float ( XMP_Uns8 ifd, XMP_Uns16 id, float* data ) const
{
const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id );
if ( thisTag == 0 ) return false;
if ( (thisTag->type != kTIFF_FloatType) || (thisTag->dataLen != 4) ) return false;
if ( data != 0 ) *data = this->GetFloat ( thisTag->dataPtr );
return true;
} // TIFF_FileWriter::GetTag_Float
// =================================================================================================
// TIFF_FileWriter::GetTag_Double
// ==============================
bool TIFF_FileWriter::GetTag_Double ( XMP_Uns8 ifd, XMP_Uns16 id, double* data ) const
{
const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id );
if ( (thisTag == 0) || (thisTag->dataPtr == 0) ) return false;
if ( (thisTag->type != kTIFF_DoubleType) || (thisTag->dataLen != 8) ) return false;
if ( data != 0 ) *data = this->GetDouble ( thisTag->dataPtr );
return true;
} // TIFF_FileWriter::GetTag_Double
// =================================================================================================
// TIFF_FileWriter::GetTag_ASCII
// =============================
bool TIFF_FileWriter::GetTag_ASCII ( XMP_Uns8 ifd, XMP_Uns16 id, XMP_StringPtr* dataPtr, XMP_StringLen* dataLen ) const
{
const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id );
if ( thisTag == 0 ) return false;
if ( (thisTag->dataLen > 4) && (thisTag->dataPtr == 0) ) return false;
if ( thisTag->type != kTIFF_ASCIIType ) return false;
if ( dataPtr != 0 ) *dataPtr = (XMP_StringPtr)thisTag->dataPtr;
if ( dataLen != 0 ) *dataLen = thisTag->dataLen;
return true;
} // TIFF_FileWriter::GetTag_ASCII
// =================================================================================================
// TIFF_FileWriter::GetTag_EncodedString
// =====================================
bool TIFF_FileWriter::GetTag_EncodedString ( XMP_Uns8 ifd, XMP_Uns16 id, std::string* utf8Str ) const
{
const InternalTagInfo* thisTag = this->FindTagInIFD ( ifd, id );
if ( thisTag == 0 ) return false;
if ( thisTag->type != kTIFF_UndefinedType ) return false;
if ( utf8Str == 0 ) return true; // Return true if the converted string is not wanted.
bool ok = this->DecodeString ( thisTag->dataPtr, thisTag->dataLen, utf8Str );
return ok;
} // TIFF_FileWriter::GetTag_EncodedString
// =================================================================================================
// TIFF_FileWriter::SetTag_EncodedString
// =====================================
void TIFF_FileWriter::SetTag_EncodedString ( XMP_Uns8 ifd, XMP_Uns16 id, const std::string& utf8Str, XMP_Uns8 encoding )
{
std::string encodedStr;
this->EncodeString ( utf8Str, encoding, &encodedStr );
this->SetTag ( ifd, id, kTIFF_UndefinedType, (XMP_Uns32)encodedStr.size(), encodedStr.c_str() );
} // TIFF_FileWriter::SetTag_EncodedString
// =================================================================================================
// TIFF_FileWriter::IsLegacyChanged
// ================================
bool TIFF_FileWriter::IsLegacyChanged()
{
if ( ! this->changed ) return false;
if ( this->legacyDeleted ) return true;
for ( int ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) {
InternalIFDInfo & thisIFD = this->containedIFDs[ifd];
if ( ! thisIFD.changed ) continue;
InternalTagMap::iterator tagPos;
InternalTagMap::iterator tagEnd = thisIFD.tagMap.end();
for ( tagPos = thisIFD.tagMap.begin(); tagPos != tagEnd; ++tagPos ) {
InternalTagInfo & thisTag = tagPos->second;
if ( thisTag.changed && (thisTag.id != kTIFF_XMP) ) return true;
}
}
return false; // Can get here if the XMP tag is the only one changed.
} // TIFF_FileWriter::IsLegacyChanged
// =================================================================================================
// TIFF_FileWriter::ParseMemoryStream
// ==================================
void TIFF_FileWriter::ParseMemoryStream ( const void* data, XMP_Uns32 length, bool copyData /* = true */ )
{
this->DeleteExistingInfo();
this->memParsed = true;
if ( length == 0 ) return;
// Allocate space for the full in-memory stream and copy it.
if ( ! copyData ) {
XMP_Assert ( ! this->ownedStream );
this->memStream = (XMP_Uns8*) data;
} else {
if ( length > 100*1024*1024 ) XMP_Throw ( "Outrageous length for memory-based TIFF", kXMPErr_BadTIFF );
this->memStream = (XMP_Uns8*) malloc(length);
this->ownedStream = true;
if ( this->memStream == 0 ) XMP_Throw ( "Out of memory", kXMPErr_NoMemory );
memcpy ( this->memStream, data, length ); // AUDIT: Safe, malloc'ed length bytes above.
this->ownedStream = true;
}
this->tiffLength = length;
XMP_Uns32 ifdLimit = this->tiffLength - 6; // An IFD must start before this offset.
// Find and process the primary, thumbnail, Exif, GPS, and Interoperability IFDs.
XMP_Uns32 primaryIFDOffset = this->CheckTIFFHeader ( this->memStream, length );
if ( primaryIFDOffset != 0 ) {
XMP_Uns32 tnailOffset = this->ProcessMemoryIFD ( primaryIFDOffset, kTIFF_PrimaryIFD );
if ( tnailOffset != 0 ) {
if ( IsOffsetValid ( tnailOffset, 8, ifdLimit ) ) { // Remove a bad Thumbnail IFD Offset
( void ) this->ProcessMemoryIFD ( tnailOffset, kTIFF_TNailIFD );
} else {
XMP_Error error ( kXMPErr_BadTIFF, "Bad IFD offset" );
this->NotifyClient (kXMPErrSev_Recoverable, error );
this->DeleteTag ( kTIFF_PrimaryIFD, kTIFF_TNailIFD );
}
}
}
const InternalTagInfo* exifIFDTag = this->FindTagInIFD ( kTIFF_PrimaryIFD, kTIFF_ExifIFDPointer );
if ( (exifIFDTag != 0) && (exifIFDTag->type == kTIFF_LongType) && (exifIFDTag->dataLen == 4) ) {
XMP_Uns32 exifOffset = this->GetUns32 ( exifIFDTag->dataPtr );
(void) this->ProcessMemoryIFD ( exifOffset, kTIFF_ExifIFD );
}
const InternalTagInfo* gpsIFDTag = this->FindTagInIFD ( kTIFF_PrimaryIFD, kTIFF_GPSInfoIFDPointer );
if ( (gpsIFDTag != 0) && (gpsIFDTag->type == kTIFF_LongType) && (gpsIFDTag->dataLen == 4) ) {
XMP_Uns32 gpsOffset = this->GetUns32 ( gpsIFDTag->dataPtr );
if ( IsOffsetValid ( gpsOffset, 8, ifdLimit ) ) { // Remove a bad GPS IFD offset.
(void) this->ProcessMemoryIFD ( gpsOffset, kTIFF_GPSInfoIFD );
} else {
XMP_Error error ( kXMPErr_BadTIFF, "Bad IFD offset" );
this->NotifyClient (kXMPErrSev_Recoverable, error );
this->DeleteTag ( kTIFF_PrimaryIFD, kTIFF_GPSInfoIFDPointer );
}
}
const InternalTagInfo* interopIFDTag = this->FindTagInIFD ( kTIFF_ExifIFD, kTIFF_InteroperabilityIFDPointer );
if ( (interopIFDTag != 0) && (interopIFDTag->type == kTIFF_LongType) && (interopIFDTag->dataLen == 4) ) {
XMP_Uns32 interopOffset = this->GetUns32 ( interopIFDTag->dataPtr );
if ( IsOffsetValid ( interopOffset, 8, ifdLimit ) ) { // Remove a bad Interoperability IFD offset.
(void) this->ProcessMemoryIFD ( interopOffset, kTIFF_InteropIFD );
} else {
XMP_Error error ( kXMPErr_BadTIFF, "Bad IFD offset" );
this->NotifyClient (kXMPErrSev_Recoverable, error );
this->DeleteTag ( kTIFF_ExifIFD, kTIFF_InteroperabilityIFDPointer );
}
}
#if 0
{
printf ( "\nExiting TIFF_FileWriter::ParseMemoryStream\n" );
for ( int ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) {
InternalIFDInfo & thisIFD = this->containedIFDs[ifd];
printf ( "\n IFD %d, count %d, mapped %d, offset %d (0x%X), next IFD %d (0x%X)\n",
ifd, thisIFD.origCount, thisIFD.tagMap.size(),
thisIFD.origDataOffset, thisIFD.origDataOffset, thisIFD.origNextIFD, thisIFD.origNextIFD );
InternalTagMap::iterator tagPos;
InternalTagMap::iterator tagEnd = thisIFD.tagMap.end();
for ( tagPos = thisIFD.tagMap.begin(); tagPos != tagEnd; ++tagPos ) {
InternalTagInfo & thisTag = tagPos->second;
printf ( " Tag %d, smallValue 0x%X, origDataLen %d, origDataOffset %d (0x%X)\n",
thisTag.id, thisTag.smallValue, thisTag.origDataLen, thisTag.origDataOffset, thisTag.origDataOffset );
}
}
printf ( "\n" );
}
#endif
} // TIFF_FileWriter::ParseMemoryStream
// =================================================================================================
// TIFF_FileWriter::ProcessMemoryIFD
// =================================
XMP_Uns32 TIFF_FileWriter::ProcessMemoryIFD ( XMP_Uns32 ifdOffset, XMP_Uns8 ifd )
{
InternalIFDInfo& ifdInfo ( this->containedIFDs[ifd] );
if ( (ifdOffset < 8) || (ifdOffset > (this->tiffLength - kEmptyIFDLength)) ) {
XMP_Error error ( kXMPErr_BadTIFF, "Bad IFD offset" );
this->NotifyClient ( kXMPErrSev_FileFatal, error );
}
XMP_Uns8* ifdPtr = this->memStream + ifdOffset;
XMP_Uns16 tagCount = this->GetUns16 ( ifdPtr );
RawIFDEntry* ifdEntries = (RawIFDEntry*)(ifdPtr+2);
if ( tagCount >= 0x8000 ) {
XMP_Error error ( kXMPErr_BadTIFF, "Outrageous IFD count" );
this->NotifyClient ( kXMPErrSev_FileFatal, error );
}
if ( (XMP_Uns32)(2 + tagCount*12 + 4) > (this->tiffLength - ifdOffset) ) {
XMP_Error error ( kXMPErr_BadTIFF, "Out of bounds IFD" );
this->NotifyClient ( kXMPErrSev_FileFatal, error );
}
ifdInfo.origIFDOffset = ifdOffset;
ifdInfo.origCount = tagCount;
for ( size_t i = 0; i < tagCount; ++i ) {
RawIFDEntry* rawTag = &ifdEntries[i];
XMP_Uns16 tagType = this->GetUns16 ( &rawTag->type );
if ( (tagType < kTIFF_ByteType) || (tagType > kTIFF_LastType) ) continue; // Bad type, skip this tag.
XMP_Uns16 tagID = this->GetUns16 ( &rawTag->id );
XMP_Uns32 tagCount = this->GetUns32 ( &rawTag->count );
InternalTagMap::value_type mapValue ( tagID, InternalTagInfo ( tagID, tagType, tagCount, kIsMemoryBased ) );
InternalTagMap::iterator newPos = ifdInfo.tagMap.insert ( ifdInfo.tagMap.end(), mapValue );
InternalTagInfo& mapTag = newPos->second;
mapTag.dataLen = mapTag.origDataLen = mapTag.count * (XMP_Uns32)kTIFF_TypeSizes[mapTag.type];
#if SUNOS_SPARC
mapTag.smallValue = IE.getUns32(&rawTag->dataOrOffset);
#else
mapTag.smallValue = GetUns32AsIs ( &rawTag->dataOrOffset ); // Keep the value or offset in stream byte ordering.
#endif //#if SUNOS_SPARC
if ( mapTag.dataLen <= 4 ) {
mapTag.origDataOffset = ifdOffset + 2 + (12 * (XMP_Uns32)i) + 8; // Compute the data offset.
} else {
mapTag.origDataOffset = this->GetUns32 ( &rawTag->dataOrOffset ); // Extract the data offset.
// printf ( "FW_ProcessMemoryIFD tag %d large value @ %.8X\n", mapTag.id, mapTag.dataPtr );
if ( (mapTag.origDataOffset < 8) || (mapTag.origDataOffset >= this->tiffLength) ) {
mapTag.count = mapTag.dataLen = mapTag.origDataLen = mapTag.smallValue = 0;
mapTag.origDataOffset = ifdOffset + 2 + (12 * (XMP_Uns32)i) + 8; // Make this bad tag look empty
}
if ( mapTag.dataLen > (this->tiffLength - mapTag.origDataOffset) ) {
mapTag.count = mapTag.dataLen = mapTag.origDataLen = mapTag.smallValue = 0;
mapTag.origDataOffset = ifdOffset + 2 + (12 * (XMP_Uns32)i) + 8; // Make this bad tag look empty
}
}
mapTag.dataPtr = this->memStream + mapTag.origDataOffset;
}
ifdPtr += (2 + tagCount*12);
ifdInfo.origNextIFD = this->GetUns32 ( ifdPtr );
return ifdInfo.origNextIFD;
} // TIFF_FileWriter::ProcessMemoryIFD
// =================================================================================================
// TIFF_FileWriter::ParseFileStream
// ================================
//
// The buffered I/O model is worth the logic complexity - as opposed to a simple seek/read for each
// part of the TIFF stream. The vast majority of real-world TIFFs have the primary IFD, Exif IFD,
// and all of their interesting tag values within the first 64K of the file. Well, at least before
// we get around to our edit-by-append approach.
void TIFF_FileWriter::ParseFileStream ( XMP_IO* fileRef )
{
this->DeleteExistingInfo();
this->fileParsed = true;
this->tiffLength = (XMP_Uns32) fileRef->Length();
if ( this->tiffLength < 8 ) return; // Ignore empty or impossibly short.
fileRef->Rewind ( );
XMP_Uns32 ifdLimit = this->tiffLength - 6; // An IFD must start before this offset.
// Find and process the primary, Exif, GPS, and Interoperability IFDs.
XMP_Uns8 tiffHeader [8];
fileRef->ReadAll ( tiffHeader, sizeof(tiffHeader) );
XMP_Uns32 primaryIFDOffset = this->CheckTIFFHeader ( tiffHeader, this->tiffLength );
if ( primaryIFDOffset == 0 ) {
return;
} else {
XMP_Uns32 tnailOffset = this->ProcessFileIFD ( kTIFF_PrimaryIFD, primaryIFDOffset, fileRef );
if ( tnailOffset != 0 ) {
if ( IsOffsetValid ( tnailOffset, 8, ifdLimit ) ) {
( void ) this->ProcessFileIFD ( kTIFF_TNailIFD, tnailOffset, fileRef );
} else {
XMP_Error error ( kXMPErr_BadTIFF, "Bad IFD offset" );
this->NotifyClient ( kXMPErrSev_Recoverable, error );
this->DeleteTag( kTIFF_PrimaryIFD, kTIFF_TNailIFD );
}
}
}
const InternalTagInfo* exifIFDTag = this->FindTagInIFD ( kTIFF_PrimaryIFD, kTIFF_ExifIFDPointer );
if ( (exifIFDTag != 0) && (exifIFDTag->type == kTIFF_LongType) && (exifIFDTag->count == 1) ) {
XMP_Uns32 exifOffset = this->GetUns32 ( exifIFDTag->dataPtr );
(void) this->ProcessFileIFD ( kTIFF_ExifIFD, exifOffset, fileRef );
}
const InternalTagInfo* gpsIFDTag = this->FindTagInIFD ( kTIFF_PrimaryIFD, kTIFF_GPSInfoIFDPointer );
if ( (gpsIFDTag != 0) && (gpsIFDTag->type == kTIFF_LongType) && (gpsIFDTag->count == 1) ) {
XMP_Uns32 gpsOffset = this->GetUns32 ( gpsIFDTag->dataPtr );
if ( IsOffsetValid (gpsOffset, 8, ifdLimit ) ) { // Remove a bad GPS IFD offset.
(void) this->ProcessFileIFD ( kTIFF_GPSInfoIFD, gpsOffset, fileRef );
} else {
XMP_Error error ( kXMPErr_BadTIFF, "Bad IFD offset" );
this->NotifyClient ( kXMPErrSev_Recoverable, error );
this->DeleteTag ( kTIFF_PrimaryIFD, kTIFF_GPSInfoIFDPointer );
}
}
const InternalTagInfo* interopIFDTag = this->FindTagInIFD ( kTIFF_ExifIFD, kTIFF_InteroperabilityIFDPointer );
if ( (interopIFDTag != 0) && (interopIFDTag->type == kTIFF_LongType) && (interopIFDTag->dataLen == 4) ) {
XMP_Uns32 interopOffset = this->GetUns32 ( interopIFDTag->dataPtr );
if ( IsOffsetValid (interopOffset, 8, ifdLimit ) ) { // Remove a bad Interoperability IFD offset.
(void) this->ProcessFileIFD ( kTIFF_InteropIFD, interopOffset, fileRef );
} else {
XMP_Error error ( kXMPErr_BadTIFF, "Bad IFD offset" );
this->NotifyClient ( kXMPErrSev_Recoverable, error );
this->DeleteTag ( kTIFF_ExifIFD, kTIFF_InteroperabilityIFDPointer );
}
}
#if 0
{
printf ( "\nExiting TIFF_FileWriter::ParseFileStream\n" );
for ( int ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) {
InternalIFDInfo & thisIFD = this->containedIFDs[ifd];
printf ( "\n IFD %d, count %d, mapped %d, offset %d (0x%X), next IFD %d (0x%X)\n",
ifd, thisIFD.origCount, thisIFD.tagMap.size(),
thisIFD.origDataOffset, thisIFD.origDataOffset, thisIFD.origNextIFD, thisIFD.origNextIFD );
InternalTagMap::iterator tagPos;
InternalTagMap::iterator tagEnd = thisIFD.tagMap.end();
for ( tagPos = thisIFD.tagMap.begin(); tagPos != tagEnd; ++tagPos ) {
InternalTagInfo & thisTag = tagPos->second;
printf ( " Tag %d, smallValue 0x%X, origDataLen %d, origDataOffset %d (0x%X)\n",
thisTag.id, thisTag.smallValue, thisTag.origDataLen, thisTag.origDataOffset, thisTag.origDataOffset );
}
}
printf ( "\n" );
}
#endif
} // TIFF_FileWriter::ParseFileStream
// =================================================================================================
// TIFF_FileWriter::ProcessFileIFD
// ===============================
//
// Each IFD has a UInt16 count of IFD entries, a sequence of 12 byte IFD entries, then a UInt32
// offset to the next IFD. The integer byte order is determined by the II or MM at the TIFF start.
XMP_Uns32 TIFF_FileWriter::ProcessFileIFD ( XMP_Uns8 ifd, XMP_Uns32 ifdOffset, XMP_IO* fileRef )
{
static const size_t ifdBufferSize = 12*65536; // Enough for the largest possible IFD.
std::vector<XMP_Uns8> ifdBuffer(ifdBufferSize);
XMP_Uns8 intBuffer [4]; // For the IFD count and offset to next IFD.
InternalIFDInfo& ifdInfo ( this->containedIFDs[ifd] );
if ( (ifdOffset < 8) || (ifdOffset > (this->tiffLength - kEmptyIFDLength)) ) {
XMP_Throw ( "Bad IFD offset", kXMPErr_BadTIFF );
}
fileRef->Seek ( ifdOffset, kXMP_SeekFromStart );
if ( ! XIO::CheckFileSpace ( fileRef, 2 ) ) return 0; // Bail for a truncated file.
fileRef->ReadAll ( intBuffer, 2 );
XMP_Uns16 tagCount = this->GetUns16 ( intBuffer );
if ( tagCount >= 0x8000 ) return 0; // Maybe wrong byte order.
if ( ! XIO::CheckFileSpace ( fileRef, 12*tagCount ) ) return 0; // Bail for a truncated file.
fileRef->ReadAll ( &ifdBuffer[0], 12*tagCount );
if ( ! XIO::CheckFileSpace ( fileRef, 4 ) ) {
ifdInfo.origNextIFD = 0; // Tolerate a trncated file, do the remaining processing.
} else {
fileRef->ReadAll ( intBuffer, 4 );
ifdInfo.origNextIFD = this->GetUns32 ( intBuffer );
}
ifdInfo.origIFDOffset = ifdOffset;
ifdInfo.origCount = tagCount;
// ---------------------------------------------------------------------------------------------
// First create all of the IFD map entries, capturing short values, and get the next IFD offset.
// We're using a std::map for storage, it automatically eliminates duplicates and provides
// sorted output. Plus the "map[key] = value" assignment conveniently keeps the last encountered
// value, following Photoshop's behavior.
XMP_Uns8* ifdPtr = &ifdBuffer[0]; // Move to the first IFD entry.
for ( XMP_Uns16 i = 0; i < tagCount; ++i, ifdPtr += 12 ) {
RawIFDEntry* rawTag = (RawIFDEntry*)ifdPtr;
XMP_Uns16 tagType = this->GetUns16 ( &rawTag->type );
if ( (tagType < kTIFF_ByteType) || (tagType > kTIFF_LastType) ) continue; // Bad type, skip this tag.
XMP_Uns16 tagID = this->GetUns16 ( &rawTag->id );
XMP_Uns32 tagCount = this->GetUns32 ( &rawTag->count );
InternalTagMap::value_type mapValue ( tagID, InternalTagInfo ( tagID, tagType, tagCount, kIsFileBased ) );
InternalTagMap::iterator newPos = ifdInfo.tagMap.insert ( ifdInfo.tagMap.end(), mapValue );
InternalTagInfo& mapTag = newPos->second;
mapTag.dataLen = mapTag.origDataLen = mapTag.count * (XMP_Uns32)kTIFF_TypeSizes[mapTag.type];
mapTag.smallValue = GetUns32AsIs ( &rawTag->dataOrOffset ); // Keep the value or offset in stream byte ordering.
if ( mapTag.dataLen <= 4 ) {
mapTag.dataPtr = (XMP_Uns8*) &mapTag.smallValue;
mapTag.origDataOffset = ifdOffset + 2 + (12 * i) + 8; // Compute the data offset.
} else {
mapTag.origDataOffset = this->GetUns32 ( &rawTag->dataOrOffset ); // Extract the data offset.
if ( (mapTag.origDataOffset < 8) || (mapTag.origDataOffset >= this->tiffLength) ) {
mapTag.dataPtr = (XMP_Uns8*) &mapTag.smallValue; // Make this bad tag look empty.
mapTag.origDataOffset = ifdOffset + 2 + (12 * i) + 8;
mapTag.count = mapTag.dataLen = mapTag.origDataLen = mapTag.smallValue = 0;
}
if ( mapTag.dataLen > (this->tiffLength - mapTag.origDataOffset) ) {
mapTag.dataPtr = (XMP_Uns8*) &mapTag.smallValue; // Make this bad tag look empty.
mapTag.origDataOffset = ifdOffset + 2 + (12 * i) + 8;
mapTag.count = mapTag.dataLen = mapTag.origDataLen = mapTag.smallValue = 0;
}
}
}
// ------------------------------------------------------------------------
// Go back over the tag map and extract the data for large recognized tags.
InternalTagMap::iterator tagPos = ifdInfo.tagMap.begin();
InternalTagMap::iterator tagEnd = ifdInfo.tagMap.end();
const XMP_Uns16* knownTagPtr = sKnownTags[ifd]; // Points into the ordered recognized tag list.
for ( ; tagPos != tagEnd; ++tagPos ) {
InternalTagInfo* currTag = &tagPos->second;
if ( currTag->dataLen <= 4 ) continue; // Short values are already in the smallValue field.
while ( *knownTagPtr < currTag->id ) ++knownTagPtr;
if ( *knownTagPtr != currTag->id ) continue; // Skip unrecognized tags.
fileRef->Seek ( currTag->origDataOffset, kXMP_SeekFromStart );
currTag->dataPtr = (XMP_Uns8*) malloc ( currTag->dataLen );
if ( currTag->dataPtr == 0 ) XMP_Throw ( "No data block", kXMPErr_NoMemory );
fileRef->ReadAll ( currTag->dataPtr, currTag->dataLen );
}
// Done, return the next IFD offset.
return ifdInfo.origNextIFD;
} // TIFF_FileWriter::ProcessFileIFD
// =================================================================================================
// TIFF_FileWriter::IntegrateFromPShop6
// ====================================
//
// See comments for ProcessPShop6IFD.
void TIFF_FileWriter::IntegrateFromPShop6 ( const void * buriedPtr, size_t buriedLen )
{
TIFF_MemoryReader buriedExif;
buriedExif.ParseMemoryStream ( buriedPtr, (XMP_Uns32) buriedLen );
this->ProcessPShop6IFD ( buriedExif, kTIFF_PrimaryIFD );
this->ProcessPShop6IFD ( buriedExif, kTIFF_ExifIFD );
this->ProcessPShop6IFD ( buriedExif, kTIFF_GPSInfoIFD );
} // TIFF_FileWriter::IntegrateFromPShop6
// =================================================================================================
// TIFF_FileWriter::CopyTagToMasterIFD
// ===================================
//
// Create a new master IFD entry from a buried Photoshop 6 IFD entry. Don't try to get clever with
// large values, just create a new copy. This preserves a clean separation between the memory-based
// and file-based TIFF processing.
void* TIFF_FileWriter::CopyTagToMasterIFD ( const TagInfo & ps6Tag, InternalIFDInfo * masterIFD )
{
InternalTagMap::value_type mapValue ( ps6Tag.id, InternalTagInfo ( ps6Tag.id, ps6Tag.type, ps6Tag.count, this->fileParsed ) );
InternalTagMap::iterator newPos = masterIFD->tagMap.insert ( masterIFD->tagMap.end(), mapValue );
InternalTagInfo& newTag = newPos->second;
newTag.dataLen = ps6Tag.dataLen;
if ( newTag.dataLen <= 4 ) {
newTag.dataPtr = (XMP_Uns8*) &newTag.smallValue;
newTag.smallValue = *((XMP_Uns32*)ps6Tag.dataPtr);
} else {
newTag.dataPtr = (XMP_Uns8*) malloc ( newTag.dataLen );
if ( newTag.dataPtr == 0 ) XMP_Throw ( "Out of memory", kXMPErr_NoMemory );
memcpy ( newTag.dataPtr, ps6Tag.dataPtr, newTag.dataLen ); // AUDIT: Safe, malloc'ed dataLen bytes above.
}
newTag.changed = true; // ! See comments with ProcessPShop6IFD.
XMP_Assert ( (newTag.origDataLen == 0) && (newTag.origDataOffset == 0) );
masterIFD->changed = true;
return newPos->second.dataPtr; // ! Return the address within the map entry for small values.
} // TIFF_FileWriter::CopyTagToMasterIFD
// =================================================================================================
// FlipCFATable
// ============
//
// The CFA pattern table is trivial, a pair of short counts followed by n*m bytes.
static bool FlipCFATable ( void* voidPtr, XMP_Uns32 tagLen, GetUns16_Proc GetUns16 )
{
if ( tagLen < 4 ) return false;
XMP_Uns16* u16Ptr = (XMP_Uns16*)voidPtr;
Flip2 ( &u16Ptr[0] ); // Flip the counts to match the master TIFF.
Flip2 ( &u16Ptr[1] );
XMP_Uns16 columns = GetUns16 ( &u16Ptr[0] ); // Fetch using the master TIFF's routine.
XMP_Uns16 rows = GetUns16 ( &u16Ptr[1] );
if ( tagLen != (XMP_Uns32)(4 + columns*rows) ) return false;
return true;
} // FlipCFATable
// =================================================================================================
// FlipDSDTable
// ============
//
// The device settings description table is trivial, a pair of short counts followed by UTF-16
// strings. So the whole value should be flipped as a sequence of 16 bit items.
// ! The Exif 2.2 description is a bit garbled. It might be wrong. It would be nice to have a real example.
static bool FlipDSDTable ( void* voidPtr, XMP_Uns32 tagLen, GetUns16_Proc GetUns16 )
{
if ( tagLen < 4 ) return false;
XMP_Uns16* u16Ptr = (XMP_Uns16*)voidPtr;
for ( size_t i = tagLen/2; i > 0; --i, ++u16Ptr ) Flip2 ( u16Ptr );
return true;
} // FlipDSDTable
// =================================================================================================
// FlipOECFSFRTable
// ================
//
// The OECF and SFR tables have the same layout:
// 2 short counts, columns and rows
// c ASCII strings, null terminated, column names
// c*r rationals
static bool FlipOECFSFRTable ( void* voidPtr, XMP_Uns32 tagLen, GetUns16_Proc GetUns16 )
{
XMP_Uns16* u16Ptr = (XMP_Uns16*)voidPtr;
Flip2 ( &u16Ptr[0] ); // Flip the data to match the master TIFF.
Flip2 ( &u16Ptr[1] );
XMP_Uns16 columns = GetUns16 ( &u16Ptr[0] ); // Fetch using the master TIFF's routine.
XMP_Uns16 rows = GetUns16 ( &u16Ptr[1] );
XMP_Uns32 minLen = 4 + columns + (8 * columns * rows); // Minimum legit tag size.
if ( tagLen < minLen ) return false;
// Compute the start of the rationals from the end of value. No need to walk through the names.
XMP_Uns32* u32Ptr = (XMP_Uns32*) ((XMP_Uns8*)voidPtr + tagLen - (8 * columns * rows));
for ( size_t i = 2*columns*rows; i > 0; --i, ++u32Ptr ) Flip4 ( u32Ptr );
return true;
} // FlipOECFSFRTable
// =================================================================================================
// TIFF_FileWriter::ProcessPShop6IFD
// =================================
//
// Photoshop 6 wrote wacky TIFF files that have much of the Exif metadata buried inside of image
// resource 1058, which is itself within tag 34377 in the 0th IFD. This routine moves the buried
// tags up to the parent file. Existing tags are not replaced.
//
// While it is tempting to try to directly use the TIFF_MemoryReader's tweaked IFD info, making that
// visible would compromise implementation separation. Better to pay the modest runtime cost of
// using the official GetIFD method, letting it build the map.
//
// The tags that get moved are marked as being changed, as is the IFD they are moved into, but the
// overall TIFF_FileWriter object is not. We don't want this integration on its own to force a file
// update, but a file update should include these changes.
// ! Be careful to not move tags that are the nasty Exif explicit offsets, e.g. the Exif or GPS IFD
// ! "pointers". These are tags with a LONG type and count of 1, whose value is an offset into the
// ! buried TIFF stream. We can't reliably plant that offset into the outer IFD structure.
// ! To make things even more fun, the buried Exif might not have the same endianness as the outer!
void TIFF_FileWriter::ProcessPShop6IFD ( const TIFF_MemoryReader& buriedExif, XMP_Uns8 ifd )
{
bool ok, found;
TagInfoMap ps6IFD;
found = buriedExif.GetIFD ( ifd, &ps6IFD );
if ( ! found ) return;
bool needsFlipping = (this->bigEndian != buriedExif.IsBigEndian());
InternalIFDInfo* masterIFD = &this->containedIFDs[ifd];
TagInfoMap::const_iterator ps6Pos = ps6IFD.begin();
TagInfoMap::const_iterator ps6End = ps6IFD.end();
for ( ; ps6Pos != ps6End; ++ps6Pos ) {
// Copy buried tags to the master IFD if they don't already exist there.
const TagInfo& ps6Tag = ps6Pos->second;
if ( this->FindTagInIFD ( ifd, ps6Tag.id ) != 0 ) continue; // Keep existing master tags.
if ( needsFlipping && (ps6Tag.id == 37500) ) continue; // Don't copy an unflipped MakerNote.
if ( (ps6Tag.id == kTIFF_ExifIFDPointer) || // Skip the tags that are explicit offsets.
(ps6Tag.id == kTIFF_GPSInfoIFDPointer) ||
(ps6Tag.id == kTIFF_JPEGInterchangeFormat) ||
(ps6Tag.id == kTIFF_InteroperabilityIFDPointer) ) continue;
void* voidPtr = this->CopyTagToMasterIFD ( ps6Tag, masterIFD );
if ( needsFlipping ) {
switch ( ps6Tag.type ) {
case kTIFF_ByteType:
case kTIFF_SByteType:
case kTIFF_ASCIIType:
// Nothing more to do.
break;
case kTIFF_ShortType:
case kTIFF_SShortType:
{
XMP_Uns16* u16Ptr = (XMP_Uns16*)voidPtr;
for ( size_t i = ps6Tag.count; i > 0; --i, ++u16Ptr ) Flip2 ( u16Ptr );
}
break;
case kTIFF_LongType:
case kTIFF_SLongType:
case kTIFF_FloatType:
{
XMP_Uns32* u32Ptr = (XMP_Uns32*)voidPtr;
for ( size_t i = ps6Tag.count; i > 0; --i, ++u32Ptr ) Flip4 ( u32Ptr );
}
break;
case kTIFF_RationalType:
case kTIFF_SRationalType:
{
XMP_Uns32* ratPtr = (XMP_Uns32*)voidPtr;
for ( size_t i = (2 * ps6Tag.count); i > 0; --i, ++ratPtr ) Flip4 ( ratPtr );
}
break;
case kTIFF_DoubleType:
{
XMP_Uns64* u64Ptr = (XMP_Uns64*)voidPtr;
for ( size_t i = ps6Tag.count; i > 0; --i, ++u64Ptr ) Flip8 ( u64Ptr );
}
break;
case kTIFF_UndefinedType:
// Fix up the few kinds of special tables that Exif 2.2 defines.
ok = true; // Keep everything that isn't a special table.
if ( ps6Tag.id == kTIFF_CFAPattern ) {
ok = FlipCFATable ( voidPtr, ps6Tag.dataLen, this->GetUns16 );
} else if ( ps6Tag.id == kTIFF_DeviceSettingDescription ) {
ok = FlipDSDTable ( voidPtr, ps6Tag.dataLen, this->GetUns16 );
} else if ( (ps6Tag.id == kTIFF_OECF) || (ps6Tag.id == kTIFF_SpatialFrequencyResponse) ) {
ok = FlipOECFSFRTable ( voidPtr, ps6Tag.dataLen, this->GetUns16 );
}
if ( ! ok ) this->DeleteTag ( ifd, ps6Tag.id );
break;
default:
// ? XMP_Throw ( "Unexpected tag type", kXMPErr_InternalFailure );
this->DeleteTag ( ifd, ps6Tag.id );
break;
}
}
}
} // TIFF_FileWriter::ProcessPShop6IFD
// =================================================================================================
// TIFF_FileWriter::PreflightIFDLinkage
// ====================================
//
// Preflight special cases for the linkage between IFDs. Three of the IFDs are found through an
// explicit tag, the Exif, GPS, and Interop IFDs. The presence or absence of those IFDs affects the
// presence or absence of the linkage tag, which can affect the IFD containing the linkage tag. The
// thumbnail IFD is chained from the primary IFD, so if the thumbnail IFD is present we make sure
// that the primary IFD isn't empty.
void TIFF_FileWriter::PreflightIFDLinkage()
{
// Do the tag-linked IFDs bottom up, Interop then GPS then Exif.
if ( this->containedIFDs[kTIFF_InteropIFD].tagMap.empty() ) {
this->DeleteTag ( kTIFF_ExifIFD, kTIFF_InteroperabilityIFDPointer );
} else if ( ! this->GetTag ( kTIFF_ExifIFD, kTIFF_InteroperabilityIFDPointer, 0 ) ) {
this->SetTag_Long ( kTIFF_ExifIFD, kTIFF_InteroperabilityIFDPointer, 0xABADABAD );
}
if ( this->containedIFDs[kTIFF_GPSInfoIFD].tagMap.empty() ) {
this->DeleteTag ( kTIFF_PrimaryIFD, kTIFF_GPSInfoIFDPointer );
} else if ( ! this->GetTag ( kTIFF_PrimaryIFD, kTIFF_GPSInfoIFDPointer, 0 ) ) {
this->SetTag_Long ( kTIFF_PrimaryIFD, kTIFF_GPSInfoIFDPointer, 0xABADABAD );
}
if ( this->containedIFDs[kTIFF_ExifIFD].tagMap.empty() ) {
this->DeleteTag ( kTIFF_PrimaryIFD, kTIFF_ExifIFDPointer );
} else if ( ! this->GetTag ( kTIFF_PrimaryIFD, kTIFF_ExifIFDPointer, 0 ) ) {
this->SetTag_Long ( kTIFF_PrimaryIFD, kTIFF_ExifIFDPointer, 0xABADABAD );
}
// Make sure that the primary IFD is not empty if the thumbnail IFD is not empty.
if ( this->containedIFDs[kTIFF_PrimaryIFD].tagMap.empty() &&
(! this->containedIFDs[kTIFF_TNailIFD].tagMap.empty()) ) {
this->SetTag_Short ( kTIFF_PrimaryIFD, kTIFF_ResolutionUnit, 2 ); // Set Resolution unit to inches.
}
} // TIFF_FileWriter::PreflightIFDLinkage
// =================================================================================================
// TIFF_FileWriter::DetermineVisibleLength
// =======================================
XMP_Uns32 TIFF_FileWriter::DetermineVisibleLength()
{
XMP_Uns32 visibleLength = 8; // Start with the TIFF header size.
for ( int ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) {
InternalIFDInfo& ifdInfo ( this->containedIFDs[ifd] );
size_t tagCount = ifdInfo.tagMap.size();
if ( tagCount == 0 ) continue;
visibleLength += (XMP_Uns32)( 6 + (12 * tagCount) );
InternalTagMap::iterator tagPos = ifdInfo.tagMap.begin();
InternalTagMap::iterator tagEnd = ifdInfo.tagMap.end();
for ( ; tagPos != tagEnd; ++tagPos ) {
InternalTagInfo & currTag ( tagPos->second );
if ( currTag.dataLen > 4 ) visibleLength += ((currTag.dataLen + 1) & 0xFFFFFFFE); // ! Round to even lengths.
}
}
return visibleLength;
} // TIFF_FileWriter::DetermineVisibleLength
// =================================================================================================
// TIFF_FileWriter::DetermineAppendInfo
// ====================================
#ifndef Trace_DetermineAppendInfo
#define Trace_DetermineAppendInfo 0
#endif
// An IFD grows if it has more tags than before.
#define DoesIFDGrow(ifd) (this->containedIFDs[ifd].origCount < this->containedIFDs[ifd].tagMap.size())
XMP_Uns32 TIFF_FileWriter::DetermineAppendInfo ( XMP_Uns32 appendedOrigin,
bool appendedIFDs[kTIFF_KnownIFDCount],
XMP_Uns32 newIFDOffsets[kTIFF_KnownIFDCount],
bool appendAll /* = false */ )
{
XMP_Uns32 appendedLength = 0;
XMP_Assert ( (appendedOrigin & 1) == 0 ); // Make sure it is even.
#if Trace_DetermineAppendInfo
{
printf ( "\nEntering TIFF_FileWriter::DetermineAppendInfo%s\n", (appendAll ? ", append all" : "") );
for ( int ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) {
InternalIFDInfo & thisIFD = this->containedIFDs[ifd];
printf ( "\n IFD %d, origCount %d, map.size %d, origIFDOffset %d (0x%X), origNextIFD %d (0x%X)",
ifd, thisIFD.origCount, thisIFD.tagMap.size(),
thisIFD.origIFDOffset, thisIFD.origIFDOffset, thisIFD.origNextIFD, thisIFD.origNextIFD );
if ( thisIFD.changed ) printf ( ", changed" );
if ( thisIFD.origCount < thisIFD.tagMap.size() ) printf ( ", should get appended" );
printf ( "\n" );
InternalTagMap::iterator tagPos;
InternalTagMap::iterator tagEnd = thisIFD.tagMap.end();
for ( tagPos = thisIFD.tagMap.begin(); tagPos != tagEnd; ++tagPos ) {
InternalTagInfo & thisTag = tagPos->second;
printf ( " Tag %d, smallValue 0x%X, origDataLen %d, origDataOffset %d (0x%X)",
thisTag.id, thisTag.smallValue, thisTag.origDataLen, thisTag.origDataOffset, thisTag.origDataOffset );
if ( thisTag.changed ) printf ( ", changed" );
if ( (thisTag.dataLen > thisTag.origDataLen) && (thisTag.dataLen > 4) ) printf ( ", should get appended" );
printf ( "\n" );
}
}
printf ( "\n" );
}
#endif
// Determine which of the IFDs will be appended. If the Exif, GPS, or Interoperability IFDs are
// appended, set dummy values for their offsets in the "owning" IFD. This must be done first
// since this might cause the owning IFD to grow.
if ( ! appendAll ) {
for ( int i = 0; i < kTIFF_KnownIFDCount ;++i ) appendedIFDs[i] = false;
} else {
for ( int i = 0; i < kTIFF_KnownIFDCount ;++i ) appendedIFDs[i] = (this->containedIFDs[i].tagMap.size() > 0);
}
appendedIFDs[kTIFF_InteropIFD] |= DoesIFDGrow ( kTIFF_InteropIFD );
if ( appendedIFDs[kTIFF_InteropIFD] ) {
this->SetTag_Long ( kTIFF_ExifIFD, kTIFF_InteroperabilityIFDPointer, 0xABADABAD );
}
appendedIFDs[kTIFF_GPSInfoIFD] |= DoesIFDGrow ( kTIFF_GPSInfoIFD );
if ( appendedIFDs[kTIFF_GPSInfoIFD] ) {
this->SetTag_Long ( kTIFF_PrimaryIFD, kTIFF_GPSInfoIFDPointer, 0xABADABAD );
}
appendedIFDs[kTIFF_ExifIFD] |= DoesIFDGrow ( kTIFF_ExifIFD );
if ( appendedIFDs[kTIFF_ExifIFD] ) {
this->SetTag_Long ( kTIFF_PrimaryIFD, kTIFF_ExifIFDPointer, 0xABADABAD );
}
appendedIFDs[kTIFF_PrimaryIFD] |= DoesIFDGrow ( kTIFF_PrimaryIFD );
// The appended data (if any) will be a sequence of an IFD followed by its large values.
// Determine the new offsets for the appended IFDs and tag values, and the total amount of
// appended stuff. The final IFD offset is set in newIFDOffsets for all IFDs, changed or not.
// This makes it easier to set the offsets to the primary and thumbnail IFDs when writing.
for ( int ifd = 0; ifd < kTIFF_KnownIFDCount ;++ifd ) {
InternalIFDInfo& ifdInfo ( this->containedIFDs[ifd] );
size_t tagCount = ifdInfo.tagMap.size();
newIFDOffsets[ifd] = ifdInfo.origIFDOffset; // Make the new offset valid for unchanged IFDs.
if ( ! (appendAll | ifdInfo.changed) ) continue;
if ( tagCount == 0 ) continue;
if ( appendedIFDs[ifd] ) {
newIFDOffsets[ifd] = appendedOrigin + appendedLength;
appendedLength += (XMP_Uns32)( 6 + (12 * tagCount) );
}
InternalTagMap::iterator tagPos = ifdInfo.tagMap.begin();
InternalTagMap::iterator tagEnd = ifdInfo.tagMap.end();
for ( ; tagPos != tagEnd; ++tagPos ) {
InternalTagInfo & currTag ( tagPos->second );
if ( (! (appendAll | currTag.changed)) || (currTag.dataLen <= 4) ) continue;
if ( (currTag.dataLen <= currTag.origDataLen) && (! appendAll) ) {
this->PutUns32 ( currTag.origDataOffset, &currTag.smallValue ); // Reuse the old space.
} else {
this->PutUns32 ( (appendedOrigin + appendedLength), &currTag.smallValue ); // Set the appended offset.
appendedLength += ((currTag.dataLen + 1) & 0xFFFFFFFEUL); // Round to an even size.
}
}
}
// If the Exif, GPS, or Interoperability IFDs get appended, update the tag values for their new offsets.
if ( appendedIFDs[kTIFF_ExifIFD] ) {
this->SetTag_Long ( kTIFF_PrimaryIFD, kTIFF_ExifIFDPointer, newIFDOffsets[kTIFF_ExifIFD] );
}
if ( appendedIFDs[kTIFF_GPSInfoIFD] ) {
this->SetTag_Long ( kTIFF_PrimaryIFD, kTIFF_GPSInfoIFDPointer, newIFDOffsets[kTIFF_GPSInfoIFD] );
}
if ( appendedIFDs[kTIFF_InteropIFD] ) {
this->SetTag_Long ( kTIFF_ExifIFD, kTIFF_InteroperabilityIFDPointer, newIFDOffsets[kTIFF_InteropIFD] );
}
#if Trace_DetermineAppendInfo
{
printf ( "Exiting TIFF_FileWriter::DetermineAppendInfo\n" );
for ( int ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) {
InternalIFDInfo & thisIFD = this->containedIFDs[ifd];
printf ( "\n IFD %d, origCount %d, map.size %d, origIFDOffset %d (0x%X), origNextIFD %d (0x%X)",
ifd, thisIFD.origCount, thisIFD.tagMap.size(),
thisIFD.origIFDOffset, thisIFD.origIFDOffset, thisIFD.origNextIFD, thisIFD.origNextIFD );
if ( thisIFD.changed ) printf ( ", changed" );
if ( appendedIFDs[ifd] ) printf ( ", will be appended at %d (0x%X)", newIFDOffsets[ifd], newIFDOffsets[ifd] );
printf ( "\n" );
InternalTagMap::iterator tagPos;
InternalTagMap::iterator tagEnd = thisIFD.tagMap.end();
for ( tagPos = thisIFD.tagMap.begin(); tagPos != tagEnd; ++tagPos ) {
InternalTagInfo & thisTag = tagPos->second;
printf ( " Tag %d, smallValue 0x%X, origDataLen %d, origDataOffset %d (0x%X)",
thisTag.id, thisTag.smallValue, thisTag.origDataLen, thisTag.origDataOffset, thisTag.origDataOffset );
if ( thisTag.changed ) printf ( ", changed" );
if ( (thisTag.dataLen > thisTag.origDataLen) && (thisTag.dataLen > 4) ) {
XMP_Uns32 newOffset = this->GetUns32 ( &thisTag.smallValue );
printf ( ", will be appended at %d (0x%X)", newOffset, newOffset );
}
printf ( "\n" );
}
}
printf ( "\n" );
}
#endif
return appendedLength;
} // TIFF_FileWriter::DetermineAppendInfo
// =================================================================================================
// TIFF_FileWriter::UpdateMemByAppend
// ==================================
//
// Normally we update TIFF in a conservative "by-append" manner. Changes are written in-place where
// they fit, anything requiring growth is appended to the end and the old space is abandoned. The
// end for memory-based TIFF is the end of the data block, the end for file-based TIFF is the end of
// the file. This update-by-append model has the advantage of not perturbing any hidden offsets, a
// common feature of proprietary MakerNotes.
//
// When doing the update-by-append we're only going to be modifying things that have changed. This
// means IFDs with changed, added, or deleted tags, and large values for changed or added tags. The
// IFDs and tag values are updated in-place if they fit, leaving holes in the stream if the new
// value is smaller than the old.
// ** Someday we might want to use the FreeOffsets and FreeByteCounts tags to track free space.
// ** Probably not a huge win in practice though, and the TIFF spec says they are not recommended
// ** for general interchange use.
void TIFF_FileWriter::UpdateMemByAppend ( XMP_Uns8** newStream_out, XMP_Uns32* newLength_out,
bool appendAll /* = false */, XMP_Uns32 extraSpace /* = 0 */ )
{
bool appendedIFDs[kTIFF_KnownIFDCount];
XMP_Uns32 newIFDOffsets[kTIFF_KnownIFDCount];
XMP_Uns32 appendedOrigin = ((this->tiffLength + 1) & 0xFFFFFFFEUL); // Start at an even offset.
XMP_Uns32 appendedLength = DetermineAppendInfo ( appendedOrigin, appendedIFDs, newIFDOffsets, appendAll );
// Allocate the new block of memory for the full stream. Copy the original stream. Write the
// modified IFDs and values. Finally rebuild the internal IFD info and tag map.
XMP_Uns32 newLength = appendedOrigin + appendedLength;
XMP_Uns8* newStream = (XMP_Uns8*) malloc ( newLength + extraSpace );
if ( newStream == 0 ) XMP_Throw ( "Out of memory", kXMPErr_NoMemory );
memcpy ( newStream, this->memStream, this->tiffLength ); // AUDIT: Safe, malloc'ed newLength bytes above.
if ( this->tiffLength < appendedOrigin ) {
XMP_Assert ( appendedOrigin == (this->tiffLength + 1) );
newStream[this->tiffLength] = 0; // Clear the pad byte.
}
try { // We might get exceptions from the next part and must delete newStream on the way out.
// Write the modified IFDs and values. Rewrite the full IFD from scratch to make sure the
// tags are now unique and sorted. Copy large changed values to their appropriate location.
XMP_Uns32 appendedOffset = appendedOrigin;
for ( int ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) {
InternalIFDInfo& ifdInfo ( this->containedIFDs[ifd] );
size_t tagCount = ifdInfo.tagMap.size();
if ( ! (appendAll | ifdInfo.changed) ) continue;
if ( tagCount == 0 ) continue;
XMP_Uns8* ifdPtr = newStream + newIFDOffsets[ifd];
if ( appendedIFDs[ifd] ) {
XMP_Assert ( newIFDOffsets[ifd] == appendedOffset );
appendedOffset += (XMP_Uns32)( 6 + (12 * tagCount) );
}
this->PutUns16 ( (XMP_Uns16)tagCount, ifdPtr );
ifdPtr += 2;
InternalTagMap::iterator tagPos = ifdInfo.tagMap.begin();
InternalTagMap::iterator tagEnd = ifdInfo.tagMap.end();
for ( ; tagPos != tagEnd; ++tagPos ) {
InternalTagInfo & currTag ( tagPos->second );
this->PutUns16 ( currTag.id, ifdPtr );
ifdPtr += 2;
this->PutUns16 ( currTag.type, ifdPtr );
ifdPtr += 2;
this->PutUns32 ( currTag.count, ifdPtr );
ifdPtr += 4;
PutUns32AsIs ( currTag.smallValue, ifdPtr );
if ( (appendAll | currTag.changed) && (currTag.dataLen > 4) ) {
XMP_Uns32 valueOffset = this->GetUns32 ( &currTag.smallValue );
if ( (currTag.dataLen <= currTag.origDataLen) && (! appendAll) ) {
XMP_Assert ( valueOffset == currTag.origDataOffset );
} else {
XMP_Assert ( valueOffset == appendedOffset );
appendedOffset += ((currTag.dataLen + 1) & 0xFFFFFFFEUL);
}
XMP_Assert ( valueOffset <= newLength ); // Provably true, valueOffset is in the old span, newLength is the new bigger span.
if ( currTag.dataLen > (newLength - valueOffset) ) XMP_Throw ( "Buffer overrun", kXMPErr_InternalFailure );
memcpy ( (newStream + valueOffset), currTag.dataPtr, currTag.dataLen ); // AUDIT: Protected by the above check.
if ( (currTag.dataLen & 1) != 0 ) newStream[valueOffset+currTag.dataLen] = 0;
}
ifdPtr += 4;
}
this->PutUns32 ( ifdInfo.origNextIFD, ifdPtr );
ifdPtr += 4;
}
XMP_Assert ( appendedOffset == newLength );
// Back fill the offsets for the primary and thumnbail IFDs, if they are now appended.
if ( appendedIFDs[kTIFF_PrimaryIFD] ) {
this->PutUns32 ( newIFDOffsets[kTIFF_PrimaryIFD], (newStream + 4) );
}
if ( appendedIFDs[kTIFF_TNailIFD] ) {
size_t primaryTagCount = this->containedIFDs[kTIFF_PrimaryIFD].tagMap.size();
if ( primaryTagCount > 0 ) {
XMP_Uns32 tnailLinkOffset = newIFDOffsets[kTIFF_PrimaryIFD] + 2 + (12 * primaryTagCount);
this->PutUns32 ( newIFDOffsets[kTIFF_TNailIFD], (newStream + tnailLinkOffset) );
}
}
} catch ( ... ) {
free ( newStream );
throw;
}
*newStream_out = newStream;
*newLength_out = newLength;
} // TIFF_FileWriter::UpdateMemByAppend
// =================================================================================================
// TIFF_FileWriter::UpdateMemByRewrite
// ===================================
//
// Normally we update TIFF in a conservative "by-append" manner. Changes are written in-place where
// they fit, anything requiring growth is appended to the end and the old space is abandoned. The
// end for memory-based TIFF is the end of the data block, the end for file-based TIFF is the end of
// the file. This update-by-append model has the advantage of not perturbing any hidden offsets, a
// common feature of proprietary MakerNotes.
//
// The condenseStream parameter can be used to rewrite the full stream instead of appending. This
// will discard any MakerNote tag and risks breaking offsets that are hidden. This can be necessary
// though to try to make the TIFF fit in a JPEG file.
//
// We don't do most of the actual rewrite here. We set things up so that UpdateMemByAppend can be
// called to append onto a bare TIFF header. Additional hidden offsets are then handled here.
//
// The hidden offsets for the Exif, GPS, and Interoperability IFDs (tags 34665, 34853, and 40965)
// are handled by the code in DetermineAppendInfo, which is called from UpdateMemByAppend, which is
// called from here.
//
// These other tags are recognized as being hidden offsets when composing a condensed stream:
// 273 - StripOffsets, lengths in tag 279
// 288 - FreeOffsets, lengths in tag 289
// 324 - TileOffsets, lengths in tag 325
// 330 - SubIFDs, lengths within the IFDs (Plus subIFD values and possible chaining!)
// 513 - JPEGInterchangeFormat, length in tag 514
// 519 - JPEGQTables, each table is 64 bytes
// 520 - JPEGDCTables, lengths ???
// 521 - JPEGACTables, lengths ???
//
// Some of these will handled and kept, some will be thrown out, some will cause the rewrite to fail.
// At this time only the JPEG thumbnail tags, 513 and 514, contain hidden data that is kept. The
// final stream layout is whatever UpdateMemByAppend does for the visible content, followed by the
// hidden offset data. The Exif, GPS, and Interoperability IFDs are visible to UpdateMemByAppend.
// ! So far, a memory-based TIFF rewrite would only be done for the Exif portion of a JPEG file.
// ! In which case we're probably OK to handle JPEGInterchangeFormat (used for compressed thumbnails)
// ! and complain about any of the other hidden offset tags.
// tag count type
// 273 n short or long
// 279 n short or long
// 288 n long
// 289 n long
// 324 n long
// 325 n short or long
// 330 n long
// 513 1 long
// 514 1 long
// 519 n long
// 520 n long
// 521 n long
static XMP_Uns16 kNoGoTags[] =
{
kTIFF_StripOffsets, // 273 *** Should be handled?
kTIFF_StripByteCounts, // 279 *** Should be handled?
kTIFF_FreeOffsets, // 288 *** Should be handled?
kTIFF_FreeByteCounts, // 289 *** Should be handled?
kTIFF_TileOffsets, // 324 *** Should be handled?
kTIFF_TileByteCounts, // 325 *** Should be handled?
kTIFF_SubIFDs, // 330 *** Should be handled?
kTIFF_JPEGQTables, // 519
kTIFF_JPEGDCTables, // 520
kTIFF_JPEGACTables, // 521
0xFFFF // Must be last as a sentinel.
};
static XMP_Uns16 kBanishedTags[] =
{
kTIFF_MakerNote, // *** Should someday support MakerNoteSafety.
0xFFFF // Must be last as a sentinel.
};
struct SimpleHiddenContentInfo {
XMP_Uns8 ifd;
XMP_Uns16 offsetTag, lengthTag;
};
struct SimpleHiddenContentLocations {
XMP_Uns32 length, oldOffset, newOffset;
SimpleHiddenContentLocations() : length(0), oldOffset(0), newOffset(0) {};
};
enum { kSimpleHiddenContentCount = 1 };
static const SimpleHiddenContentInfo kSimpleHiddenContentInfo [kSimpleHiddenContentCount] =
{
{ kTIFF_TNailIFD, kTIFF_JPEGInterchangeFormat, kTIFF_JPEGInterchangeFormatLength }
};
// -------------------------------------------------------------------------------------------------
void TIFF_FileWriter::UpdateMemByRewrite ( XMP_Uns8** newStream_out, XMP_Uns32* newLength_out )
{
const InternalTagInfo* tagInfo;
// Check for tags that we don't tolerate because they have data we can't (or refuse to) find.
for ( XMP_Uns8 ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) {
for ( int i = 0; kNoGoTags[i] != 0xFFFF; ++i ) {
tagInfo = this->FindTagInIFD ( ifd, kNoGoTags[i] );
if ( tagInfo != 0 ) XMP_Throw ( "Tag not tolerated for TIFF rewrite", kXMPErr_Unimplemented );
}
}
// Delete unwanted tags.
for ( XMP_Uns8 ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) {
for ( int i = 0; kBanishedTags[i] != 0xFFFF; ++i ) {
this->DeleteTag ( ifd, kBanishedTags[i] );
}
}
// Determine the offsets and additional size for the hidden offset content. Set the offset tags
// to the new offset so that UpdateMemByAppend writes the new offsets.
XMP_Uns32 hiddenContentLength = 0;
XMP_Uns32 hiddenContentOrigin = this->DetermineVisibleLength();
SimpleHiddenContentLocations hiddenLocations [kSimpleHiddenContentCount];
for ( int i = 0; i < kSimpleHiddenContentCount; ++i ) {
const SimpleHiddenContentInfo & hiddenInfo ( kSimpleHiddenContentInfo[i] );
bool haveLength = this->GetTag_Integer ( hiddenInfo.ifd, hiddenInfo.lengthTag, &hiddenLocations[i].length );
bool haveOffset = this->GetTag_Integer ( hiddenInfo.ifd, hiddenInfo.offsetTag, &hiddenLocations[i].oldOffset );
if ( haveLength != haveOffset ) XMP_Throw ( "Unpaired simple hidden content tag", kXMPErr_BadTIFF );
if ( (! haveLength) || (hiddenLocations[i].length == 0) ) continue;
hiddenLocations[i].newOffset = hiddenContentOrigin + hiddenContentLength;
this->SetTag_Long ( hiddenInfo.ifd, hiddenInfo.offsetTag, hiddenLocations[i].newOffset );
hiddenContentLength += ((hiddenLocations[i].length + 1) & 0xFFFFFFFE); // ! Round up for even offsets.
}
// Save any old memory stream for the content behind hidden offsets. Setup a bare TIFF header.
XMP_Uns8* oldStream = this->memStream;
bool ownedOldStream = this->ownedStream;
XMP_Uns8 bareTIFF [8];
if ( this->bigEndian ) {
bareTIFF[0] = 0x4D; bareTIFF[1] = 0x4D; bareTIFF[2] = 0x00; bareTIFF[3] = 0x2A;
} else {
bareTIFF[0] = 0x49; bareTIFF[1] = 0x49; bareTIFF[2] = 0x2A; bareTIFF[3] = 0x00;
}
*((XMP_Uns32*)&bareTIFF[4]) = 0;
this->memStream = &bareTIFF[0];
this->tiffLength = sizeof ( bareTIFF );
this->ownedStream = false;
// Call UpdateMemByAppend to write the new stream, telling it to append everything.
this->UpdateMemByAppend ( newStream_out, newLength_out, true, hiddenContentLength );
// Copy the hidden content and update the output stream length;
XMP_Assert ( *newLength_out == hiddenContentOrigin );
*newLength_out += hiddenContentLength;
for ( int i = 0; i < kSimpleHiddenContentCount; ++i ) {
if ( hiddenLocations[i].length == 0 ) continue;
XMP_Uns8* srcPtr = oldStream + hiddenLocations[i].oldOffset;
XMP_Uns8* destPtr = *newStream_out + hiddenLocations[i].newOffset;
memcpy ( destPtr, srcPtr, hiddenLocations[i].length ); // AUDIT: Safe copy, not user data, computed length.
}
// Delete the old stream if appropriate.
if ( ownedOldStream ) delete ( oldStream );
} // TIFF_FileWriter::UpdateMemByRewrite
// =================================================================================================
// TIFF_FileWriter::UpdateMemoryStream
// ===================================
//
// Normally we update TIFF in a conservative "by-append" manner. Changes are written in-place where
// they fit, anything requiring growth is appended to the end and the old space is abandoned. The
// end for memory-based TIFF is the end of the data block, the end for file-based TIFF is the end of
// the file. This update-by-append model has the advantage of not perturbing any hidden offsets, a
// common feature of proprietary MakerNotes.
//
// The condenseStream parameter can be used to rewrite the full stream instead of appending. This
// will discard any MakerNote tags and risks breaking offsets that are hidden. This can be necessary
// though to try to make the TIFF fit in a JPEG file.
XMP_Uns32 TIFF_FileWriter::UpdateMemoryStream ( void** dataPtr, bool condenseStream /* = false */ )
{
if ( this->fileParsed ) XMP_Throw ( "Not memory based", kXMPErr_EnforceFailure );
this->changed |= condenseStream; // Make sure a compaction request takes effect.
if ( ! this->changed ) {
if ( dataPtr != 0 ) *dataPtr = this->memStream;
return this->tiffLength;
}
this->PreflightIFDLinkage();
bool nowEmpty = true;
for ( size_t i = 0; i < kTIFF_KnownIFDCount; ++i ) {
if ( ! this->containedIFDs[i].tagMap.empty() ) {
nowEmpty = false;
break;
}
}
XMP_Uns8* newStream = 0;
XMP_Uns32 newLength = 0;
if ( nowEmpty ) {
this->DeleteExistingInfo(); // Prepare for an empty reparse.
} else {
if ( this->tiffLength == 0 ) { // ! An empty parse does set this->memParsed.
condenseStream = true; // Makes "conjured" TIFF take the full rewrite path.
}
if ( condenseStream ) {
this->UpdateMemByRewrite ( &newStream, &newLength );
} else {
this->UpdateMemByAppend ( &newStream, &newLength );
}
}
// Parse the revised stream. This is the cleanest way to rebuild the tag map.
this->ParseMemoryStream ( newStream, newLength, kDoNotCopyData );
XMP_Assert ( this->tiffLength == newLength );
this->ownedStream = (newLength > 0); // ! We really do own the new stream, if not empty.
if ( dataPtr != 0 ) *dataPtr = this->memStream;
return newLength;
} // TIFF_FileWriter::UpdateMemoryStream
// =================================================================================================
// TIFF_FileWriter::UpdateFileStream
// =================================
//
// Updating a file stream is done in the same general manner as updating a memory stream, the intro
// comments for UpdateMemoryStream largely apply. The file update happens in 3 phases:
// 1. Determine which IFDs will be appended, and the new offsets for the appended IFDs and tags.
// 2. Do the in-place update for the IFDs and tag values that fit.
// 3. Append the IFDs and tag values that grow.
//
// The file being updated must match the file that was previously parsed. Offsets and lengths saved
// when parsing are used to decide if something can be updated in-place or must be appended.
// *** The general linked structure of TIFF makes it very difficult to process the file in a single
// *** sequential pass. This implementation uses a simple seek/write model for the in-place updates.
// *** In the future we might want to consider creating a map of what gets updated, allowing use of
// *** a possibly more efficient buffered model.
// ** Someday we might want to use the FreeOffsets and FreeByteCounts tags to track free space.
// ** Probably not a huge win in practice though, and the TIFF spec says they are not recommended
// ** for general interchange use.
#ifndef Trace_UpdateFileStream
#define Trace_UpdateFileStream 0
#endif
void TIFF_FileWriter::UpdateFileStream ( XMP_IO* fileRef, XMP_ProgressTracker* progressTracker )
{
if ( this->memParsed ) XMP_Throw ( "Not file based", kXMPErr_EnforceFailure );
if ( ! this->changed ) return;
XMP_Int64 origDataLength = fileRef->Length();
if ( (origDataLength >> 32) != 0 ) XMP_Throw ( "TIFF files can't exceed 4GB", kXMPErr_BadTIFF );
bool appendedIFDs[kTIFF_KnownIFDCount];
XMP_Uns32 newIFDOffsets[kTIFF_KnownIFDCount];
#if Trace_UpdateFileStream
printf ( "\nStarting update of TIFF file stream\n" );
#endif
XMP_Uns32 appendedOrigin = (XMP_Uns32)origDataLength;
if ( (appendedOrigin & 1) != 0 ) {
++appendedOrigin; // Start at an even offset.
fileRef->Seek ( 0, kXMP_SeekFromEnd );
fileRef->Write ( "\0", 1 );
}
this->PreflightIFDLinkage();
XMP_Uns32 appendedLength = DetermineAppendInfo ( appendedOrigin, appendedIFDs, newIFDOffsets );
if ( appendedLength > (0xFFFFFFFFUL - appendedOrigin) ) XMP_Throw ( "TIFF files can't exceed 4GB", kXMPErr_BadTIFF );
if ( progressTracker != 0 ) {
float filesize=0;
for ( int ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) {
InternalIFDInfo & thisIFD = this->containedIFDs[ifd];
if ( ! thisIFD.changed ) continue;
filesize += (thisIFD.tagMap.size() * sizeof(RawIFDEntry) + 6);
InternalTagMap::iterator tagPos;
InternalTagMap::iterator tagEnd = thisIFD.tagMap.end();
for ( tagPos = thisIFD.tagMap.begin(); tagPos != tagEnd; ++tagPos ) {
InternalTagInfo & thisTag = tagPos->second;
if ( (! thisTag.changed) || (thisTag.dataLen <= 4) ) continue;
filesize += (thisTag.dataLen) ;
}
}
if ( appendedIFDs[kTIFF_PrimaryIFD] ) filesize += 4;
XMP_Assert ( progressTracker->WorkInProgress() );
progressTracker->AddTotalWork ( filesize );
}
// Do the in-place update for the IFDs and tag values that fit. This part does separate seeks
// and writes for the IFDs and values. Things to be updated can be anywhere in the file.
// *** This might benefit from a map of the in-place updates. This would allow use of a possibly
// *** more efficient sequential I/O model. Could even incorporate the safe update file copying.
for ( int ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) {
InternalIFDInfo & thisIFD = this->containedIFDs[ifd];
if ( ! thisIFD.changed ) continue;
// In order to get a little bit of locality, write the IFD first then the changed tags that
// have large values and fit in-place.
if ( ! appendedIFDs[ifd] ) {
#if Trace_UpdateFileStream
printf ( " Updating IFD %d in-place at offset %d (0x%X)\n", ifd, thisIFD.origIFDOffset, thisIFD.origIFDOffset );
#endif
fileRef->Seek ( thisIFD.origIFDOffset, kXMP_SeekFromStart );
this->WriteFileIFD ( fileRef, thisIFD );
}
InternalTagMap::iterator tagPos;
InternalTagMap::iterator tagEnd = thisIFD.tagMap.end();
for ( tagPos = thisIFD.tagMap.begin(); tagPos != tagEnd; ++tagPos ) {
InternalTagInfo & thisTag = tagPos->second;
if ( (! thisTag.changed) || (thisTag.dataLen <= 4) || (thisTag.dataLen > thisTag.origDataLen) ) continue;
#if Trace_UpdateFileStream
printf ( " Updating tag %d in IFD %d in-place at offset %d (0x%X)\n", thisTag.id, ifd, thisTag.origDataOffset, thisTag.origDataOffset );
#endif
fileRef->Seek ( thisTag.origDataOffset, kXMP_SeekFromStart );
fileRef->Write ( thisTag.dataPtr, thisTag.dataLen );
}
}
// Append the IFDs and tag values that grow.
XMP_Int64 fileEnd = fileRef->Seek ( 0, kXMP_SeekFromEnd );
XMP_Assert ( fileEnd == appendedOrigin );
for ( int ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) {
InternalIFDInfo & thisIFD = this->containedIFDs[ifd];
if ( ! thisIFD.changed ) continue;
if ( appendedIFDs[ifd] ) {
#if Trace_UpdateFileStream
printf ( " Updating IFD %d by append at offset %d (0x%X)\n", ifd, newIFDOffsets[ifd], newIFDOffsets[ifd] );
#endif
XMP_Assert ( newIFDOffsets[ifd] == fileRef->Length() );
this->WriteFileIFD ( fileRef, thisIFD );
}
InternalTagMap::iterator tagPos;
InternalTagMap::iterator tagEnd = thisIFD.tagMap.end();
for ( tagPos = thisIFD.tagMap.begin(); tagPos != tagEnd; ++tagPos ) {
InternalTagInfo & thisTag = tagPos->second;
if ( (! thisTag.changed) || (thisTag.dataLen <= 4) || (thisTag.dataLen <= thisTag.origDataLen) ) continue;
#if Trace_UpdateFileStream
XMP_Uns32 newOffset = this->GetUns32(&thisTag.origDataOffset);
printf ( " Updating tag %d in IFD %d by append at offset %d (0x%X)\n", thisTag.id, ifd, newOffset, newOffset );
#endif
XMP_Assert ( this->GetUns32(&thisTag.smallValue) == fileRef->Length() );
fileRef->Write ( thisTag.dataPtr, thisTag.dataLen );
if ( (thisTag.dataLen & 1) != 0 ) fileRef->Write ( "\0", 1 );
}
}
// Back-fill the offset for the primary IFD, if it is now appended.
XMP_Uns32 newOffset;
if ( appendedIFDs[kTIFF_PrimaryIFD] ) {
this->PutUns32 ( newIFDOffsets[kTIFF_PrimaryIFD], &newOffset );
#if TraceUpdateFileStream
printf ( " Back-filling offset of primary IFD, pointing to %d (0x%X)\n", newOffset, newOffset );
#endif
fileRef->Seek ( 4, kXMP_SeekFromStart );
fileRef->Write ( &newOffset, 4 );
}
// Reset the changed flags and original length/offset values. This simulates a reparse of the
// updated file.
for ( int ifd = 0; ifd < kTIFF_KnownIFDCount; ++ifd ) {
InternalIFDInfo & thisIFD = this->containedIFDs[ifd];
if ( ! thisIFD.changed ) continue;
thisIFD.changed = false;
thisIFD.origCount = (XMP_Uns16)( thisIFD.tagMap.size() );
thisIFD.origIFDOffset = newIFDOffsets[ifd];
InternalTagMap::iterator tagPos;
InternalTagMap::iterator tagEnd = thisIFD.tagMap.end();
for ( tagPos = thisIFD.tagMap.begin(); tagPos != tagEnd; ++tagPos ) {
InternalTagInfo & thisTag = tagPos->second;
if ( ! thisTag.changed ) continue;
thisTag.changed = false;
thisTag.origDataLen = thisTag.dataLen;
if ( thisTag.origDataLen > 4 ) thisTag.origDataOffset = this->GetUns32 ( &thisTag.smallValue );
}
}
this->tiffLength = (XMP_Uns32) fileRef->Length();
fileRef->Seek ( 0, kXMP_SeekFromEnd ); // Can't hurt.
#if Trace_UpdateFileStream
printf ( "\nFinished update of TIFF file stream\n" );
#endif
} // TIFF_FileWriter::UpdateFileStream
// =================================================================================================
// TIFF_FileWriter::WriteFileIFD
// =============================
void TIFF_FileWriter::WriteFileIFD ( XMP_IO* fileRef, InternalIFDInfo & thisIFD )
{
XMP_Uns16 tagCount;
this->PutUns16 ( (XMP_Uns16)thisIFD.tagMap.size(), &tagCount );
fileRef->Write ( &tagCount, 2 );
InternalTagMap::iterator tagPos;
InternalTagMap::iterator tagEnd = thisIFD.tagMap.end();
for ( tagPos = thisIFD.tagMap.begin(); tagPos != tagEnd; ++tagPos ) {
InternalTagInfo & thisTag = tagPos->second;
RawIFDEntry ifdEntry;
this->PutUns16 ( thisTag.id, &ifdEntry.id );
this->PutUns16 ( thisTag.type, &ifdEntry.type );
this->PutUns32 ( thisTag.count, &ifdEntry.count );
ifdEntry.dataOrOffset = thisTag.smallValue; // ! Already in stream endianness.
fileRef->Write ( &ifdEntry, sizeof(ifdEntry) );
XMP_Assert ( sizeof(ifdEntry) == 12 );
}
XMP_Uns32 nextIFD;
this->PutUns32 ( thisIFD.origNextIFD, &nextIFD );
fileRef->Write ( &nextIFD, 4 );
} // TIFF_FileWriter::WriteFileIFD