Add EXIF auto-orient and aspect ratio lock toggle

- Implement auto_orient_from_exif() that reads EXIF Orientation tag
  and applies the correct rotation/flip for all 8 EXIF orientations
- Add aspect ratio lock toggle to resize step Width/Height mode
- When lock is active, changing width auto-calculates height from
  the first loaded image's aspect ratio, and vice versa
- Uses recursive update guard (Cell<bool>) to prevent infinite loops
This commit is contained in:
2026-03-06 18:18:04 +02:00
parent 0a7a6ee95c
commit 704f556867
2 changed files with 103 additions and 1 deletions

View File

@@ -2,6 +2,15 @@ use adw::prelude::*;
use gtk::glib;
use crate::app::AppState;
/// Get the aspect ratio (width/height) of the first loaded image.
/// Returns 0.0 if no images are loaded or the image can't be read.
fn get_first_image_aspect(files: &[std::path::PathBuf]) -> f64 {
let Some(first) = files.first() else { return 0.0 };
let Ok((w, h)) = image::image_dimensions(first) else { return 0.0 };
if h == 0 { return 0.0; }
w as f64 / h as f64
}
pub fn build_resize_page(state: &AppState) -> adw::NavigationPage {
let scrolled = gtk::ScrolledWindow::builder()
.hscrollbar_policy(gtk::PolicyType::Never)
@@ -61,6 +70,13 @@ pub fn build_resize_page(state: &AppState) -> adw::NavigationPage {
.adjustment(&gtk::Adjustment::new(cfg.resize_width as f64, 0.0, 10000.0, 1.0, 100.0, 0.0))
.build();
let lock_row = adw::SwitchRow::builder()
.title("Lock Aspect Ratio")
.subtitle("Changing one dimension auto-calculates the other")
.active(true)
.build();
lock_row.add_prefix(&gtk::Image::from_icon_name("changes-prevent-symbolic"));
let height_row = adw::SpinRow::builder()
.title("Height")
.subtitle("Target height in pixels (0 = auto from aspect ratio)")
@@ -68,6 +84,7 @@ pub fn build_resize_page(state: &AppState) -> adw::NavigationPage {
.build();
wh_group.add(&width_row);
wh_group.add(&lock_row);
wh_group.add(&height_row);
wh_box.append(&wh_group);
@@ -524,28 +541,70 @@ pub fn build_resize_page(state: &AppState) -> adw::NavigationPage {
jc.borrow_mut().resize_enabled = row.is_active();
});
}
// Shared flag to prevent recursive updates from aspect ratio lock
let updating_lock = std::rc::Rc::new(std::cell::Cell::new(false));
{
let jc = state.job_config.clone();
let pw = preview_width.clone();
let ph = preview_height.clone();
let draw = drawing.clone();
let rt = render_thumb.clone();
let lock = lock_row.clone();
let hr = height_row.clone();
let files = state.loaded_files.clone();
let upd = updating_lock.clone();
width_row.connect_value_notify(move |row| {
if upd.get() { return; }
let val = row.value() as u32;
jc.borrow_mut().resize_width = val;
*pw.borrow_mut() = val;
// Auto-calculate height if lock is active
if lock.is_active() && val > 0 {
let aspect = get_first_image_aspect(&files.borrow());
if aspect > 0.0 {
let new_h = (val as f64 / aspect).round() as u32;
upd.set(true);
hr.set_value(new_h as f64);
jc.borrow_mut().resize_height = new_h;
*ph.borrow_mut() = new_h;
upd.set(false);
}
}
draw.queue_draw();
rt();
});
}
{
let jc = state.job_config.clone();
let pw = preview_width.clone();
let ph = preview_height.clone();
let draw = drawing.clone();
let rt = render_thumb.clone();
let lock = lock_row.clone();
let wr = width_row.clone();
let files = state.loaded_files.clone();
let upd = updating_lock.clone();
height_row.connect_value_notify(move |row| {
if upd.get() { return; }
let val = row.value() as u32;
jc.borrow_mut().resize_height = val;
*ph.borrow_mut() = val;
// Auto-calculate width if lock is active
if lock.is_active() && val > 0 {
let aspect = get_first_image_aspect(&files.borrow());
if aspect > 0.0 {
let new_w = (val as f64 * aspect).round() as u32;
upd.set(true);
wr.set_value(new_w as f64);
jc.borrow_mut().resize_width = new_w;
*pw.borrow_mut() = new_w;
upd.set(false);
}
}
draw.queue_draw();
rt();
});