properties_view: Use physical size and resolution

Replace the API that got removed from glycin
This commit is contained in:
Sophie Herold 2026-07-09 22:40:39 +02:00
parent ffe42a7c6c
commit c7223f6f69
11 changed files with 254 additions and 101 deletions

View file

@ -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();

View file

@ -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<String> {
self.image_info
.as_ref()
.and_then(|x| x.info_dimensions_text().map(|x| x.to_string()))
}
pub fn file_name(&self) -> Option<String> {
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<physical_dimension::PixelDensity> {
self.frame_info.as_ref().and_then(|x| x.pixel_density())
}
pub fn physical_size(&self) -> Option<physical_dimension::PhysicalSize> {
self.frame_info.as_ref().and_then(|x| x.physical_size())
}
}

View file

@ -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<Vec<u8>> {
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())
}
}
}

View file

@ -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();
}

View file

@ -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};

View file

@ -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);

View file

@ -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<Vec<u8>> {
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())
}
}
}

View file

@ -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<adw::ActionRow>,
#[template_child]
physical_size: TemplateChild<adw::ActionRow>,
#[template_child]
pixel_density: TemplateChild<adw::ActionRow>,
#[template_child]
image_format: TemplateChild<adw::ActionRow>,
#[template_child]
file_size: TemplateChild<adw::ActionRow>,
@ -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<String> {
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 "<width> x <height>"
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 "<width> x <height>"
gettext_f(
r"{}\u{202F}\u{D7}\u{202F}{}",
[width.to_string(), height.to_string()],
),
)
} else {
None
}
} else {
None
}
}
fn physical_size(&self) -> Option<String> {
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 "<width> x <height>"
gettext_f(
r"{}{}\u{202F}\u{D7}\u{202F}{}{}",
[x.to_string(), x_unit, y.to_string(), y_unit],
),
)
}
fn pixel_density(&self) -> Option<String> {
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-resolution> x <y-resolution>"
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<gio::File> {
self.image.borrow().as_ref().and_then(|x| x.file())
}

View file

@ -61,6 +61,26 @@
</style>
</object>
</child>
<child>
<object class="AdwActionRow" id="physical_size">
<property name="title" translatable="yes">Physical Size</property>
<property name="subtitle-selectable">True</property>
<property name="use-markup">False</property>
<style>
<class name="property" />
</style>
</object>
</child>
<child>
<object class="AdwActionRow" id="pixel_density">
<property name="title" translatable="yes">Resolution</property>
<property name="subtitle-selectable">True</property>
<property name="use-markup">False</property>
<style>
<class name="property" />
</style>
</object>
</child>
<child>
<object class="AdwActionRow" id="image_format">
<property name="title" translatable="yes">Image Format</property>