diff --git a/Cargo.lock b/Cargo.lock index edf6699..56f0277 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -946,8 +946,8 @@ dependencies = [ [[package]] name = "glycin" -version = "4.0.0-alpha" -source = "git+https://gitlab.gnome.org/GNOME/glycin.git#2b0f1b1e217ce17ebe45aecdda7096ed4eea8343" +version = "4.0.0-alpha.1" +source = "git+https://gitlab.gnome.org/GNOME/glycin.git?rev=32083724ee730fb6b35b6382fb8af7b6ab1f2992#32083724ee730fb6b35b6382fb8af7b6ab1f2992" dependencies = [ "glycin-builtin", "glycin-external", @@ -955,16 +955,16 @@ dependencies = [ [[package]] name = "glycin-builtin" -version = "4.0.0-alpha" -source = "git+https://gitlab.gnome.org/GNOME/glycin.git#2b0f1b1e217ce17ebe45aecdda7096ed4eea8343" +version = "4.0.0-alpha.1" +source = "git+https://gitlab.gnome.org/GNOME/glycin.git?rev=32083724ee730fb6b35b6382fb8af7b6ab1f2992#32083724ee730fb6b35b6382fb8af7b6ab1f2992" dependencies = [ "glycin-core", ] [[package]] name = "glycin-common" -version = "2.0.0-alpha" -source = "git+https://gitlab.gnome.org/GNOME/glycin.git#2b0f1b1e217ce17ebe45aecdda7096ed4eea8343" +version = "2.0.0-alpha.1" +source = "git+https://gitlab.gnome.org/GNOME/glycin.git?rev=32083724ee730fb6b35b6382fb8af7b6ab1f2992#32083724ee730fb6b35b6382fb8af7b6ab1f2992" dependencies = [ "bitflags 2.13.0", "gufo-common", @@ -981,8 +981,8 @@ dependencies = [ [[package]] name = "glycin-core" -version = "4.0.0-alpha" -source = "git+https://gitlab.gnome.org/GNOME/glycin.git#2b0f1b1e217ce17ebe45aecdda7096ed4eea8343" +version = "4.0.0-alpha.1" +source = "git+https://gitlab.gnome.org/GNOME/glycin.git?rev=32083724ee730fb6b35b6382fb8af7b6ab1f2992#32083724ee730fb6b35b6382fb8af7b6ab1f2992" dependencies = [ "async-fs", "async-global-executor", @@ -1002,8 +1002,9 @@ dependencies = [ "glycin-common", "glycin-image-rs", "glycin-utils", + "gufo", "gufo-common", - "gufo-exif", + "gufo-exif 0.5.0-alpha.3 (registry+https://github.com/rust-lang/crates.io-index)", "half", "libc", "libseccomp", @@ -1018,36 +1019,38 @@ dependencies = [ [[package]] name = "glycin-external" -version = "4.0.0-alpha" -source = "git+https://gitlab.gnome.org/GNOME/glycin.git#2b0f1b1e217ce17ebe45aecdda7096ed4eea8343" +version = "4.0.0-alpha.1" +source = "git+https://gitlab.gnome.org/GNOME/glycin.git?rev=32083724ee730fb6b35b6382fb8af7b6ab1f2992#32083724ee730fb6b35b6382fb8af7b6ab1f2992" dependencies = [ "glycin-core", ] [[package]] name = "glycin-image-rs" -version = "2.2.0-alpha" -source = "git+https://gitlab.gnome.org/GNOME/glycin.git#2b0f1b1e217ce17ebe45aecdda7096ed4eea8343" +version = "2.2.0-alpha.6" +source = "git+https://gitlab.gnome.org/GNOME/glycin.git?rev=32083724ee730fb6b35b6382fb8af7b6ab1f2992#32083724ee730fb6b35b6382fb8af7b6ab1f2992" dependencies = [ + "bytemuck", "exr", "glycin-utils", "gufo", "gufo-common", - "gufo-exif", - "gufo-jpeg", + "gufo-exif 0.5.0-alpha.3 (registry+https://github.com/rust-lang/crates.io-index)", + "gufo-jpeg 0.5.0-alpha.2", "hayro-jpeg2000", "image", "image-extras", "jpeg-encoder", "log", "moxcms", + "tiff", "zune-jpeg", ] [[package]] name = "glycin-utils" -version = "5.0.0-alpha" -source = "git+https://gitlab.gnome.org/GNOME/glycin.git#2b0f1b1e217ce17ebe45aecdda7096ed4eea8343" +version = "5.0.0-alpha.1" +source = "git+https://gitlab.gnome.org/GNOME/glycin.git?rev=32083724ee730fb6b35b6382fb8af7b6ab1f2992#32083724ee730fb6b35b6382fb8af7b6ab1f2992" dependencies = [ "async-lock", "bitflags 2.13.0", @@ -1192,14 +1195,13 @@ dependencies = [ [[package]] name = "gufo" -version = "0.5.0-alpha" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d97d857a79df06e10cd669541ed0a42d471a45a5201ce99535baf99b18c4129a" +version = "0.5.0-alpha.3" +source = "git+https://github.com/gufo-rs/gufo.git?rev=ac92bd426543bc33b36b6cbc8443455b6a882581#ac92bd426543bc33b36b6cbc8443455b6a882581" dependencies = [ "chrono", "gufo-common", - "gufo-exif", - "gufo-jpeg", + "gufo-exif 0.5.0-alpha.3 (git+https://github.com/gufo-rs/gufo.git?rev=ac92bd426543bc33b36b6cbc8443455b6a882581)", + "gufo-jpeg 0.5.0-alpha.3", "gufo-png", "gufo-tiff", "gufo-webp", @@ -1210,9 +1212,8 @@ dependencies = [ [[package]] name = "gufo-common" -version = "2.0.0-alpha" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "970e26402b2750cf3fbbf49116dca9fc010c939013125623c94da632f1ba76e8" +version = "2.0.0-alpha.2" +source = "git+https://github.com/gufo-rs/gufo.git?rev=ac92bd426543bc33b36b6cbc8443455b6a882581#ac92bd426543bc33b36b6cbc8443455b6a882581" dependencies = [ "chrono", "paste", @@ -1223,9 +1224,22 @@ dependencies = [ [[package]] name = "gufo-exif" -version = "0.5.0-alpha.1" +version = "0.5.0-alpha.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1597401629a6778cbea663e6af6680fbcd07ab243cc8eee9bc5ef5d2c9e5eab8" +checksum = "e68d453f54f396ca43664463e3fa93de6ece835e113dd63f694349d669382821" +dependencies = [ + "gufo-common", + "indexmap", + "ouroboros", + "thiserror", + "tracing", + "zerocopy", +] + +[[package]] +name = "gufo-exif" +version = "0.5.0-alpha.3" +source = "git+https://github.com/gufo-rs/gufo.git?rev=ac92bd426543bc33b36b6cbc8443455b6a882581#ac92bd426543bc33b36b6cbc8443455b6a882581" dependencies = [ "chrono", "gufo-common", @@ -1238,22 +1252,34 @@ dependencies = [ [[package]] name = "gufo-jpeg" -version = "0.5.0-alpha" +version = "0.5.0-alpha.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "004fec8f321842386ba7decc6ee03dde7ed35f29c3586861a5c708cca1e4259b" +checksum = "fa46d802b33586d3a1fd412b3fbb2b10ffbcde5aecbe5fddb4d61c827a001b9e" dependencies = [ "gufo-common", "indexmap", "jpeg-encoder", "thiserror", "tracing", + "zerocopy", +] + +[[package]] +name = "gufo-jpeg" +version = "0.5.0-alpha.3" +source = "git+https://github.com/gufo-rs/gufo.git?rev=ac92bd426543bc33b36b6cbc8443455b6a882581#ac92bd426543bc33b36b6cbc8443455b6a882581" +dependencies = [ + "gufo-common", + "indexmap", + "thiserror", + "tracing", + "zerocopy", ] [[package]] name = "gufo-png" -version = "0.5.0-alpha" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "810e7750b869abb84c6162ad01a240f388a924d2d469ae30a0c2c11a28e81813" +version = "0.5.0-alpha.3" +source = "git+https://github.com/gufo-rs/gufo.git?rev=ac92bd426543bc33b36b6cbc8443455b6a882581#ac92bd426543bc33b36b6cbc8443455b6a882581" dependencies = [ "crc32fast", "encoding_rs", @@ -1265,9 +1291,8 @@ dependencies = [ [[package]] name = "gufo-tiff" -version = "0.5.0-alpha" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a937aa0a245c9602aa9ed4857b6fc8f184cc43cf91e18d982c74c698e20afd5" +version = "0.5.0-alpha.3" +source = "git+https://github.com/gufo-rs/gufo.git?rev=ac92bd426543bc33b36b6cbc8443455b6a882581#ac92bd426543bc33b36b6cbc8443455b6a882581" dependencies = [ "gufo-common", "thiserror", @@ -1275,9 +1300,8 @@ dependencies = [ [[package]] name = "gufo-webp" -version = "0.5.0-alpha" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "662c18ba5eef44d65c1694ebede033783ab5b48eb81366c1625deaaf75c47a18" +version = "0.5.0-alpha.3" +source = "git+https://github.com/gufo-rs/gufo.git?rev=ac92bd426543bc33b36b6cbc8443455b6a882581#ac92bd426543bc33b36b6cbc8443455b6a882581" dependencies = [ "gufo-common", "thiserror", @@ -1285,9 +1309,8 @@ dependencies = [ [[package]] name = "gufo-xmp" -version = "0.5.0-alpha" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea3cc4abbf29cddc6ffc1a4473eec79f11cf742bc113f9bd319ca1d7fcedfc39" +version = "0.5.0-alpha.3" +source = "git+https://github.com/gufo-rs/gufo.git?rev=ac92bd426543bc33b36b6cbc8443455b6a882581#ac92bd426543bc33b36b6cbc8443455b6a882581" dependencies = [ "chrono", "gufo-common", @@ -1625,7 +1648,6 @@ dependencies = [ "glycin", "gtk4", "gufo", - "gufo-common", "gvdb-macros", "indexmap", "libadwaita", diff --git a/Cargo.toml b/Cargo.toml index 569a247..8711e7f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,9 @@ rust-version = "1.93" license = "GPL-3.0-or-later" [patch.crates-io] -glycin = { git = "https://gitlab.gnome.org/GNOME/glycin.git" } +glycin = { git = "https://gitlab.gnome.org/GNOME/glycin.git", rev = "32083724ee730fb6b35b6382fb8af7b6ab1f2992" } +gufo = { git = "https://github.com/gufo-rs/gufo.git", rev = "ac92bd426543bc33b36b6cbc8443455b6a882581" } +gufo-common = { git = "https://github.com/gufo-rs/gufo.git", rev = "ac92bd426543bc33b36b6cbc8443455b6a882581" } [profile.release] lto = true @@ -36,8 +38,7 @@ gtk = { package = "gtk4", version = "0.11.0", features = [ "v4_14", "xml_validation", ] } -gufo = { version = "0.5.0-alpha", features = ["chrono"] } -gufo-common = { version = "2.0.0-alpha", features = ["serde"] } +gufo = { version = "0.5.0-alpha.2", features = ["chrono"] } gvdb-macros = "0.1.6" indexmap = "2.0.0" libc = "0.2.158" diff --git a/src/editing/preview.rs b/src/editing/preview.rs index d0a0113..c96fd38 100644 --- a/src/editing/preview.rs +++ b/src/editing/preview.rs @@ -17,7 +17,7 @@ use glycin::{Operation, Operations}; use gtk::prelude::*; -use gufo_common::orientation::Rotation; +use gufo::common::orientation::Rotation; use crate::deps::*; @@ -145,7 +145,7 @@ impl OperationsSnapshot { fn rotate( &mut self, - rotation: gufo_common::orientation::Rotation, + rotation: gufo::common::orientation::Rotation, ) -> Result<(), OperationError> { let snapshot = gtk::Snapshot::new(); diff --git a/src/metadata.rs b/src/metadata.rs index a87c446..10ef9d1 100644 --- a/src/metadata.rs +++ b/src/metadata.rs @@ -30,6 +30,7 @@ use glib::TimeZone; use glycin::{Frame, FrameDetails, ImageDetails, MemoryFormat}; use gufo::common::datetime::DateTime; use gufo::common::orientation::Orientation; +use gufo::common::physical_dimension; use crate::deps::*; use crate::util; @@ -169,16 +170,6 @@ impl Metadata { } } - pub fn dimensions_inch(&self) -> Option<(f64, f64)> { - self.image_info.as_ref().and_then(|x| x.dimensions_inch()) - } - - pub fn dimensions_text(&self) -> Option { - self.image_info - .as_ref() - .and_then(|x| x.info_dimensions_text().map(|x| x.to_string())) - } - pub fn file_name(&self) -> Option { self.file_info.as_ref().map(|x| x.display_name.to_string()) } @@ -436,4 +427,12 @@ impl Metadata { None } } + + pub fn pixel_density(&self) -> Option { + self.frame_info.as_ref().and_then(|x| x.pixel_density()) + } + + pub fn physical_size(&self) -> Option { + self.frame_info.as_ref().and_then(|x| x.physical_size()) + } } diff --git a/src/util.rs b/src/util.rs index 9466dba..0ebf2c7 100644 --- a/src/util.rs +++ b/src/util.rs @@ -267,3 +267,30 @@ pub enum ErrorType { Loader, General, } + +pub fn locale_uses_inch() -> bool { + // Use inch for USA and Liberia + if let Some(unit_locale) = getlocale(gettextrs::LocaleCategory::LcMeasurement) + && let Some(locale) = unit_locale + .split(|x| *x == b'_') + .nth(1) + .and_then(|x| x.get(0..2)) + && (locale == b"US" || locale == b"LR") + { + dbg!(true) + } else { + false + } +} + +// TODO: Upstream to gettext if possible +fn getlocale(category: gettextrs::LocaleCategory) -> Option> { + unsafe { + let ret = gettext_sys::setlocale(category as i32, std::ptr::null()); + if ret.is_null() { + None + } else { + Some(std::ffi::CStr::from_ptr(ret).to_bytes().to_owned()) + } + } +} diff --git a/src/widgets/edit/crop.rs b/src/widgets/edit/crop.rs index 609760c..81c25fe 100644 --- a/src/widgets/edit/crop.rs +++ b/src/widgets/edit/crop.rs @@ -254,14 +254,14 @@ mod imp { fn apply_rotate_cw(&self) { self.apply_crop(); - self.add_operation(Operation::Rotate(gufo_common::orientation::Rotation::_270)); + self.add_operation(Operation::Rotate(gufo::common::orientation::Rotation::_270)); self.reset_selection(); } fn apply_rotate_ccw(&self) { self.apply_crop(); - self.add_operation(Operation::Rotate(gufo_common::orientation::Rotation::_90)); + self.add_operation(Operation::Rotate(gufo::common::orientation::Rotation::_90)); self.reset_selection(); } diff --git a/src/widgets/image.rs b/src/widgets/image.rs index 64059c5..b454e81 100644 --- a/src/widgets/image.rs +++ b/src/widgets/image.rs @@ -53,7 +53,7 @@ use adw::prelude::*; use adw::subclass::prelude::*; use glib::subclass::Signal; use glib::{Properties, SignalGroup}; -use gufo_common::orientation::Orientation; +use gufo::common::orientation::Orientation; use crate::decoder::tiling::SharedFrameBuffer; use crate::decoder::{self, Decoder, DecoderUpdate, tiling}; diff --git a/src/widgets/image/rotation.rs b/src/widgets/image/rotation.rs index 8498daa..fb8b522 100644 --- a/src/widgets/image/rotation.rs +++ b/src/widgets/image/rotation.rs @@ -104,8 +104,8 @@ impl LpImage { zoom_animation.play(); } - if let Ok(r) = gufo_common::orientation::Rotation::try_from(angle) - && r != gufo_common::orientation::Rotation::_0 + if let Ok(r) = gufo::common::orientation::Rotation::try_from(angle) + && r != gufo::common::orientation::Rotation::_0 { tracing::debug!("Editing image to rotate by {r:?}"); let operation = glycin::Operation::Rotate(r); diff --git a/src/widgets/print.rs b/src/widgets/print.rs index bd0045e..0f2f902 100644 --- a/src/widgets/print.rs +++ b/src/widgets/print.rs @@ -201,6 +201,8 @@ impl Unit { } mod imp { + use crate::util; + use super::*; #[derive(Debug, Default, CompositeTemplate, Properties)] @@ -388,13 +390,7 @@ mod imp { .set_value(size_unit.round(obj.original_size().0 * obj.width_unit_factor())); // Default to inch for USA and Liberia - if let Some(unit_locale) = getlocale(gettextrs::LocaleCategory::LcMeasurement) - && let Some(locale) = unit_locale - .split(|x| *x == b'_') - .nth(1) - .and_then(|x| x.get(0..2)) - && (locale == b"US" || locale == b"LR") - { + if util::locale_uses_inch() { imp.margin_unit.set_selected(Unit::Inch as u32); imp.size_unit.set_selected(Unit::Inch as u32); } @@ -525,13 +521,22 @@ impl LpPrint { pub fn original_size(&self) -> (f64, f64) { let image = self.image(); + let (x_pixels, y_pixels) = image.image_size(); let metadata = image.metadata(); - if let Some((w, h)) = metadata.dimensions_inch() { + + if let Some(physical_size) = metadata.physical_size().map(|x| x.dpi()).or_else(|| { + metadata + .pixel_density() + .map(|x| x.physical_size(x_pixels as u32, y_pixels as u32)) + }) { let dpi = self.dpi(); - ((w * dpi).round(), (h * dpi).round()) + ( + (physical_size.x.value() * dpi).round(), + (physical_size.y.value() * dpi).round(), + ) } else { - let (w, h) = self.image().image_size(); + let (w, h) = image.image_size(); (w as f64, h as f64) } @@ -968,15 +973,3 @@ impl LpPrint { Ok(()) } } - -// TODO: Upstream to gettext if possible -pub fn getlocale(category: gettextrs::LocaleCategory) -> Option> { - unsafe { - let ret = gettext_sys::setlocale(category as i32, std::ptr::null()); - if ret.is_null() { - None - } else { - Some(std::ffi::CStr::from_ptr(ret).to_bytes().to_owned()) - } - } -} diff --git a/src/widgets/properties_view.rs b/src/widgets/properties_view.rs index 145e1a7..7ca7e39 100644 --- a/src/widgets/properties_view.rs +++ b/src/widgets/properties_view.rs @@ -34,6 +34,8 @@ use crate::widgets::image::LpImage; const FALLBACK: &str = "–"; mod imp { + use gufo::common::physical_dimension::PhysicalDimensionUnit; + use super::*; #[derive(Debug, Default, CompositeTemplate, Properties)] @@ -54,6 +56,10 @@ mod imp { #[template_child] image_size: TemplateChild, #[template_child] + physical_size: TemplateChild, + #[template_child] + pixel_density: TemplateChild, + #[template_child] image_format: TemplateChild, #[template_child] file_size: TemplateChild, @@ -219,6 +225,8 @@ mod imp { // Image info Self::update_row(&self.image_size, self.image_size()); + Self::update_row(&self.physical_size, self.physical_size()); + Self::update_row(&self.pixel_density, self.pixel_density()); Self::update_row(&self.image_format, self.format_name()); Self::update_row(&self.file_size, metadata.file_size()); @@ -289,30 +297,113 @@ mod imp { fn image_size(&self) -> Option { let obj = self.obj(); + let image = obj.image()?; + let physical_size = image.metadata().physical_size(); - if let Some(image) = obj.image() { - match image.metadata().dimensions_text() { - Some(string) => Some(string.to_string()), - _ => { - let (width, height) = image.image_size(); - if width > 0 && height > 0 { - Some( - // Translators: Image " x " - gettext_f( - r"{}\u{202F}\u{D7}\u{202F}{}", - [width.to_string(), height.to_string()], - ), - ) - } else { - None - } - } + if physical_size.is_none() { + let (width, height) = image.image_size(); + if width > 0 && height > 0 { + Some( + // Translators: Image " x " + gettext_f( + r"{}\u{202F}\u{D7}\u{202F}{}", + [width.to_string(), height.to_string()], + ), + ) + } else { + None } } else { None } } + fn physical_size(&self) -> Option { + let obj = self.obj(); + let image = obj.image()?; + let (width, height) = image.image_size(); + let metadata = image.metadata(); + + let default_unit = if util::locale_uses_inch() { + PhysicalDimensionUnit::Inch + } else { + PhysicalDimensionUnit::Centimeter + }; + + let physical_size = metadata.physical_size().or_else(|| { + metadata.pixel_density().map(|x| { + x.physical_size(width as u32, height as u32) + .convert(default_unit) + }) + })?; + + const ROUND: f64 = 100.; + let x = (physical_size.x.value() * ROUND).round() / ROUND; + let y = (physical_size.y.value() * ROUND).round() / ROUND; + + let unit = |x| { + match x { + PhysicalDimensionUnit::Centimeter => { + // Translators: Shorthand for centimeter + gettext("\u{202F}cm") + } + PhysicalDimensionUnit::Inch => { + // Translators: Shorthand for inch + gettext("\u{2033}") + } + PhysicalDimensionUnit::Meter => { + // Translators: Shorthand for meter + gettext("\u{202F}m") + } + PhysicalDimensionUnit::Millimeter => { + // Translators: Shorthand for millimeter + gettext("\u{202F}mm") + } + PhysicalDimensionUnit::Pica => { + // Translators: Shorthand for pica (unit) + gettext("\u{202F}pc") + } + PhysicalDimensionUnit::Point => { + // Translators: Shorthand for pica (unit) + gettext("\u{202F}pt") + } + x => x.shorthand().to_string(), + } + }; + + let x_unit = unit(physical_size.x.unit()); + let y_unit = unit(physical_size.y.unit()); + + Some( + // Translators: Image " x " + gettext_f( + r"{}{}\u{202F}\u{D7}\u{202F}{}{}", + [x.to_string(), x_unit, y.to_string(), y_unit], + ), + ) + } + + fn pixel_density(&self) -> Option { + let obj = self.obj(); + let image = obj.image()?; + let metadata = image.metadata(); + let pixel_density = metadata.pixel_density()?.dpi(); + + const ROUND: f64 = 10.; + let x = (pixel_density.x().value() * ROUND).round() / ROUND; + let y = (pixel_density.y().value() * ROUND).round() / ROUND; + + Some(if x == y { + gettext_f(r"{}\u{202F}DPI", [x.to_string()]) + } else { + // Translators: Image " x " + gettext_f( + r"{}\u{202F}DPI\u{202F}\u{D7}\u{202F}{}\u{202F}DPI", + [x.to_string(), y.to_string()], + ) + }) + } + fn file(&self) -> Option { self.image.borrow().as_ref().and_then(|x| x.file()) } diff --git a/src/widgets/properties_view.ui b/src/widgets/properties_view.ui index 93ff8cb..30a8fd1 100644 --- a/src/widgets/properties_view.ui +++ b/src/widgets/properties_view.ui @@ -61,6 +61,26 @@ + + + Physical Size + True + False + + + + + + Resolution + True + False + + + Image Format