From acc625cd2072ddcf0312e7b3652c00751183c99b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Wed, 21 Aug 2024 12:07:50 +0200 Subject: [PATCH] utils: Allow to provide the requested size when loading an image Useful for SVGs, so the renderer can render it at the size we want, instead of returning the texture at the default size which we resize later and that might result in a blurry image, defeating the purpose of using a vector image. --- src/components/avatar/editable.rs | 21 +++++++++-- src/components/avatar/editable.ui | 2 +- src/components/avatar/image.rs | 12 ++++--- src/components/media/content_viewer.rs | 2 +- .../history_viewer/visual_media_item.rs | 11 +++--- .../room_history/message_row/visual_media.rs | 2 +- src/utils/media/image.rs | 35 +++++++++++++++++-- 7 files changed, 68 insertions(+), 17 deletions(-) diff --git a/src/components/avatar/editable.rs b/src/components/avatar/editable.rs index de48a6e5..e5241e93 100644 --- a/src/components/avatar/editable.rs +++ b/src/components/avatar/editable.rs @@ -14,7 +14,10 @@ use super::{AvatarData, AvatarImage}; use crate::{ components::{ActionButton, ActionState}, toast, - utils::{expression, media::image::load_image}, + utils::{ + expression, + media::image::{load_image, ImageDimensions}, + }, }; /// The state of the editable avatar. @@ -73,6 +76,8 @@ mod imp { #[template_child] pub stack: TemplateChild, #[template_child] + pub temp_avatar: TemplateChild, + #[template_child] pub button_remove: TemplateChild, #[template_child] pub button_edit: TemplateChild, @@ -332,8 +337,20 @@ impl EditableAvatar { self.imp().remove_sensitive.set(sensitive); } + /// The dimensions of the avatar in this widget. + fn avatar_dimensions(&self) -> ImageDimensions { + let scale_factor = self.scale_factor(); + let avatar_size = self.imp().temp_avatar.size(); + let size = (avatar_size * scale_factor) as u32; + + ImageDimensions { + width: size, + height: size, + } + } + async fn set_temp_image_from_file(&self, file: gio::File) { - let paintable = load_image(file).await.ok(); + let paintable = load_image(file, Some(self.avatar_dimensions())).await.ok(); self.set_temp_image(paintable); } diff --git a/src/components/avatar/editable.ui b/src/components/avatar/editable.ui index d5fd21b9..1566fdaa 100644 --- a/src/components/avatar/editable.ui +++ b/src/components/avatar/editable.ui @@ -23,7 +23,7 @@ temp - + 128 true diff --git a/src/components/avatar/image.rs b/src/components/avatar/image.rs index 4bca17eb..dfb15d60 100644 --- a/src/components/avatar/image.rs +++ b/src/components/avatar/image.rs @@ -199,7 +199,12 @@ impl AvatarImage { async fn load_inner(&self, uri: OwnedMxcUri) { let client = self.session().client(); let info = self.info(); + let needed_size = self.needed_size(); + let dimensions = ImageDimensions { + width: needed_size, + height: needed_size, + }; let downloader = ThumbnailDownloader { main: ImageSource { @@ -210,10 +215,7 @@ impl AvatarImage { alt: None, }; let settings = ThumbnailSettings { - dimensions: ImageDimensions { - width: needed_size, - height: needed_size, - }, + dimensions, method: Method::Crop, animated: true, prefer_thumbnail: true, @@ -221,7 +223,7 @@ impl AvatarImage { match downloader.download_to_file(&client, settings).await { Ok(file) => { - let paintable = load_image(file).await.ok(); + let paintable = load_image(file, Some(dimensions)).await.ok(); self.imp().set_paintable(paintable); } Err(error) => error!("Could not fetch avatar: {error}"), diff --git a/src/components/media/content_viewer.rs b/src/components/media/content_viewer.rs index ca740d42..105663d0 100644 --- a/src/components/media/content_viewer.rs +++ b/src/components/media/content_viewer.rs @@ -197,7 +197,7 @@ impl MediaContentViewer { .unwrap_or_default(); match content_type { - ContentType::Image => match load_image(file).await { + ContentType::Image => match load_image(file, None).await { Ok(texture) => { self.view_image(&texture); return; diff --git a/src/session/view/content/room_details/history_viewer/visual_media_item.rs b/src/session/view/content/room_details/history_viewer/visual_media_item.rs index 0a668217..5aaab794 100644 --- a/src/session/view/content/room_details/history_viewer/visual_media_item.rs +++ b/src/session/view/content/room_details/history_viewer/visual_media_item.rs @@ -162,12 +162,13 @@ mod imp { let scale_factor = self.obj().scale_factor(); let size = (THUMBNAIL_SIZE * scale_factor) as u32; + let dimensions = ImageDimensions { + width: size, + height: size, + }; let settings = ThumbnailSettings { - dimensions: ImageDimensions { - width: size, - height: size, - }, + dimensions, method: Method::Scale, animated: false, prefer_thumbnail: false, @@ -182,7 +183,7 @@ mod imp { } }; - match load_image(file).await { + match load_image(file, Some(dimensions)).await { Ok(paintable) => { self.picture.set_paintable(Some(&paintable)); } diff --git a/src/session/view/content/room_history/message_row/visual_media.rs b/src/session/view/content/room_history/message_row/visual_media.rs index 4a91a324..d1d77e48 100644 --- a/src/session/view/content/room_history/message_row/visual_media.rs +++ b/src/session/view/content/room_history/message_row/visual_media.rs @@ -334,7 +334,7 @@ impl MessageVisualMedia { } }; - match load_image(file).await { + match load_image(file, None).await { Ok(paintable) => { let child = if let Some(child) = imp.media.child().and_downcast::() { diff --git a/src/utils/media/image.rs b/src/utils/media/image.rs index 77d93fca..82a9b61a 100644 --- a/src/utils/media/image.rs +++ b/src/utils/media/image.rs @@ -73,11 +73,32 @@ async fn image_reader(file: gio::File) -> Result, glycin: } /// Load the given file as an image into a `GdkPaintable`. -pub async fn load_image(file: gio::File) -> Result { +/// +/// Set `request_dimensions` if the image will be shown at specific dimensions. +/// To show the image at its natural size, set it to `None`. +pub async fn load_image( + file: gio::File, + request_dimensions: Option, +) -> Result { let image = image_reader(file).await?; + let frame_request = request_dimensions.map(|request| { + let image_info = image.info(); + + let original_dimensions = ImageDimensions { + width: image_info.width, + height: image_info.height, + }; + + original_dimensions.to_image_loader_request(request) + }); + let (image, first_frame) = spawn_tokio!(async move { - let first_frame = image.next_frame().await?; + let first_frame = if let Some(frame_request) = frame_request { + image.specific_frame(frame_request).await? + } else { + image.next_frame().await? + }; Ok((image, first_frame)) }) .await @@ -374,6 +395,16 @@ impl ImageDimensions { Some(self.resize(thumbnail_dimensions, ResizeStrategy::Contain)) } + + /// Convert these dimensions to a request for the image loader with the + /// requested dimensions. + pub fn to_image_loader_request( + self, + requested_dimensions: ImageDimensions, + ) -> glycin::FrameRequest { + let resized_dimensions = self.resize(requested_dimensions, ResizeStrategy::Cover); + glycin::FrameRequest::new().scale(resized_dimensions.width, resized_dimensions.height) + } } impl From for BaseImageInfo {