editor: Apply rotation to files
This commit is contained in:
parent
4164fa61c3
commit
afcc24e235
10 changed files with 116 additions and 3 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -1584,6 +1584,7 @@ dependencies = [
|
||||||
"arc-swap",
|
"arc-swap",
|
||||||
"ashpd",
|
"ashpd",
|
||||||
"async-channel",
|
"async-channel",
|
||||||
|
"async-lock",
|
||||||
"cairo-rs",
|
"cairo-rs",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
|
|
@ -1593,6 +1594,7 @@ dependencies = [
|
||||||
"gio 0.20.0",
|
"gio 0.20.0",
|
||||||
"glycin",
|
"glycin",
|
||||||
"gtk4",
|
"gtk4",
|
||||||
|
"gufo-common",
|
||||||
"gvdb-macros",
|
"gvdb-macros",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"kamadak-exif",
|
"kamadak-exif",
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ ashpd = { version = "0.9.0", features = [
|
||||||
"gtk4_wayland",
|
"gtk4_wayland",
|
||||||
], git = "https://github.com/sophie-h/ashpd.git", branch = "wip/sophie-h/gtk-rs-0.20" }
|
], git = "https://github.com/sophie-h/ashpd.git", branch = "wip/sophie-h/gtk-rs-0.20" }
|
||||||
async-channel = "2.1.0"
|
async-channel = "2.1.0"
|
||||||
|
async-lock = "3.4.0"
|
||||||
cairo-rs = { version = "*", features = [
|
cairo-rs = { version = "*", features = [
|
||||||
"pdf",
|
"pdf",
|
||||||
], git = "https://github.com/gtk-rs/gtk-rs-core.git", branch = "master" }
|
], git = "https://github.com/gtk-rs/gtk-rs-core.git", branch = "master" }
|
||||||
|
|
@ -49,3 +50,6 @@ log = "0.4.17"
|
||||||
once_cell = "1.16.0"
|
once_cell = "1.16.0"
|
||||||
strum = { version = "0.26.2", features = ["derive"] }
|
strum = { version = "0.26.2", features = ["derive"] }
|
||||||
url = "2.3.1"
|
url = "2.3.1"
|
||||||
|
gufo-common = { version = "0.1.0", git = "https://github.com/gufo-rs/gufo.git", features = [
|
||||||
|
"serde",
|
||||||
|
] }
|
||||||
|
|
|
||||||
|
|
@ -152,11 +152,14 @@
|
||||||
{
|
{
|
||||||
"name": "glycin",
|
"name": "glycin",
|
||||||
"buildsystem": "meson",
|
"buildsystem": "meson",
|
||||||
|
"config-opts": [
|
||||||
|
"-Dlibglycin=false"
|
||||||
|
],
|
||||||
"sources": [
|
"sources": [
|
||||||
{
|
{
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://gitlab.gnome.org/sophie-h/glycin.git",
|
"url": "https://gitlab.gnome.org/sophie-h/glycin.git",
|
||||||
"commit": "4b5d7f06ae23bb8f5045ce069c687c3aa05c5f84"
|
"commit": "acdca33b6ba9541959fd9688697ac32617437c1b"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ src/decoder/formats.rs
|
||||||
src/decoder/formats/glycin_proxy.rs
|
src/decoder/formats/glycin_proxy.rs
|
||||||
src/decoder/formats/svg.rs
|
src/decoder/formats/svg.rs
|
||||||
src/decoder/tiling.rs
|
src/decoder/tiling.rs
|
||||||
|
src/editing.rs
|
||||||
src/file_model.rs
|
src/file_model.rs
|
||||||
src/main.rs
|
src/main.rs
|
||||||
src/metadata.rs
|
src/metadata.rs
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ impl Glycin {
|
||||||
updater.clone().spawn_error_handled(async move {
|
updater.clone().spawn_error_handled(async move {
|
||||||
log::trace!("Setting up loader");
|
log::trace!("Setting up loader");
|
||||||
let mut loader = glycin::Loader::new(file);
|
let mut loader = glycin::Loader::new(file);
|
||||||
|
loader.apply_transformations(false);
|
||||||
|
|
||||||
#[cfg(feature = "disable-glycin-sandbox")]
|
#[cfg(feature = "disable-glycin-sandbox")]
|
||||||
loader.sandbox_mechanism(Some(glycin::SandboxMechanism::NotSandboxed));
|
loader.sandbox_mechanism(Some(glycin::SandboxMechanism::NotSandboxed));
|
||||||
|
|
|
||||||
63
src/editing.rs
Normal file
63
src/editing.rs
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use arc_swap::ArcSwap;
|
||||||
|
use glycin::{EditOutcome, Operations};
|
||||||
|
|
||||||
|
use crate::deps::*;
|
||||||
|
use crate::widgets::LpImage;
|
||||||
|
|
||||||
|
/// Queue for image edit operations that still have to be applied.
|
||||||
|
///
|
||||||
|
/// A queue is needed since further operations might be triggered while an
|
||||||
|
/// editing operation is in progress.
|
||||||
|
#[derive(Clone, Default, Debug)]
|
||||||
|
pub struct Queue {
|
||||||
|
operations: Arc<ArcSwap<Vec<glycin::Operation>>>,
|
||||||
|
/// Lock for in progress image operation
|
||||||
|
lock: Arc<async_lock::Mutex<()>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Queue {
|
||||||
|
pub fn push(&self, operation: glycin::Operation) {
|
||||||
|
self.operations.rcu(move |current_operations| {
|
||||||
|
let mut new = (**current_operations).clone();
|
||||||
|
new.push(operation.clone());
|
||||||
|
new
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_to_image(&self, image: &LpImage) {
|
||||||
|
let queue = self.clone();
|
||||||
|
let image = image.clone();
|
||||||
|
glib::spawn_future_local(async move {
|
||||||
|
let lock: async_lock::MutexGuard<()> = queue.lock.lock().await;
|
||||||
|
if let Err(err) = queue.apply(image).await {
|
||||||
|
log::error!("Err: {err}");
|
||||||
|
}
|
||||||
|
drop(lock);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn apply(&self, image: LpImage) -> anyhow::Result<()> {
|
||||||
|
let file = image.file().unwrap();
|
||||||
|
#[allow(unused_mut)]
|
||||||
|
let mut editor = glycin::Editor::new(file.clone());
|
||||||
|
#[cfg(feature = "disable-glycin-sandbox")]
|
||||||
|
editor.sandbox_mechanism(Some(glycin::SandboxMechanism::NotSandboxed));
|
||||||
|
|
||||||
|
let mut list = Vec::new();
|
||||||
|
self.operations.rcu(|current_operations| {
|
||||||
|
list.clone_from(&(**current_operations));
|
||||||
|
Vec::new()
|
||||||
|
});
|
||||||
|
|
||||||
|
let operations = Operations::new(list);
|
||||||
|
let edit_instruction = editor.apply_sparse(operations).await?;
|
||||||
|
|
||||||
|
if edit_instruction.apply_to(file).await? == EditOutcome::Unchanged {
|
||||||
|
log::error!("Writing new files is not supported yet");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -55,6 +55,7 @@ mod about;
|
||||||
mod application;
|
mod application;
|
||||||
mod config;
|
mod config;
|
||||||
mod decoder;
|
mod decoder;
|
||||||
|
mod editing;
|
||||||
mod file_model;
|
mod file_model;
|
||||||
mod metadata;
|
mod metadata;
|
||||||
mod util;
|
mod util;
|
||||||
|
|
|
||||||
|
|
@ -135,11 +135,15 @@ pub enum FitMode {
|
||||||
mod imp {
|
mod imp {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::decoder::tiling::SharedFrameBuffer;
|
use crate::decoder::tiling::SharedFrameBuffer;
|
||||||
|
use crate::editing;
|
||||||
|
|
||||||
#[derive(Debug, Default, Properties)]
|
#[derive(Debug, Default, Properties)]
|
||||||
#[properties(wrapper_type = super::LpImage)]
|
#[properties(wrapper_type = super::LpImage)]
|
||||||
pub struct LpImage {
|
pub struct LpImage {
|
||||||
pub(super) file: RefCell<Option<gio::File>>,
|
pub(super) file: RefCell<Option<gio::File>>,
|
||||||
|
/// Contains a file from which to reload the image after animation has
|
||||||
|
/// finished.
|
||||||
|
pub(super) queued_reload: RefCell<Option<gio::File>>,
|
||||||
#[property(get)]
|
#[property(get)]
|
||||||
pub(super) is_deleted: Cell<bool>,
|
pub(super) is_deleted: Cell<bool>,
|
||||||
#[property(get)]
|
#[property(get)]
|
||||||
|
|
@ -235,6 +239,9 @@ mod imp {
|
||||||
|
|
||||||
/// Number of snapshots created, debug only
|
/// Number of snapshots created, debug only
|
||||||
pub(super) nth_snapshot: Cell<u8>,
|
pub(super) nth_snapshot: Cell<u8>,
|
||||||
|
|
||||||
|
/// Editing queue
|
||||||
|
pub(super) editing_queue: editing::Queue,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[glib::object_subclass]
|
#[glib::object_subclass]
|
||||||
|
|
|
||||||
|
|
@ -283,6 +283,12 @@ impl LpImage {
|
||||||
let imp = self.imp();
|
let imp = self.imp();
|
||||||
log::debug!("Loading file {}", file.uri());
|
log::debug!("Loading file {}", file.uri());
|
||||||
|
|
||||||
|
if imp.rotation_animation().state() == adw::AnimationState::Playing {
|
||||||
|
log::debug!("Queueing image reload due to playing rotate animation.");
|
||||||
|
self.imp().queued_reload.replace(Some(file.clone()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
imp.metadata.replace(Metadata::default());
|
imp.metadata.replace(Metadata::default());
|
||||||
self.imp().emmit_metadata_changed();
|
self.imp().emmit_metadata_changed();
|
||||||
self.imp().set_file_loaded(file).await;
|
self.imp().set_file_loaded(file).await;
|
||||||
|
|
|
||||||
|
|
@ -46,11 +46,27 @@ impl imp::LpImage {
|
||||||
pub(super) fn rotation_animation(&self) -> &adw::TimedAnimation {
|
pub(super) fn rotation_animation(&self) -> &adw::TimedAnimation {
|
||||||
let obj = self.obj().to_owned();
|
let obj = self.obj().to_owned();
|
||||||
self.rotation_animation.get_or_init(|| {
|
self.rotation_animation.get_or_init(|| {
|
||||||
adw::TimedAnimation::builder()
|
let animation = adw::TimedAnimation::builder()
|
||||||
.duration(ROTATION_ANIMATION_DURATION)
|
.duration(ROTATION_ANIMATION_DURATION)
|
||||||
.widget(&obj)
|
.widget(&obj)
|
||||||
.target(&adw::PropertyAnimationTarget::new(&obj, "rotation"))
|
.target(&adw::PropertyAnimationTarget::new(&obj, "rotation"))
|
||||||
.build()
|
.build();
|
||||||
|
|
||||||
|
animation.connect_done(glib::clone!(
|
||||||
|
#[weak]
|
||||||
|
obj,
|
||||||
|
move |_| {
|
||||||
|
let imp = obj.imp();
|
||||||
|
if let Some(new_file) = imp.queued_reload.replace(None) {
|
||||||
|
glib::spawn_future_local(async move {
|
||||||
|
log::debug!("Animation finished: Executing delayed reload");
|
||||||
|
obj.load(&new_file).await;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
animation
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -58,6 +74,7 @@ impl imp::LpImage {
|
||||||
impl LpImage {
|
impl LpImage {
|
||||||
/// Set rotation and mirroring to the state would have after loading
|
/// Set rotation and mirroring to the state would have after loading
|
||||||
pub fn reset_rotation(&self) {
|
pub fn reset_rotation(&self) {
|
||||||
|
log::debug!("Resetting rotation");
|
||||||
let orientation = self.metadata().orientation();
|
let orientation = self.metadata().orientation();
|
||||||
self.imp().rotation_target.set(-orientation.rotation);
|
self.imp().rotation_target.set(-orientation.rotation);
|
||||||
self.set_mirrored(orientation.mirrored);
|
self.set_mirrored(orientation.mirrored);
|
||||||
|
|
@ -84,5 +101,13 @@ impl LpImage {
|
||||||
animation.set_value_to(imp.zoom_level_best_fit_for_rotation(target.get()));
|
animation.set_value_to(imp.zoom_level_best_fit_for_rotation(target.get()));
|
||||||
animation.play();
|
animation.play();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Ok(r) = gufo_common::orientation::Rotation::try_from(angle) {
|
||||||
|
log::debug!("Editing image to rotate by {r:?}");
|
||||||
|
let operation = glycin::Operation::Rotate(r);
|
||||||
|
let editing_queue = &self.imp().editing_queue;
|
||||||
|
editing_queue.push(operation);
|
||||||
|
editing_queue.write_to_image(self);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue