editing: Fix preroateted images

Editing does not work correctly if PNGs or JPEGs are already rotated via
an Exif orientation entry before editing. This needs the glycin-image-rs
1.2.1 loader to work correctly.

Closes #481
This commit is contained in:
Sophie Herold 2025-03-29 19:41:23 +01:00
parent a49096ffde
commit ef5f94859b
10 changed files with 74 additions and 40 deletions

8
Cargo.lock generated
View file

@ -819,9 +819,9 @@ dependencies = [
[[package]] [[package]]
name = "glycin" name = "glycin"
version = "2.1.0" version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1496acef16890b0022c61298052d4ca3e1a121dd4696166c3d19242581bdb9a" checksum = "37cb9e103cb6b8925bf5e8a1cf8a1166797d8aaefaabd03e68cf6ad7443a1baa"
dependencies = [ dependencies = [
"async-fs", "async-fs",
"async-io", "async-io",
@ -851,9 +851,9 @@ dependencies = [
[[package]] [[package]]
name = "glycin-utils" name = "glycin-utils"
version = "3.0.0" version = "3.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4348643b905fc981acf8ed0315ab8e315ce771cedb65034562f477cbaa4b1ee1" checksum = "c0816d1db00696479cda3cd6c914fb07115982b019dac96555d203c0d5b6d37a"
dependencies = [ dependencies = [
"env_logger", "env_logger",
"gufo-common", "gufo-common",

View file

@ -27,7 +27,7 @@ chrono = { version = "0.4.38", default-features = false, features = ["std"] }
gettext-rs = { version = "0.7.0", features = ["gettext-system"] } gettext-rs = { version = "0.7.0", features = ["gettext-system"] }
gettext-sys = { version = "0.22.5", features = ["gettext-system"] } gettext-sys = { version = "0.22.5", features = ["gettext-system"] }
gio = { version = "0.20.0", features = ["v2_74"] } 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 = [ gtk = { package = "gtk4", version = "0.9.5", features = [
"v4_14", "v4_14",
"xml_validation", "xml_validation",

View file

@ -112,8 +112,8 @@
"sources": [ "sources": [
{ {
"type": "archive", "type": "archive",
"url": "https://download.gnome.org/sources/glycin/1.2/glycin-1.2.0.tar.xz", "url": "https://download.gnome.org/sources/glycin/1.2/glycin-1.2.1.tar.xz",
"sha256": "16ab4a48398639ef701c7c3e27a2d31075b30e0dcaf3f1652c907b188f4290d0" "sha256": "ccc578e9a3e83d0dc1535737d1fda09baa95c7167f5e5ecb15f786654094eed5"
} }
] ]
}, },
@ -132,4 +132,4 @@
] ]
} }
] ]
} }

View file

@ -0,0 +1 @@
Editing does not work correctly if PNGs or JPEGs are already rotated via an Exif orientation entry before editing.

View file

