summaryrefslogtreecommitdiff
path: root/gpr/source/lib/dng_sdk/dng_xmp.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'gpr/source/lib/dng_sdk/dng_xmp.cpp')
-rw-r--r--gpr/source/lib/dng_sdk/dng_xmp.cpp4417
1 files changed, 4417 insertions, 0 deletions
diff --git a/gpr/source/lib/dng_sdk/dng_xmp.cpp b/gpr/source/lib/dng_sdk/dng_xmp.cpp
new file mode 100644
index 0000000..13ba6c3
--- /dev/null
+++ b/gpr/source/lib/dng_sdk/dng_xmp.cpp
@@ -0,0 +1,4417 @@
+/*****************************************************************************/
+// 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 $ */
+
+/*****************************************************************************/
+
+#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"
+
+#include "stdc_includes.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",
+ &degrees,
+ &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
+
+/*****************************************************************************/