diff --git a/src/decoder/tiling.rs b/src/decoder/tiling.rs index 7508b37..d19dc1b 100644 --- a/src/decoder/tiling.rs +++ b/src/decoder/tiling.rs @@ -99,7 +99,7 @@ impl Tile { options: &RenderOptions, ) { // Multiply by scale factor to get the image scaled to `zoom` in physical pixels - let zoom = output_zoom as f32 * options.scale_factor as f32 / self.zoom(); + let zoom = output_zoom as f32 * options.scaling as f32 / self.zoom(); let area = round(&self.area().scale(zoom, zoom)); let area_with_bleed = round(&self.area_with_bleed().scale(zoom, zoom)); @@ -214,7 +214,7 @@ pub struct TileLayer { #[derive(Debug, Clone)] pub struct RenderOptions { pub scaling_filter: gsk::ScalingFilter, - pub scale_factor: i32, + pub scaling: f64, pub background_color: Option, } @@ -231,16 +231,13 @@ impl TiledImage { log::trace!("Rendering {} tiles", tiles.len()); // Scale from application pixels back to physical pixels - snapshot.scale( - 1. / options.scale_factor as f32, - 1. / options.scale_factor as f32, - ); + snapshot.scale(1. / options.scaling as f32, 1. / options.scaling as f32); // reverse to put least fitting tiles in the background for (_, tile) in tiles.iter().rev() { tile.add_to_snapshot(snapshot, zoom, options); } // reset scale - snapshot.scale(options.scale_factor as f32, options.scale_factor as f32); + snapshot.scale(options.scaling as f32, options.scaling as f32); } pub fn push(&mut self, tile: Tile) { diff --git a/src/widgets/image.rs b/src/widgets/image.rs index 7fb64bd..80bbe35 100644 --- a/src/widgets/image.rs +++ b/src/widgets/image.rs @@ -53,6 +53,7 @@ use adw::subclass::prelude::*; use arc_swap::ArcSwap; use futures::prelude::*; use glib::subclass::Signal; +use glib::{Properties, SignalGroup}; use once_cell::sync::Lazy; use std::cell::{Cell, OnceCell, RefCell}; @@ -119,7 +120,6 @@ const THUMBNAIL_SIZE: f32 = 128.; mod imp { use super::*; - use glib::Properties; #[derive(Debug, Default, Properties)] #[properties(wrapper_type = super::LpImage)] @@ -215,7 +215,8 @@ mod imp { pub(super) locked_gestured: Cell>, pub(super) widget_dimensions: Cell<(i32, i32)>, - pub(super) scale_factor: Cell, + pub(super) scaling: Cell, + pub(super) surface_signals: OnceCell, } #[glib::object_subclass] @@ -256,13 +257,29 @@ mod imp { self.zoom.set(1.); self.zoom_target.set(1.); self.best_fit.set(true); - self.scale_factor.set(obj.scale_factor()); + self.scaling.set(obj.scaling()); self.connect_input_handling(); - obj.connect_scale_factor_notify(|obj| { - obj.queue_resize(); - }); + let surface_signals = self + .surface_signals + .get_or_init(SignalGroup::new::); + + surface_signals.connect_notify_local( + Some("scale"), + glib::clone!(@weak obj => move |_, _| { + log::debug!("Scale changed signal"); + obj.queue_resize(); + }), + ); + + obj.connect_realize(glib::clone!(@weak surface_signals => move |obj| { + surface_signals.set_target(obj.native().map(|x| x.surface()).as_ref()); + })); + + obj.connect_unrealize(glib::clone!(@weak surface_signals => move |_| { + surface_signals.set_target(gdk::Surface::NONE); + })); adw::StyleManager::default().connect_dark_notify(glib::clone!(@weak obj => move |_| { glib::spawn_future_local(async move { diff --git a/src/widgets/image/drag.rs b/src/widgets/image/drag.rs index 103e3e3..5e71e62 100644 --- a/src/widgets/image/drag.rs +++ b/src/widgets/image/drag.rs @@ -34,7 +34,7 @@ impl LpImage { let scale = f32::min(1., THUMBNAIL_SIZE / long_side as f32); let render_options = tiling::RenderOptions { scaling_filter: gsk::ScalingFilter::Trilinear, - scale_factor: self.scale_factor(), + scaling: self.scaling(), background_color: Some(self.background_color()), }; diff --git a/src/widgets/image/guess_background_color.rs b/src/widgets/image/guess_background_color.rs index f229302..873534a 100644 --- a/src/widgets/image/guess_background_color.rs +++ b/src/widgets/image/guess_background_color.rs @@ -71,7 +71,7 @@ impl LpImage { let render_options = tiling::RenderOptions { scaling_filter: gsk::ScalingFilter::Nearest, background_color: None, - scale_factor: 1, + scaling: 1., }; self.imp() .frame_buffer diff --git a/src/widgets/image/printing.rs b/src/widgets/image/printing.rs index 261f59d..bf64649 100644 --- a/src/widgets/image/printing.rs +++ b/src/widgets/image/printing.rs @@ -24,7 +24,7 @@ impl LpImage { let render_options = tiling::RenderOptions { scaling_filter: gsk::ScalingFilter::Trilinear, background_color: None, - scale_factor: 1, + scaling: 1., }; let snapshot = gtk::Snapshot::new(); diff --git a/src/widgets/image/rendering.rs b/src/widgets/image/rendering.rs index a7c43dc..7f3bd8f 100644 --- a/src/widgets/image/rendering.rs +++ b/src/widgets/image/rendering.rs @@ -22,9 +22,9 @@ impl WidgetImpl for imp::LpImage { fn size_allocate(&self, width: i32, height: i32, _baseline: i32) { let obj = self.obj(); - let (scale_changed, scale_change) = if obj.scale_factor() != self.scale_factor.get() { - let scale_change = obj.scale_factor() as f64 / self.scale_factor.get() as f64; - self.scale_factor.set(obj.scale_factor()); + let (scale_changed, scale_change) = if obj.scaling() != self.scaling.get() { + let scale_change = obj.scaling() / self.scaling.get(); + self.scaling.set(obj.scaling()); (true, scale_change) } else { (false, 1.) @@ -78,7 +78,7 @@ impl WidgetImpl for imp::LpImage { let render_options = tiling::RenderOptions { scaling_filter, - scale_factor: obj.scale_factor(), + scaling: obj.scaling(), background_color: Some(obj.background_color()), }; @@ -128,65 +128,54 @@ impl WidgetImpl for imp::LpImage { } fn measure(&self, orientation: gtk::Orientation, _for_size: i32) -> (i32, i32, i32, i32) { - let (image_width, image_height) = self.obj().image_size(); + let obj = self.obj(); + let (image_width, image_height) = obj.image_size(); if image_width > 0 && image_height > 0 { - if let Some(display) = gdk::Display::default() { - if let Some(native) = self.obj().native() { - if let Some(monitor) = display.monitor_at_surface(&native.surface()) { - let hidpi_scale = self.obj().scale_factor() as f64; + if let Some((monitor_width, monitor_height)) = obj.monitor_size() { + let hidpi_scale = self.obj().scaling(); - let monitor_geometry = monitor.geometry(); - // TODO: Per documentation those dimensions should not be physical - // pixels. But on Wayland they are physical - // pixels and on X11 not. Taking the version - // that works on Wayland for now. - let monitor_width = monitor_geometry.width() as f64 - 40.; - let monitor_height = monitor_geometry.height() as f64 - 60.; + // areas + let monitor_area = monitor_width * monitor_height; + let image_area = image_width as f64 * image_height as f64; - // areas - let monitor_area = monitor_width * monitor_height; - let image_area = image_width as f64 * image_height as f64; + let occupy_area_factor = if monitor_area < 1024. * 768. { + // for small monitors occupy 80% of the area + 0.8 + } else { + // for large monitors occupy 30% of the area + 0.3 + }; - let occupy_area_factor = if monitor_area < 1024. * 768. { - // for small monitors occupy 80% of the area - 0.8 - } else { - // for large monitors occupy 30% of the area - 0.3 - }; + // factor for width and height that will achieve the desired area + // occupation derived from: + // monitor_area * occupy_area_factor == + // (image_width * size_scale) * (image_height * size_scale) + let size_scale = f64::sqrt(monitor_area / image_area * occupy_area_factor); + // ensure that we never increase image size + let target_scale = f64::min(1.0, size_scale); + let mut nat_width = image_width as f64 * target_scale; + let mut nat_height = image_height as f64 * target_scale; - // factor for width and height that will achieve the desired area - // occupation derived from: - // monitor_area * occupy_area_factor == - // (image_width * size_scale) * (image_height * size_scale) - let size_scale = f64::sqrt(monitor_area / image_area * occupy_area_factor); - // ensure that we never increase image size - let target_scale = f64::min(1.0, size_scale); - let mut nat_width = image_width as f64 * target_scale; - let mut nat_height = image_height as f64 * target_scale; - - // scale down if targeted occupation does not fit in one direction - if nat_width > monitor_width { - nat_width = monitor_width; - nat_height = nat_height * monitor_width / nat_width; - } - - // same for other direction - if nat_height > monitor_height { - nat_height = monitor_height; - nat_width = nat_width * monitor_height / nat_height; - } - - let size = match orientation { - gtk::Orientation::Horizontal => (nat_width / hidpi_scale).round(), - gtk::Orientation::Vertical => (nat_height / hidpi_scale).round(), - _ => unreachable!(), - }; - - return (0, size as i32, -1, -1); - } + // scale down if targeted occupation does not fit in one direction + if nat_width > monitor_width { + nat_width = monitor_width; + nat_height = nat_height * monitor_width / nat_width; } + + // same for other direction + if nat_height > monitor_height { + nat_height = monitor_height; + nat_width = nat_width * monitor_height / nat_height; + } + + let size = match orientation { + gtk::Orientation::Horizontal => (nat_width / hidpi_scale).round(), + gtk::Orientation::Vertical => (nat_height / hidpi_scale).round(), + _ => unreachable!(), + }; + + return (0, size as i32, -1, -1); } } @@ -247,11 +236,11 @@ impl LpImage { /// Returns the area of the image that is visible in physical pixels pub(super) fn viewport(&self) -> graphene::Rect { - let scale_factor = self.scale_factor() as f32; - let x = self.hadjustment().value() as f32 * scale_factor; - let y = self.vadjustment().value() as f32 * scale_factor; - let width = self.width() as f32 * scale_factor; - let height = self.height() as f32 * scale_factor; + let scaling = self.scaling() as f32; + let x = self.hadjustment().value() as f32 * scaling; + let y = self.vadjustment().value() as f32 * scaling; + let width = self.width() as f32 * scaling; + let height = self.height() as f32 * scaling; graphene::Rect::new(x, y, width, height) } @@ -273,7 +262,7 @@ impl LpImage { return number; } - let scale = self.scale_factor() as f64; + let scale = self.scaling(); (number * scale).round() / scale } @@ -282,7 +271,41 @@ impl LpImage { return number; } - let scale = self.scale_factor() as f32; + let scale = self.scaling() as f32; (number * scale).round() / scale } + + pub fn scaling(&self) -> f64 { + self.native() + .map(|x| x.surface().scale()) + .unwrap_or_else(|| self.scale_factor() as f64) + } + + /// Monitor size in physical pixels + pub fn monitor_size(&self) -> Option<(f64, f64)> { + if let Some(display) = gdk::Display::default() { + if let Some(native) = self.native() { + if let Some(monitor) = display.monitor_at_surface(&native.surface()) { + let hidpi_scale = self.scaling(); + let monitor_geometry = monitor.geometry(); + let monitor_size = ( + monitor_geometry.width() as f64, + monitor_geometry.height() as f64, + ); + + // Assume scale-monitor-framebuffer disabled for non-fractional scaling + // + let physical_geometry = if hidpi_scale == self.scale_factor() as f64 { + monitor_size + } else { + (monitor_size.0 * hidpi_scale, monitor_size.1 * hidpi_scale) + }; + + return Some(physical_geometry); + } + } + } + + None + } } diff --git a/src/widgets/image/zoom.rs b/src/widgets/image/zoom.rs index 28da9f9..bdabfb8 100644 --- a/src/widgets/image/zoom.rs +++ b/src/widgets/image/zoom.rs @@ -43,8 +43,8 @@ impl 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.scale_factor() as f64, - self.original_dimensions().1 as f64 / self.scale_factor() as f64, + self.original_dimensions().0 as f64 / self.scaling(), + self.original_dimensions().1 as f64 / self.scaling(), ); let texture_aspect_ratio = image_width / image_height; let widget_aspect_ratio = self.width() as f64 / self.height() as f64; @@ -81,7 +81,7 @@ impl LpImage { } pub(super) fn applicable_zoom(&self) -> f64 { - decoder::tiling::zoom_normalize(self.zoom()) / self.scale_factor() as f64 + decoder::tiling::zoom_normalize(self.zoom()) / self.scaling() } /// Maximal zoom allowed for this image