diff --git a/Cargo.lock b/Cargo.lock index 15983de..b03c364 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -819,9 +819,9 @@ dependencies = [ [[package]] name = "glycin" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1496acef16890b0022c61298052d4ca3e1a121dd4696166c3d19242581bdb9a" +checksum = "37cb9e103cb6b8925bf5e8a1cf8a1166797d8aaefaabd03e68cf6ad7443a1baa" dependencies = [ "async-fs", "async-io", @@ -851,9 +851,9 @@ dependencies = [ [[package]] name = "glycin-utils" -version = "3.0.0" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4348643b905fc981acf8ed0315ab8e315ce771cedb65034562f477cbaa4b1ee1" +checksum = "c0816d1db00696479cda3cd6c914fb07115982b019dac96555d203c0d5b6d37a" dependencies = [ "env_logger", "gufo-common", diff --git a/Cargo.toml b/Cargo.toml index 041ce77..b464fb3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,7 @@ chrono = { version = "0.4.38", default-features = false, features = ["std"] } gettext-rs = { version = "0.7.0", features = ["gettext-system"] } gettext-sys = { version = "0.22.5", features = ["gettext-system"] } gio = { version = "0.20.0", features = ["v2_74"] } -glycin = { version = "2.1.0", features = ["gdk4"] } +glycin = { version = "2.1.1", features = ["gdk4"] } gtk = { package = "gtk4", version = "0.9.5", features = [ "v4_14", "xml_validation", diff --git a/build-aux/flatpak/org.gnome.Loupe.Devel.json b/build-aux/flatpak/org.gnome.Loupe.Devel.json index 93ea30d..64e2914 100644 --- a/build-aux/flatpak/org.gnome.Loupe.Devel.json +++ b/build-aux/flatpak/org.gnome.Loupe.Devel.json @@ -112,8 +112,8 @@ "sources": [ { "type": "archive", - "url": "https://download.gnome.org/sources/glycin/1.2/glycin-1.2.0.tar.xz", - "sha256": "16ab4a48398639ef701c7c3e27a2d31075b30e0dcaf3f1652c907b188f4290d0" + "url": "https://download.gnome.org/sources/glycin/1.2/glycin-1.2.1.tar.xz", + "sha256": "ccc578e9a3e83d0dc1535737d1fda09baa95c7167f5e5ecb15f786654094eed5" } ] }, @@ -132,4 +132,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/news.d/48.1/fixes-editing-pre-rotated-images b/news.d/48.1/fixes-editing-pre-rotated-images new file mode 100644 index 0000000..29ab361 --- /dev/null +++ b/news.d/48.1/fixes-editing-pre-rotated-images @@ -0,0 +1 @@ +Editing does not work correctly if PNGs or JPEGs are already rotated via an Exif orientation entry before editing. diff --git a/src/editing/preview.rs b/src/editing/preview.rs index a14f537..dc03f81 100644 --- a/src/editing/preview.rs +++ b/src/editing/preview.rs @@ -25,12 +25,20 @@ pub fn apply_operations( node: gsk::RenderNode, width: u32, height: u32, - operations: &Operations, + operations: Option<&Operations>, snapshot: >k::Snapshot, scale: f64, + initial_operations: &Operations, ) -> Result<(u32, u32), EditingError> { - let os = OperationsSnapshot::new(node, width, height, scale as f32); - os.apply(operations, snapshot) + let mut os = OperationsSnapshot::new(node, width, height, scale as f32); + + os.apply(initial_operations)?; + + if let Some(operations) = operations { + os.apply_and_commit(operations, snapshot) + } else { + os.apply_and_commit(&Operations::new(Vec::new()), snapshot) + } } struct OperationsSnapshot { @@ -50,11 +58,19 @@ impl OperationsSnapshot { } } - fn apply( + fn apply_and_commit( mut self, operations: &Operations, snapshot: >k::Snapshot, ) -> Result<(u32, u32), EditingError> { + self.apply(operations)?; + + snapshot.append_node(self.current_node); + + Ok((self.width, self.height)) + } + + fn apply(&mut self, operations: &Operations) -> Result<(), EditingError> { for operation in operations.operations() { let operation_result = match operation { Operation::Clip(rect) => self.clip(*rect), @@ -77,9 +93,7 @@ impl OperationsSnapshot { } } - snapshot.append_node(self.current_node); - - Ok((self.width, self.height)) + Ok(()) } fn clip(&mut self, (x, y, width, height): (u32, u32, u32, u32)) -> Result<(), OperationError> { @@ -138,7 +152,11 @@ impl OperationsSnapshot { let x_center = self.width as f32 * self.scale / 2.; let y_center = self.height as f32 * self.scale / 2.; - snapshot.translate(&graphene::Point::new(y_center, x_center)); + if rotation == Rotation::_90 || rotation == Rotation::_270 { + snapshot.translate(&graphene::Point::new(y_center, x_center)); + } else { + snapshot.translate(&graphene::Point::new(x_center, y_center)); + } snapshot.rotate(-(rotation.degrees() as f32)); snapshot.translate(&graphene::Point::new(-x_center, -y_center)); diff --git a/src/widgets/edit/crop.rs b/src/widgets/edit/crop.rs index c71c33a..c4d1729 100644 --- a/src/widgets/edit/crop.rs +++ b/src/widgets/edit/crop.rs @@ -18,11 +18,12 @@ //! Shows an resizable cropping selection use std::cell::OnceCell; +use std::sync::Arc; use adw::prelude::*; use adw::subclass::prelude::*; use adw::{glib, gtk}; -use glycin::Operation; +use glycin::{Operation, Operations}; use crate::deps::*; use crate::editing::preview::EditingError; @@ -269,7 +270,9 @@ mod imp { fn apply_reset(&self) -> Result<(), EditingError> { let obj = self.obj(); - self.image.set_operations(None)?; + self.image + .set_operations(Some(Arc::new(Operations::new(Vec::new()))))?; + obj.edit_window().set_operations(None); self.reset_selection(); diff --git a/src/widgets/edit_window.rs b/src/widgets/edit_window.rs index 7bc36b3..0c8e547 100644 --- a/src/widgets/edit_window.rs +++ b/src/widgets/edit_window.rs @@ -349,7 +349,7 @@ mod imp { if let Some(operations) = obj.operations() { log::debug!("Computing edited image."); - let result = editor.apply_complete(&operations).await; + let result = editor.apply_complete(dbg!(&operations)).await; match result { Err(err) if matches!(err.error(), glycin::Error::Canceled(_)) => { log::debug!("Computing edited image canceled"); diff --git a/src/widgets/image.rs b/src/widgets/image.rs index 135049e..ea20a00 100644 --- a/src/widgets/image.rs +++ b/src/widgets/image.rs @@ -52,6 +52,7 @@ use adw::prelude::*; use adw::subclass::prelude::*; use glib::subclass::Signal; use glib::{Properties, SignalGroup}; +use gufo_common::orientation::Orientation; use crate::decoder::{self, tiling, Decoder, DecoderUpdate}; use crate::deps::*; @@ -225,6 +226,7 @@ mod imp { /// Currently EXIF data pub(super) metadata: RefCell, + pub(super) original_orientation: Cell>, #[property(get=Self::image_size_available)] _image_size_available: bool, @@ -393,5 +395,8 @@ impl LpImage { *imp.file.borrow_mut() = original_imp.file.borrow().clone(); imp.frame_buffer .swap(Arc::new((*(original_imp.frame_buffer).load_full()).clone())); + + imp.original_orientation + .set(Some(original_imp.metadata.borrow().orientation())); } } diff --git a/src/widgets/image/editing.rs b/src/widgets/image/editing.rs index 25a1ec8..f57ae44 100644 --- a/src/widgets/image/editing.rs +++ b/src/widgets/image/editing.rs @@ -1,4 +1,4 @@ -// Copyright (c) 2024 Sophie Herold +// Copyright (c) 2024-2025 Sophie Herold // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -15,6 +15,7 @@ // // SPDX-License-Identifier: GPL-3.0-or-later +use glycin::Operations; use tiling::RenderOptions; use super::*; @@ -26,19 +27,25 @@ impl imp::LpImage { node: gsk::RenderNode, snapshot: >k::Snapshot, ) -> Result<(), crate::editing::preview::EditingError> { - if let Some(operations) = self.operations.borrow().as_ref() { - let (width, height) = self.original_dimensions(); - let new_dimensions = crate::editing::preview::apply_operations( - node, - width as u32, - height as u32, - operations, - snapshot, - self.applicable_zoom(), - )?; + let (width, height) = self.original_dimensions(); - self.overwrite_dimensions.set(Some(new_dimensions)); - } + let initial_operations = if let Some(orientation) = self.original_orientation.get() { + Operations::new_orientation(orientation) + } else { + Operations::new(Vec::new()) + }; + + let new_dimensions = crate::editing::preview::apply_operations( + node, + width as u32, + height as u32, + self.operations.borrow().as_ref().map(|x| x.as_ref()), + snapshot, + self.applicable_zoom(), + &initial_operations, + )?; + + self.overwrite_dimensions.set(Some(new_dimensions)); Ok(()) } diff --git a/src/widgets/image/rendering.rs b/src/widgets/image/rendering.rs index 3f0810f..881cbda 100644 --- a/src/widgets/image/rendering.rs +++ b/src/widgets/image/rendering.rs @@ -114,14 +114,6 @@ impl WidgetImpl for imp::LpImage { let y = obj.image_rendering_y(); snapshot.translate(&graphene::Point::new(x as f32, y as f32)); - // Apply rotation and mirroring - self.snapshot_rotate_mirror( - snapshot, - obj.rotation() as f32, - obj.mirrored(), - applicable_zoom, - ); - // Add texture(s) // Don't use current frame buffer if it empty during an image reload @@ -138,6 +130,14 @@ impl WidgetImpl for imp::LpImage { log::error!("Render node is empty"); } } else { + // Apply rotation and mirroring + self.snapshot_rotate_mirror( + snapshot, + obj.rotation() as f32, + obj.mirrored(), + applicable_zoom, + ); + frame_buffer.add_to_snapshot(snapshot, applicable_zoom, &render_options); }