[niepce] xmp: implement merge missing properties.



commit ae7d84202cd8a1c81a520fe41111d1b511052699
Author: Hubert Figuière <hub figuiere net>
Date:   Thu Oct 12 22:05:12 2017 -0400

    xmp: implement merge missing properties.
    
    - remove unused C API
    - some other cleanup
    - bump the exempi version

 Cargo.toml                   |    2 +-
 src/engine/db/libmetadata.rs |    2 +-
 src/fwk/base/mod.rs          |    3 +-
 src/fwk/mod.rs               |    2 +-
 src/fwk/utils/exempi.cpp     |   12 ---
 src/fwk/utils/exempi.hpp     |    7 +-
 src/fwk/utils/exempi.rs      |  197 ++++++++++++++++++++++++++++++++----------
 src/fwk/utils/test2.xmp      |  176 +++++++++++++++++++++++++++++++++++++
 8 files changed, 335 insertions(+), 66 deletions(-)
---
diff --git a/Cargo.toml b/Cargo.toml
index cedb2b0..0e47c19 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -6,7 +6,7 @@ build = "build.rs"
 
 [dependencies]
 chrono = "0.4.0"
-exempi = { version = "2.4.4", git = "https://github.com/hfiguiere/exempi-rs.git"; }
+exempi = { version = "2.5.0", git = "https://github.com/hfiguiere/exempi-rs.git"; }
 glib-sys = "0.4.0"
 gio-sys = "0.4.0"
 gio = "0.2.0"
