edit/crop: Read rendered image size from selection

Closes #454
This commit is contained in:
Sophie Herold 2025-01-27 20:12:48 +01:00
parent 1f250b6465
commit e2785d9154
4 changed files with 203 additions and 259 deletions

View file

@ -17,7 +17,7 @@
//! Shows an resizable cropping selection
use std::cell::{Cell, OnceCell};
use std::cell::OnceCell;
use std::marker::PhantomData;
use adw::prelude::*;
@ -68,15 +68,10 @@ mod imp {
#[template(file = "crop.ui")]
pub struct LpEditCrop {
#[template_child]
image: TemplateChild<LpImage>,
pub(super) image: TemplateChild<LpImage>,
#[template_child]
pub(super) selection: TemplateChild<LpEditCropSelection>,
#[property(get, set, builder(LpAspectRatio::default()))]
aspect_ratio: Cell<LpAspectRatio>,
#[property(get, set, builder(LpOrientation::default()))]
orientation: Cell<LpOrientation>,
#[property(get, construct_only)]
original_image: OnceCell<LpImage>,
#[property(get=Self::cropped)]
@ -84,24 +79,17 @@ mod imp {
#[property(get, set)]
child: OnceCell<gtk::Widget>,
/// Last selected crop area
crop_area_image_coord: Cell<Option<(u32, u32, u32, u32)>>,
last_allocation: Cell<(i32, i32, i32)>,
}
#[glib::object_subclass]
impl ObjectSubclass for LpEditCrop {
const NAME: &'static str = "LpEditCrop";
type Type = super::LpEditCrop;
type ParentType = gtk::Widget;
type ParentType = adw::Bin;
fn class_init(klass: &mut Self::Class) {
Self::bind_template(klass);
klass.install_property_action("edit-crop.aspect-ratio", "aspect_ratio");
klass.install_property_action("edit-crop.orientation", "orientation");
klass.install_action("edit-crop.mirror-horizontally", None, |obj, _, _| {
obj.imp().apply_mirror_horizontally();
});
@ -138,41 +126,21 @@ mod imp {
self.image.duplicate_from(&obj.original_image());
let actions = gio::SimpleActionGroup::new();
actions.add_action(&gio::PropertyAction::new(
"aspect-ratio",
&*self.selection,
"aspect_ratio",
));
actions.add_action(&gio::PropertyAction::new(
"orientation",
&*self.selection,
"orientation",
));
obj.insert_action_group("edit-crop", Some(&actions));
obj.action_set_enabled("edit-crop.reset", false);
// 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) {
@ -180,118 +148,20 @@ mod imp {
}
}
impl WidgetImpl for LpEditCrop {
fn root(&self) {
self.parent_root();
let _ = self.apply_reset();
}
fn size_allocate(&self, total_width: i32, total_height: i32, baseline: i32) {
let obj = self.obj();
let image = &self.image;
obj.child()
.allocate(total_width, total_height, baseline, None);
// Adjust position and size for crop selection after widget resize
if self.last_allocation.get() != (total_width, total_height, baseline) {
self.last_allocation
.replace((total_width, total_height, baseline));
let (x, y, width, height) = (
image.image_rendering_x(),
image.image_rendering_y(),
image.image_rendering_width(),
image.image_rendering_height(),
);
self.selection.ensure_initialized(x, y, width, height);
self.selection.set_image_area(x, y, width, height);
let (x, y, width, height) = self.crop_area_widget_coord();
self.selection.set_crop_size(x, y, width, height);
// Allocate just the crop overlay again after it knows its dimensions based on
// the image's dimensions and position
self.selection.allocate(0, 0, -1, None);
}
}
fn measure(&self, orientation: gtk::Orientation, for_size: i32) -> (i32, i32, i32, i32) {
self.obj().child().measure(orientation, for_size)
}
}
impl WidgetImpl for LpEditCrop {}
impl BinImpl for LpEditCrop {}
impl LpEditCrop {
fn selection_changed(&self) {
let crop_area_image_coord = self.crop_area_image_coord();
if self.selection.is_in_user_change() {
log::trace!("Setting crop are in image coordinates to {crop_area_image_coord:?}");
self.crop_area_image_coord.replace(crop_area_image_coord);
}
self.obj().notify_cropped();
}
fn crop_area_image_coord(&self) -> Option<(u32, u32, u32, u32)> {
if !self.cropped() {
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 crop_area_widget_coord(&self) -> (f64, f64, f64, f64) {
let size = self.image.image_size();
let (x, y, w, h) =
self.crop_area_image_coord
.get()
.unwrap_or((0, 0, size.0 as u32, size.1 as u32));
let (x, y) = self.image.img_to_draw_coord((x as f64, y as f64));
let (w, h) = self.image.img_to_draw_coord((w as f64, h as f64));
(x.round(), y.round(), w.round(), h.round())
}
fn cropped(&self) -> bool {
self.selection.is_cropped()
}
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);
self.selection.reset();
}
pub(super) fn apply_crop(&self) {
if let Some(crop) = self.crop_area_image_coord.get() {
if let Some(crop) = self.selection.crop_area_image_coord() {
self.add_operation(Operation::Clip(crop));
self.reset_selection();
@ -410,4 +280,8 @@ impl LpEditCrop {
pub fn apply_crop(&self) {
self.imp().apply_crop();
}
pub fn image(&self) -> LpImage {
self.imp().image.clone()
}
}