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.
4417 lines
75 KiB
4417 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 |
|
/*****************************************************************************/
|
|
|