1 // =================================================================================================
2 // ADOBE SYSTEMS INCORPORATED
3 // Copyright 2002-2008 Adobe Systems Incorporated
4 // All Rights Reserved
5 //
6 // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
7 // of the Adobe license agreement accompanying it.
8 // =================================================================================================
9
10 #include "TIFF_Handler.hpp"
11
12 #include "TIFF_Support.hpp"
13 #include "PSIR_Support.hpp"
14 #include "IPTC_Support.hpp"
15 #include "ReconcileLegacy.hpp"
16
17 #include "MD5.h"
18
19 using namespace std;
20
21 // =================================================================================================
22 /// \file TIFF_Handler.cpp
23 /// \brief File format handler for TIFF.
24 ///
25 /// This handler ...
26 ///
27 // =================================================================================================
28
29 // =================================================================================================
30 // TIFF_CheckFormat
31 // ================
32
33 // For TIFF we just check for the II/42 or MM/42 in the first 4 bytes and that there are at least
34 // 26 bytes of data (4+4+2+12+4).
35 //
36 // ! The CheckXyzFormat routines don't track the filePos, that is left to ScanXyzFile.
37
TIFF_CheckFormat(XMP_FileFormat format,XMP_StringPtr filePath,LFA_FileRef fileRef,XMPFiles * parent)38 bool TIFF_CheckFormat ( XMP_FileFormat format,
39 XMP_StringPtr filePath,
40 LFA_FileRef fileRef,
41 XMPFiles * parent )
42 {
43 IgnoreParam(format); IgnoreParam(filePath); IgnoreParam(parent);
44 XMP_Assert ( format == kXMP_TIFFFile );
45
46 enum { kMinimalTIFFSize = 4+4+2+12+4 }; // Header plus IFD with 1 entry.
47
48 IOBuffer ioBuf;
49
50 LFA_Seek ( fileRef, 0, SEEK_SET );
51 if ( ! CheckFileSpace ( fileRef, &ioBuf, kMinimalTIFFSize ) ) return false;
52
53 bool leTIFF = CheckBytes ( ioBuf.ptr, "\x49\x49\x2A\x00", 4 );
54 bool beTIFF = CheckBytes ( ioBuf.ptr, "\x4D\x4D\x00\x2A", 4 );
55
56 return (leTIFF | beTIFF);
57
58 } // TIFF_CheckFormat
59
60 // =================================================================================================
61 // TIFF_MetaHandlerCTor
62 // ====================
63
TIFF_MetaHandlerCTor(XMPFiles * parent)64 XMPFileHandler * TIFF_MetaHandlerCTor ( XMPFiles * parent )
65 {
66 return new TIFF_MetaHandler ( parent );
67
68 } // TIFF_MetaHandlerCTor
69
70 // =================================================================================================
71 // TIFF_MetaHandler::TIFF_MetaHandler
72 // ==================================
73
TIFF_MetaHandler(XMPFiles * _parent)74 TIFF_MetaHandler::TIFF_MetaHandler ( XMPFiles * _parent ) : psirMgr(0), iptcMgr(0)
75 {
76 this->parent = _parent;
77 this->handlerFlags = kTIFF_HandlerFlags;
78 this->stdCharForm = kXMP_Char8Bit;
79
80 } // TIFF_MetaHandler::TIFF_MetaHandler
81
82 // =================================================================================================
83 // TIFF_MetaHandler::~TIFF_MetaHandler
84 // ===================================
85
~TIFF_MetaHandler()86 TIFF_MetaHandler::~TIFF_MetaHandler()
87 {
88
89 if ( this->psirMgr != 0 ) delete ( this->psirMgr );
90 if ( this->iptcMgr != 0 ) delete ( this->iptcMgr );
91
92 } // TIFF_MetaHandler::~TIFF_MetaHandler
93
94 // =================================================================================================
95 // TIFF_MetaHandler::CacheFileData
96 // ===============================
97 //
98 // The data caching for TIFF is easy to explain and implement, but does more processing than one
99 // might at first expect. This seems unavoidable given the need to close the disk file after calling
100 // CacheFileData. We parse the TIFF stream and cache the values for all tags of interest, and note
101 // whether XMP is present. We do not parse the XMP, Photoshop image resources, or IPTC datasets.
102
103 // *** This implementation simply returns when invalid TIFF is encountered. Should we throw instead?
104
CacheFileData()105 void TIFF_MetaHandler::CacheFileData()
106 {
107 LFA_FileRef fileRef = this->parent->fileRef;
108 XMP_PacketInfo & packetInfo = this->packetInfo;
109
110 XMP_AbortProc abortProc = this->parent->abortProc;
111 void * abortArg = this->parent->abortArg;
112 const bool checkAbort = (abortProc != 0);
113
114 XMP_Assert ( (! this->containsXMP) && (! this->containsTNail) );
115 // Set containsXMP to true here only if the XMP tag is found.
116
117 if ( checkAbort && abortProc(abortArg) ) {
118 XMP_Throw ( "TIFF_MetaHandler::CacheFileData - User abort", kXMPErr_UserAbort );
119 }
120
121 this->tiffMgr.ParseFileStream ( fileRef );
122
123 TIFF_Manager::TagInfo dngInfo;
124 if ( this->tiffMgr.GetTag ( kTIFF_PrimaryIFD, kTIFF_DNGVersion, &dngInfo ) ) {
125
126 // Reject DNG files that are version 2.0 or beyond, this is being written at the time of
127 // DNG version 1.2. The DNG team says it is OK to use 2.0, not strictly 1.2. Use the
128 // DNGBackwardVersion if it is present, else the DNGVersion. Note that the version value is
129 // supposed to be type BYTE, so the file order is always essentially big endian.
130
131 XMP_Uns8 majorVersion = *((XMP_Uns8*)dngInfo.dataPtr); // Start with DNGVersion.
132 if ( this->tiffMgr.GetTag ( kTIFF_PrimaryIFD, kTIFF_DNGBackwardVersion, &dngInfo ) ) {
133 majorVersion = *((XMP_Uns8*)dngInfo.dataPtr); // Use DNGBackwardVersion if possible.
134 }
135 if ( majorVersion > 1 ) XMP_Throw ( "DNG version beyond 1.x", kXMPErr_BadTIFF );
136
137 }
138
139 TIFF_Manager::TagInfo xmpInfo;
140 bool found = this->tiffMgr.GetTag ( kTIFF_PrimaryIFD, kTIFF_XMP, &xmpInfo );
141
142 if ( found ) {
143
144 this->packetInfo.offset = this->tiffMgr.GetValueOffset ( kTIFF_PrimaryIFD, kTIFF_XMP );
145 this->packetInfo.length = xmpInfo.dataLen;
146 this->packetInfo.padSize = 0; // Assume for now, set these properly in ProcessXMP.
147 this->packetInfo.charForm = kXMP_CharUnknown;
148 this->packetInfo.writeable = true;
149
150 this->xmpPacket.assign ( (XMP_StringPtr)xmpInfo.dataPtr, xmpInfo.dataLen );
151
152 this->containsXMP = true;
153
154 }
155
156 } // TIFF_MetaHandler::CacheFileData
157
158 // =================================================================================================
159 // TIFF_MetaHandler::ProcessTNail
160 // ==============================
161 //
162 // Do the same processing as JPEG, even though Exif says that uncompressed images can only have
163 // uncompressed thumbnails. Who know what someone might write?
164
165 // *** Should extract this code into a utility shared with the JPEG handler.
166
ProcessTNail()167 void TIFF_MetaHandler::ProcessTNail()
168 {
169 this->processedTNail = true; // Make sure we only come through here once.
170 this->containsTNail = false; // Set it to true after all of the info is gathered.
171
172 this->containsTNail = this->tiffMgr.GetTNailInfo ( &this->tnailInfo );
173 if ( this->containsTNail ) this->tnailInfo.fileFormat = this->parent->format;
174
175 } // TIFF_MetaHandler::ProcessTNail
176
177 // =================================================================================================
178 // TIFF_MetaHandler::ProcessXMP
179 // ============================
180 //
181 // Process the raw XMP and legacy metadata that was previously cached. The legacy metadata in TIFF
182 // is messy because there are 2 copies of the IPTC and because of a Photoshop 6 bug/quirk in the way
183 // Exif metadata is saved.
184
ProcessXMP()185 void TIFF_MetaHandler::ProcessXMP()
186 {
187
188 this->processedXMP = true; // Make sure we only come through here once.
189
190 // Set up everything for the legacy import, but don't do it yet. This lets us do a forced legacy
191 // import if the XMP packet gets parsing errors.
192
193 // Parse the IPTC and PSIR, determine the last-legacy priority. For TIFF files the relevant
194 // legacy priorities (ignoring Mac pnot and ANPA resources) are:
195 // kLegacyJTP_TIFF_IPTC - highest
196 // kLegacyJTP_TIFF_TIFF_Tags
197 // kLegacyJTP_PSIR_OldCaption
198 // kLegacyJTP_PSIR_IPTC - yes, a TIFF file can have the IPTC in 2 places
199 // kLegacyJTP_None - lowest
200
201 // ! Photoshop 6 wrote annoyingly wacky TIFF files. It buried a lot of the Exif metadata inside
202 // ! image resource 1058, itself inside of tag 34377 in the 0th IFD. Take care of this before
203 // ! doing any of the legacy metadata presence or priority analysis. Delete image resource 1058
204 // ! to get rid of the buried Exif, but don't mark the XMPFiles object as changed. This change
205 // ! should not trigger an update, but should be included as part of a normal update.
206
207 bool found;
208 RecJTP_LegacyPriority lastLegacy = kLegacyJTP_None;
209
210 bool readOnly = ((this->parent->openFlags & kXMPFiles_OpenForUpdate) == 0);
211
212 if ( readOnly ) {
213 this->psirMgr = new PSIR_MemoryReader();
214 this->iptcMgr = new IPTC_Reader();
215 } else {
216 this->psirMgr = new PSIR_FileWriter();
217 #if ! XMP_UNIXBuild
218 this->iptcMgr = new IPTC_Writer(); // ! Parse it later.
219 #else
220 // ! Hack until the legacy-as-local issues are resolved for generic UNIX.
221 this->iptcMgr = new IPTC_Reader(); // ! Import IPTC but don't export it.
222 #endif
223 }
224
225 TIFF_Manager & tiff = this->tiffMgr; // Give the compiler help in recognizing non-aliases.
226 PSIR_Manager & psir = *this->psirMgr;
227 IPTC_Manager & iptc = *this->iptcMgr;
228
229 TIFF_Manager::TagInfo psirInfo;
230 bool havePSIR = tiff.GetTag ( kTIFF_PrimaryIFD, kTIFF_PSIR, &psirInfo );
231
232 TIFF_Manager::TagInfo iptcInfo;
233 bool haveIPTC = tiff.GetTag ( kTIFF_PrimaryIFD, kTIFF_IPTC, &iptcInfo ); // The TIFF IPTC tag.
234
235 if ( havePSIR ) { // ! Do the Photoshop 6 integration before other legacy analysis.
236 psir.ParseMemoryResources ( psirInfo.dataPtr, psirInfo.dataLen );
237 PSIR_Manager::ImgRsrcInfo buriedExif;
238 found = psir.GetImgRsrc ( kPSIR_Exif, &buriedExif );
239 if ( found ) {
240 tiff.IntegrateFromPShop6 ( buriedExif.dataPtr, buriedExif.dataLen );
241 if ( ! readOnly ) psir.DeleteImgRsrc ( kPSIR_Exif );
242 }
243 }
244
245 if ( haveIPTC ) { // At this point "haveIPTC" means from TIFF tag 33723.
246 iptc.ParseMemoryDataSets ( iptcInfo.dataPtr, iptcInfo.dataLen );
247 lastLegacy = kLegacyJTP_TIFF_IPTC;
248 }
249
250 if ( lastLegacy < kLegacyJTP_TIFF_TIFF_Tags ) {
251 found = tiff.GetTag ( kTIFF_PrimaryIFD, kTIFF_ImageDescription, 0 );
252 if ( ! found ) found = tiff.GetTag ( kTIFF_PrimaryIFD, kTIFF_Artist, 0 );
253 if ( ! found ) found = tiff.GetTag ( kTIFF_PrimaryIFD, kTIFF_Copyright, 0 );
254 if ( found ) lastLegacy = kLegacyJTP_TIFF_TIFF_Tags;
255 }
256
257 if ( havePSIR ) {
258
259 if ( lastLegacy < kLegacyJTP_PSIR_OldCaption ) {
260 found = psir.GetImgRsrc ( kPSIR_OldCaption, 0 );
261 if ( ! found ) found = psir.GetImgRsrc ( kPSIR_OldCaptionPStr, 0 );
262 if ( found ) lastLegacy = kLegacyJTP_PSIR_OldCaption;
263 }
264
265 if ( ! haveIPTC ) {
266 PSIR_Manager::ImgRsrcInfo iptcInfo;
267 haveIPTC = psir.GetImgRsrc ( kPSIR_IPTC, &iptcInfo );
268 if ( haveIPTC ) {
269 iptc.ParseMemoryDataSets ( iptcInfo.dataPtr, iptcInfo.dataLen );
270 if ( lastLegacy < kLegacyJTP_PSIR_IPTC ) lastLegacy = kLegacyJTP_PSIR_IPTC;
271 }
272 }
273
274 }
275
276 XMP_OptionBits options = k2XMP_FileHadExif; // TIFF files are presumed to have Exif legacy.
277 if ( this->containsXMP ) options |= k2XMP_FileHadXMP;
278 if ( haveIPTC || (lastLegacy == kLegacyJTP_PSIR_OldCaption) ) options |= k2XMP_FileHadIPTC;
279
280 // Process the XMP packet. If it fails to parse, do a forced legacy import but still throw an
281 // exception. This tells the caller that an error happened, but gives them recovered legacy
282 // should they want to proceed with that.
283
284 if ( ! this->xmpPacket.empty() ) {
285 XMP_Assert ( this->containsXMP );
286 // Common code takes care of packetInfo.charForm, .padSize, and .writeable.
287 XMP_StringPtr packetStr = this->xmpPacket.c_str();
288 XMP_StringLen packetLen = (XMP_StringLen)this->xmpPacket.size();
289 try {
290 this->xmpObj.ParseFromBuffer ( packetStr, packetLen );
291 } catch ( ... ) {
292 XMP_ClearOption ( options, k2XMP_FileHadXMP );
293 ImportJTPtoXMP ( kXMP_TIFFFile, lastLegacy, &tiff, psir, &iptc, &this->xmpObj, options );
294 throw; // ! Rethrow the exception, don't absorb it.
295 }
296 }
297
298 // Process the legacy metadata.
299
300 ImportJTPtoXMP ( kXMP_TIFFFile, lastLegacy, &tiff, psir, &iptc, &this->xmpObj, options );
301 this->containsXMP = true; // Assume we now have something in the XMP.
302
303 } // TIFF_MetaHandler::ProcessXMP
304
305 // =================================================================================================
306 // TIFF_MetaHandler::UpdateFile
307 // ============================
308 //
309 // There is very little to do directly in UpdateFile. ExportXMPtoJTP takes care of setting all of
310 // the necessary TIFF tags, including things like the 2nd copy of the IPTC in the Photoshop image
311 // resources in tag 34377. TIFF_FileWriter::UpdateFileStream does all of the update-by-append I/O.
312
313 // *** Need to pass the abort proc and arg to TIFF_FileWriter::UpdateFileStream.
314
UpdateFile(bool doSafeUpdate)315 void TIFF_MetaHandler::UpdateFile ( bool doSafeUpdate )
316 {
317 XMP_Assert ( ! doSafeUpdate ); // This should only be called for "unsafe" updates.
318
319 LFA_FileRef destRef = this->parent->fileRef;
320 XMP_AbortProc abortProc = this->parent->abortProc;
321 void * abortArg = this->parent->abortArg;
322
323 // Decide whether to do an in-place update. This can only happen if all of the following are true:
324 // - There is an XMP packet in the file.
325 // - The are no changes to the legacy tags. (The IPTC and PSIR are in the TIFF tags.)
326 // - The new XMP can fit in the old space.
327
328 ExportXMPtoJTP ( kXMP_TIFFFile, &this->xmpObj, &this->tiffMgr, this->psirMgr, this->iptcMgr );
329
330 XMP_Int64 oldPacketOffset = this->packetInfo.offset;
331 XMP_Int32 oldPacketLength = this->packetInfo.length;
332
333 if ( oldPacketOffset == kXMPFiles_UnknownOffset ) oldPacketOffset = 0; // ! Simplify checks.
334 if ( oldPacketLength == kXMPFiles_UnknownLength ) oldPacketLength = 0;
335
336 bool doInPlace = (this->xmpPacket.size() <= (size_t)this->packetInfo.length);
337 if ( this->tiffMgr.IsLegacyChanged() ) doInPlace = false;
338
339 if ( doInPlace ) {
340
341 #if GatherPerformanceData
342 sAPIPerf->back().extraInfo += ", TIFF in-place update";
343 #endif
344
345 if ( this->xmpPacket.size() < (size_t)this->packetInfo.length ) {
346 // They ought to match, cheap to be sure.
347 size_t extraSpace = (size_t)this->packetInfo.length - this->xmpPacket.size();
348 this->xmpPacket.append ( extraSpace, ' ' );
349 }
350
351 LFA_FileRef liveFile = this->parent->fileRef;
352
353 XMP_Assert ( this->xmpPacket.size() == (size_t)oldPacketLength ); // ! Done by common PutXMP logic.
354
355 LFA_Seek ( liveFile, oldPacketOffset, SEEK_SET );
356 LFA_Write ( liveFile, this->xmpPacket.c_str(), (XMP_Int32)this->xmpPacket.size() );
357
358 } else {
359
360 #if GatherPerformanceData
361 sAPIPerf->back().extraInfo += ", TIFF append update";
362 #endif
363
364 // Reserialize the XMP to get standard padding, PutXMP has probably done an in-place serialize.
365 this->xmpObj.SerializeToBuffer ( &this->xmpPacket, kXMP_UseCompactFormat );
366 this->packetInfo.offset = kXMPFiles_UnknownOffset;
367 this->packetInfo.length = (XMP_Int32)this->xmpPacket.size();
368 FillPacketInfo ( this->xmpPacket, &this->packetInfo );
369
370 this->tiffMgr.SetTag ( kTIFF_PrimaryIFD, kTIFF_XMP, kTIFF_UndefinedType, (XMP_Uns32)this->xmpPacket.size(), this->xmpPacket.c_str() );
371
372 this->tiffMgr.UpdateFileStream ( destRef );
373
374 }
375
376 this->needsUpdate = false;
377
378 } // TIFF_MetaHandler::UpdateFile
379
380 // =================================================================================================
381 // TIFF_MetaHandler::WriteFile
382 // ===========================
383 //
384 // The structure of TIFF makes it hard to do a sequential source-to-dest copy with interleaved
385 // updates. So, copy the existing source to the destination and call UpdateFile.
386
WriteFile(LFA_FileRef sourceRef,const std::string & sourcePath)387 void TIFF_MetaHandler::WriteFile ( LFA_FileRef sourceRef, const std::string & sourcePath )
388 {
389 LFA_FileRef destRef = this->parent->fileRef;
390 XMP_AbortProc abortProc = this->parent->abortProc;
391 void * abortArg = this->parent->abortArg;
392
393 XMP_Int64 fileLen = LFA_Measure ( sourceRef );
394 if ( fileLen > 0xFFFFFFFFLL ) { // Check before making a copy of the file.
395 XMP_Throw ( "TIFF fles can't exceed 4GB", kXMPErr_BadTIFF );
396 }
397
398 LFA_Seek ( sourceRef, 0, SEEK_SET );
399 LFA_Truncate ( destRef, 0 );
400 LFA_Copy ( sourceRef, destRef, fileLen, abortProc, abortArg );
401
402 this->UpdateFile ( false );
403
404 } // TIFF_MetaHandler::WriteFile
405
406 // =================================================================================================
407