use rusqlite::Row;
use crate::catalog::CatalogVersion;
use crate::fromdb::FromDb;
use crate::lrobject::{LrId, LrObject};
use crate::lron;
use crate::{AspectRatio, Point, Rect};
#[derive(Default, Debug)]
pub struct Properties {
loupe_focus: Option<Point>,
crop_aspect_ratio: Option<AspectRatio>,
default_crop: Option<Rect>,
}
impl Properties {
fn loupe_focus(value: &[lron::Value]) -> Option<Point> {
let mut x: Option<f64> = None;
let mut y: Option<f64> = None;
let mut is_point = false;
value.iter().for_each(|o| {
if let Some(p) = o.as_pair() {
match p.key.as_str() {
"_ag_className" => is_point = p.value.as_str() == Some("AgPoint"),
"y" => y = p.value.as_float(),
"x" => x = p.value.as_float(),
_ => {}
}
}
});
if is_point
&& let Some(x) = x
&& let Some(y) = y
{
Some(Point { x, y })
} else {
None
}
}
fn properties_from(value: &[lron::Value]) -> Self {
let mut props = Properties::default();
let mut crop_aspect_h: Option<i64> = None;
let mut crop_aspect_w: Option<i64> = None;
let mut top: Option<f64> = None;
let mut bottom: Option<f64> = None;
let mut left: Option<f64> = None;
let mut right: Option<f64> = None;
value.iter().for_each(|o| {
if let Some(p) = o.as_pair() {
match p.key.as_str() {
"loupeFocusPoint" => {
if let Some(v) = p.value.as_dict() {
props.loupe_focus = Self::loupe_focus(v);
}
}
"cropAspectH" => {
crop_aspect_h = p.value.as_int();
}
"cropAspectW" => {
crop_aspect_w = p.value.as_int();
}
"defaultCropBottom" => {
bottom = p.value.as_float();
}
"defaultCropLeft" => {
left = p.value.as_float();
}
"defaultCropRight" => {
right = p.value.as_float();
}
"defaultCropTop" => {
top = p.value.as_float();
}
_ => {}
}
}
});
if let Some(height) = crop_aspect_h
&& let Some(width) = crop_aspect_w
{
props.crop_aspect_ratio = Some(AspectRatio { width, height });
}
if let Some(top) = top
&& let Some(bottom) = bottom
&& let Some(left) = left
&& let Some(right) = right
{
props.default_crop = Some(Rect {
top,
bottom,
left,
right,
});
}
props
}
}
impl From<lron::Value> for Properties {
fn from(object: lron::Value) -> Self {
if let Some(s) = object.as_pair()
&& &s.key == "properties"
&& let Some(dict) = s.value.as_dict()
{
return Self::properties_from(dict);
}
Properties::default()
}
}
pub struct Image {
id: LrId,
uuid: String,
pub master_image: Option<LrId>,
pub copy_name: Option<String>,
pub rating: Option<i64>,
pub root_file: LrId,
pub file_format: String,
pub pick: i64,
pub orientation: Option<String>,
pub capture_time: String,
pub xmp: String,
pub xmp_embedded: bool,
pub xmp_external_dirty: bool,
pub properties: Option<Properties>,
pub keywords: Vec<LrId>,
}
impl Image {
pub fn exif_orientation(&self) -> i32 {
self.orientation.as_ref().map_or(0, |s| match s.as_ref() {
"AB" => 1,
"DA" => 8,
"BC" => 6,
"CD" => 3,
_ => -1,
})
}
}
impl LrObject for Image {
fn id(&self) -> LrId {
self.id
}
fn uuid(&self) -> &str {
&self.uuid
}
}
impl FromDb for Image {
fn read_from(_version: CatalogVersion, row: &Row) -> crate::Result<Self> {
let properties = row
.get::<usize, String>(13)
.ok()
.and_then(|v| lron::Value::from_string(&v).ok())
.map(Properties::from);
Ok(Image {
id: row.get(0)?,
uuid: row.get(1)?,
master_image: row.get(2).ok(),
rating: row.get(3).ok(),
root_file: row.get(4)?,
file_format: row.get(5)?,
pick: row.get(6)?,
orientation: row.get(7).ok(),
capture_time: row.get(8)?,
copy_name: row.get(9).ok(),
xmp: row.get(10)?,
xmp_embedded: row.get(11)?,
xmp_external_dirty: row.get(12)?,
properties,
keywords: vec![],
})
}
fn read_db_tables(_version: CatalogVersion) -> &'static str {
"Adobe_images as img,Adobe_AdditionalMetadata as meta,Adobe_imageProperties as props"
}
fn read_db_columns(_version: CatalogVersion) -> &'static str {
"img.id_local,img.id_global,img.masterImage,img.rating,img.rootFile,img.fileFormat,cast(img.pick as integer) as pick,img.orientation,img.captureTime,img.copyName,meta.xmp,meta.embeddedXmp,meta.externalXmpIsDirty,props.propertiesString"
}
fn read_join_where(_version: CatalogVersion) -> &'static str {
"meta.image = img.id_local and props.image = img.id_local"
}
}
#[cfg(test)]
mod tests {
use super::Image;
use super::Properties;
use crate::lron;
#[test]
fn test_exif_orientation() {
let mut image = Image {
id: 1,
uuid: String::new(),
master_image: None,
rating: None,
root_file: 2,
file_format: String::from("RAW"),
pick: 0,
orientation: None,
capture_time: String::new(),
copy_name: None,
xmp: String::new(),
xmp_embedded: false,
xmp_external_dirty: false,
properties: None,
keywords: vec![],
};
assert_eq!(image.exif_orientation(), 0);
image.orientation = Some(String::from("ZZ"));
assert_eq!(image.exif_orientation(), -1);
image.orientation = Some(String::from("AB"));
assert_eq!(image.exif_orientation(), 1);
image.orientation = Some(String::from("DA"));
assert_eq!(image.exif_orientation(), 8);
image.orientation = Some(String::from("BC"));
assert_eq!(image.exif_orientation(), 6);
image.orientation = Some(String::from("CD"));
assert_eq!(image.exif_orientation(), 3);
}
#[test]
fn test_properties_loading() {
const LRON1: &str = "properties = { \
cropAspectH = 9, \
cropAspectW = 16, \
defaultCropBottom = 0.92105263157895, \
defaultCropLeft = 0, \
defaultCropRight = 1, \
defaultCropTop = 0.078947368421053, \
loupeFocusPoint = { \
_ag_className = \"AgPoint\", \
x = 0.6377015605549, \
y = 0.70538265910057, \
}, \
}";
let object = lron::Value::from_string(LRON1);
assert!(object.is_ok());
let object = object.unwrap();
let properties = Properties::from(object);
assert!(properties.loupe_focus.is_some());
if let Some(ref loupe_focus) = properties.loupe_focus {
assert_eq!(loupe_focus.x, 0.6377015605549);
assert_eq!(loupe_focus.y, 0.70538265910057);
}
assert!(properties.crop_aspect_ratio.is_some());
if let Some(ref ar) = properties.crop_aspect_ratio {
assert_eq!(ar.height, 9);
assert_eq!(ar.width, 16);
}
assert!(properties.default_crop.is_some());
if let Some(ref crop) = properties.default_crop {
assert_eq!(crop.top, 0.078947368421053);
assert_eq!(crop.bottom, 0.92105263157895);
assert_eq!(crop.left, 0.0);
assert_eq!(crop.right, 1.0);
}
}
}