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| {
|
||||
let mut new_store = (**tiling_store).clone();
|
||||
new_store.current().original_dimensions = Some(size);
|
||||
new_store.original_dimensions();
|
||||
Arc::new(new_store)
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@
|
|||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
pub mod preview;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
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
|
||||
[`LpSlidingView`]: widgets::LpSlidingView
|
||||
[`LpWindowContents`]: widgets::LpWindowContents
|
||||
[`LpWindow`]: window::LpWindow
|
||||
[`LpWindow`]: widgets::LpWindow
|
||||
*/
|
||||
|
||||
use gettextrs::*;
|
||||
|
|
|
|||
|
|
@ -53,6 +53,8 @@ pub enum LpOrientation {
|
|||
}
|
||||
|
||||
mod imp {
|
||||
use glycin::Operation;
|
||||
|
||||
use super::*;
|
||||
use crate::widgets::LpImage;
|
||||
#[derive(Debug, Default, gtk::CompositeTemplate, glib::Properties)]
|
||||
|
|
@ -63,6 +65,8 @@ mod imp {
|
|||
image: TemplateChild<LpImage>,
|
||||
#[template_child]
|
||||
pub(super) selection: TemplateChild<LpEditCropSelection>,
|
||||
#[template_child]
|
||||
pub apply_crop: TemplateChild<gtk::Button>,
|
||||
|
||||
#[property(get, set, builder(LpAspectRatio::default()))]
|
||||
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.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>) {
|
||||
|
|
@ -104,6 +120,48 @@ mod imp {
|
|||
obj.child().set_parent(obj);
|
||||
|
||||
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) {
|
||||
|
|
@ -115,7 +173,7 @@ mod imp {
|
|||
fn size_allocate(&self, width: i32, height: i32, baseline: i32) {
|
||||
let obj = self.obj();
|
||||
|
||||
let image: &TemplateChild<LpImage> = &self.image;
|
||||
let image = &self.image;
|
||||
|
||||
obj.child().allocate(width, height, baseline, None);
|
||||
|
||||
|
|
@ -128,20 +186,6 @@ mod imp {
|
|||
|
||||
self.selection.ensure_initialized(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) {
|
||||
|
|
@ -149,6 +193,80 @@ mod imp {
|
|||
}
|
||||
}
|
||||
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! {
|
||||
|
|
|
|||
|
|
@ -71,11 +71,15 @@
|
|||
</object>
|
||||
</child>
|
||||
<child type="overlay">
|
||||
<object class="GtkButton">
|
||||
<object class="GtkButton" id="apply_crop">
|
||||
<property name="label">Apply</property>
|
||||
<property name="action-name">edit.apply-crop</property>
|
||||
<property name="halign">end</property>
|
||||
<property name="valign">start</property>
|
||||
<style>
|
||||
<class name="osd" />
|
||||
<class name="pill" />
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
|
|
@ -144,6 +148,7 @@
|
|||
<child>
|
||||
<object class="GtkButton">
|
||||
<property name="icon-name">edit-mirror-symbolic</property>
|
||||
<property name="action-name">edit-crop.mirror</property>
|
||||
<property name="valign">start</property>
|
||||
<style>
|
||||
<class name="flat" />
|
||||
|
|
@ -160,6 +165,7 @@
|
|||
<child>
|
||||
<object class="GtkButton">
|
||||
<property name="icon-name">object-rotate-left-symbolic</property>
|
||||
<property name="action-name">edit-crop.rotate-ccw</property>
|
||||
<property name="valign">start</property>
|
||||
<style>
|
||||
<class name="flat" />
|
||||
|
|
@ -169,6 +175,7 @@
|
|||
<child>
|
||||
<object class="GtkButton">
|
||||
<property name="icon-name">object-rotate-right-symbolic</property>
|
||||
<property name="action-name">edit-crop.rotate-cw</property>
|
||||
<property name="valign">start</property>
|
||||
<style>
|
||||
<class name="flat" />
|
||||
|
|
@ -185,6 +192,7 @@
|
|||
<child>
|
||||
<object class="GtkButton">
|
||||
<property name="label">Reset</property>
|
||||
<property name="action-name">edit-crop.reset</property>
|
||||
<property name="valign">start</property>
|
||||
<style>
|
||||
<class name="flat" />
|
||||
|
|
|
|||
|
|
@ -767,6 +767,12 @@ impl LpEditCropSelection {
|
|||
|
||||
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_top(y 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_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 drag;
|
||||
mod editing;
|
||||
mod input_handling;
|
||||
mod loading;
|
||||
mod metadata;
|
||||
|
|
@ -132,6 +133,7 @@ pub enum FitMode {
|
|||
|
||||
mod imp {
|
||||
use decoder::DecoderError;
|
||||
use glycin::Operations;
|
||||
|
||||
use super::*;
|
||||
use crate::decoder::tiling::SharedFrameBuffer;
|
||||
|
|
@ -164,6 +166,7 @@ mod imp {
|
|||
pub(super) frame_buffer: Arc<SharedFrameBuffer>,
|
||||
pub(super) previous_frame_buffer: SharedFrameBuffer,
|
||||
pub(super) decoder: RefCell<Option<Arc<Decoder>>>,
|
||||
pub(super) overwrite_dimensions: Cell<Option<(u32, u32)>>,
|
||||
|
||||
/// Rotation final value (can differ from `rotation` during animation)
|
||||
pub(super) rotation_target: Cell<f64>,
|
||||
|
|
@ -244,6 +247,7 @@ mod imp {
|
|||
|
||||
/// Editing queue
|
||||
pub(super) editing_queue: editing::Queue,
|
||||
pub(super) operations: RefCell<Option<Operations>>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
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();
|
||||
}
|
||||
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();
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue