editing: Preview cropping/rotate/mirror operations
This commit is contained in:
parent
e9869bcb40
commit
367fdf207d
14 changed files with 419 additions and 36 deletions
|
|
@ -682,6 +682,7 @@ impl SharedFrameBuffer {
|
||||||
self.buffer.rcu(|tiling_store| {
|
self.buffer.rcu(|tiling_store| {
|
||||||
let mut new_store = (**tiling_store).clone();
|
let mut new_store = (**tiling_store).clone();
|
||||||
new_store.current().original_dimensions = Some(size);
|
new_store.current().original_dimensions = Some(size);
|
||||||
|
new_store.original_dimensions();
|
||||||
Arc::new(new_store)
|
Arc::new(new_store)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
pub mod preview;
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use arc_swap::ArcSwap;
|
use arc_swap::ArcSwap;
|
||||||
|
|
|
||||||
138
src/editing/preview.rs
Normal file
138
src/editing/preview.rs
Normal file
|
|
@ -0,0 +1,138 @@
|
||||||
|
use crate::deps::*;
|
||||||
|
use glycin::{Operation, Operations};
|
||||||
|
use gtk::prelude::*;
|
||||||
|
use gufo_common::orientation::Rotation;
|
||||||
|
|
||||||
|
pub fn apply_operations(
|
||||||
|
node: gsk::RenderNode,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
operations: &Operations,
|
||||||
|
snapshot: >k::Snapshot,
|
||||||
|
scale: f64,
|
||||||
|
) -> Result<(u32, u32), EditingError> {
|
||||||
|
let os = OperationsSnapshot::new(node, width, height, scale as f32);
|
||||||
|
os.apply(operations, snapshot)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct OperationsSnapshot {
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
current_node: gsk::RenderNode,
|
||||||
|
scale: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OperationsSnapshot {
|
||||||
|
fn new(node: gsk::RenderNode, width: u32, height: u32, scale: f32) -> Self {
|
||||||
|
Self {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
current_node: node,
|
||||||
|
scale,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply(
|
||||||
|
mut self,
|
||||||
|
operations: &Operations,
|
||||||
|
snapshot: >k::Snapshot,
|
||||||
|
) -> Result<(u32, u32), EditingError> {
|
||||||
|
for operation in operations.operations() {
|
||||||
|
let operation_result = match operation {
|
||||||
|
Operation::Clip(rect) => self.clip(*rect),
|
||||||
|
Operation::Rotate(rotation) => self.rotate(*rotation),
|
||||||
|
Operation::MirrorHorizontally => self.mirror_horizontally(),
|
||||||
|
unsupported_operation => {
|
||||||
|
return Err(EditingError::UnsupportedOperation(
|
||||||
|
unsupported_operation.clone(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(err) = operation_result {
|
||||||
|
match err {
|
||||||
|
OperationError::EmptyNode => {
|
||||||
|
return Err(EditingError::EmptyNode(operation.clone()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
snapshot.append_node(self.current_node);
|
||||||
|
|
||||||
|
Ok((self.width, self.height))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clip(&mut self, (x, y, width, height): (u32, u32, u32, u32)) -> Result<(), OperationError> {
|
||||||
|
let snapshot = gtk::Snapshot::new();
|
||||||
|
|
||||||
|
self.width = width;
|
||||||
|
self.height = height;
|
||||||
|
|
||||||
|
let x = x as f32 * self.scale;
|
||||||
|
let y = y as f32 * self.scale;
|
||||||
|
let width = width as f32 * self.scale;
|
||||||
|
let height = height as f32 * self.scale;
|
||||||
|
|
||||||
|
snapshot.translate(&graphene::Point::new(-x, -y));
|
||||||
|
snapshot.push_clip(&graphene::Rect::new(x, y, width, height));
|
||||||
|
|
||||||
|
snapshot.append_node(self.current_node.clone());
|
||||||
|
|
||||||
|
snapshot.pop();
|
||||||
|
|
||||||
|
self.current_node = snapshot.to_node().ok_or(OperationError::EmptyNode)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mirror_horizontally(&mut self) -> Result<(), OperationError> {
|
||||||
|
let snapshot = gtk::Snapshot::new();
|
||||||
|
|
||||||
|
snapshot.translate(&graphene::Point::new(self.width as f32 * self.scale, 0.));
|
||||||
|
snapshot.scale(-1., 1.);
|
||||||
|
snapshot.append_node(self.current_node.clone());
|
||||||
|
|
||||||
|
self.current_node = snapshot.to_node().ok_or(OperationError::EmptyNode)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rotate(
|
||||||
|
&mut self,
|
||||||
|
rotation: gufo_common::orientation::Rotation,
|
||||||
|
) -> Result<(), OperationError> {
|
||||||
|
let snapshot = gtk::Snapshot::new();
|
||||||
|
|
||||||
|
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));
|
||||||
|
snapshot.rotate(rotation.degrees() as f32);
|
||||||
|
snapshot.translate(&graphene::Point::new(-x_center, -y_center));
|
||||||
|
|
||||||
|
snapshot.append_node(self.current_node.clone());
|
||||||
|
|
||||||
|
self.current_node = snapshot.to_node().ok_or(OperationError::EmptyNode)?;
|
||||||
|
|
||||||
|
if rotation == Rotation::_90 || rotation == Rotation::_270 {
|
||||||
|
std::mem::swap(&mut self.width, &mut self.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, thiserror::Error)]
|
||||||
|
pub enum EditingError {
|
||||||
|
#[error("Node empty after the following operation: {0:?}")]
|
||||||
|
EmptyNode(Operation),
|
||||||
|
#[error("The following editing operation is not supported: {0:?}")]
|
||||||
|
UnsupportedOperation(Operation),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, thiserror::Error)]
|
||||||
|
enum OperationError {
|
||||||
|
#[error("Empty node")]
|
||||||
|
EmptyNode,
|
||||||
|
}
|
||||||
|
|
@ -43,7 +43,7 @@ Simplified widget arrangement including the most important widgets.
|
||||||
[`LpPropertiesView`]: widgets::LpPropertiesView
|
[`LpPropertiesView`]: widgets::LpPropertiesView
|
||||||
[`LpSlidingView`]: widgets::LpSlidingView
|
[`LpSlidingView`]: widgets::LpSlidingView
|
||||||
[`LpWindowContents`]: widgets::LpWindowContents
|
[`LpWindowContents`]: widgets::LpWindowContents
|
||||||
[`LpWindow`]: window::LpWindow
|
[`LpWindow`]: widgets::LpWindow
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use gettextrs::*;
|
use gettextrs::*;
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,8 @@ pub enum LpOrientation {
|
||||||
}
|
}
|
||||||
|
|
||||||
mod imp {
|
mod imp {
|
||||||
|
use glycin::Operation;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::widgets::LpImage;
|
use crate::widgets::LpImage;
|
||||||
#[derive(Debug, Default, gtk::CompositeTemplate, glib::Properties)]
|
#[derive(Debug, Default, gtk::CompositeTemplate, glib::Properties)]
|
||||||
|
|
@ -63,6 +65,8 @@ mod imp {
|
||||||
image: TemplateChild<LpImage>,
|
image: TemplateChild<LpImage>,
|
||||||
#[template_child]
|
#[template_child]
|
||||||
pub(super) selection: TemplateChild<LpEditCropSelection>,
|
pub(super) selection: TemplateChild<LpEditCropSelection>,
|
||||||
|
#[template_child]
|
||||||
|
pub apply_crop: TemplateChild<gtk::Button>,
|
||||||
|
|
||||||
#[property(get, set, builder(LpAspectRatio::default()))]
|
#[property(get, set, builder(LpAspectRatio::default()))]
|
||||||
aspect_ratio: Cell<LpAspectRatio>,
|
aspect_ratio: Cell<LpAspectRatio>,
|
||||||
|
|
@ -87,6 +91,18 @@ mod imp {
|
||||||
|
|
||||||
klass.install_property_action("edit-crop.aspect-ratio", "aspect_ratio");
|
klass.install_property_action("edit-crop.aspect-ratio", "aspect_ratio");
|
||||||
klass.install_property_action("edit-crop.orientation", "orientation");
|
klass.install_property_action("edit-crop.orientation", "orientation");
|
||||||
|
klass.install_action("edit-crop.mirror", None, |obj, _, _| {
|
||||||
|
obj.imp().apply_mirror();
|
||||||
|
});
|
||||||
|
klass.install_action("edit-crop.rotate-cw", None, |obj, _, _| {
|
||||||
|
obj.imp().apply_rotate_cw();
|
||||||
|
});
|
||||||
|
klass.install_action("edit-crop.rotate-ccw", None, |obj, _, _| {
|
||||||
|
obj.imp().apply_rotate_ccw();
|
||||||
|
});
|
||||||
|
klass.install_action("edit-crop.reset", None, |obj, _, _| {
|
||||||
|
obj.imp().apply_reset();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
|
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
|
||||||
|
|
@ -104,6 +120,48 @@ mod imp {
|
||||||
obj.child().set_parent(obj);
|
obj.child().set_parent(obj);
|
||||||
|
|
||||||
self.image.duplicate_from(&obj.original_image());
|
self.image.duplicate_from(&obj.original_image());
|
||||||
|
|
||||||
|
self.apply_crop.connect_clicked(glib::clone!(
|
||||||
|
#[weak]
|
||||||
|
obj,
|
||||||
|
move |_| {
|
||||||
|
obj.imp().apply_crop();
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
// Selection changed notifications
|
||||||
|
|
||||||
|
self.selection.connect_crop_x_notify(glib::clone!(
|
||||||
|
#[weak]
|
||||||
|
obj,
|
||||||
|
move |_| {
|
||||||
|
obj.imp().selection_changed();
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
self.selection.connect_crop_y_notify(glib::clone!(
|
||||||
|
#[weak]
|
||||||
|
obj,
|
||||||
|
move |_| {
|
||||||
|
obj.imp().selection_changed();
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
self.selection.connect_crop_width_notify(glib::clone!(
|
||||||
|
#[weak]
|
||||||
|
obj,
|
||||||
|
move |_| {
|
||||||
|
obj.imp().selection_changed();
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
self.selection.connect_crop_height_notify(glib::clone!(
|
||||||
|
#[weak]
|
||||||
|
obj,
|
||||||
|
move |_| {
|
||||||
|
obj.imp().selection_changed();
|
||||||
|
}
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dispose(&self) {
|
fn dispose(&self) {
|
||||||
|
|
@ -115,7 +173,7 @@ mod imp {
|
||||||
fn size_allocate(&self, width: i32, height: i32, baseline: i32) {
|
fn size_allocate(&self, width: i32, height: i32, baseline: i32) {
|
||||||
let obj = self.obj();
|
let obj = self.obj();
|
||||||
|
|
||||||
let image: &TemplateChild<LpImage> = &self.image;
|
let image = &self.image;
|
||||||
|
|
||||||
obj.child().allocate(width, height, baseline, None);
|
obj.child().allocate(width, height, baseline, None);
|
||||||
|
|
||||||
|
|
@ -128,20 +186,6 @@ mod imp {
|
||||||
|
|
||||||
self.selection.ensure_initialized(x, y, width, height);
|
self.selection.ensure_initialized(x, y, width, height);
|
||||||
self.selection.set_size(x, y, width, height);
|
self.selection.set_size(x, y, width, height);
|
||||||
|
|
||||||
dbg!(self.image.widget_to_img_coord((
|
|
||||||
self.image.image_rendering_x() + self.selection.crop_x() as f64,
|
|
||||||
self.image.image_rendering_y() + self.selection.crop_y() as f64
|
|
||||||
)));
|
|
||||||
|
|
||||||
dbg!(self.image.widget_to_img_coord((
|
|
||||||
self.image.image_rendering_x()
|
|
||||||
+ self.selection.crop_x() as f64
|
|
||||||
+ self.selection.crop_width() as f64,
|
|
||||||
self.image.image_rendering_y()
|
|
||||||
+ self.selection.crop_y() as f64
|
|
||||||
+ self.selection.crop_height() as f64
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn measure(&self, orientation: gtk::Orientation, for_size: i32) -> (i32, i32, i32, i32) {
|
fn measure(&self, orientation: gtk::Orientation, for_size: i32) -> (i32, i32, i32, i32) {
|
||||||
|
|
@ -149,6 +193,80 @@ mod imp {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl BinImpl for LpEditCrop {}
|
impl BinImpl for LpEditCrop {}
|
||||||
|
|
||||||
|
impl LpEditCrop {
|
||||||
|
fn selection_changed(&self) {
|
||||||
|
let apply_sensitive = self.selection.is_copped();
|
||||||
|
self.apply_crop.set_sensitive(apply_sensitive);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn crop_area_image_coord(&self) -> Option<(u32, u32, u32, u32)> {
|
||||||
|
if !self.selection.is_copped() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (x1, y1) = self.image.widget_to_img_coord((
|
||||||
|
self.image.image_rendering_x() + self.selection.crop_x() as f64,
|
||||||
|
self.image.image_rendering_y() + self.selection.crop_y() as f64,
|
||||||
|
));
|
||||||
|
let (x2, y2) = self.image.widget_to_img_coord((
|
||||||
|
self.image.image_rendering_x()
|
||||||
|
+ self.selection.crop_x() as f64
|
||||||
|
+ self.selection.crop_width() as f64,
|
||||||
|
self.image.image_rendering_y()
|
||||||
|
+ self.selection.crop_y() as f64
|
||||||
|
+ self.selection.crop_height() as f64,
|
||||||
|
));
|
||||||
|
|
||||||
|
Some((
|
||||||
|
x1.round() as u32,
|
||||||
|
y1.round() as u32,
|
||||||
|
(x2 - x1).round() as u32,
|
||||||
|
(y2 - y1).round() as u32,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset_selection(&self) {
|
||||||
|
let image = &self.image;
|
||||||
|
|
||||||
|
let (x, y, width, height) = (
|
||||||
|
image.image_rendering_x(),
|
||||||
|
image.image_rendering_y(),
|
||||||
|
image.image_rendering_width(),
|
||||||
|
image.image_rendering_height(),
|
||||||
|
);
|
||||||
|
self.selection.reset(x, y, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_crop(&self) {
|
||||||
|
if let Some(crop) = self.crop_area_image_coord() {
|
||||||
|
self.image.add_operation(Operation::Clip(crop));
|
||||||
|
self.reset_selection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_mirror(&self) {
|
||||||
|
self.image.add_operation(Operation::MirrorHorizontally);
|
||||||
|
self.reset_selection();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_rotate_cw(&self) {
|
||||||
|
self.image
|
||||||
|
.add_operation(Operation::Rotate(gufo_common::orientation::Rotation::_90));
|
||||||
|
self.reset_selection();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_rotate_ccw(&self) {
|
||||||
|
self.image
|
||||||
|
.add_operation(Operation::Rotate(gufo_common::orientation::Rotation::_270));
|
||||||
|
self.reset_selection();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_reset(&self) {
|
||||||
|
self.image.set_operations(None);
|
||||||
|
self.reset_selection();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
glib::wrapper! {
|
glib::wrapper! {
|
||||||
|
|
|
||||||
|
|
@ -71,11 +71,15 @@
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child type="overlay">
|
<child type="overlay">
|
||||||
<object class="GtkButton">
|
<object class="GtkButton" id="apply_crop">
|
||||||
<property name="label">Apply</property>
|
<property name="label">Apply</property>
|
||||||
<property name="action-name">edit.apply-crop</property>
|
<property name="action-name">edit.apply-crop</property>
|
||||||
<property name="halign">end</property>
|
<property name="halign">end</property>
|
||||||
<property name="valign">start</property>
|
<property name="valign">start</property>
|
||||||
|
<style>
|
||||||
|
<class name="osd" />
|
||||||
|
<class name="pill" />
|
||||||
|
</style>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
|
|
@ -144,6 +148,7 @@
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkButton">
|
<object class="GtkButton">
|
||||||
<property name="icon-name">edit-mirror-symbolic</property>
|
<property name="icon-name">edit-mirror-symbolic</property>
|
||||||
|
<property name="action-name">edit-crop.mirror</property>
|
||||||
<property name="valign">start</property>
|
<property name="valign">start</property>
|
||||||
<style>
|
<style>
|
||||||
<class name="flat" />
|
<class name="flat" />
|
||||||
|
|
@ -160,6 +165,7 @@
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkButton">
|
<object class="GtkButton">
|
||||||
<property name="icon-name">object-rotate-left-symbolic</property>
|
<property name="icon-name">object-rotate-left-symbolic</property>
|
||||||
|
<property name="action-name">edit-crop.rotate-ccw</property>
|
||||||
<property name="valign">start</property>
|
<property name="valign">start</property>
|
||||||
<style>
|
<style>
|
||||||
<class name="flat" />
|
<class name="flat" />
|
||||||
|
|
@ -169,6 +175,7 @@
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkButton">
|
<object class="GtkButton">
|
||||||
<property name="icon-name">object-rotate-right-symbolic</property>
|
<property name="icon-name">object-rotate-right-symbolic</property>
|
||||||
|
<property name="action-name">edit-crop.rotate-cw</property>
|
||||||
<property name="valign">start</property>
|
<property name="valign">start</property>
|
||||||
<style>
|
<style>
|
||||||
<class name="flat" />
|
<class name="flat" />
|
||||||
|
|
@ -185,6 +192,7 @@
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkButton">
|
<object class="GtkButton">
|
||||||
<property name="label">Reset</property>
|
<property name="label">Reset</property>
|
||||||
|
<property name="action-name">edit-crop.reset</property>
|
||||||
<property name="valign">start</property>
|
<property name="valign">start</property>
|
||||||
<style>
|
<style>
|
||||||
<class name="flat" />
|
<class name="flat" />
|
||||||
|
|
|
||||||
|
|
@ -767,6 +767,12 @@ impl LpEditCropSelection {
|
||||||
|
|
||||||
imp.initialized.set(true);
|
imp.initialized.set(true);
|
||||||
|
|
||||||
|
self.reset(x, y, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset(&self, x: f64, y: f64, width: f64, height: f64) {
|
||||||
|
let imp = self.imp();
|
||||||
|
|
||||||
self.set_margin_start(x as i32);
|
self.set_margin_start(x as i32);
|
||||||
self.set_margin_top(y as i32);
|
self.set_margin_top(y as i32);
|
||||||
self.set_total_width(width as i32);
|
self.set_total_width(width as i32);
|
||||||
|
|
@ -791,4 +797,12 @@ impl LpEditCropSelection {
|
||||||
self.set_total_width(width as i32);
|
self.set_total_width(width as i32);
|
||||||
self.set_total_height(height as i32);
|
self.set_total_height(height as i32);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_copped(&self) -> bool {
|
||||||
|
let untouched = self.crop_x() == 0.
|
||||||
|
&& self.crop_y() == 0.
|
||||||
|
&& self.crop_width() as i32 == self.total_width()
|
||||||
|
&& self.crop_height() as i32 == self.total_height();
|
||||||
|
!untouched
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@
|
||||||
|
|
||||||
mod background_color;
|
mod background_color;
|
||||||
mod drag;
|
mod drag;
|
||||||
|
mod editing;
|
||||||
mod input_handling;
|
mod input_handling;
|
||||||
mod loading;
|
mod loading;
|
||||||
mod metadata;
|
mod metadata;
|
||||||
|
|
@ -132,6 +133,7 @@ pub enum FitMode {
|
||||||
|
|
||||||
mod imp {
|
mod imp {
|
||||||
use decoder::DecoderError;
|
use decoder::DecoderError;
|
||||||
|
use glycin::Operations;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::decoder::tiling::SharedFrameBuffer;
|
use crate::decoder::tiling::SharedFrameBuffer;
|
||||||
|
|
@ -164,6 +166,7 @@ mod imp {
|
||||||
pub(super) frame_buffer: Arc<SharedFrameBuffer>,
|
pub(super) frame_buffer: Arc<SharedFrameBuffer>,
|
||||||
pub(super) previous_frame_buffer: SharedFrameBuffer,
|
pub(super) previous_frame_buffer: SharedFrameBuffer,
|
||||||
pub(super) decoder: RefCell<Option<Arc<Decoder>>>,
|
pub(super) decoder: RefCell<Option<Arc<Decoder>>>,
|
||||||
|
pub(super) overwrite_dimensions: Cell<Option<(u32, u32)>>,
|
||||||
|
|
||||||
/// Rotation final value (can differ from `rotation` during animation)
|
/// Rotation final value (can differ from `rotation` during animation)
|
||||||
pub(super) rotation_target: Cell<f64>,
|
pub(super) rotation_target: Cell<f64>,
|
||||||
|
|
@ -244,6 +247,7 @@ mod imp {
|
||||||
|
|
||||||
/// Editing queue
|
/// Editing queue
|
||||||
pub(super) editing_queue: editing::Queue,
|
pub(super) editing_queue: editing::Queue,
|
||||||
|
pub(super) operations: RefCell<Option<Operations>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[glib::object_subclass]
|
#[glib::object_subclass]
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ impl imp::LpImage {
|
||||||
return Some(Self::default_background_color());
|
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);
|
let max_size = i32::max(width, height);
|
||||||
|
|
||||||
// Only use max 200px size scaled image for analysis
|
// Only use max 200px size scaled image for analysis
|
||||||
|
|
|
||||||
73
src/widgets/image/editing.rs
Normal file
73
src/widgets/image/editing.rs
Normal 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: >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(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
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, >k::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)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -81,7 +81,7 @@ impl imp::LpImage {
|
||||||
obj.reset_rotation();
|
obj.reset_rotation();
|
||||||
}
|
}
|
||||||
DecoderUpdate::Dimensions => {
|
DecoderUpdate::Dimensions => {
|
||||||
log::debug!("Received dimensions: {:?}", self.original_dimensions());
|
log::debug!("Received dimensions: {:?}", self.untransformed_dimensions());
|
||||||
obj.notify_image_size_available();
|
obj.notify_image_size_available();
|
||||||
self.configure_best_fit();
|
self.configure_best_fit();
|
||||||
self.request_tiles();
|
self.request_tiles();
|
||||||
|
|
|
||||||
|
|
@ -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
|
/// Image width with current zoom factor and rotation
|
||||||
///
|
///
|
||||||
/// During rotation it is an interpolated size that does not
|
/// 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 {
|
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();
|
let rotated = self.obj().rotation().to_radians().sin().abs();
|
||||||
|
|
||||||
|
|
@ -51,7 +59,7 @@ impl imp::LpImage {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn image_height(&self, zoom: f64) -> f64 {
|
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();
|
let rotated = self.obj().rotation().to_radians().sin().abs();
|
||||||
|
|
||||||
|
|
@ -104,7 +112,7 @@ impl LpImage {
|
||||||
let (x, y) = self.imp().original_dimensions();
|
let (x, y) = self.imp().original_dimensions();
|
||||||
(y, x)
|
(y, x)
|
||||||
} else {
|
} else {
|
||||||
self.imp().original_dimensions()
|
self.imp().untransformed_dimensions()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
use tiling::FrameBuffer;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
impl WidgetImpl for imp::LpImage {
|
impl WidgetImpl for imp::LpImage {
|
||||||
|
|
@ -121,17 +123,21 @@ impl WidgetImpl for imp::LpImage {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Add texture(s)
|
// Add texture(s)
|
||||||
let frame_buffer = self.frame_buffer.load();
|
|
||||||
|
|
||||||
// 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
|
||||||
let use_frame_buffer = if frame_buffer.is_empty() {
|
let frame_buffer = self.active_frame_buffer();
|
||||||
// Fallback to previous buffer to show previous image until new one is available
|
|
||||||
self.previous_frame_buffer.load()
|
|
||||||
} else {
|
|
||||||
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();
|
snapshot.restore();
|
||||||
|
|
||||||
|
|
@ -230,7 +236,7 @@ impl imp::LpImage {
|
||||||
return;
|
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 width = self.image_width(zoom) as f32;
|
||||||
let height = self.image_height(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
|
// Needed for rotating around the center of the image, and
|
||||||
// mirroring the image does not put it to a completely different position.
|
// mirroring the image does not put it to a completely different position.
|
||||||
snapshot.translate(&graphene::Point::new(
|
snapshot.translate(&graphene::Point::new(
|
||||||
-self.round_f32(original_width as f32 * zoom as f32 / 2.),
|
-self.round_f32(untransformed_width as f32 * zoom as f32 / 2.),
|
||||||
-self.round_f32(original_height 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)
|
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 {
|
pub fn widget_width(&self) -> f64 {
|
||||||
self.obj().width() as f64
|
self.obj().width() as f64
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,8 +40,8 @@ impl imp::LpImage {
|
||||||
pub(super) fn zoom_level_best_fit_for_rotation(&self, rotation: f64) -> f64 {
|
pub(super) fn zoom_level_best_fit_for_rotation(&self, rotation: f64) -> f64 {
|
||||||
let rotated = rotation.to_radians().sin().abs();
|
let rotated = rotation.to_radians().sin().abs();
|
||||||
let (image_width, image_height) = (
|
let (image_width, image_height) = (
|
||||||
self.original_dimensions().0 as f64 / self.scaling(),
|
self.untransformed_dimensions().0 as f64 / self.scaling(),
|
||||||
self.original_dimensions().1 as f64 / self.scaling(),
|
self.untransformed_dimensions().1 as f64 / self.scaling(),
|
||||||
);
|
);
|
||||||
let texture_aspect_ratio = image_width / image_height;
|
let texture_aspect_ratio = image_width / image_height;
|
||||||
let widget_aspect_ratio = self.widget_width() / self.widget_height();
|
let widget_aspect_ratio = self.widget_width() / self.widget_height();
|
||||||
|
|
@ -93,7 +93,7 @@ impl imp::LpImage {
|
||||||
let obj = self.obj();
|
let obj = self.obj();
|
||||||
|
|
||||||
if obj.metadata().is_svg() {
|
if obj.metadata().is_svg() {
|
||||||
let (width, height) = self.original_dimensions();
|
let (width, height) = self.untransformed_dimensions();
|
||||||
// Avoid division by 0
|
// Avoid division by 0
|
||||||
let long_side = f64::max(1., i32::max(width, height) as f64);
|
let long_side = f64::max(1., i32::max(width, height) as f64);
|
||||||
// Limit to maz size supported by rsvg
|
// Limit to maz size supported by rsvg
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue