You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4418 lines
75 KiB
4418 lines
75 KiB
/*****************************************************************************/
|
|
// 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
|
|
/*****************************************************************************/
|