editing: Preview cropping/rotate/mirror operations

This commit is contained in:
Sophie Herold 2024-10-04 14:05:11 +02:00
parent e9869bcb40
commit 367fdf207d
14 changed files with 419 additions and 36 deletions

View file

@ -70,7 +70,7 @@ impl imp::LpImage {
return Some(Self::default_background_color());
}
let (width, height) = self.original_dimensions();
let (width, height) = self.untransformed_dimensions();
let max_size = i32::max(width, height);
// Only use max 200px size scaled image for analysis

View file

@ -0,0 +1,73 @@
use glycin::Operations;
use tiling::RenderOptions;
use super::*;
impl imp::LpImage {
pub(super) fn apply_operations(
&self,
node: gsk::RenderNode,
snapshot: &gtk::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(),
)?;
self.overwrite_dimensions.set(Some(new_dimensions));
}
Ok(())
}
}
impl LpImage {
pub fn set_operations(&self, operations: Option<glycin::Operations>) {
let imp = self.imp();
if operations.is_none() {
imp.overwrite_dimensions.set(None);
}
imp.operations.replace(operations);
let tmp_snapshot = gtk::Snapshot::new();
imp.active_frame_buffer().add_to_snapshot(
&tmp_snapshot,
1.,
&RenderOptions {
background_color: None,
scaling: 1.,
scaling_filter: gsk::ScalingFilter::Nearest,
},
);
if let Some(node) = tmp_snapshot.to_node() {
imp.apply_operations(node, &gtk::Snapshot::new());
} else {
log::error!("Render node is empty");
}
imp.configure_best_fit();
self.queue_draw();
}
pub fn add_operation(&self, operation: glycin::Operation) {
let imp = self.imp();
let mut operations = imp
.operations
.borrow()
.as_ref()
.map(|x| x.operations().to_vec())
.unwrap_or_default();
operations.push(operation);
self.set_operations(Some(Operations::new(operations)));
}
}

View file

@ -81,7 +81,7 @@ impl imp::LpImage {
obj.reset_rotation();
}
DecoderUpdate::Dimensions => {
log::debug!("Received dimensions: {:?}", self.original_dimensions());
log::debug!("Received dimensions: {:?}", self.untransformed_dimensions());
obj.notify_image_size_available();
self.configure_best_fit();
self.request_tiles();

View file

@ -29,6 +29,14 @@ impl imp::LpImage {
}
}
pub(super) fn untransformed_dimensions(&self) -> (i32, i32) {
if let Some((width, height)) = self.overwrite_dimensions.get() {
(width as i32, height as i32)
} else {
self.original_dimensions()
}
}
/// Image width with current zoom factor and rotation
///
/// During rotation it is an interpolated size that does not
@ -43,7 +51,7 @@ impl imp::LpImage {
}
pub fn image_width(&self, zoom: f64) -> f64 {
let (width, height) = self.original_dimensions();
let (width, height) = self.untransformed_dimensions();
let rotated = self.obj().rotation().to_radians().sin().abs();
@ -51,7 +59,7 @@ impl imp::LpImage {
}
pub fn image_height(&self, zoom: f64) -> f64 {
let (width, height) = self.original_dimensions();
let (width, height) = self.untransformed_dimensions();
let rotated = self.obj().rotation().to_radians().sin().abs();
@ -104,7 +112,7 @@ impl LpImage {
let (x, y) = self.imp().original_dimensions();
(y, x)
} else {
self.imp().original_dimensions()
self.imp().untransformed_dimensions()
}
}
}

View file

@ -15,6 +15,8 @@
//
// SPDX-License-Identifier: GPL-3.0-or-later
use tiling::FrameBuffer;
use super::*;
impl WidgetImpl for imp::LpImage {
@ -121,17 +123,21 @@ impl WidgetImpl for imp::LpImage {
);
// Add texture(s)
let frame_buffer = self.frame_buffer.load();
// Don't use current frame buffer if it empty during an image reload
let use_frame_buffer = if frame_buffer.is_empty() {
// Fallback to previous buffer to show previous image until new one is available
self.previous_frame_buffer.load()
} else {
frame_buffer
};
let frame_buffer = self.active_frame_buffer();
use_frame_buffer.add_to_snapshot(snapshot, applicable_zoom, &render_options);
if self.operations.borrow().is_some() {
let tmp_snapshot = gtk::Snapshot::new();
frame_buffer.add_to_snapshot(&tmp_snapshot, applicable_zoom, &render_options);
if let Some(node) = tmp_snapshot.to_node() {
self.apply_operations(node, snapshot);
} else {
log::error!("Render node is empty");
}
} else {
frame_buffer.add_to_snapshot(snapshot, applicable_zoom, &render_options);
}
snapshot.restore();
@ -230,7 +236,7 @@ impl imp::LpImage {
return;
}
let (original_width, original_height) = self.original_dimensions();
let (untransformed_width, untransformed_height) = self.untransformed_dimensions();
let width = self.image_width(zoom) as f32;
let height = self.image_height(zoom) as f32;
@ -250,8 +256,8 @@ impl imp::LpImage {
// Needed for rotating around the center of the image, and
// mirroring the image does not put it to a completely different position.
snapshot.translate(&graphene::Point::new(
-self.round_f32(original_width as f32 * zoom as f32 / 2.),
-self.round_f32(original_height as f32 * zoom as f32 / 2.),
-self.round_f32(untransformed_width as f32 * zoom as f32 / 2.),
-self.round_f32(untransformed_height as f32 * zoom as f32 / 2.),
));
}
@ -268,6 +274,17 @@ impl imp::LpImage {
graphene::Rect::new(x, y, width, height)
}
pub(super) fn active_frame_buffer(&self) -> Arc<FrameBuffer> {
let frame_buffer = self.frame_buffer.load_full();
if frame_buffer.is_empty() {
// Fallback to previous buffer to show previous image until new one is available
self.previous_frame_buffer.load_full()
} else {
frame_buffer
}
}
pub fn widget_width(&self) -> f64 {
self.obj().width() as f64
}

View file

@ -40,8 +40,8 @@ impl imp::LpImage {
pub(super) fn zoom_level_best_fit_for_rotation(&self, rotation: f64) -> f64 {
let rotated = rotation.to_radians().sin().abs();
let (image_width, image_height) = (
self.original_dimensions().0 as f64 / self.scaling(),
self.original_dimensions().1 as f64 / self.scaling(),
self.untransformed_dimensions().0 as f64 / self.scaling(),
self.untransformed_dimensions().1 as f64 / self.scaling(),
);
let texture_aspect_ratio = image_width / image_height;
let widget_aspect_ratio = self.widget_width() / self.widget_height();
@ -93,7 +93,7 @@ impl imp::LpImage {
let obj = self.obj();
if obj.metadata().is_svg() {
let (width, height) = self.original_dimensions();
let (width, height) = self.untransformed_dimensions();
// Avoid division by 0
let long_side = f64::max(1., i32::max(width, height) as f64);
// Limit to maz size supported by rsvg