| /*****************************************************************************/ |
| // Copyright 2006-2008 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. |
| /*****************************************************************************/ |
| |
| /* $Id: //mondo/dng_sdk_1_4/dng_sdk/source/dng_xmp.cpp#1 $ */ |
| /* $DateTime: 2012/05/30 13:28:51 $ */ |
| /* $Change: 832332 $ */ |
| /* $Author: tknoll $ */ |
| |
| /*****************************************************************************/ |
| #if qDNGUseXMP |
| |
| #include "dng_xmp.h" |
| |
| #include "dng_assertions.h" |
| #include "dng_date_time.h" |
| #include "dng_exceptions.h" |
| #include "dng_exif.h" |
| #include "dng_image_writer.h" |
| #include "dng_iptc.h" |
| #include "dng_negative.h" |
| #include "dng_string.h" |
| #include "dng_string_list.h" |
| #include "dng_utils.h" |
| #include "dng_xmp_sdk.h" |
| |
| /*****************************************************************************/ |
| |
| dng_xmp::dng_xmp (dng_memory_allocator &allocator) |
| |
| : fAllocator (allocator) |
| |
| , fSDK (NULL) |
| |
| { |
| |
| fSDK = new dng_xmp_sdk (); |
| |
| if (!fSDK) |
| { |
| ThrowMemoryFull (); |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| dng_xmp::dng_xmp (const dng_xmp &xmp) |
| |
| : fAllocator (xmp.fAllocator) |
| |
| , fSDK (NULL) |
| |
| { |
| |
| fSDK = new dng_xmp_sdk (*xmp.fSDK); |
| |
| if (!fSDK) |
| { |
| ThrowMemoryFull (); |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| dng_xmp::~dng_xmp () |
| { |
| |
| if (fSDK) |
| { |
| |
| delete fSDK; |
| |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| dng_xmp * dng_xmp::Clone () const |
| { |
| |
| dng_xmp *result = new dng_xmp (*this); |
| |
| if (!result) |
| { |
| ThrowMemoryFull (); |
| } |
| |
| return result; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_xmp::TrimDecimal (char *s) |
| { |
| |
| uint32 len = (uint32) strlen (s); |
| |
| while (len > 0) |
| { |
| |
| if (s [len - 1] == '0') |
| s [--len] = 0; |
| |
| else |
| break; |
| |
| } |
| |
| if (len > 0) |
| { |
| |
| if (s [len - 1] == '.') |
| s [--len] = 0; |
| |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| dng_string dng_xmp::EncodeFingerprint (const dng_fingerprint &f, |
| bool allowInvalid) |
| { |
| |
| dng_string result; |
| |
| if (f.IsValid () || allowInvalid) |
| { |
| |
| char s [dng_fingerprint::kDNGFingerprintSize * 2 + 1]; |
| |
| f.ToUtf8HexString (s); |
| |
| result.Set (s); |
| |
| } |
| |
| return result; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| dng_fingerprint dng_xmp::DecodeFingerprint (const dng_string &s) |
| { |
| |
| dng_fingerprint result; |
| |
| if (s.Length () == 32) |
| result.FromUtf8HexString (s.Get ()); |
| |
| return result; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| dng_string dng_xmp::EncodeGPSVersion (uint32 version) |
| { |
| |
| dng_string result; |
| |
| if (version) |
| { |
| |
| uint8 b0 = (uint8) (version >> 24); |
| uint8 b1 = (uint8) (version >> 16); |
| uint8 b2 = (uint8) (version >> 8); |
| uint8 b3 = (uint8) (version ); |
| |
| if (b0 <= 9 && b1 <= 9 && b2 <= 9 && b3 <= 9) |
| { |
| |
| char s [32]; |
| |
| sprintf (s, |
| "%u.%u.%u.%u", |
| (unsigned) b0, |
| (unsigned) b1, |
| (unsigned) b2, |
| (unsigned) b3); |
| |
| result.Set (s); |
| |
| } |
| |
| } |
| |
| return result; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| uint32 dng_xmp::DecodeGPSVersion (const dng_string &s) |
| { |
| |
| uint32 result = 0; |
| |
| if (s.Length () == 7) |
| { |
| |
| unsigned b0 = 0; |
| unsigned b1 = 0; |
| unsigned b2 = 0; |
| unsigned b3 = 0; |
| |
| if (sscanf (s.Get (), |
| "%u.%u.%u.%u", |
| &b0, |
| &b1, |
| &b2, |
| &b3) == 4) |
| { |
| |
| result = (b0 << 24) | |
| (b1 << 16) | |
| (b2 << 8) | |
| (b3 ); |
| |
| } |
| |
| } |
| |
| return result; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| dng_string dng_xmp::EncodeGPSCoordinate (const dng_string &ref, |
| const dng_urational *coord) |
| { |
| |
| dng_string result; |
| |
| if (ref.Length () == 1 && coord [0].IsValid () && |
| coord [1].IsValid ()) |
| { |
| |
| char refChar = ForceUppercase (ref.Get () [0]); |
| |
| if (refChar == 'N' || |
| refChar == 'S' || |
| refChar == 'E' || |
| refChar == 'W') |
| { |
| |
| char s [256]; |
| |
| // Use the seconds case if all three values are |
| // integers. |
| |
| if (coord [0].d == 1 && |
| coord [1].d == 1 && |
| coord [2].d == 1) |
| { |
| |
| sprintf (s, |
| "%u,%u,%u%c", |
| (unsigned) coord [0].n, |
| (unsigned) coord [1].n, |
| (unsigned) coord [2].n, |
| refChar); |
| |
| } |
| |
| // Else we need to use the fractional minutes case. |
| |
| else |
| { |
| |
| // Find value minutes. |
| |
| real64 x = coord [0].As_real64 () * 60.0 + |
| coord [1].As_real64 () + |
| coord [2].As_real64 () * (1.0 / 60.0); |
| |
| // Round to fractional four decimal places. |
| |
| uint32 y = Round_uint32 (x * 10000.0); |
| |
| // Split into degrees and minutes. |
| |
| uint32 d = y / (60 * 10000); |
| uint32 m = y % (60 * 10000); |
| |
| char min [32]; |
| |
| sprintf (min, "%.4f", m * (1.0 / 10000.0)); |
| |
| TrimDecimal (min); |
| |
| sprintf (s, |
| "%u,%s%c", |
| (unsigned) d, |
| min, |
| refChar); |
| |
| } |
| |
| result.Set (s); |
| |
| } |
| |
| } |
| |
| return result; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_xmp::DecodeGPSCoordinate (const dng_string &s, |
| dng_string &ref, |
| dng_urational *coord) |
| { |
| |
| ref.Clear (); |
| |
| coord [0].Clear (); |
| coord [1].Clear (); |
| coord [2].Clear (); |
| |
| if (s.Length () > 1) |
| { |
| |
| char refChar = ForceUppercase (s.Get () [s.Length () - 1]); |
| |
| if (refChar == 'N' || |
| refChar == 'S' || |
| refChar == 'E' || |
| refChar == 'W') |
| { |
| |
| dng_string ss (s); |
| |
| ss.Truncate (ss.Length () - 1); |
| |
| ss.NormalizeAsCommaSeparatedNumbers(); |
| |
| int degrees = 0; |
| |
| real64 minutes = 0.0; |
| real64 seconds = 0.0; |
| |
| int count = sscanf (ss.Get (), |
| "%d,%lf,%lf", |
| °rees, |
| &minutes, |
| &seconds); |
| |
| if (count < 1) |
| { |
| return; |
| } |
| |
| // The degree, minute, second values should always be positive. |
| |
| if (degrees < 0 || minutes < 0 || seconds < 0) |
| { |
| return; |
| } |
| |
| coord [0] = dng_urational ((uint32) degrees, 1); |
| |
| if (count <= 2) |
| { |
| coord [1].Set_real64 (minutes, 10000); |
| coord [2] = dng_urational (0, 1); |
| } |
| else |
| { |
| coord [1].Set_real64 (minutes, 1); |
| coord [2].Set_real64 (seconds, 100); |
| } |
| |
| char r [2]; |
| |
| r [0] = refChar; |
| r [1] = 0; |
| |
| ref.Set (r); |
| |
| } |
| |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| dng_string dng_xmp::EncodeGPSDateTime (const dng_string &dateStamp, |
| const dng_urational *timeStamp) |
| { |
| |
| dng_string result; |
| |
| if (timeStamp [0].IsValid () && |
| timeStamp [1].IsValid () && |
| timeStamp [2].IsValid ()) |
| { |
| |
| char s [256]; |
| |
| char sec [32]; |
| |
| sprintf (sec, |
| "%09.6f", |
| timeStamp [2].As_real64 ()); |
| |
| TrimDecimal (sec); |
| |
| int year = 0; |
| int month = 0; |
| int day = 0; |
| |
| if (dateStamp.NotEmpty ()) |
| { |
| |
| sscanf (dateStamp.Get (), |
| "%d:%d:%d", |
| &year, |
| &month, |
| &day); |
| |
| } |
| |
| if (year >= 1 && year <= 9999 && |
| month >= 1 && month <= 12 && |
| day >= 1 && day <= 31) |
| { |
| |
| sprintf (s, |
| "%04d-%02d-%02dT%02u:%02u:%sZ", |
| year, |
| month, |
| day, |
| (unsigned) Round_uint32 (timeStamp [0].As_real64 ()), |
| (unsigned) Round_uint32 (timeStamp [1].As_real64 ()), |
| sec); |
| |
| } |
| |
| else |
| { |
| |
| sprintf (s, |
| "%02u:%02u:%sZ", |
| (unsigned) Round_uint32 (timeStamp [0].As_real64 ()), |
| (unsigned) Round_uint32 (timeStamp [1].As_real64 ()), |
| sec); |
| |
| } |
| |
| result.Set (s); |
| |
| } |
| |
| return result; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_xmp::DecodeGPSDateTime (const dng_string &s, |
| dng_string &dateStamp, |
| dng_urational *timeStamp) |
| { |
| |
| dateStamp.Clear (); |
| |
| timeStamp [0].Clear (); |
| timeStamp [1].Clear (); |
| timeStamp [2].Clear (); |
| |
| if (s.NotEmpty ()) |
| { |
| |
| unsigned year = 0; |
| unsigned month = 0; |
| unsigned day = 0; |
| unsigned hour = 0; |
| unsigned minute = 0; |
| |
| double second = 0.0; |
| |
| if (sscanf (s.Get (), |
| "%u-%u-%uT%u:%u:%lf", |
| &year, |
| &month, |
| &day, |
| &hour, |
| &minute, |
| &second) == 6) |
| { |
| |
| if (year >= 1 && year <= 9999 && |
| month >= 1 && month <= 12 && |
| day >= 1 && day <= 31 ) |
| { |
| |
| char ss [64]; |
| |
| sprintf (ss, |
| "%04u:%02u:%02u", |
| year, |
| month, |
| day); |
| |
| dateStamp.Set (ss); |
| |
| } |
| |
| } |
| |
| else if (sscanf (s.Get (), |
| "%u:%u:%lf", |
| &hour, |
| &minute, |
| &second) != 3) |
| { |
| |
| return; |
| |
| } |
| |
| timeStamp [0] = dng_urational ((uint32) hour , 1); |
| timeStamp [1] = dng_urational ((uint32) minute, 1); |
| |
| timeStamp [2].Set_real64 (second, 1000); |
| |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_xmp::Parse (dng_host &host, |
| const void *buffer, |
| uint32 count) |
| { |
| |
| fSDK->Parse (host, |
| (const char *) buffer, |
| count); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| dng_memory_block * dng_xmp::Serialize (bool asPacket, |
| uint32 targetBytes, |
| uint32 padBytes, |
| bool forJPEG, |
| bool compact) const |
| { |
| |
| return fSDK->Serialize (fAllocator, |
| asPacket, |
| targetBytes, |
| padBytes, |
| forJPEG, |
| compact); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_xmp::PackageForJPEG (AutoPtr<dng_memory_block> &stdBlock, |
| AutoPtr<dng_memory_block> &extBlock, |
| dng_string &extDigest) const |
| { |
| |
| fSDK->PackageForJPEG (fAllocator, |
| stdBlock, |
| extBlock, |
| extDigest); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_xmp::MergeFromJPEG (const dng_xmp &xmp) |
| { |
| |
| fSDK->MergeFromJPEG (xmp.fSDK); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| bool dng_xmp::HasMeta () const |
| { |
| |
| return fSDK->HasMeta (); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void * dng_xmp::GetPrivateMeta () |
| { |
| |
| return fSDK->GetPrivateMeta (); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| bool dng_xmp::Exists (const char *ns, |
| const char *path) const |
| { |
| |
| return fSDK->Exists (ns, path); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| bool dng_xmp::HasNameSpace (const char *ns) const |
| { |
| |
| return fSDK->HasNameSpace (ns); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| bool dng_xmp::IteratePaths (IteratePathsCallback *callback, |
| void *callbackData, |
| const char *ns, |
| const char *path) |
| { |
| |
| return fSDK->IteratePaths (callback, callbackData, ns, path); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_xmp::Remove (const char *ns, |
| const char *path) |
| { |
| |
| fSDK->Remove (ns, path); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_xmp::RemoveProperties (const char *ns) |
| { |
| |
| fSDK->RemoveProperties (ns); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_xmp::RemoveEmptyStringOrArray (const char *ns, |
| const char *path) |
| { |
| |
| if (path == NULL || path [0] == 0) |
| { |
| return; |
| } |
| |
| if (fSDK->IsEmptyString (ns, path) || |
| fSDK->IsEmptyArray (ns, path)) |
| { |
| |
| Remove (ns, path); |
| |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| static bool RemoveEmptyStringsAndArraysCallback (const char *ns, |
| const char *path, |
| void *callbackData) |
| { |
| |
| dng_xmp *xmp = (dng_xmp *) callbackData; |
| |
| xmp->RemoveEmptyStringOrArray (ns, path); |
| |
| return true; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_xmp::RemoveEmptyStringsAndArrays (const char *ns) |
| { |
| |
| IteratePaths (RemoveEmptyStringsAndArraysCallback, |
| (void *) this, |
| ns, |
| NULL); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_xmp::Set (const char *ns, |
| const char *path, |
| const char *text) |
| { |
| |
| fSDK->Set (ns, path, text); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| bool dng_xmp::GetString (const char *ns, |
| const char *path, |
| dng_string &s) const |
| { |
| |
| return fSDK->GetString (ns, path, s); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_xmp::SetString (const char *ns, |
| const char *path, |
| const dng_string &s) |
| { |
| |
| fSDK->SetString (ns, path, s); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| bool dng_xmp::SyncString (const char *ns, |
| const char *path, |
| dng_string &s, |
| uint32 options) |
| { |
| |
| bool isDefault = s.IsEmpty (); |
| |
| // Sync 1: Force XMP to match non-XMP. |
| |
| if (options & ignoreXMP) |
| { |
| |
| if (isDefault || (options & removeXMP)) |
| { |
| |
| Remove (ns, path); |
| |
| } |
| |
| else |
| { |
| |
| SetString (ns, path, s); |
| |
| } |
| |
| return false; |
| |
| } |
| |
| // Sync 2: From non-XMP to XMP if non-XMP is prefered. |
| |
| if ((options & preferNonXMP) && !isDefault) |
| { |
| |
| if (options & removeXMP) |
| { |
| |
| Remove (ns, path); |
| |
| } |
| |
| else |
| { |
| |
| SetString (ns, path, s); |
| |
| } |
| |
| return false; |
| |
| } |
| |
| // Sync 3: From XMP to non-XMP if XMP is prefered or default non-XMP. |
| |
| if ((options & preferXMP) || isDefault) |
| { |
| |
| if (GetString (ns, path, s)) |
| { |
| |
| if (options & removeXMP) |
| { |
| |
| Remove (ns, path); |
| |
| } |
| |
| return true; |
| |
| } |
| |
| } |
| |
| // Sync 4: From non-XMP to XMP. |
| |
| if (options & removeXMP) |
| { |
| |
| Remove (ns, path); |
| |
| } |
| |
| else if (!isDefault) |
| { |
| |
| SetString (ns, path, s); |
| |
| } |
| |
| return false; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| bool dng_xmp::GetStringList (const char *ns, |
| const char *path, |
| dng_string_list &list) const |
| { |
| |
| return fSDK->GetStringList (ns, path, list); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_xmp::SetStringList (const char *ns, |
| const char *path, |
| const dng_string_list &list, |
| bool isBag) |
| { |
| |
| fSDK->SetStringList (ns, path, list, isBag); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_xmp::SyncStringList (const char *ns, |
| const char *path, |
| dng_string_list &list, |
| bool isBag, |
| uint32 options) |
| { |
| |
| bool isDefault = (list.Count () == 0); |
| |
| // First make sure the XMP is not badly formatted, since |
| // this breaks some Photoshop logic. |
| |
| ValidateStringList (ns, path); |
| |
| // Sync 1: Force XMP to match non-XMP. |
| |
| if (options & ignoreXMP) |
| { |
| |
| if (isDefault) |
| { |
| |
| Remove (ns, path); |
| |
| } |
| |
| else |
| { |
| |
| SetStringList (ns, path, list, isBag); |
| |
| } |
| |
| return; |
| |
| } |
| |
| // Sync 2: From non-XMP to XMP if non-XMP is prefered. |
| |
| if ((options & preferNonXMP) && !isDefault) |
| { |
| |
| SetStringList (ns, path, list, isBag); |
| |
| return; |
| |
| } |
| |
| // Sync 3: From XMP to non-XMP if XMP is prefered or default non-XMP. |
| |
| if ((options & preferXMP) || isDefault) |
| { |
| |
| if (GetStringList (ns, path, list)) |
| { |
| |
| return; |
| |
| } |
| |
| } |
| |
| // Sync 4: From non-XMP to XMP. |
| |
| if (!isDefault) |
| { |
| |
| SetStringList (ns, path, list, isBag); |
| |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_xmp::SetStructField (const char *ns, |
| const char *path, |
| const char *fieldNS, |
| const char *fieldName, |
| const dng_string &s) |
| { |
| |
| dng_string ss (s); |
| |
| ss.SetLineEndings ('\n'); |
| |
| ss.StripLowASCII (); |
| |
| fSDK->SetStructField (ns, path, fieldNS, fieldName, ss.Get ()); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_xmp::SetStructField (const char *ns, |
| const char *path, |
| const char *fieldNS, |
| const char *fieldName, |
| const char *s) |
| { |
| |
| fSDK->SetStructField (ns, path, fieldNS, fieldName, s); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_xmp::DeleteStructField (const char *ns, |
| const char *path, |
| const char *fieldNS, |
| const char *fieldName) |
| { |
| |
| fSDK->DeleteStructField (ns, path, fieldNS, fieldName); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| bool dng_xmp::GetStructField (const char *ns, |
| const char *path, |
| const char *fieldNS, |
| const char *fieldName, |
| dng_string &s) const |
| { |
| |
| return fSDK->GetStructField (ns, path, fieldNS, fieldName, s); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_xmp::SetAltLangDefault (const char *ns, |
| const char *path, |
| const dng_string &s) |
| { |
| |
| fSDK->SetAltLangDefault (ns, path, s); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| bool dng_xmp::GetAltLangDefault (const char *ns, |
| const char *path, |
| dng_string &s) const |
| { |
| |
| return fSDK->GetAltLangDefault (ns, path, s); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| bool dng_xmp::SyncAltLangDefault (const char *ns, |
| const char *path, |
| dng_string &s, |
| uint32 options) |
| { |
| |
| bool isDefault = s.IsEmpty (); |
| |
| // Sync 1: Force XMP to match non-XMP. |
| |
| if (options & ignoreXMP) |
| { |
| |
| if (isDefault) |
| { |
| |
| Remove (ns, path); |
| |
| } |
| |
| else |
| { |
| |
| SetAltLangDefault (ns, path, s); |
| |
| } |
| |
| return false; |
| |
| } |
| |
| // Sync 2: From non-XMP to XMP if non-XMP is prefered. |
| |
| if ((options & preferNonXMP) && !isDefault) |
| { |
| |
| SetAltLangDefault (ns, path, s); |
| |
| return false; |
| |
| } |
| |
| // Sync 3: From XMP to non-XMP if XMP is prefered or default non-XMP. |
| |
| if ((options & preferXMP) || isDefault) |
| { |
| |
| if (GetAltLangDefault (ns, path, s)) |
| { |
| |
| return true; |
| |
| } |
| |
| } |
| |
| // Sync 4: From non-XMP to XMP. |
| |
| if (!isDefault) |
| { |
| |
| SetAltLangDefault (ns, path, s); |
| |
| } |
| |
| return false; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| bool dng_xmp::GetBoolean (const char *ns, |
| const char *path, |
| bool &x) const |
| { |
| |
| dng_string s; |
| |
| if (GetString (ns, path, s)) |
| { |
| |
| if (s.Matches ("True")) |
| { |
| |
| x = true; |
| |
| return true; |
| |
| } |
| |
| if (s.Matches ("False")) |
| { |
| |
| x = false; |
| |
| return true; |
| |
| } |
| |
| } |
| |
| return false; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_xmp::SetBoolean (const char *ns, |
| const char *path, |
| bool x) |
| { |
| |
| Set (ns, path, x ? "True" : "False"); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| bool dng_xmp::Get_int32 (const char *ns, |
| const char *path, |
| int32 &x) const |
| { |
| |
| dng_string s; |
| |
| if (GetString (ns, path, s)) |
| { |
| |
| if (s.NotEmpty ()) |
| { |
| |
| int y = 0; |
| |
| if (sscanf (s.Get (), "%d", &y) == 1) |
| { |
| |
| x = y; |
| |
| return true; |
| |
| } |
| |
| } |
| |
| } |
| |
| return false; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_xmp::Set_int32 (const char *ns, |
| const char *path, |
| int32 x, |
| bool usePlus) |
| { |
| |
| char s [64]; |
| |
| if (x > 0 && usePlus) |
| { |
| sprintf (s, "+%d", (int) x); |
| } |
| else |
| { |
| sprintf (s, "%d", (int) x); |
| } |
| |
| Set (ns, path, s); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| bool dng_xmp::Get_uint32 (const char *ns, |
| const char *path, |
| uint32 &x) const |
| { |
| |
| dng_string s; |
| |
| if (GetString (ns, path, s)) |
| { |
| |
| if (s.NotEmpty ()) |
| { |
| |
| unsigned y = 0; |
| |
| if (sscanf (s.Get (), "%u", &y) == 1) |
| { |
| |
| x = y; |
| |
| return true; |
| |
| } |
| |
| } |
| |
| } |
| |
| return false; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_xmp::Set_uint32 (const char *ns, |
| const char *path, |
| uint32 x) |
| { |
| |
| char s [64]; |
| |
| sprintf (s, |
| "%u", |
| (unsigned) x); |
| |
| Set (ns, path, s); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_xmp::Sync_uint32 (const char *ns, |
| const char *path, |
| uint32 &x, |
| bool isDefault, |
| uint32 options) |
| { |
| |
| // Sync 1: Force XMP to match non-XMP. |
| |
| if (options & ignoreXMP) |
| { |
| |
| if (isDefault || (options & removeXMP)) |
| { |
| |
| Remove (ns, path); |
| |
| } |
| |
| else |
| { |
| |
| Set_uint32 (ns, path, x); |
| |
| } |
| |
| return; |
| |
| } |
| |
| // Sync 2: From non-XMP to XMP if non-XMP is prefered. |
| |
| if ((options & preferNonXMP) && !isDefault) |
| { |
| |
| if (options & removeXMP) |
| { |
| |
| Remove (ns, path); |
| |
| } |
| |
| else |
| { |
| |
| Set_uint32 (ns, path, x); |
| |
| } |
| |
| return; |
| |
| } |
| |
| // Sync 3: From XMP to non-XMP if XMP is prefered or default non-XMP. |
| |
| if ((options & preferXMP) || isDefault) |
| { |
| |
| if (Get_uint32 (ns, path, x)) |
| { |
| |
| if (options & removeXMP) |
| { |
| |
| Remove (ns, path); |
| |
| } |
| |
| return; |
| |
| } |
| |
| } |
| |
| // Sync 4: From non-XMP to XMP. |
| |
| if (options & removeXMP) |
| { |
| |
| Remove (ns, path); |
| |
| } |
| |
| else if (!isDefault) |
| { |
| |
| Set_uint32 (ns, path, x); |
| |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_xmp::Sync_uint32_array (const char *ns, |
| const char *path, |
| uint32 *data, |
| uint32 &count, |
| uint32 maxCount, |
| uint32 options) |
| { |
| |
| dng_string_list list; |
| |
| for (uint32 j = 0; j < count; j++) |
| { |
| |
| char s [32]; |
| |
| sprintf (s, "%u", (unsigned) data [j]); |
| |
| dng_string ss; |
| |
| ss.Set (s); |
| |
| list.Append (ss); |
| |
| } |
| |
| SyncStringList (ns, |
| path, |
| list, |
| false, |
| options); |
| |
| count = 0; |
| |
| for (uint32 k = 0; k < maxCount; k++) |
| { |
| |
| data [k] = 0; |
| |
| if (k < list.Count ()) |
| { |
| |
| unsigned x = 0; |
| |
| if (sscanf (list [k].Get (), "%u", &x) == 1) |
| { |
| |
| data [count++] = x; |
| |
| } |
| |
| } |
| |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| bool dng_xmp::Get_real64 (const char *ns, |
| const char *path, |
| real64 &x) const |
| { |
| |
| dng_string s; |
| |
| if (GetString (ns, path, s)) |
| { |
| |
| if (s.NotEmpty ()) |
| { |
| |
| double y = 0; |
| |
| if (sscanf (s.Get (), "%lf", &y) == 1) |
| { |
| |
| x = y; |
| |
| return true; |
| |
| } |
| |
| } |
| |
| } |
| |
| return false; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_xmp::Set_real64 (const char *ns, |
| const char *path, |
| real64 x, |
| uint32 places, |
| bool trim, |
| bool usePlus) |
| { |
| |
| char s [64]; |
| |
| if (x > 0.0 && usePlus) |
| { |
| sprintf (s, "+%0.*f", (unsigned) places, (double) x); |
| } |
| else |
| { |
| sprintf (s, "%0.*f", (unsigned) places, (double) x); |
| } |
| |
| if (trim) |
| { |
| |
| while (s [strlen (s) - 1] == '0') |
| { |
| s [strlen (s) - 1] = 0; |
| } |
| |
| if (s [strlen (s) - 1] == '.') |
| { |
| s [strlen (s) - 1] = 0; |
| } |
| |
| } |
| |
| Set (ns, path, s); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| bool dng_xmp::Get_urational (const char *ns, |
| const char *path, |
| dng_urational &r) const |
| { |
| |
| dng_string s; |
| |
| if (GetString (ns, path, s)) |
| { |
| |
| if (s.NotEmpty ()) |
| { |
| |
| unsigned n = 0; |
| unsigned d = 0; |
| |
| if (sscanf (s.Get (), "%u/%u", &n, &d) == 2) |
| { |
| |
| if (d != 0) |
| { |
| |
| r = dng_urational (n, d); |
| |
| return true; |
| |
| } |
| |
| } |
| |
| } |
| |
| } |
| |
| return false; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_xmp::Set_urational (const char *ns, |
| const char *path, |
| const dng_urational &r) |
| { |
| |
| char s [64]; |
| |
| sprintf (s, |
| "%u/%u", |
| (unsigned) r.n, |
| (unsigned) r.d); |
| |
| Set (ns, path, s); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_xmp::Sync_urational (const char *ns, |
| const char *path, |
| dng_urational &r, |
| uint32 options) |
| { |
| |
| bool isDefault = r.NotValid (); |
| |
| // Sync 1: Force XMP to match non-XMP. |
| |
| if (options & ignoreXMP) |
| { |
| |
| if (isDefault || (options & removeXMP)) |
| { |
| |
| Remove (ns, path); |
| |
| } |
| |
| else |
| { |
| |
| Set_urational (ns, path, r); |
| |
| } |
| |
| return; |
| |
| } |
| |
| // Sync 2: From non-XMP to XMP if non-XMP is prefered. |
| |
| if ((options & preferNonXMP) && !isDefault) |
| { |
| |
| if (options & removeXMP) |
| { |
| |
| Remove (ns, path); |
| |
| } |
| |
| else |
| { |
| |
| Set_urational (ns, path, r); |
| |
| } |
| |
| return; |
| |
| } |
| |
| // Sync 3: From XMP to non-XMP if XMP is prefered or default non-XMP. |
| |
| if ((options & preferXMP) || isDefault) |
| { |
| |
| if (Get_urational (ns, path, r)) |
| { |
| |
| if (options & removeXMP) |
| { |
| |
| Remove (ns, path); |
| |
| } |
| |
| return; |
| |
| } |
| |
| } |
| |
| // Sync 4: From non-XMP to XMP. |
| |
| if (options & removeXMP) |
| { |
| |
| Remove (ns, path); |
| |
| } |
| |
| else if (!isDefault) |
| { |
| |
| Set_urational (ns, path, r); |
| |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| bool dng_xmp::Get_srational (const char *ns, |
| const char *path, |
| dng_srational &r) const |
| { |
| |
| dng_string s; |
| |
| if (GetString (ns, path, s)) |
| { |
| |
| if (s.NotEmpty ()) |
| { |
| |
| int n = 0; |
| int d = 0; |
| |
| if (sscanf (s.Get (), "%d/%d", &n, &d) == 2) |
| { |
| |
| if (d != 0) |
| { |
| |
| r = dng_srational (n, d); |
| |
| return true; |
| |
| } |
| |
| } |
| |
| } |
| |
| } |
| |
| return false; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_xmp::Set_srational (const char *ns, |
| const char *path, |
| const dng_srational &r) |
| { |
| |
| char s [64]; |
| |
| sprintf (s, |
| "%d/%d", |
| (int) r.n, |
| (int) r.d); |
| |
| Set (ns, path, s); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_xmp::Sync_srational (const char *ns, |
| const char *path, |
| dng_srational &r, |
| uint32 options) |
| { |
| |
| bool isDefault = r.NotValid (); |
| |
| // Sync 1: Force XMP to match non-XMP. |
| |
| if (options & ignoreXMP) |
| { |
| |
| if (isDefault || (options & removeXMP)) |
| { |
| |
| Remove (ns, path); |
| |
| } |
| |
| else |
| { |
| |
| Set_srational (ns, path, r); |
| |
| } |
| |
| return; |
| |
| } |
| |
| // Sync 2: From non-XMP to XMP if non-XMP is prefered. |
| |
| if ((options & preferNonXMP) && !isDefault) |
| { |
| |
| if (options & removeXMP) |
| { |
| |
| Remove (ns, path); |
| |
| } |
| |
| else |
| { |
| |
| Set_srational (ns, path, r); |
| |
| } |
| |
| return; |
| |
| } |
| |
| // Sync 3: From XMP to non-XMP if XMP is prefered or default non-XMP. |
| |
| if ((options & preferXMP) || isDefault) |
| { |
| |
| if (Get_srational (ns, path, r)) |
| { |
| |
| if (options & removeXMP) |
| { |
| |
| Remove (ns, path); |
| |
| } |
| |
| return; |
| |
| } |
| |
| } |
| |
| // Sync 4: From non-XMP to XMP. |
| |
| if (options & removeXMP) |
| { |
| |
| Remove (ns, path); |
| |
| } |
| |
| else if (!isDefault) |
| { |
| |
| Set_srational (ns, path, r); |
| |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| bool dng_xmp::GetFingerprint (const char *ns, |
| const char *path, |
| dng_fingerprint &print) const |
| { |
| |
| dng_string s; |
| |
| if (GetString (ns, path, s)) |
| { |
| |
| dng_fingerprint temp = DecodeFingerprint (s); |
| |
| if (temp.IsValid ()) |
| { |
| |
| print = temp; |
| |
| return true; |
| |
| } |
| |
| } |
| |
| return false; |
| |
| } |
| |
| /******************************************************************************/ |
| |
| void dng_xmp::SetFingerprint (const char *ns, |
| const char *tag, |
| const dng_fingerprint &print, |
| bool allowInvalid) |
| { |
| |
| dng_string s = EncodeFingerprint (print, allowInvalid); |
| |
| if (s.IsEmpty ()) |
| { |
| |
| Remove (ns, tag); |
| |
| } |
| |
| else |
| { |
| |
| SetString (ns, tag, s); |
| |
| } |
| |
| } |
| |
| /******************************************************************************/ |
| |
| void dng_xmp::SetVersion2to4 (const char *ns, |
| const char *path, |
| uint32 version) |
| { |
| |
| char buf [32]; |
| |
| if (version & 0x000000ff) |
| { |
| |
| // x.x.x.x |
| |
| sprintf (buf, |
| "%u.%u.%u.%u", |
| (unsigned) ((version >> 24) & 0xff), |
| (unsigned) ((version >> 16) & 0xff), |
| (unsigned) ((version >> 8) & 0xff), |
| (unsigned) ((version ) & 0xff)); |
| |
| } |
| |
| else if (version & 0x0000ff00) |
| { |
| |
| // x.x.x |
| |
| sprintf (buf, |
| "%u.%u.%u", |
| (unsigned) ((version >> 24) & 0xff), |
| (unsigned) ((version >> 16) & 0xff), |
| (unsigned) ((version >> 8) & 0xff)); |
| |
| } |
| |
| else |
| { |
| |
| // x.x |
| |
| sprintf (buf, |
| "%u.%u", |
| (unsigned) ((version >> 24) & 0xff), |
| (unsigned) ((version >> 16) & 0xff)); |
| |
| } |
| |
| Set (ns, path, buf); |
| |
| } |
| |
| /******************************************************************************/ |
| |
| dng_fingerprint dng_xmp::GetIPTCDigest () const |
| { |
| |
| dng_fingerprint digest; |
| |
| if (GetFingerprint (XMP_NS_PHOTOSHOP, |
| "LegacyIPTCDigest", |
| digest)) |
| { |
| |
| return digest; |
| |
| } |
| |
| return dng_fingerprint (); |
| |
| } |
| |
| /******************************************************************************/ |
| |
| void dng_xmp::SetIPTCDigest (dng_fingerprint &digest) |
| { |
| |
| SetFingerprint (XMP_NS_PHOTOSHOP, |
| "LegacyIPTCDigest", |
| digest); |
| |
| } |
| |
| /******************************************************************************/ |
| |
| void dng_xmp::ClearIPTCDigest () |
| { |
| |
| Remove (XMP_NS_PHOTOSHOP, "LegacyIPTCDigest"); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_xmp::SyncIPTC (dng_iptc &iptc, |
| uint32 options) |
| { |
| |
| SyncAltLangDefault (XMP_NS_DC, |
| "title", |
| iptc.fTitle, |
| options); |
| |
| SyncString (XMP_NS_PHOTOSHOP, |
| "Category", |
| iptc.fCategory, |
| options); |
| |
| { |
| |
| uint32 x = 0xFFFFFFFF; |
| |
| if (iptc.fUrgency >= 0) |
| { |
| |
| x = (uint32) iptc.fUrgency; |
| |
| } |
| |
| Sync_uint32 (XMP_NS_PHOTOSHOP, |
| "Urgency", |
| x, |
| x == 0xFFFFFFFF, |
| options); |
| |
| if (x <= 9) |
| { |
| |
| iptc.fUrgency = (int32) x; |
| |
| } |
| |
| } |
| |
| SyncStringList (XMP_NS_PHOTOSHOP, |
| "SupplementalCategories", |
| iptc.fSupplementalCategories, |
| true, |
| options); |
| |
| SyncStringList (XMP_NS_PHOTOSHOP, |
| "Keywords", |
| iptc.fKeywords, |
| true, |
| options); |
| |
| SyncString (XMP_NS_PHOTOSHOP, |
| "Instructions", |
| iptc.fInstructions, |
| options); |
| |
| { |
| |
| dng_string s = iptc.fDateTimeCreated.Encode_ISO_8601 (); |
| |
| if (SyncString (XMP_NS_PHOTOSHOP, |
| "DateCreated", |
| s, |
| options)) |
| { |
| |
| iptc.fDateTimeCreated.Decode_ISO_8601 (s.Get ()); |
| |
| } |
| |
| } |
| |
| { |
| |
| dng_string s = iptc.fDigitalCreationDateTime.Encode_ISO_8601 (); |
| |
| if (SyncString (XMP_NS_EXIF, |
| "DateTimeDigitized", |
| s, |
| options)) |
| { |
| |
| iptc.fDigitalCreationDateTime.Decode_ISO_8601 (s.Get ()); |
| |
| } |
| |
| } |
| |
| SyncStringList (XMP_NS_DC, |
| "creator", |
| iptc.fAuthors, |
| false, |
| options); |
| |
| SyncString (XMP_NS_PHOTOSHOP, |
| "AuthorsPosition", |
| iptc.fAuthorsPosition, |
| options); |
| |
| SyncString (XMP_NS_PHOTOSHOP, |
| "City", |
| iptc.fCity, |
| options); |
| |
| SyncString (XMP_NS_PHOTOSHOP, |
| "State", |
| iptc.fState, |
| options); |
| |
| SyncString (XMP_NS_PHOTOSHOP, |
| "Country", |
| iptc.fCountry, |
| options); |
| |
| SyncString (XMP_NS_IPTC, |
| "CountryCode", |
| iptc.fCountryCode, |
| options); |
| |
| SyncString (XMP_NS_IPTC, |
| "Location", |
| iptc.fLocation, |
| options); |
| |
| SyncString (XMP_NS_PHOTOSHOP, |
| "TransmissionReference", |
| iptc.fTransmissionReference, |
| options); |
| |
| SyncString (XMP_NS_PHOTOSHOP, |
| "Headline", |
| iptc.fHeadline, |
| options); |
| |
| SyncString (XMP_NS_PHOTOSHOP, |
| "Credit", |
| iptc.fCredit, |
| options); |
| |
| SyncString (XMP_NS_PHOTOSHOP, |
| "Source", |
| iptc.fSource, |
| options); |
| |
| SyncAltLangDefault (XMP_NS_DC, |
| "rights", |
| iptc.fCopyrightNotice, |
| options); |
| |
| SyncAltLangDefault (XMP_NS_DC, |
| "description", |
| iptc.fDescription, |
| options); |
| |
| SyncString (XMP_NS_PHOTOSHOP, |
| "CaptionWriter", |
| iptc.fDescriptionWriter, |
| options); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_xmp::IngestIPTC (dng_metadata &metadata, |
| bool xmpIsNewer) |
| { |
| |
| if (metadata.IPTCLength ()) |
| { |
| |
| // Parse the IPTC block. |
| |
| dng_iptc iptc; |
| |
| iptc.Parse (metadata.IPTCData (), |
| metadata.IPTCLength (), |
| metadata.IPTCOffset ()); |
| |
| // Compute fingerprint of IPTC data both ways, including and |
| // excluding the padding data. |
| |
| dng_fingerprint iptcDigest1 = metadata.IPTCDigest (true ); |
| dng_fingerprint iptcDigest2 = metadata.IPTCDigest (false); |
| |
| // See if there is an IPTC fingerprint stored in the XMP. |
| |
| dng_fingerprint xmpDigest = GetIPTCDigest (); |
| |
| if (xmpDigest.IsValid ()) |
| { |
| |
| // If they match, the XMP was already synced with this |
| // IPTC block, and we should not resync since it might |
| // overwrite changes in the XMP data. |
| |
| if (iptcDigest1 == xmpDigest) |
| { |
| |
| return; |
| |
| } |
| |
| // If it matches the incorrectly computed digest, skip |
| // the sync, but fix the digest in the XMP. |
| |
| if (iptcDigest2 == xmpDigest) |
| { |
| |
| SetIPTCDigest (iptcDigest1); |
| |
| return; |
| |
| } |
| |
| // Else the IPTC has changed, so force an update. |
| |
| xmpIsNewer = false; |
| |
| } |
| |
| else |
| { |
| |
| // There is no IPTC digest. Previously we would |
| // prefer the IPTC in this case, but the MWG suggests |
| // that we prefer the XMP in this case. |
| |
| xmpIsNewer = true; |
| |
| } |
| |
| // Remember the fingerprint of the IPTC we are syncing with. |
| |
| SetIPTCDigest (iptcDigest1); |
| |
| // Find the sync options. |
| |
| uint32 options = xmpIsNewer ? preferXMP |
| : preferNonXMP; |
| |
| // Synchronize the fields. |
| |
| SyncIPTC (iptc, options); |
| |
| } |
| |
| // After the IPTC data is moved to XMP, we don't need it anymore. |
| |
| metadata.ClearIPTC (); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_xmp::RebuildIPTC (dng_metadata &metadata, |
| dng_memory_allocator &allocator, |
| bool padForTIFF) |
| { |
| |
| // If there is no XMP, then there is no IPTC. |
| |
| if (!fSDK->HasMeta ()) |
| { |
| return; |
| } |
| |
| // Extract the legacy IPTC fields from the XMP data. |
| |
| dng_iptc iptc; |
| |
| SyncIPTC (iptc, preferXMP); |
| |
| // Build legacy IPTC record |
| |
| if (iptc.NotEmpty ()) |
| { |
| |
| AutoPtr<dng_memory_block> block (iptc.Spool (allocator, |
| padForTIFF)); |
| |
| metadata.SetIPTC (block); |
| |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_xmp::SyncFlash (uint32 &flashState, |
| uint32 &flashMask, |
| uint32 options) |
| { |
| |
| bool isDefault = (flashState == 0xFFFFFFFF); |
| |
| if ((options & ignoreXMP) || !isDefault) |
| { |
| |
| Remove (XMP_NS_EXIF, "Flash"); |
| |
| } |
| |
| if (!isDefault) |
| { |
| |
| fSDK->SetStructField (XMP_NS_EXIF, |
| "Flash", |
| XMP_NS_EXIF, |
| "Fired", |
| (flashState & 0x1) ? "True" : "False"); |
| |
| if (((flashMask >> 1) & 3) == 3) |
| { |
| |
| char s [8]; |
| |
| sprintf (s, "%u", (unsigned) ((flashState >> 1) & 3)); |
| |
| fSDK->SetStructField (XMP_NS_EXIF, |
| "Flash", |
| XMP_NS_EXIF, |
| "Return", |
| s); |
| |
| } |
| |
| if (((flashMask >> 3) & 3) == 3) |
| { |
| |
| char s [8]; |
| |
| sprintf (s, "%u", (unsigned) ((flashState >> 3) & 3)); |
| |
| fSDK->SetStructField (XMP_NS_EXIF, |
| "Flash", |
| XMP_NS_EXIF, |
| "Mode", |
| s); |
| |
| } |
| |
| if ((flashMask & (1 << 5)) != 0) |
| { |
| |
| fSDK->SetStructField (XMP_NS_EXIF, |
| "Flash", |
| XMP_NS_EXIF, |
| "Function", |
| (flashState & (1 << 5)) ? "True" : "False"); |
| |
| } |
| |
| if ((flashMask & (1 << 6)) != 0) |
| { |
| |
| fSDK->SetStructField (XMP_NS_EXIF, |
| "Flash", |
| XMP_NS_EXIF, |
| "RedEyeMode", |
| (flashState & (1 << 6)) ? "True" : "False"); |
| |
| } |
| |
| } |
| |
| else if (fSDK->Exists (XMP_NS_EXIF, "Flash")) |
| { |
| |
| dng_string s; |
| |
| if (fSDK->GetStructField (XMP_NS_EXIF, |
| "Flash", |
| XMP_NS_EXIF, |
| "Fired", |
| s)) |
| { |
| |
| flashState = 0; |
| flashMask = 1; |
| |
| if (s.Matches ("True")) |
| { |
| flashState |= 1; |
| } |
| |
| if (fSDK->GetStructField (XMP_NS_EXIF, |
| "Flash", |
| XMP_NS_EXIF, |
| "Return", |
| s)) |
| { |
| |
| unsigned x = 0; |
| |
| if (sscanf (s.Get (), "%u", &x) == 1 && x <= 3) |
| { |
| |
| flashState |= x << 1; |
| flashMask |= 3 << 1; |
| |
| } |
| |
| } |
| |
| if (fSDK->GetStructField (XMP_NS_EXIF, |
| "Flash", |
| XMP_NS_EXIF, |
| "Mode", |
| s)) |
| { |
| |
| unsigned x = 0; |
| |
| if (sscanf (s.Get (), "%u", &x) == 1 && x <= 3) |
| { |
| |
| flashState |= x << 3; |
| flashMask |= 3 << 3; |
| |
| } |
| |
| } |
| |
| if (fSDK->GetStructField (XMP_NS_EXIF, |
| "Flash", |
| XMP_NS_EXIF, |
| "Function", |
| s)) |
| { |
| |
| flashMask |= 1 << 5; |
| |
| if (s.Matches ("True")) |
| { |
| flashState |= 1 << 5; |
| } |
| |
| } |
| |
| if (fSDK->GetStructField (XMP_NS_EXIF, |
| "Flash", |
| XMP_NS_EXIF, |
| "RedEyeMode", |
| s)) |
| { |
| |
| flashMask |= 1 << 6; |
| |
| if (s.Matches ("True")) |
| { |
| flashState |= 1 << 6; |
| } |
| |
| } |
| |
| } |
| |
| } |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_xmp::SyncExif (dng_exif &exif, |
| const dng_exif *originalExif, |
| bool doingUpdateFromXMP, |
| bool removeFromXMP) |
| { |
| |
| DNG_ASSERT (!doingUpdateFromXMP || originalExif, |
| "Must have original EXIF if doingUpdateFromXMP"); |
| |
| // Default synchronization options for the read-only fields. |
| |
| uint32 readOnly = doingUpdateFromXMP ? ignoreXMP |
| : preferNonXMP; |
| |
| // Option for removable fields. |
| |
| uint32 removable = removeFromXMP ? removeXMP |
| : 0; |
| |
| // Make: |
| |
| SyncString (XMP_NS_TIFF, |
| "Make", |
| exif.fMake, |
| readOnly + removable); |
| |
| // Model: |
| |
| SyncString (XMP_NS_TIFF, |
| "Model", |
| exif.fModel, |
| readOnly + removable); |
| |
| // Exif version number: |
| |
| { |
| |
| dng_string exifVersion; |
| |
| if (exif.fExifVersion) |
| { |
| |
| unsigned b0 = ((exif.fExifVersion >> 24) & 0x0FF) - '0'; |
| unsigned b1 = ((exif.fExifVersion >> 16) & 0x0FF) - '0'; |
| unsigned b2 = ((exif.fExifVersion >> 8) & 0x0FF) - '0'; |
| unsigned b3 = ((exif.fExifVersion ) & 0x0FF) - '0'; |
| |
| if (b0 <= 9 && b1 <= 9 && b2 <= 9 && b3 <= 9) |
| { |
| |
| char s [5]; |
| |
| sprintf (s, |
| "%1u%1u%1u%1u", |
| b0, |
| b1, |
| b2, |
| b3); |
| |
| exifVersion.Set (s); |
| |
| } |
| |
| } |
| |
| SyncString (XMP_NS_EXIF, |
| "ExifVersion", |
| exifVersion, |
| readOnly); |
| |
| if (exifVersion.NotEmpty ()) |
| { |
| |
| unsigned b0; |
| unsigned b1; |
| unsigned b2; |
| unsigned b3; |
| |
| if (sscanf (exifVersion.Get (), |
| "%1u%1u%1u%1u", |
| &b0, |
| &b1, |
| &b2, |
| &b3) == 4) |
| { |
| |
| if (b0 <= 9 && b1 <= 9 && b2 <= 9 && b3 <= 9) |
| { |
| |
| b0 += '0'; |
| b1 += '0'; |
| b2 += '0'; |
| b3 += '0'; |
| |
| exif.fExifVersion = (b0 << 24) | |
| (b1 << 16) | |
| (b2 << 8) | |
| (b3 ); |
| |
| } |
| |
| } |
| |
| } |
| |
| // Provide default value for ExifVersion. |
| |
| if (!exif.fExifVersion) |
| { |
| |
| exif.fExifVersion = DNG_CHAR4 ('0','2','2','1'); |
| |
| Set (XMP_NS_EXIF, |
| "ExifVersion", |
| "0221"); |
| |
| } |
| |
| if (removeFromXMP) |
| { |
| |
| Remove (XMP_NS_EXIF, "ExifVersion"); |
| |
| } |
| |
| } |
| |
| // ExposureTime / ShutterSpeedValue: |
| |
| { |
| |
| // Process twice in case XMP contains only one of the |
| // two fields. |
| |
| for (uint32 pass = 0; pass < 2; pass++) |
| { |
| |
| dng_urational et = exif.fExposureTime; |
| |
| Sync_urational (XMP_NS_EXIF, |
| "ExposureTime", |
| et, |
| readOnly); |
| |
| if (et.IsValid ()) |
| { |
| |
| exif.SetExposureTime (et.As_real64 (), false); |
| |
| } |
| |
| dng_srational ss = exif.fShutterSpeedValue; |
| |
| Sync_srational (XMP_NS_EXIF, |
| "ShutterSpeedValue", |
| ss, |
| readOnly); |
| |
| if (ss.IsValid ()) |
| { |
| |
| exif.SetShutterSpeedValue (ss.As_real64 ()); |
| |
| } |
| |
| } |
| |
| if (removeFromXMP) |
| { |
| |
| Remove (XMP_NS_EXIF, "ExposureTime"); |
| |
| Remove (XMP_NS_EXIF, "ShutterSpeedValue"); |
| |
| } |
| |
| } |
| |
| // FNumber / ApertureValue: |
| |
| { |
| |
| for (uint32 pass = 0; pass < 2; pass++) |
| { |
| |
| dng_urational fs = exif.fFNumber; |
| |
| Sync_urational (XMP_NS_EXIF, |
| "FNumber", |
| fs, |
| readOnly); |
| |
| if (fs.IsValid ()) |
| { |
| |
| exif.SetFNumber (fs.As_real64 ()); |
| |
| } |
| |
| dng_urational av = exif.fApertureValue; |
| |
| Sync_urational (XMP_NS_EXIF, |
| "ApertureValue", |
| av, |
| readOnly); |
| |
| if (av.IsValid ()) |
| { |
| |
| exif.SetApertureValue (av.As_real64 ()); |
| |
| } |
| |
| } |
| |
| if (removeFromXMP) |
| { |
| |
| Remove (XMP_NS_EXIF, "FNumber"); |
| |
| Remove (XMP_NS_EXIF, "ApertureValue"); |
| |
| } |
| |
| } |
| |
| // Exposure program: |
| |
| Sync_uint32 (XMP_NS_EXIF, |
| "ExposureProgram", |
| exif.fExposureProgram, |
| exif.fExposureProgram == 0xFFFFFFFF, |
| readOnly + removable); |
| |
| // ISO Speed Ratings: |
| |
| { |
| |
| uint32 isoSpeedRatingsCount = 0; |
| |
| uint32 isoSpeedRatingsOptions = readOnly; |
| |
| uint32 oldISOSpeedRatings [3]; |
| |
| memcpy (oldISOSpeedRatings, |
| exif.fISOSpeedRatings, |
| sizeof (oldISOSpeedRatings)); |
| |
| bool checkXMPForHigherISO = false; |
| |
| for (uint32 j = 0; j < 3; j++) |
| { |
| |
| // Special case: the EXIF 2.2x standard represents ISO speed ratings with |
| // 2 bytes, which cannot hold ISO speed ratings above 65535 (e.g., |
| // 102400). If the EXIF ISO speed rating value is 65535, prefer the XMP |
| // ISOSpeedRatings tag value. |
| |
| if (exif.fISOSpeedRatings [j] == 65535) |
| { |
| |
| isoSpeedRatingsOptions = preferXMP; |
| |
| checkXMPForHigherISO = true; |
| |
| isoSpeedRatingsCount = 0; |
| |
| break; |
| |
| } |
| |
| else if (exif.fISOSpeedRatings [j] == 0) |
| { |
| break; |
| } |
| |
| isoSpeedRatingsCount++; |
| |
| } |
| |
| Sync_uint32_array (XMP_NS_EXIF, |
| "ISOSpeedRatings", |
| exif.fISOSpeedRatings, |
| isoSpeedRatingsCount, |
| 3, |
| isoSpeedRatingsOptions); |
| |
| // If the EXIF ISO was 65535 and we failed to find anything meaningful in the |
| // XMP, then we fall back to the EXIF ISO. |
| |
| if (checkXMPForHigherISO && (isoSpeedRatingsCount == 0)) |
| { |
| |
| memcpy (exif.fISOSpeedRatings, |
| oldISOSpeedRatings, |
| sizeof (oldISOSpeedRatings)); |
| |
| } |
| |
| // Only remove the ISO tag if there are not ratings over 65535. |
| |
| if (removeFromXMP) |
| { |
| |
| bool hasHighISO = false; |
| |
| for (uint32 j = 0; j < 3; j++) |
| { |
| |
| if (exif.fISOSpeedRatings [j] == 0) |
| { |
| break; |
| } |
| |
| hasHighISO = hasHighISO || (exif.fISOSpeedRatings [j] > 65535); |
| |
| } |
| |
| if (!hasHighISO) |
| { |
| |
| Remove (XMP_NS_EXIF, "ISOSpeedRatings"); |
| |
| } |
| |
| } |
| |
| } |
| |
| // SensitivityType: |
| |
| Sync_uint32 (XMP_NS_EXIF, |
| "SensitivityType", |
| exif.fSensitivityType, |
| exif.fSensitivityType == stUnknown, |
| readOnly + removable); |
| |
| // StandardOutputSensitivity: |
| |
| Sync_uint32 (XMP_NS_EXIF, |
| "StandardOutputSensitivity", |
| exif.fStandardOutputSensitivity, |
| exif.fStandardOutputSensitivity == 0, |
| readOnly + removable); |
| |
| // RecommendedExposureIndex: |
| |
| Sync_uint32 (XMP_NS_EXIF, |
| "RecommendedExposureIndex", |
| exif.fRecommendedExposureIndex, |
| exif.fRecommendedExposureIndex == 0, |
| readOnly + removable); |
| |
| // ISOSpeed: |
| |
| Sync_uint32 (XMP_NS_EXIF, |
| "ISOSpeed", |
| exif.fISOSpeed, |
| exif.fISOSpeed == 0, |
| readOnly + removable); |
| |
| // ISOSpeedLatitudeyyy: |
| |
| Sync_uint32 (XMP_NS_EXIF, |
| "ISOSpeedLatitudeyyy", |
| exif.fISOSpeedLatitudeyyy, |
| exif.fISOSpeedLatitudeyyy == 0, |
| readOnly + removable); |
| |
| // ISOSpeedLatitudezzz: |
| |
| Sync_uint32 (XMP_NS_EXIF, |
| "ISOSpeedLatitudezzz", |
| exif.fISOSpeedLatitudezzz, |
| exif.fISOSpeedLatitudezzz == 0, |
| readOnly + removable); |
| |
| // ExposureIndex: |
| |
| Sync_urational (XMP_NS_EXIF, |
| "ExposureIndex", |
| exif.fExposureIndex, |
| readOnly + removable); |
| |
| // Brightness Value: |
| |
| Sync_srational (XMP_NS_EXIF, |
| "BrightnessValue", |
| exif.fBrightnessValue, |
| readOnly + removable); |
| |
| // Exposure Bias: |
| |
| Sync_srational (XMP_NS_EXIF, |
| "ExposureBiasValue", |
| exif.fExposureBiasValue, |
| readOnly + removable); |
| |
| // Max Aperture: |
| |
| Sync_urational (XMP_NS_EXIF, |
| "MaxApertureValue", |
| exif.fMaxApertureValue, |
| readOnly + removable); |
| |
| // Subject Distance: |
| |
| Sync_urational (XMP_NS_EXIF, |
| "SubjectDistance", |
| exif.fSubjectDistance, |
| readOnly + removable); |
| |
| // Metering Mode: |
| |
| Sync_uint32 (XMP_NS_EXIF, |
| "MeteringMode", |
| exif.fMeteringMode, |
| exif.fMeteringMode == 0xFFFFFFFF, |
| readOnly + removable); |
| |
| // Light Source: |
| |
| Sync_uint32 (XMP_NS_EXIF, |
| "LightSource", |
| exif.fLightSource, |
| exif.fLightSource > 0x0FFFF, |
| readOnly + removable); |
| |
| // Flash State: |
| |
| SyncFlash (exif.fFlash, |
| exif.fFlashMask, |
| readOnly); |
| |
| if (removeFromXMP) |
| { |
| Remove (XMP_NS_EXIF, "Flash"); |
| } |
| |
| // Focal Length: |
| |
| Sync_urational (XMP_NS_EXIF, |
| "FocalLength", |
| exif.fFocalLength, |
| readOnly + removable); |
| |
| // Sensing Method. |
| |
| Sync_uint32 (XMP_NS_EXIF, |
| "SensingMethod", |
| exif.fSensingMethod, |
| exif.fSensingMethod > 0x0FFFF, |
| readOnly + removable); |
| |
| // File Source. |
| |
| Sync_uint32 (XMP_NS_EXIF, |
| "FileSource", |
| exif.fFileSource, |
| exif.fFileSource > 0x0FF, |
| readOnly + removable); |
| |
| // Scene Type. |
| |
| Sync_uint32 (XMP_NS_EXIF, |
| "SceneType", |
| exif.fSceneType, |
| exif.fSceneType > 0x0FF, |
| readOnly + removable); |
| |
| // Focal Length in 35mm Film: |
| |
| Sync_uint32 (XMP_NS_EXIF, |
| "FocalLengthIn35mmFilm", |
| exif.fFocalLengthIn35mmFilm, |
| exif.fFocalLengthIn35mmFilm == 0, |
| readOnly + removable); |
| |
| // Custom Rendered: |
| |
| Sync_uint32 (XMP_NS_EXIF, |
| "CustomRendered", |
| exif.fCustomRendered, |
| exif.fCustomRendered > 0x0FFFF, |
| readOnly + removable); |
| |
| // Exposure Mode: |
| |
| Sync_uint32 (XMP_NS_EXIF, |
| "ExposureMode", |
| exif.fExposureMode, |
| exif.fExposureMode > 0x0FFFF, |
| readOnly + removable); |
| |
| // White Balance: |
| |
| Sync_uint32 (XMP_NS_EXIF, |
| "WhiteBalance", |
| exif.fWhiteBalance, |
| exif.fWhiteBalance > 0x0FFFF, |
| readOnly + removable); |
| |
| // Scene Capture Type: |
| |
| Sync_uint32 (XMP_NS_EXIF, |
| "SceneCaptureType", |
| exif.fSceneCaptureType, |
| exif.fSceneCaptureType > 0x0FFFF, |
| readOnly + removable); |
| |
| // Gain Control: |
| |
| Sync_uint32 (XMP_NS_EXIF, |
| "GainControl", |
| exif.fGainControl, |
| exif.fGainControl > 0x0FFFF, |
| readOnly + removable); |
| |
| // Contrast: |
| |
| Sync_uint32 (XMP_NS_EXIF, |
| "Contrast", |
| exif.fContrast, |
| exif.fContrast > 0x0FFFF, |
| readOnly + removable); |
| |
| // Saturation: |
| |
| Sync_uint32 (XMP_NS_EXIF, |
| "Saturation", |
| exif.fSaturation, |
| exif.fSaturation > 0x0FFFF, |
| readOnly + removable); |
| |
| // Sharpness: |
| |
| Sync_uint32 (XMP_NS_EXIF, |
| "Sharpness", |
| exif.fSharpness, |
| exif.fSharpness > 0x0FFFF, |
| readOnly + removable); |
| |
| // Subject Distance Range: |
| |
| Sync_uint32 (XMP_NS_EXIF, |
| "SubjectDistanceRange", |
| exif.fSubjectDistanceRange, |
| exif.fSubjectDistanceRange > 0x0FFFF, |
| readOnly + removable); |
| |
| // Subject Area: |
| |
| Sync_uint32_array (XMP_NS_EXIF, |
| "SubjectArea", |
| exif.fSubjectArea, |
| exif.fSubjectAreaCount, |
| sizeof (exif.fSubjectArea ) / |
| sizeof (exif.fSubjectArea [0]), |
| readOnly); |
| |
| if (removeFromXMP) |
| { |
| Remove (XMP_NS_EXIF, "SubjectArea"); |
| } |
| |
| // Digital Zoom Ratio: |
| |
| Sync_urational (XMP_NS_EXIF, |
| "DigitalZoomRatio", |
| exif.fDigitalZoomRatio, |
| readOnly + removable); |
| |
| // Focal Plane Resolution: |
| |
| Sync_urational (XMP_NS_EXIF, |
| "FocalPlaneXResolution", |
| exif.fFocalPlaneXResolution, |
| readOnly + removable); |
| |
| Sync_urational (XMP_NS_EXIF, |
| "FocalPlaneYResolution", |
| exif.fFocalPlaneYResolution, |
| readOnly + removable); |
| |
| Sync_uint32 (XMP_NS_EXIF, |
| "FocalPlaneResolutionUnit", |
| exif.fFocalPlaneResolutionUnit, |
| exif.fFocalPlaneResolutionUnit > 0x0FFFF, |
| readOnly + removable); |
| |
| // ImageDescription: (XMP is is always preferred) |
| |
| if (fSDK->GetAltLangDefault (XMP_NS_DC, |
| "description", |
| exif.fImageDescription)) |
| |
| { |
| |
| } |
| |
| else if (doingUpdateFromXMP) |
| { |
| |
| exif.fImageDescription.Clear (); |
| |
| if (originalExif->fImageDescription.NotEmpty ()) |
| { |
| |
| fSDK->SetAltLangDefault (XMP_NS_DC, |
| "description", |
| dng_string ()); |
| |
| } |
| |
| } |
| |
| else if (exif.fImageDescription.NotEmpty ()) |
| { |
| |
| fSDK->SetAltLangDefault (XMP_NS_DC, |
| "description", |
| exif.fImageDescription); |
| |
| } |
| |
| // Artist: (XMP is is always preferred) |
| |
| { |
| |
| dng_string_list xmpList; |
| |
| if (fSDK->GetStringList (XMP_NS_DC, |
| "creator", |
| xmpList)) |
| { |
| |
| exif.fArtist.Clear (); |
| |
| if (xmpList.Count () > 0) |
| { |
| |
| uint32 j; |
| |
| uint32 bufferSize = xmpList.Count () * 4 + 1; |
| |
| for (j = 0; j < xmpList.Count (); j++) |
| { |
| |
| bufferSize += xmpList [j].Length () * 2; |
| |
| } |
| |
| dng_memory_data temp (bufferSize); |
| |
| char *t = temp.Buffer_char (); |
| |
| for (j = 0; j < xmpList.Count (); j++) |
| { |
| |
| const char *s = xmpList [j].Get (); |
| |
| bool needQuotes = xmpList [j].Contains ("; ") || |
| s [0] == '\"'; |
| |
| if (needQuotes) |
| { |
| *(t++) = '\"'; |
| } |
| |
| while (s [0] != 0) |
| { |
| |
| if (s [0] == '\"' && needQuotes) |
| { |
| *(t++) = '\"'; |
| } |
| |
| *(t++) = *(s++); |
| |
| } |
| |
| if (needQuotes) |
| { |
| *(t++) = '\"'; |
| } |
| |
| if (j != xmpList.Count () - 1) |
| { |
| *(t++) = ';'; |
| *(t++) = ' '; |
| } |
| else |
| { |
| *t = 0; |
| } |
| |
| } |
| |
| exif.fArtist.Set (temp.Buffer_char ()); |
| |
| } |
| |
| } |
| |
| else if (doingUpdateFromXMP) |
| { |
| |
| exif.fArtist.Clear (); |
| |
| if (originalExif->fArtist.NotEmpty ()) |
| { |
| |
| dng_string_list fakeList; |
| |
| fakeList.Append (dng_string ()); |
| |
| SetStringList (XMP_NS_DC, |
| "creator", |
| fakeList, |
| false); |
| |
| } |
| |
| } |
| |
| else if (exif.fArtist.NotEmpty ()) |
| { |
| |
| dng_string_list newList; |
| |
| dng_memory_data temp (exif.fArtist.Length () + 1); |
| |
| const char *s = exif.fArtist.Get (); |
| |
| char *t = temp.Buffer_char (); |
| |
| bool first = true; |
| |
| bool quoted = false; |
| |
| bool valid = true; |
| |
| while (s [0] != 0 && valid) |
| { |
| |
| if (first) |
| { |
| |
| if (s [0] == '\"') |
| { |
| |
| quoted = true; |
| |
| s++; |
| |
| } |
| |
| } |
| |
| first = false; |
| |
| if (quoted) |
| { |
| |
| if (s [0] == '\"' && |
| s [1] == '\"') |
| { |
| |
| s+= 2; |
| |
| *(t++) = '\"'; |
| |
| } |
| |
| else if (s [0] == '\"') |
| { |
| |
| s++; |
| |
| quoted = false; |
| |
| valid = valid && ((s [0] == 0) || ((s [0] == ';' && s [1] == ' '))); |
| |
| } |
| |
| else |
| { |
| |
| *(t++) = *(s++); |
| |
| } |
| |
| } |
| |
| else if (s [0] == ';' && |
| s [1] == ' ') |
| { |
| |
| s += 2; |
| |
| t [0] = 0; |
| |
| dng_string ss; |
| |
| ss.Set (temp.Buffer_char ()); |
| |
| newList.Append (ss); |
| |
| t = temp.Buffer_char (); |
| |
| first = true; |
| |
| } |
| |
| else |
| { |
| |
| *(t++) = *(s++); |
| |
| } |
| |
| } |
| |
| if (quoted) |
| { |
| |
| valid = false; |
| |
| } |
| |
| if (valid) |
| { |
| |
| if (t != temp.Buffer_char ()) |
| { |
| |
| t [0] = 0; |
| |
| dng_string ss; |
| |
| ss.Set (temp.Buffer_char ()); |
| |
| newList.Append (ss); |
| |
| } |
| |
| } |
| |
| else |
| { |
| |
| newList.Clear (); |
| |
| newList.Append (exif.fArtist); |
| |
| } |
| |
| SetStringList (XMP_NS_DC, |
| "creator", |
| newList, |
| false); |
| |
| } |
| |
| } |
| |
| // Software: (XMP is is always preferred) |
| |
| if (fSDK->GetString (XMP_NS_XAP, |
| "CreatorTool", |
| exif.fSoftware)) |
| |
| { |
| |
| } |
| |
| else if (doingUpdateFromXMP) |
| { |
| |
| exif.fSoftware.Clear (); |
| |
| if (originalExif->fSoftware.NotEmpty ()) |
| { |
| |
| fSDK->SetString (XMP_NS_XAP, |
| "CreatorTool", |
| dng_string ()); |
| |
| } |
| |
| } |
| |
| else if (exif.fSoftware.NotEmpty ()) |
| { |
| |
| fSDK->SetString (XMP_NS_XAP, |
| "CreatorTool", |
| exif.fSoftware); |
| |
| } |
| |
| // Copyright: (XMP is is always preferred) |
| |
| if (fSDK->GetAltLangDefault (XMP_NS_DC, |
| "rights", |
| exif.fCopyright)) |
| |
| { |
| |
| } |
| |
| else if (doingUpdateFromXMP) |
| { |
| |
| exif.fCopyright.Clear (); |
| |
| if (originalExif->fCopyright.NotEmpty ()) |
| { |
| |
| fSDK->SetAltLangDefault (XMP_NS_DC, |
| "rights", |
| dng_string ()); |
| |
| } |
| |
| } |
| |
| else if (exif.fCopyright.NotEmpty ()) |
| { |
| |
| fSDK->SetAltLangDefault (XMP_NS_DC, |
| "rights", |
| exif.fCopyright); |
| |
| } |
| |
| // Camera serial number private tag: |
| |
| SyncString (XMP_NS_AUX, |
| "SerialNumber", |
| exif.fCameraSerialNumber, |
| readOnly); |
| |
| // Lens Info: |
| |
| { |
| |
| dng_string s; |
| |
| if (exif.fLensInfo [0].IsValid ()) |
| { |
| |
| char ss [256]; |
| |
| sprintf (ss, |
| "%u/%u %u/%u %u/%u %u/%u", |
| (unsigned) exif.fLensInfo [0].n, |
| (unsigned) exif.fLensInfo [0].d, |
| (unsigned) exif.fLensInfo [1].n, |
| (unsigned) exif.fLensInfo [1].d, |
| (unsigned) exif.fLensInfo [2].n, |
| (unsigned) exif.fLensInfo [2].d, |
| (unsigned) exif.fLensInfo [3].n, |
| (unsigned) exif.fLensInfo [3].d); |
| |
| s.Set (ss); |
| |
| } |
| |
| SyncString (XMP_NS_AUX, |
| "LensInfo", |
| s, |
| readOnly); |
| |
| if (s.NotEmpty ()) |
| { |
| |
| unsigned n [4]; |
| unsigned d [4]; |
| |
| if (sscanf (s.Get (), |
| "%u/%u %u/%u %u/%u %u/%u", |
| &n [0], |
| &d [0], |
| &n [1], |
| &d [1], |
| &n [2], |
| &d [2], |
| &n [3], |
| &d [3]) == 8) |
| { |
| |
| for (uint32 j = 0; j < 4; j++) |
| { |
| |
| exif.fLensInfo [j] = dng_urational (n [j], d [j]); |
| |
| } |
| |
| } |
| |
| |
| } |
| |
| } |
| |
| // Lens name: |
| |
| { |
| |
| // EXIF lens names are sometimes missing or wrong (esp. when non-OEM lenses |
| // are used). So prefer the value from XMP. |
| |
| SyncString (XMP_NS_AUX, |
| "Lens", |
| exif.fLensName, |
| preferXMP); |
| |
| // Generate default lens name from lens info if required. |
| // Ignore names names that end in "f/0.0" due to third party bug. |
| |
| if ((exif.fLensName.IsEmpty () || |
| exif.fLensName.EndsWith ("f/0.0")) && exif.fLensInfo [0].IsValid ()) |
| { |
| |
| char s [256]; |
| |
| real64 minFL = exif.fLensInfo [0].As_real64 (); |
| real64 maxFL = exif.fLensInfo [1].As_real64 (); |
| |
| // The f-stop numbers are optional. |
| |
| if (exif.fLensInfo [2].IsValid ()) |
| { |
| |
| real64 minFS = exif.fLensInfo [2].As_real64 (); |
| real64 maxFS = exif.fLensInfo [3].As_real64 (); |
| |
| if (minFL == maxFL) |
| sprintf (s, "%.1f mm f/%.1f", minFL, minFS); |
| |
| else if (minFS == maxFS) |
| sprintf (s, "%.1f-%.1f mm f/%.1f", minFL, maxFL, minFS); |
| |
| else |
| sprintf (s, "%.1f-%.1f mm f/%.1f-%.1f", minFL, maxFL, minFS, maxFS); |
| |
| } |
| |
| else |
| { |
| |
| if (minFL == maxFL) |
| sprintf (s, "%.1f mm", minFL); |
| |
| else |
| sprintf (s, "%.1f-%.1f mm", minFL, maxFL); |
| |
| } |
| |
| exif.fLensName.Set (s); |
| |
| SetString (XMP_NS_AUX, |
| "Lens", |
| exif.fLensName); |
| |
| } |
| |
| } |
| |
| // Lens ID: |
| |
| SyncString (XMP_NS_AUX, |
| "LensID", |
| exif.fLensID, |
| readOnly); |
| |
| // Lens Make: |
| |
| SyncString (XMP_NS_EXIF, |
| "LensMake", |
| exif.fLensMake, |
| readOnly + removable); |
| |
| // Lens Serial Number: |
| |
| SyncString (XMP_NS_AUX, |
| "LensSerialNumber", |
| exif.fLensSerialNumber, |
| readOnly); |
| |
| // Image Number: |
| |
| Sync_uint32 (XMP_NS_AUX, |
| "ImageNumber", |
| exif.fImageNumber, |
| exif.fImageNumber == 0xFFFFFFFF, |
| readOnly); |
| |
| // User Comment: |
| |
| if (exif.fUserComment.NotEmpty ()) |
| { |
| |
| fSDK->SetAltLangDefault (XMP_NS_EXIF, |
| "UserComment", |
| exif.fUserComment); |
| |
| } |
| |
| else |
| { |
| |
| (void) fSDK->GetAltLangDefault (XMP_NS_EXIF, |
| "UserComment", |
| exif.fUserComment); |
| |
| } |
| |
| if (removeFromXMP) |
| { |
| Remove (XMP_NS_EXIF, "UserComment"); |
| } |
| |
| // Approximate focus distance: |
| |
| SyncApproximateFocusDistance (exif, |
| readOnly); |
| |
| // Flash Compensation: |
| |
| Sync_srational (XMP_NS_AUX, |
| "FlashCompensation", |
| exif.fFlashCompensation, |
| readOnly); |
| |
| // Owner Name: (allow XMP updates) |
| |
| SyncString (XMP_NS_AUX, |
| "OwnerName", |
| exif.fOwnerName, |
| preferXMP); |
| |
| // Firmware: |
| |
| SyncString (XMP_NS_AUX, |
| "Firmware", |
| exif.fFirmware, |
| readOnly); |
| |
| // Image Unique ID: |
| |
| { |
| |
| dng_string s = EncodeFingerprint (exif.fImageUniqueID); |
| |
| SyncString (XMP_NS_EXIF, |
| "ImageUniqueID", |
| s, |
| readOnly + removable); |
| |
| exif.fImageUniqueID = DecodeFingerprint (s); |
| |
| } |
| |
| // Allow EXIF GPS to be updated via updates from XMP. |
| |
| if (doingUpdateFromXMP) |
| { |
| |
| // Require that at least one basic GPS field exist in the |
| // XMP before overrriding the EXIF GPS fields. |
| |
| if (Exists (XMP_NS_EXIF, "GPSVersionID" ) || |
| Exists (XMP_NS_EXIF, "GPSLatitude" ) || |
| Exists (XMP_NS_EXIF, "GPSLongitude" ) || |
| Exists (XMP_NS_EXIF, "GPSAltitude" ) || |
| Exists (XMP_NS_EXIF, "GPSTimeStamp" ) || |
| Exists (XMP_NS_EXIF, "GPSProcessingMethod")) |
| { |
| |
| // Clear out the GPS info from the EXIF so it will |
| // replaced by the GPS info from the XMP. |
| |
| dng_exif blankExif; |
| |
| exif.CopyGPSFrom (blankExif); |
| |
| } |
| |
| } |
| |
| // GPS Version ID: |
| |
| { |
| |
| dng_string s = EncodeGPSVersion (exif.fGPSVersionID); |
| |
| if (SyncString (XMP_NS_EXIF, |
| "GPSVersionID", |
| s, |
| preferNonXMP + removable)) |
| { |
| |
| exif.fGPSVersionID = DecodeGPSVersion (s); |
| |
| } |
| |
| } |
| |
| // GPS Latitude: |
| |
| { |
| |
| dng_string s = EncodeGPSCoordinate (exif.fGPSLatitudeRef, |
| exif.fGPSLatitude); |
| |
| if (SyncString (XMP_NS_EXIF, |
| "GPSLatitude", |
| s, |
| preferNonXMP + removable)) |
| { |
| |
| DecodeGPSCoordinate (s, |
| exif.fGPSLatitudeRef, |
| exif.fGPSLatitude); |
| |
| } |
| |
| } |
| |
| // GPS Longitude: |
| |
| { |
| |
| dng_string s = EncodeGPSCoordinate (exif.fGPSLongitudeRef, |
| exif.fGPSLongitude); |
| |
| if (SyncString (XMP_NS_EXIF, |
| "GPSLongitude", |
| s, |
| preferNonXMP + removable)) |
| { |
| |
| DecodeGPSCoordinate (s, |
| exif.fGPSLongitudeRef, |
| exif.fGPSLongitude); |
| |
| } |
| |
| } |
| |
| // Handle simple case of incorrectly written GPS altitude where someone didn't understand the GPSAltitudeRef and assumed the GPSAltitude RATIONAL is signed. |
| // Only handle this case as we do not want to misinterpret e.g. a fixed point representation of very high GPS altitudes. |
| |
| uint32 &altitudeRef = exif.fGPSAltitudeRef; |
| dng_urational &altitude = exif.fGPSAltitude; |
| |
| if (altitude.IsValid () && |
| (altitudeRef == 0 || altitudeRef == 0xFFFFFFFF)) // If the file contains a "below sea level" altitudeRef, assume the writing software is working according to the spec. |
| { |
| |
| if ((altitude.n & (1U << 31)) && |
| altitude.d < 7) // As the denominator increases, large numerator values become possibly valid distances. Pick a limit on the conservative side (approx 33e6m) to prevent misinterpretation. |
| // Noting that the normal case for this mistake has a denominator of 1 |
| { |
| |
| altitude.n = ~altitude.n + 1; |
| altitudeRef = 1; |
| |
| } |
| |
| } |
| |
| // GPS Altitude Reference: |
| |
| Sync_uint32 (XMP_NS_EXIF, |
| "GPSAltitudeRef", |
| altitudeRef, |
| altitudeRef == 0xFFFFFFFF, |
| preferNonXMP + removable); |
| |
| // GPS Altitude: |
| |
| Sync_urational (XMP_NS_EXIF, |
| "GPSAltitude", |
| altitude, |
| preferNonXMP + removable); |
| |
| // GPS Date/Time: |
| |
| { |
| |
| dng_string s = EncodeGPSDateTime (exif.fGPSDateStamp, |
| exif.fGPSTimeStamp); |
| |
| if (SyncString (XMP_NS_EXIF, |
| "GPSTimeStamp", |
| s, |
| preferNonXMP + removable)) |
| { |
| |
| DecodeGPSDateTime (s, |
| exif.fGPSDateStamp, |
| exif.fGPSTimeStamp); |
| |
| } |
| |
| } |
| |
| // GPS Satellites: |
| |
| SyncString (XMP_NS_EXIF, |
| "GPSSatellites", |
| exif.fGPSSatellites, |
| preferNonXMP + removable); |
| |
| // GPS Status: |
| |
| SyncString (XMP_NS_EXIF, |
| "GPSStatus", |
| exif.fGPSStatus, |
| preferNonXMP + removable); |
| |
| // GPS Measure Mode: |
| |
| SyncString (XMP_NS_EXIF, |
| "GPSMeasureMode", |
| exif.fGPSMeasureMode, |
| preferNonXMP + removable); |
| |
| // GPS DOP: |
| |
| Sync_urational (XMP_NS_EXIF, |
| "GPSDOP", |
| exif.fGPSDOP, |
| preferNonXMP + removable); |
| |
| // GPS Speed Reference: |
| |
| SyncString (XMP_NS_EXIF, |
| "GPSSpeedRef", |
| exif.fGPSSpeedRef, |
| preferNonXMP + removable); |
| |
| // GPS Speed: |
| |
| Sync_urational (XMP_NS_EXIF, |
| "GPSSpeed", |
| exif.fGPSSpeed, |
| preferNonXMP + removable); |
| |
| // GPS Track Reference: |
| |
| SyncString (XMP_NS_EXIF, |
| "GPSTrackRef", |
| exif.fGPSTrackRef, |
| preferNonXMP + removable); |
| |
| // GPS Track: |
| |
| Sync_urational (XMP_NS_EXIF, |
| "GPSTrack", |
| exif.fGPSTrack, |
| preferNonXMP + removable); |
| |
| // GPS Image Direction Reference: |
| |
| SyncString (XMP_NS_EXIF, |
| "GPSImgDirectionRef", |
| exif.fGPSImgDirectionRef, |
| preferNonXMP + removable); |
| |
| // GPS Image Direction: |
| |
| Sync_urational (XMP_NS_EXIF, |
| "GPSImgDirection", |
| exif.fGPSImgDirection, |
| preferNonXMP + removable); |
| |
| // GPS Map Datum: |
| |
| SyncString (XMP_NS_EXIF, |
| "GPSMapDatum", |
| exif.fGPSMapDatum, |
| preferNonXMP + removable); |
| |
| // GPS Destination Latitude: |
| |
| { |
| |
| dng_string s = EncodeGPSCoordinate (exif.fGPSDestLatitudeRef, |
| exif.fGPSDestLatitude); |
| |
| if (SyncString (XMP_NS_EXIF, |
| "GPSDestLatitude", |
| s, |
| preferNonXMP + removable)) |
| { |
| |
| DecodeGPSCoordinate (s, |
| exif.fGPSDestLatitudeRef, |
| exif.fGPSDestLatitude); |
| |
| } |
| |
| } |
| |
| // GPS Destination Longitude: |
| |
| { |
| |
| dng_string s = EncodeGPSCoordinate (exif.fGPSDestLongitudeRef, |
| exif.fGPSDestLongitude); |
| |
| if (SyncString (XMP_NS_EXIF, |
| "GPSDestLongitude", |
| s, |
| preferNonXMP + removable)) |
| { |
| |
| DecodeGPSCoordinate (s, |
| exif.fGPSDestLongitudeRef, |
| exif.fGPSDestLongitude); |
| |
| } |
| |
| } |
| |
| // GPS Destination Bearing Reference: |
| |
| SyncString (XMP_NS_EXIF, |
| "GPSDestBearingRef", |
| exif.fGPSDestBearingRef, |
| preferNonXMP + removable); |
| |
| // GPS Destination Bearing: |
| |
| Sync_urational (XMP_NS_EXIF, |
| "GPSDestBearing", |
| exif.fGPSDestBearing, |
| preferNonXMP + removable); |
| |
| // GPS Destination Distance Reference: |
| |
| SyncString (XMP_NS_EXIF, |
| "GPSDestDistanceRef", |
| exif.fGPSDestDistanceRef, |
| preferNonXMP + removable); |
| |
| // GPS Destination Distance: |
| |
| Sync_urational (XMP_NS_EXIF, |
| "GPSDestDistance", |
| exif.fGPSDestDistance, |
| preferNonXMP + removable); |
| |
| // GPS Processing Method: |
| |
| SyncString (XMP_NS_EXIF, |
| "GPSProcessingMethod", |
| exif.fGPSProcessingMethod, |
| preferNonXMP + removable); |
| |
| // GPS Area Information: |
| |
| SyncString (XMP_NS_EXIF, |
| "GPSAreaInformation", |
| exif.fGPSAreaInformation, |
| preferNonXMP + removable); |
| |
| // GPS Differential: |
| |
| Sync_uint32 (XMP_NS_EXIF, |
| "GPSDifferential", |
| exif.fGPSDifferential, |
| exif.fGPSDifferential == 0xFFFFFFFF, |
| preferNonXMP + removable); |
| |
| // GPS Horizontal Positioning Error: |
| |
| Sync_urational (XMP_NS_EXIF, |
| "GPSHPositioningError", |
| exif.fGPSHPositioningError, |
| preferNonXMP + removable); |
| |
| // Sync date/times. |
| |
| UpdateExifDates (exif, removeFromXMP); |
| |
| // We are syncing EXIF and XMP, but we are not updating the |
| // NativeDigest tags. It is better to just delete them than leave |
| // the stale values around. |
| |
| Remove (XMP_NS_EXIF, "NativeDigest"); |
| Remove (XMP_NS_TIFF, "NativeDigest"); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_xmp::SyncApproximateFocusDistance (dng_exif &exif, |
| const uint32 readOnly) |
| { |
| |
| Sync_urational (XMP_NS_AUX, |
| "ApproximateFocusDistance", |
| exif.fApproxFocusDistance, |
| readOnly); |
| |
| } |
| |
| /******************************************************************************/ |
| |
| void dng_xmp::ValidateStringList (const char *ns, |
| const char *path) |
| { |
| |
| fSDK->ValidateStringList (ns, path); |
| |
| } |
| |
| /******************************************************************************/ |
| |
| void dng_xmp::ValidateMetadata () |
| { |
| |
| // The following values should be arrays, but are not always. So |
| // fix them up because Photoshop sometimes has problems parsing invalid |
| // tags. |
| |
| ValidateStringList (XMP_NS_DC, "creator"); |
| |
| ValidateStringList (XMP_NS_PHOTOSHOP, "Keywords"); |
| ValidateStringList (XMP_NS_PHOTOSHOP, "SupplementalCategories"); |
| |
| } |
| |
| /******************************************************************************/ |
| |
| bool dng_xmp::DateTimeIsDateOnly (const char *ns, |
| const char *path) |
| { |
| |
| dng_string s; |
| |
| if (GetString (ns, path, s)) |
| { |
| |
| uint32 len = s.Length (); |
| |
| if (len) |
| { |
| |
| for (uint32 j = 0; j < len; j++) |
| { |
| |
| if (s.Get () [j] == 'T') |
| { |
| |
| return false; |
| |
| } |
| |
| } |
| |
| return true; |
| |
| } |
| |
| } |
| |
| return false; |
| |
| } |
| |
| /******************************************************************************/ |
| |
| void dng_xmp::UpdateExifDates (dng_exif &exif, |
| bool removeFromXMP) |
| { |
| |
| // For the following three date/time fields, we always prefer XMP to |
| // the EXIF values. This is to allow the user to correct the date/times |
| // via changes in a sidecar XMP file, without modifying the original |
| // raw file. |
| |
| // Kludge: The Nikon D4 is writing date only date/times into XMP, so |
| // prefer the EXIF values if the XMP only contains a date. |
| |
| // Modification Date/Time: |
| // exif.fDateTime |
| // kXMP_NS_XMP:"ModifyDate" & kXMP_NS_TIFF:"DateTime" are aliased |
| |
| { |
| |
| dng_string s = exif.fDateTime.Encode_ISO_8601 (); |
| |
| bool dateOnly = DateTimeIsDateOnly (XMP_NS_TIFF, "DateTime"); |
| |
| SyncString (XMP_NS_TIFF, |
| "DateTime", |
| s, |
| dateOnly ? preferNonXMP : preferXMP); |
| |
| if (s.NotEmpty ()) |
| { |
| |
| exif.fDateTime.Decode_ISO_8601 (s.Get ()); |
| |
| // Round trip again in case we need to add a fake time zone. |
| |
| s = exif.fDateTime.Encode_ISO_8601 (); |
| |
| SetString (XMP_NS_TIFF, |
| "DateTime", |
| s); |
| |
| } |
| |
| } |
| |
| // Original Date/Time: |
| // exif.fDateTimeOriginal |
| // IPTC: DateCreated |
| // XMP_NS_EXIF:"DateTimeOriginal" & XMP_NS_PHOTOSHOP:"DateCreated" |
| // Adobe has decided to keep the two XMP fields separate. |
| |
| { |
| |
| dng_string s = exif.fDateTimeOriginal.Encode_ISO_8601 (); |
| |
| bool dateOnly = DateTimeIsDateOnly (XMP_NS_EXIF, "DateTimeOriginal"); |
| |
| SyncString (XMP_NS_EXIF, |
| "DateTimeOriginal", |
| s, |
| dateOnly ? preferNonXMP : preferXMP); |
| |
| if (s.NotEmpty ()) |
| { |
| |
| exif.fDateTimeOriginal.Decode_ISO_8601 (s.Get ()); |
| |
| // Round trip again in case we need to add a fake time zone. |
| |
| s = exif.fDateTimeOriginal.Encode_ISO_8601 (); |
| |
| SetString (XMP_NS_EXIF, |
| "DateTimeOriginal", |
| s); |
| |
| } |
| |
| // Sync the IPTC value to the EXIF value if only the EXIF |
| // value exists. |
| |
| if (s.NotEmpty () && !Exists (XMP_NS_PHOTOSHOP, "DateCreated")) |
| { |
| |
| SetString (XMP_NS_PHOTOSHOP, "DateCreated", s); |
| |
| } |
| |
| if (removeFromXMP) |
| { |
| |
| Remove (XMP_NS_EXIF, "DateTimeOriginal"); |
| |
| } |
| |
| } |
| |
| // Date Time Digitized: |
| // XMP_NS_EXIF:"DateTimeDigitized" & kXMP_NS_XMP:"CreateDate" are aliased |
| |
| { |
| |
| dng_string s = exif.fDateTimeDigitized.Encode_ISO_8601 (); |
| |
| bool dateOnly = DateTimeIsDateOnly (XMP_NS_EXIF, "DateTimeDigitized"); |
| |
| SyncString (XMP_NS_EXIF, |
| "DateTimeDigitized", |
| s, |
| dateOnly ? preferNonXMP : preferXMP); |
| |
| if (s.NotEmpty ()) |
| { |
| |
| exif.fDateTimeDigitized.Decode_ISO_8601 (s.Get ()); |
| |
| // Round trip again in case we need to add a fake time zone. |
| |
| s = exif.fDateTimeDigitized.Encode_ISO_8601 (); |
| |
| SetString (XMP_NS_EXIF, |
| "DateTimeDigitized", |
| s); |
| |
| } |
| |
| } |
| |
| } |
| |
| /******************************************************************************/ |
| |
| void dng_xmp::UpdateDateTime (const dng_date_time_info &dt) |
| { |
| |
| dng_string s = dt.Encode_ISO_8601 (); |
| |
| SetString (XMP_NS_TIFF, |
| "DateTime", |
| s); |
| |
| } |
| |
| /******************************************************************************/ |
| |
| void dng_xmp::UpdateMetadataDate (const dng_date_time_info &dt) |
| { |
| |
| dng_string s = dt.Encode_ISO_8601 (); |
| |
| SetString (XMP_NS_XAP, |
| "MetadataDate", |
| s); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| bool dng_xmp::HasOrientation () const |
| { |
| |
| uint32 x = 0; |
| |
| if (Get_uint32 (XMP_NS_TIFF, |
| "Orientation", |
| x)) |
| { |
| |
| return (x >= 1) && (x <= 8); |
| |
| } |
| |
| return false; |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| dng_orientation dng_xmp::GetOrientation () const |
| { |
| |
| dng_orientation result; |
| |
| uint32 x = 0; |
| |
| if (Get_uint32 (XMP_NS_TIFF, |
| "Orientation", |
| x)) |
| { |
| |
| if ((x >= 1) && (x <= 8)) |
| { |
| |
| result.SetTIFF (x); |
| |
| } |
| |
| } |
| |
| return result; |
| |
| } |
| |
| /******************************************************************************/ |
| |
| void dng_xmp::ClearOrientation () |
| { |
| |
| fSDK->Remove (XMP_NS_TIFF, "Orientation"); |
| |
| } |
| |
| /******************************************************************************/ |
| |
| void dng_xmp::SetOrientation (const dng_orientation &orientation) |
| { |
| |
| Set_uint32 (XMP_NS_TIFF, |
| "Orientation", |
| orientation.GetTIFF ()); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_xmp::SyncOrientation (dng_negative &negative, |
| bool xmpIsMaster) |
| { |
| |
| SyncOrientation (negative.Metadata (), xmpIsMaster); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_xmp::SyncOrientation (dng_metadata &metadata, |
| bool xmpIsMaster) |
| { |
| |
| // See if XMP contains the orientation. |
| |
| bool xmpHasOrientation = HasOrientation (); |
| |
| // See if XMP is the master value. |
| |
| if (xmpHasOrientation && (xmpIsMaster || !metadata.HasBaseOrientation ())) |
| { |
| |
| metadata.SetBaseOrientation (GetOrientation ()); |
| |
| } |
| |
| else |
| { |
| |
| SetOrientation (metadata.BaseOrientation ()); |
| |
| } |
| |
| } |
| |
| /******************************************************************************/ |
| |
| void dng_xmp::ClearImageInfo () |
| { |
| |
| Remove (XMP_NS_TIFF, "ImageWidth" ); |
| Remove (XMP_NS_TIFF, "ImageLength"); |
| |
| Remove (XMP_NS_EXIF, "PixelXDimension"); |
| Remove (XMP_NS_EXIF, "PixelYDimension"); |
| |
| Remove (XMP_NS_TIFF, "BitsPerSample"); |
| |
| Remove (XMP_NS_TIFF, "Compression"); |
| |
| Remove (XMP_NS_TIFF, "PhotometricInterpretation"); |
| |
| // "Orientation" is handled separately. |
| |
| Remove (XMP_NS_TIFF, "SamplesPerPixel"); |
| |
| Remove (XMP_NS_TIFF, "PlanarConfiguration"); |
| |
| Remove (XMP_NS_TIFF, "XResolution"); |
| Remove (XMP_NS_TIFF, "YResolution"); |
| |
| Remove (XMP_NS_TIFF, "ResolutionUnit"); |
| |
| Remove (XMP_NS_PHOTOSHOP, "ColorMode" ); |
| Remove (XMP_NS_PHOTOSHOP, "ICCProfile"); |
| |
| } |
| |
| /******************************************************************************/ |
| |
| void dng_xmp::SetImageSize (const dng_point &size) |
| { |
| |
| Set_uint32 (XMP_NS_TIFF, "ImageWidth" , size.h); |
| Set_uint32 (XMP_NS_TIFF, "ImageLength", size.v); |
| |
| // Mirror these values to the EXIF tags. |
| |
| Set_uint32 (XMP_NS_EXIF, "PixelXDimension" , size.h); |
| Set_uint32 (XMP_NS_EXIF, "PixelYDimension" , size.v); |
| |
| } |
| |
| /******************************************************************************/ |
| |
| void dng_xmp::SetSampleInfo (uint32 samplesPerPixel, |
| uint32 bitsPerSample) |
| { |
| |
| Set_uint32 (XMP_NS_TIFF, "SamplesPerPixel", samplesPerPixel); |
| |
| char s [32]; |
| |
| sprintf (s, "%u", (unsigned) bitsPerSample); |
| |
| dng_string ss; |
| |
| ss.Set (s); |
| |
| dng_string_list list; |
| |
| for (uint32 j = 0; j < samplesPerPixel; j++) |
| { |
| list.Append (ss); |
| } |
| |
| SetStringList (XMP_NS_TIFF, "BitsPerSample", list, false); |
| |
| } |
| |
| /******************************************************************************/ |
| |
| void dng_xmp::SetPhotometricInterpretation (uint32 pi) |
| { |
| |
| Set_uint32 (XMP_NS_TIFF, "PhotometricInterpretation", pi); |
| |
| } |
| |
| /******************************************************************************/ |
| |
| void dng_xmp::SetResolution (const dng_resolution &res) |
| { |
| |
| Set_urational (XMP_NS_TIFF, "XResolution", res.fXResolution); |
| Set_urational (XMP_NS_TIFF, "YResolution", res.fYResolution); |
| |
| Set_uint32 (XMP_NS_TIFF, "ResolutionUnit", res.fResolutionUnit); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_xmp::ComposeArrayItemPath (const char *ns, |
| const char *arrayName, |
| int32 itemNumber, |
| dng_string &s) const |
| { |
| |
| fSDK->ComposeArrayItemPath (ns, arrayName, itemNumber, s); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_xmp::ComposeStructFieldPath (const char *ns, |
| const char *structName, |
| const char *fieldNS, |
| const char *fieldName, |
| dng_string &s) const |
| { |
| |
| fSDK->ComposeStructFieldPath (ns, structName, fieldNS, fieldName, s); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| int32 dng_xmp::CountArrayItems (const char *ns, |
| const char *path) const |
| { |
| |
| return fSDK->CountArrayItems (ns, path); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_xmp::AppendArrayItem (const char *ns, |
| const char *arrayName, |
| const char *itemValue, |
| bool isBag, |
| bool propIsStruct) |
| { |
| |
| fSDK->AppendArrayItem (ns, |
| arrayName, |
| itemValue, |
| isBag, |
| propIsStruct); |
| } |
| |
| /*****************************************************************************/ |
| |
| #if qDNGXMPDocOps |
| |
| /*****************************************************************************/ |
| |
| void dng_xmp::DocOpsOpenXMP (const char *srcMIMI) |
| { |
| |
| fSDK->DocOpsOpenXMP (srcMIMI); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_xmp::DocOpsPrepareForSave (const char *srcMIMI, |
| const char *dstMIMI, |
| bool newPath) |
| { |
| |
| fSDK->DocOpsPrepareForSave (srcMIMI, |
| dstMIMI, |
| newPath); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| void dng_xmp::DocOpsUpdateMetadata (const char *srcMIMI) |
| { |
| |
| fSDK->DocOpsUpdateMetadata (srcMIMI); |
| |
| } |
| |
| /*****************************************************************************/ |
| |
| #endif |
| |
| #endif |
| /*****************************************************************************/ |