@ -25,12 +25,20 @@ pub fn apply_operations(
node: gsk::RenderNode, node: gsk::RenderNode,
width: u32, width: u32,
height: u32, height: u32,
operations: &Operations, operations: Option<&Operations>,
snapshot: &gtk::Snapshot, snapshot: &gtk::Snapshot,
scale: f64, scale: f64,
initial_operations: &Operations,
) -> Result<(u32, u32), EditingError> { ) -> Result<(u32, u32), EditingError> {
let os = OperationsSnapshot::new(node, width, height, scale as f32); let mut os = OperationsSnapshot::new(node, width, height, scale as f32);
os.apply(operations, snapshot)
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 { struct OperationsSnapshot {
@ -50,11 +58,19 @@ impl OperationsSnapshot {
} }
} }
fn apply( fn apply_and_commit(
mut self, mut self,
operations: &Operations, operations: &Operations,
snapshot: &gtk::Snapshot, snapshot: &gtk::Snapshot,
) -> Result<(u32, u32), EditingError> { ) -> 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() { for operation in operations.operations() {
let operation_result = match operation { let operation_result = match operation {
Operation::Clip(rect) => self.clip(*rect), Operation::Clip(rect) => self.clip(*rect),
@ -77,9 +93,7 @@ impl OperationsSnapshot {
} }
} }
snapshot.append_node(self.current_node); Ok(())
Ok((self.width, self.height))
} }
fn clip(&mut self, (x, y, width, height): (u32, u32, u32, u32)) -> Result<(), OperationError> { 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 x_center = self.width as f32 * self.scale / 2.;
let y_center = self.height 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.rotate(-(rotation.degrees() as f32));
snapshot.translate(&graphene::Point::new(-x_center, -y_center)); snapshot.translate(&graphene::Point::new(-x_center, -y_center));

View file

@ -18,11 +18,12 @@
//! Shows an resizable cropping selection //! Shows an resizable cropping selection
use std::cell::OnceCell; use std::cell::OnceCell;
use std::sync::Arc;
use adw::prelude::*; use adw::prelude::*;
use adw::subclass::prelude::*; use adw::subclass::prelude::*;
use adw::{glib, gtk}; use adw::{glib, gtk};
use glycin::Operation; use glycin::{Operation, Operations};
use crate::deps::*; use crate::deps::*;
use crate::editing::preview::EditingError; use crate::editing::preview::EditingError;
@ -269,7 +270,9 @@ mod imp {
fn apply_reset(&self) -> Result<(), EditingError> { fn apply_reset(&self) -> Result<(), EditingError> {
let obj = self.obj(); 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); obj.edit_window().set_operations(None);
self.reset_selection(); self.reset_selection();

View file

@ -349,7 +349,7 @@ mod imp {
if let Some(operations) = obj.operations() { if let Some(operations) = obj.operations() {
log::debug!("Computing edited image."); log::debug!("Computing edited image.");
let result = editor.apply_complete(&operations).await; let result = editor.apply_complete(dbg!(&operations)).await;
match result { match result {
Err(err) if matches!(err.error(), glycin::Error::Canceled(_)) => { Err(err) if matches!(err.error(), glycin::Error::Canceled(_)) => {
log::debug!("Computing edited image canceled"); log::debug!("Computing edited image canceled");

View file

@ -52,6 +52,7 @@ use adw::prelude::*;
use adw::subclass::prelude::*; use adw::subclass::prelude::*;
use glib::subclass::Signal; use glib::subclass::Signal;
use glib::{Properties, SignalGroup}; use glib::{Properties, SignalGroup};
use gufo_common::orientation::Orientation;
use crate::decoder::{self, tiling, Decoder, DecoderUpdate}; use crate::decoder::{self, tiling, Decoder, DecoderUpdate};
use crate::deps::*; use crate::deps::*;
@ -225,6 +226,7 @@ mod imp {
/// Currently EXIF data /// Currently EXIF data
pub(super) metadata: RefCell<Metadata>, pub(super) metadata: RefCell<Metadata>,
pub(super) original_orientation: Cell<Option<Orientation>>,
#[property(get=Self::image_size_available)] #[property(get=Self::image_size_available)]
_image_size_available: bool, _image_size_available: bool,
@ -393,5 +395,8 @@ impl LpImage {
*imp.file.borrow_mut() = original_imp.file.borrow().clone(); *imp.file.borrow_mut() = original_imp.file.borrow().clone();
imp.frame_buffer imp.frame_buffer
.swap(Arc::new((*(original_imp.frame_buffer).load_full()).clone())); .swap(Arc::new((*(original_imp.frame_buffer).load_full()).clone()));
imp.original_orientation
.set(Some(original_imp.metadata.borrow().orientation()));
} }
} }

View file

@ -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 // 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 // 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 // SPDX-License-Identifier: GPL-3.0-or-later
use glycin::Operations;
use tiling::RenderOptions; use tiling::RenderOptions;
use super::*; use super::*;
@ -26,19 +27,25 @@ impl imp::LpImage {
node: gsk::RenderNode, node: gsk::RenderNode,
snapshot: &gtk::Snapshot, snapshot: &gtk::Snapshot,
) -> Result<(), crate::editing::preview::EditingError> { ) -> Result<(), crate::editing::preview::EditingError> {
if let Some(operations) = self.operations.borrow().as_ref() { let (width, height) = self.original_dimensions();
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(),
)?;
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(()) Ok(())
} }

View file

@ -114,14 +114,6 @@ impl WidgetImpl for imp::LpImage {
let y = obj.image_rendering_y(); let y = obj.image_rendering_y();
snapshot.translate(&graphene::Point::new(x as f32, y as f32)); 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) // Add texture(s)
// Don't use current frame buffer if it empty during an image reload // 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"); log::error!("Render node is empty");
} }
} else { } 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); frame_buffer.add_to_snapshot(snapshot, applicable_zoom, &render_options);
} }