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 {
#[allow(clippy::unnecessary_unwrap)]
fn loupe_focus(value: &[lron::Object]) -> Option<Point> {
use crate::lron::{Object, Value};
let mut x: Option<f64> = None;
let mut y: Option<f64> = None;
let mut is_point = false;
value.iter().for_each(|o| {
if let Object::Pair(p) = o {
match p.key.as_str() {
"_ag_className" => is_point = p.value == Value::Str("AgPoint".to_owned()),
"y" => y = p.value.to_number(),
"x" => x = p.value.to_number(),
_ => {}
}
}
});
if is_point && x.is_some() && y.is_some() {
Some(Point {
x: x.unwrap(),
y: y.unwrap(),
})
} else {
None
}
}
#[allow(clippy::unnecessary_unwrap)]
fn properties_from(value: &[lron::Object]) -> Self {
use crate::lron::{Object, Value};
let mut props = Properties::default();
let mut crop_aspect_h: Option<i32> = None;
let mut crop_aspect_w: Option<i32> = 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 Object::Pair(p) = o {
match p.key.as_str() {
"loupeFocusPoint" => {
if let Value::Dict(ref v) = p.value {
props.loupe_focus = Self::loupe_focus(v);
}
}
"cropAspectH" => {
if let Value::Int(i) = p.value {
crop_aspect_h = Some(i);
}
}
"cropAspectW" => {
if let Value::Int(i) = p.value {
crop_aspect_w = Some(i);
}
}
"defaultCropBottom" => {
bottom = p.value.to_number();
}
"defaultCropLeft" => {
left = p.value.to_number();
}
"defaultCropRight" => {
right = p.value.to_number();
}
"defaultCropTop" => {
top = p.value.to_number();
}
_ => {}
}
}
});
if crop_aspect_h.is_some() && crop_aspect_w.is_some() {
props.crop_aspect_ratio = Some(AspectRatio {
width: crop_aspect_w.unwrap(),
height: crop_aspect_h.unwrap(),
});
}
if top.is_some() && bottom.is_some() && left.is_some() && right.is_some() {
props.default_crop = Some(Rect {
top: top.unwrap(),
bottom: bottom.unwrap(),
left: left.unwrap(),
right: right.unwrap(),
});
}
props
}
}
impl From<lron::Object> for Properties {
fn from(object: lron::Object) -> Self {
use crate::lron::{Object, Value};
match object {
Object::Pair(ref s) => {
if &s.key == "properties" {
match s.value {
Value::Dict(ref dict) => Self::properties_from(dict),
_ => Properties::default(),
}
} else {
Properties::default()
}
}
_ => 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>,
}
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::Object::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,
})
}
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,
};
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::Object::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);
}
}
}