diff --git a/src/engine/db/libmetadata.rs b/src/engine/db/libmetadata.rs
index a65a462..f6ab6d4 100644
--- a/src/engine/db/libmetadata.rs
+++ b/src/engine/db/libmetadata.rs
@@ -180,7 +180,7 @@ impl LibMetadata {
                     let mut schema = exempi::XmpString::new();
                     let mut name = exempi::XmpString::new();
                     let mut value = exempi::XmpString::new();
-                    let mut flags = exempi::IterFlags::empty();
+                    let mut flags = exempi::PropFlags::empty();
                     while iter.next(&mut schema, &mut name, &mut value, &mut flags) {
                         keywords.push(String::from(value.to_str()));
                     }
diff --git a/src/fwk/base/mod.rs b/src/fwk/base/mod.rs
index f845971..558ad4c 100644
--- a/src/fwk/base/mod.rs
+++ b/src/fwk/base/mod.rs
@@ -19,9 +19,10 @@
 
 use std::collections::BTreeSet;
 
-pub mod date;
 #[macro_use]
 pub mod debug;
+
+pub mod date;
 pub mod fractions;
 pub mod propertybag;
 pub mod propertyvalue;
diff --git a/src/fwk/mod.rs b/src/fwk/mod.rs
index 1d8f3ed..1321fd5 100644
--- a/src/fwk/mod.rs
+++ b/src/fwk/mod.rs
@@ -17,9 +17,9 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-pub mod utils;
 #[macro_use]
 pub mod base;
+pub mod utils;
 pub mod toolkit;
 
 pub use self::utils::exempi::{
diff --git a/src/fwk/utils/exempi.cpp b/src/fwk/utils/exempi.cpp
index a84ee4a..5ec23c7 100644
--- a/src/fwk/utils/exempi.cpp
+++ b/src/fwk/utils/exempi.cpp
@@ -17,19 +17,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#include <string.h>
-#include <time.h>
-
-#include <giomm/file.h>
-#include <glib.h>
-
-#include <exempi/xmp.h>
-#include <exempi/xmpconsts.h>
-
 #include "exempi.hpp"
-#include "fwk/base/date.hpp"
-#include "fwk/base/debug.hpp"
-#include "pathutils.hpp"
 
 namespace xmp {
 
diff --git a/src/fwk/utils/exempi.hpp b/src/fwk/utils/exempi.hpp
index ac88c26..93dcdae 100644
--- a/src/fwk/utils/exempi.hpp
+++ b/src/fwk/utils/exempi.hpp
@@ -1,6 +1,3 @@
-#ifndef __UTILS_EXEMPI_H__
-#define __UTILS_EXEMPI_H__
-
 /*
  * niepce - utils/exempi.h
  *
@@ -20,7 +17,9 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#pragma once
 
+#include <memory>
 #include <vector>
 #include <string>
 
@@ -109,5 +108,3 @@ ExempiManagerPtr exempi_manager_new();
   fill-column:99
   End:
 */
-
-#endif
diff --git a/src/fwk/utils/exempi.rs b/src/fwk/utils/exempi.rs
index 04be75a..02b1260 100644
--- a/src/fwk/utils/exempi.rs
+++ b/src/fwk/utils/exempi.rs
@@ -17,19 +17,14 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-use libc::c_char;
-use std::ffi::CString;
 use std::fs::File;
 use std::io::prelude::*;
 use std::path::Path;
-use std::ptr;
 
 use chrono::{DateTime, Utc};
 use exempi;
 use exempi::Xmp;
 
-type Date = DateTime<Utc>;
-
 static NIEPCE_XMP_NAMESPACE: &'static str = "http://xmlns.figuiere.net/ns/niepce/1.0";;
 static NIEPCE_XMP_NS_PREFIX: &'static str = "niepce";
 static UFRAW_INTEROP_NAMESPACE: &'static str = "http://xmlns.figuiere.net/ns/ufraw_interop/1.0";;
@@ -40,6 +35,7 @@ pub static NS_TIFF: &'static str = "http://ns.adobe.com/tiff/1.0/";;
 pub static NS_XAP: &'static str = "http://ns.adobe.com/xap/1.0/";;
 pub static NS_EXIF: &'static str = "http://ns.adobe.com/exif/1.0/";;
 pub static NS_DC: &'static str = "http://purl.org/dc/elements/1.1/";;
+pub static NS_AUX: &'static str = "http://ns.adobe.com/exif/1.0/aux/";;
 
 pub struct NsDef {
     ns: String,
@@ -89,10 +85,11 @@ impl XmpMeta {
     }
 
     pub fn new_from_file(file: &str, sidecar_only: bool) -> Option<XmpMeta> {
+        let mut meta: Option<XmpMeta> = None;
         if !sidecar_only {
             if let Some(xmpfile) = exempi::XmpFile::open_new(file, exempi::OPEN_READ) {
                 if let Some(xmp) = xmpfile.get_new_xmp() {
-                    return Some(XmpMeta {
+                    meta = Some(XmpMeta {
                         xmp: xmp,
                         keywords: Vec::new(),
                         keywords_fetched: false
@@ -100,6 +97,8 @@ impl XmpMeta {
                 }
             }
         }
+
+        let mut sidecar_meta: Option<XmpMeta> = None;
         let filepath = Path::new(file);
         let sidecar = filepath.with_extension("xmp");
         let sidecaropen = File::open(sidecar);
@@ -109,7 +108,7 @@ impl XmpMeta {
             if result.ok().is_some() {
                 let mut xmp = exempi::Xmp::new();
                 if xmp.parse(sidecarcontent.into_bytes().as_slice()) {
-                    return Some(XmpMeta {
+                    sidecar_meta = Some(XmpMeta {
                         xmp: xmp,
                         keywords: Vec::new(),
                         keywords_fetched: false
@@ -117,9 +116,72 @@ impl XmpMeta {
                 }
             }
         }
+        if meta.is_none() || sidecar_meta.is_none() {
+            if meta.is_some() {
+                return meta;
+            }
+            return sidecar_meta;
+
+        } else {
+            let mut final_meta = sidecar_meta.unwrap();
+            if !meta.as_ref().unwrap().merge_missing_into_xmp(&mut final_meta) {
+                err_out!("xmp merge failed");
+            } else {
+                return Some(final_meta);
+            }
+        }
+        // XXX maybe we should be more permissible. Returning the
+        // built-in meta if merge failed might not be the best option.
         None
     }
 
+    ///
+    /// Merge missing properties from self (source) to destination
+    /// struct and array are considerd missing as a whole, not their content.
+    ///
+    pub fn merge_missing_into_xmp(&self, dest: &mut XmpMeta) -> bool {
+        // Merge XMP self into the dest that has more recent.
+        let source_date = self.get_date_property(NS_XAP, "MetadataDate");
+        let dest_date = dest.get_date_property(NS_XAP, "MetadataDate");
+
+        if source_date.is_none() || dest_date.is_none() {
+            dbg_out!("missing metadata date {} {}", source_date.is_some(),
+                     dest_date.is_some());
+            return false;
+        }
+        if source_date > dest_date {
+            dbg_out!("file meta is more recent than sidecar");
+            return false;
+        }
+
+        // in properties in source not in destination gets copied over.
+        let mut iter = exempi::XmpIterator::new(&self.xmp, "", "", exempi::ITER_PROPERTIES);
+        {
+            use exempi::XmpString;
+            let mut schema = XmpString::new();
+            let mut name = XmpString::new();
+            let mut value = XmpString::new();
+            let mut option = exempi::PROP_NONE;
+            while iter.next(&mut schema, &mut name, &mut value, &mut option) {
+                if name.to_str().is_empty() {
+                    continue;
+                }
+                if option.contains(exempi::PROP_VALUE_IS_ARRAY)
+                      || option.contains(exempi::PROP_VALUE_IS_STRUCT) {
+                    iter.skip(exempi::ITER_SKIP_SUBTREE);
+                    continue;
+                }
+
+                if !dest.xmp.has_property(schema.to_str(), name.to_str()) {
+                    dest.xmp.set_property(schema.to_str(), name.to_str(),
+                                          value.to_str(), exempi::PROP_NONE);
+                }
+            }
+        }
+
+        true
+    }
+
     pub fn serialize_inline(&self) -> String {
         if let Some(xmpstr) = self.xmp.serialize_and_format(
             exempi::SERIAL_OMITPACKETWRAPPER | exempi::SERIAL_OMITALLFORMATTING,
@@ -185,6 +247,19 @@ impl XmpMeta {
         None
     }
 
+    /// Get the date property and return a DateTime<Utc> parsed
+    /// from the string value.
+    pub fn get_date_property(&self, ns: &str, property: &str) -> Option<DateTime<Utc>> {
+        let mut flags: exempi::PropFlags = exempi::PropFlags::empty();
+        if let Some(xmpstring) = self.xmp.get_property(ns, property, &mut flags) {
+            if let Ok(date) = DateTime::parse_from_rfc3339(xmpstring.to_str()) {
+                let utc_date = date.with_timezone(&Utc);
+                return Some(utc_date);
+            }
+        }
+        None
+    }
+
     pub fn keywords(&mut self) -> &Vec<String> {
         if !self.keywords_fetched {
             use exempi::XmpString;
@@ -194,7 +269,7 @@ impl XmpMeta {
             let mut schema = XmpString::new();
             let mut name = XmpString::new();
             let mut value = XmpString::new();
-            let mut option = exempi::ITER_NONE;
+            let mut option = exempi::PROP_NONE;
             while iter.next(&mut schema, &mut name, &mut value, &mut option) {
                 self.keywords.push(String::from(value.to_str()));
             }
@@ -284,46 +359,15 @@ pub extern "C" fn fwk_exempi_manager_delete(em: *mut ExempiManager) {
     unsafe { Box::from_raw(em); }
 }
 
-#[no_mangle]
-pub extern "C" fn fwk_xmp_meta_get_orientation(xmp: &XmpMeta) -> i32 {
-    if let Some(o) = xmp.orientation() {
-        return o;
-    }
-    0
-}
-
-#[no_mangle]
-pub extern "C" fn fwk_xmp_meta_get_rating(xmp: &XmpMeta) -> i32 {
-    if let Some(r) = xmp.rating() {
-        return r;
-    }
-    0
-}
-
-#[no_mangle]
-pub extern "C" fn fwk_xmp_meta_get_label(xmp: &XmpMeta) -> *mut c_char {
-    if let Some(s) = xmp.label() {
-        return CString::new(s.as_bytes()).unwrap().into_raw()
-    }
-    ptr::null_mut()
-}
-
-#[no_mangle]
-pub extern "C" fn fwk_xmp_meta_get_creation_date(xmp: &XmpMeta) -> *mut Date {
-    if let Some(d) = xmp.creation_date() {
-        return Box::into_raw(Box::new(d));
-    }
-    ptr::null_mut()
-}
-
 #[cfg(test)]
 mod tests {
-    #[test]
-    fn xmp_meta_works() {
+    use std::path::PathBuf;
+    use super::ExempiManager;
+    use super::XmpMeta;
+    use exempi;
+
+    fn get_xmp_sample_path() -> PathBuf {
         use std::env;
-        use std::path::PathBuf;
-        use super::ExempiManager;
-        use super::XmpMeta;
 
         let mut dir: PathBuf;
         if let Ok(pdir) = env::var("CARGO_MANIFEST_DIR") {
@@ -334,6 +378,13 @@ mod tests {
         } else {
             dir = PathBuf::from(".");
         }
+        dir
+    }
+
+    #[test]
+    fn xmp_meta_works() {
+
+        let mut dir = get_xmp_sample_path();
         dir.push("test.xmp");
         let _xmp_manager = ExempiManager::new(None);
 
@@ -356,6 +407,62 @@ mod tests {
         }
     }
 
+    fn test_property_value(meta: &XmpMeta, ns: &str,
+                           property: &str, expected_value: &str) {
+        let mut flags: exempi::PropFlags = exempi::PropFlags::empty();
+        let value = meta.xmp.get_property(ns, property, &mut flags);
+        assert!(value.is_some());
+        assert_eq!(value.unwrap().to_str(), expected_value);
+    }
+
+    fn test_property_array_value(meta: &XmpMeta, ns: &str,
+                                 property: &str, idx: i32, expected_value: &str) {
+        let mut flags: exempi::PropFlags = exempi::PropFlags::empty();
+        let value = meta.xmp.get_array_item(ns, property, idx, &mut flags);
+        assert!(value.is_some());
+        assert_eq!(value.unwrap().to_str(), expected_value);
+    }
+
+    #[test]
+    fn test_merge_missing_into_xmp() {
+        let dir = get_xmp_sample_path();
+
+        let mut source = dir.clone();
+        source.push("test.xmp");
+
+        let mut dest = dir.clone();
+        dest.push("test2.xmp");
+        let _xmp_manager = ExempiManager::new(None);
+
+        if let Some(xmpfile) = source.to_str() {
+            let meta = XmpMeta::new_from_file(xmpfile, true);
+            assert!(meta.is_some());
+            let meta = meta.unwrap();
+
+            if let Some(xmpfile) = dest.to_str() {
+                let dstmeta = XmpMeta::new_from_file(xmpfile, true);
+                assert!(dstmeta.is_some());
+                let mut dstmeta = dstmeta.unwrap();
+
+                let result = meta.merge_missing_into_xmp(&mut dstmeta);
+                assert!(result);
+                // properties that were missing
+                test_property_value(&dstmeta, super::NS_TIFF, "Model", "Canon EOS 20D");
+                test_property_value(&dstmeta, super::NS_AUX, "Lens", "24.0-85.0 mm");
+
+                // Array property that contain less in destination
+                // Shouldn't have changed.
+                test_property_array_value(&dstmeta, super::NS_DC, "subject", 1, "night");
+                test_property_array_value(&dstmeta, super::NS_DC, "subject", 2, "ontario");
+                test_property_array_value(&dstmeta, super::NS_DC, "subject", 3, "ottawa");
+                test_property_array_value(&dstmeta, super::NS_DC, "subject", 4, "parliament of canada");
+                assert!(!dstmeta.xmp.has_property(super::NS_DC, "dc:subject[5]"));
+            }
+        } else {
+            assert!(false);
+        }
+    }
+
     #[test]
     fn gps_coord_from_works() {
         use super::gps_coord_from_xmp;
diff --git a/src/fwk/utils/test2.xmp b/src/fwk/utils/test2.xmp
new file mode 100644
index 0000000..e807b4a
--- /dev/null
+++ b/src/fwk/utils/test2.xmp
@@ -0,0 +1,176 @@
+<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Public XMP Toolkit Core 3.5">
+ <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#";>
+  <rdf:Description rdf:about=""
+    xmlns:tiff="http://ns.adobe.com/tiff/1.0/";>
+   <tiff:Make>Canon</tiff:Make>
+   <tiff:Orientation>1</tiff:Orientation>
+   <tiff:ImageWidth>3504</tiff:ImageWidth>
+  </rdf:Description>
+  <rdf:Description rdf:about=""
+    xmlns:exif="http://ns.adobe.com/exif/1.0/";>
+   <exif:ExifVersion>0221</exif:ExifVersion>
+   <exif:ExposureTime>5/1</exif:ExposureTime>
+   <exif:ShutterSpeedValue>-2321928/1000000</exif:ShutterSpeedValue>
+   <exif:FNumber>8/1</exif:FNumber>
+   <exif:ApertureValue>6/1</exif:ApertureValue>
+   <exif:ExposureProgram>1</exif:ExposureProgram>
+   <exif:ISOSpeedRatings>
+    <rdf:Seq>
+     <rdf:li>100</rdf:li>
+    </rdf:Seq>
+   </exif:ISOSpeedRatings>
+   <exif:DateTimeOriginal>2006-12-07T23:37:30-05:00</exif:DateTimeOriginal>
+   <exif:DateTimeDigitized>2006-12-07T23:37:30-05:00</exif:DateTimeDigitized>
+   <exif:ExposureBiasValue>0/2</exif:ExposureBiasValue>
+   <exif:MeteringMode>5</exif:MeteringMode>
+   <exif:Flash rdf:parseType="Resource">
+    <exif:Fired>False</exif:Fired>
+    <exif:Return>0</exif:Return>
+    <exif:Mode>2</exif:Mode>
+    <exif:Function>False</exif:Function>
+    <exif:RedEyeMode>False</exif:RedEyeMode>
+   </exif:Flash>
+   <exif:FocalLength>24/1</exif:FocalLength>
+   <exif:CustomRendered>0</exif:CustomRendered>
+   <exif:ExposureMode>1</exif:ExposureMode>
+   <exif:WhiteBalance>0</exif:WhiteBalance>
+   <exif:SceneCaptureType>0</exif:SceneCaptureType>
+   <exif:FocalPlaneXResolution>3504000/885</exif:FocalPlaneXResolution>
+   <exif:FocalPlaneYResolution>2336000/590</exif:FocalPlaneYResolution>
+   <exif:FocalPlaneResolutionUnit>2</exif:FocalPlaneResolutionUnit>
+  </rdf:Description>
+  <rdf:Description rdf:about=""
+    xmlns:xap="http://ns.adobe.com/xap/1.0/";>
+   <xap:ModifyDate>2006-12-07T23:37:30-05:00</xap:ModifyDate>
+   <xap:MetadataDate>2007-10-12T18:46:36-04:00</xap:MetadataDate>
+  </rdf:Description>
+  <rdf:Description rdf:about=""
+    xmlns:aux="http://ns.adobe.com/exif/1.0/aux/";>
+   <aux:SerialNumber>420103070</aux:SerialNumber>
+   <aux:LensInfo>24/1 85/1 0/0 0/0</aux:LensInfo>
+   <aux:ImageNumber>185</aux:ImageNumber>
+   <aux:OwnerName>unknown</aux:OwnerName>
+   <aux:Firmware>1.1.0</aux:Firmware>
+  </rdf:Description>
+  <rdf:Description rdf:about=""
+    xmlns:crs="http://ns.adobe.com/camera-raw-settings/1.0/";>
+   <crs:AlreadyApplied>False</crs:AlreadyApplied>
+  </rdf:Description>
+  <rdf:Description rdf:about=""
+    xmlns:dc="http://purl.org/dc/elements/1.1/";>
+   <dc:creator>
+    <rdf:Seq>
+     <rdf:li>unknown</rdf:li>
+    </rdf:Seq>
+   </dc:creator>
+   <dc:subject>
+    <rdf:Bag>
+     <rdf:li>night</rdf:li>
+     <rdf:li>ontario</rdf:li>
+     <rdf:li>ottawa</rdf:li>
+     <rdf:li>parliament of canada</rdf:li>
+    </rdf:Bag>
+   </dc:subject>
+  </rdf:Description>
+  <rdf:Description rdf:about=""
+    xmlns:crss="http://ns.adobe.com/camera-raw-saved-settings/1.0/";
+    xmlns:crs="http://ns.adobe.com/camera-raw-settings/1.0/";>
+   <crss:SavedSettings>
+    <rdf:Bag>
+     <rdf:li rdf:parseType="Resource">
+      <crss:Name>16/12/06 9:08:59 AM: Import</crss:Name>
+      <crss:Type>Snapshot</crss:Type>
+      <crss:Parameters rdf:parseType="Resource">
+       <crs:WhiteBalance>As Shot</crs:WhiteBalance>
+       <crs:Exposure>0.00</crs:Exposure>
+       <crs:Shadows>5</crs:Shadows>
+       <crs:Brightness>+50</crs:Brightness>
+       <crs:Contrast>+25</crs:Contrast>
+       <crs:Saturation>0</crs:Saturation>
+       <crs:Sharpness>25</crs:Sharpness>
+       <crs:LuminanceSmoothing>0</crs:LuminanceSmoothing>
+       <crs:ColorNoiseReduction>25</crs:ColorNoiseReduction>
+       <crs:ChromaticAberrationR>0</crs:ChromaticAberrationR>
+       <crs:ChromaticAberrationB>0</crs:ChromaticAberrationB>
+       <crs:VignetteAmount>0</crs:VignetteAmount>
+       <crs:ShadowTint>0</crs:ShadowTint>
+       <crs:RedHue>0</crs:RedHue>
+       <crs:RedSaturation>0</crs:RedSaturation>
+       <crs:GreenHue>0</crs:GreenHue>
+       <crs:GreenSaturation>0</crs:GreenSaturation>
+       <crs:BlueHue>0</crs:BlueHue>
+       <crs:BlueSaturation>0</crs:BlueSaturation>
+       <crs:FillLight>0</crs:FillLight>
+       <crs:Vibrance>0</crs:Vibrance>
+       <crs:HighlightRecovery>0</crs:HighlightRecovery>
+       <crs:Clarity>0</crs:Clarity>
+       <crs:Defringe>0</crs:Defringe>
+       <crs:HueAdjustmentRed>0</crs:HueAdjustmentRed>
+       <crs:HueAdjustmentOrange>0</crs:HueAdjustmentOrange>
+       <crs:HueAdjustmentYellow>0</crs:HueAdjustmentYellow>
+       <crs:HueAdjustmentGreen>0</crs:HueAdjustmentGreen>
+       <crs:HueAdjustmentAqua>0</crs:HueAdjustmentAqua>
+       <crs:HueAdjustmentBlue>0</crs:HueAdjustmentBlue>
+       <crs:HueAdjustmentPurple>0</crs:HueAdjustmentPurple>
+       <crs:HueAdjustmentMagenta>0</crs:HueAdjustmentMagenta>
+       <crs:SaturationAdjustmentRed>0</crs:SaturationAdjustmentRed>
+       <crs:SaturationAdjustmentOrange>0</crs:SaturationAdjustmentOrange>
+       <crs:SaturationAdjustmentYellow>0</crs:SaturationAdjustmentYellow>
+       <crs:SaturationAdjustmentGreen>0</crs:SaturationAdjustmentGreen>
+       <crs:SaturationAdjustmentAqua>0</crs:SaturationAdjustmentAqua>
+       <crs:SaturationAdjustmentBlue>0</crs:SaturationAdjustmentBlue>
+       <crs:SaturationAdjustmentPurple>0</crs:SaturationAdjustmentPurple>
+       <crs:SaturationAdjustmentMagenta>0</crs:SaturationAdjustmentMagenta>
+       <crs:LuminanceAdjustmentRed>0</crs:LuminanceAdjustmentRed>
+       <crs:LuminanceAdjustmentOrange>0</crs:LuminanceAdjustmentOrange>
+       <crs:LuminanceAdjustmentYellow>0</crs:LuminanceAdjustmentYellow>
+       <crs:LuminanceAdjustmentGreen>0</crs:LuminanceAdjustmentGreen>
+       <crs:LuminanceAdjustmentAqua>0</crs:LuminanceAdjustmentAqua>
+       <crs:LuminanceAdjustmentBlue>0</crs:LuminanceAdjustmentBlue>
+       <crs:LuminanceAdjustmentPurple>0</crs:LuminanceAdjustmentPurple>
+       <crs:LuminanceAdjustmentMagenta>0</crs:LuminanceAdjustmentMagenta>
+       <crs:SplitToningShadowHue>0</crs:SplitToningShadowHue>
+       <crs:SplitToningShadowSaturation>0</crs:SplitToningShadowSaturation>
+       <crs:SplitToningHighlightHue>0</crs:SplitToningHighlightHue>
+       <crs:SplitToningHighlightSaturation>0</crs:SplitToningHighlightSaturation>
+       <crs:SplitToningBalance>0</crs:SplitToningBalance>
+       <crs:ParametricShadows>0</crs:ParametricShadows>
+       <crs:ParametricDarks>0</crs:ParametricDarks>
+       <crs:ParametricLights>0</crs:ParametricLights>
+       <crs:ParametricHighlights>0</crs:ParametricHighlights>
+       <crs:ParametricShadowSplit>25</crs:ParametricShadowSplit>
+       <crs:ParametricMidtoneSplit>50</crs:ParametricMidtoneSplit>
+       <crs:ParametricHighlightSplit>75</crs:ParametricHighlightSplit>
+       <crs:SharpenRadius>+1.0</crs:SharpenRadius>
+       <crs:SharpenDetail>25</crs:SharpenDetail>
+       <crs:SharpenEdgeMasking>0</crs:SharpenEdgeMasking>
+       <crs:ConvertToGrayscale>False</crs:ConvertToGrayscale>
+       <crs:ToneCurveName>Medium Contrast</crs:ToneCurveName>
+       <crs:ToneCurve>
+        <rdf:Seq>
+         <rdf:li>0, 0</rdf:li>
+         <rdf:li>32, 22</rdf:li>
+         <rdf:li>64, 56</rdf:li>
+         <rdf:li>128, 128</rdf:li>
+         <rdf:li>192, 196</rdf:li>
+         <rdf:li>255, 255</rdf:li>
+        </rdf:Seq>
+       </crs:ToneCurve>
+      </crss:Parameters>
+     </rdf:li>
+    </rdf:Bag>
+   </crss:SavedSettings>
+  </rdf:Description>
+  <rdf:Description rdf:about=""
+    xmlns:lr="http://ns.adobe.com/lightroom/1.0/";>
+   <lr:hierarchicalSubject>
+    <rdf:Bag>
+     <rdf:li>choir</rdf:li>
+     <rdf:li>night</rdf:li>
+     <rdf:li>ontario|ottawa</rdf:li>
+     <rdf:li>ontario|ottawa|parliament of canada</rdf:li>
+    </rdf:Bag>
+   </lr:hierarchicalSubject>
+  </rdf:Description>
+ </rdf:RDF>
+</x:xmpmeta>


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]