From 9c151cc1c984b7f4181e103f98adc6d61cc6c9da Mon Sep 17 00:00:00 2001 From: Christopher Serr Date: Fri, 16 Sep 2022 23:52:09 +0200 Subject: [PATCH] Let the `Resource Allocator` decode the images We pass the encoded image data into the resource allocator now instead of trying to decode the image in the scene manager. This way the renderer has the ability to support all sorts of image formats that we might not support. For example, this would also allow the renderer to decode GIFs itself and thus properly animate them. --- benches/scene_management.rs | 4 +- src/rendering/mod.rs | 25 ++------- src/rendering/resource/allocation.rs | 13 ++--- src/rendering/resource/handles.rs | 6 +-- src/rendering/software.rs | 81 +++++++++++++++------------- 5 files changed, 62 insertions(+), 67 deletions(-) diff --git a/benches/scene_management.rs b/benches/scene_management.rs index b7a27e07..397ac294 100644 --- a/benches/scene_management.rs +++ b/benches/scene_management.rs @@ -38,7 +38,9 @@ cfg_if::cfg_if! { fn path_builder(&mut self) -> Self::PathBuilder { Dummy } - fn create_image(&mut self, _: u32, _: u32, _: &[u8]) -> Self::Image {} + fn create_image(&mut self, _: &[u8]) -> Option<(Self::Image, f32)> { + Some(((), 1.0)) + } fn create_font(&mut self, _: Option<&Font>, _: FontKind) -> Self::Font {} fn create_label( &mut self, diff --git a/src/rendering/mod.rs b/src/rendering/mod.rs index 623c92fc..a7c93672 100644 --- a/src/rendering/mod.rs +++ b/src/rendering/mod.rs @@ -444,26 +444,11 @@ impl RenderContext<'_, A> { } fn create_icon(&mut self, image_data: &[u8]) -> Option> { - #[cfg(feature = "image")] - { - if image_data.is_empty() { - return None; - } - - let image = image::load_from_memory(image_data).ok()?.to_rgba8(); - - Some(Icon { - aspect_ratio: image.width() as f32 / image.height() as f32, - image: self - .handles - .create_image(image.width(), image.height(), &image), - }) - } - #[cfg(not(feature = "image"))] - { - let _ = image_data; - None - } + let (image, aspect_ratio) = self.handles.create_image(image_data)?; + Some(Icon { + aspect_ratio, + image, + }) } fn scale(&mut self, factor: f32) { diff --git a/src/rendering/resource/allocation.rs b/src/rendering/resource/allocation.rs index 45d1b16d..8f0f6b8b 100644 --- a/src/rendering/resource/allocation.rs +++ b/src/rendering/resource/allocation.rs @@ -59,10 +59,11 @@ pub trait ResourceAllocator { builder.finish() } - /// Creates an image out of the image data provided. The image's resolution - /// is provided as well. The data is an array of RGBA8 encoded pixels (red, - /// green, blue, alpha with each channel being an u8). - fn create_image(&mut self, width: u32, height: u32, data: &[u8]) -> Self::Image; + /// Creates an image out of the image data provided. The data represents the + /// image in its original file format. It needs to be parsed in order to be + /// visualized. The parsed image as well as the aspect ratio (width / + /// height) are returned in case the image was parsed successfully. + fn create_image(&mut self, data: &[u8]) -> Option<(Self::Image, f32)>; /// Creates a font from the font description provided. It is expected that /// the the font description is used in a font matching algorithm to find @@ -191,8 +192,8 @@ impl ResourceAllocator for &mut A { MutPathBuilder((*self).path_builder()) } - fn create_image(&mut self, width: u32, height: u32, data: &[u8]) -> Self::Image { - (*self).create_image(width, height, data) + fn create_image(&mut self, data: &[u8]) -> Option<(Self::Image, f32)> { + (*self).create_image(data) } fn create_font(&mut self, font: Option<&Font>, kind: FontKind) -> Self::Font { diff --git a/src/rendering/resource/handles.rs b/src/rendering/resource/handles.rs index 2b2664eb..1fdfd5f8 100644 --- a/src/rendering/resource/handles.rs +++ b/src/rendering/resource/handles.rs @@ -78,9 +78,9 @@ impl ResourceAllocator for Handles { self.next(circle) } - fn create_image(&mut self, width: u32, height: u32, data: &[u8]) -> Self::Image { - let image = self.allocator.create_image(width, height, data); - self.next(image) + fn create_image(&mut self, data: &[u8]) -> Option<(Self::Image, f32)> { + let (image, aspect_ratio) = self.allocator.create_image(data)?; + Some((self.next(image), aspect_ratio)) } fn create_font(&mut self, font: Option<&Font>, kind: super::FontKind) -> Self::Font { diff --git a/src/rendering/software.rs b/src/rendering/software.rs index 651b96e2..efbd4a30 100644 --- a/src/rendering/software.rs +++ b/src/rendering/software.rs @@ -25,7 +25,7 @@ pub use image::{self, RgbaImage}; struct SkiaBuilder(PathBuilder); type SkiaPath = Option>; -type SkiaImage = Option>; +type SkiaImage = UnsafeRc; type SkiaFont = Font; type SkiaLabel = Label; @@ -87,16 +87,27 @@ impl ResourceAllocator for SkiaAllocator { path_builder() } - fn create_image(&mut self, width: u32, height: u32, data: &[u8]) -> Self::Image { - let mut image = Pixmap::new(width, height)?; - for (d, &[r, g, b, a]) in image - .pixels_mut() - .iter_mut() - .zip(bytemuck::cast_slice::<_, [u8; 4]>(data)) + fn create_image(&mut self, data: &[u8]) -> Option<(Self::Image, f32)> { + #[cfg(feature = "image")] { - *d = tiny_skia::ColorU8::from_rgba(r, g, b, a).premultiply(); + let src = image::load_from_memory(data).ok()?.to_rgba8(); + let (width, height) = (src.width(), src.height()); + // FIXME: Turn the image into a Pixmap directly. We should not need + // to allocate a new Pixmap here. + let mut dst = Pixmap::new(width, height)?; + for (d, &[r, g, b, a]) in dst + .pixels_mut() + .iter_mut() + .zip(bytemuck::cast_slice::(&src)) + { + *d = tiny_skia::ColorU8::from_rgba(r, g, b, a).premultiply(); + } + Some((UnsafeRc::new(dst), width as f32 / height as f32)) + } + #[cfg(not(feature = "image"))] + { + None } - Some(UnsafeRc::new(image)) } fn create_font(&mut self, font: Option<&settings::Font>, kind: FontKind) -> Self::Font { @@ -397,28 +408,26 @@ fn render_layer( } } Entity::Image(image, transform) => { - if let Some(image) = image.as_deref() { - canvas.fill_path( - rectangle, - &Paint { - shader: Pattern::new( - image.as_ref(), - SpreadMode::Pad, - FilterQuality::Bilinear, - 1.0, - tiny_skia::Transform::from_scale( - 1.0 / image.width() as f32, - 1.0 / image.height() as f32, - ), + canvas.fill_path( + rectangle, + &Paint { + shader: Pattern::new( + image.as_ref(), + SpreadMode::Pad, + FilterQuality::Bilinear, + 1.0, + tiny_skia::Transform::from_scale( + 1.0 / image.width() as f32, + 1.0 / image.height() as f32, ), - anti_alias: true, - ..Default::default() - }, - FillRule::Winding, - convert_transform(transform), - None, - ); - } + ), + anti_alias: true, + ..Default::default() + }, + FillRule::Winding, + convert_transform(transform), + None, + ); } Entity::Label(label, shader, transform) => { let label = label.read().unwrap(); @@ -622,13 +631,11 @@ fn calculate_bounds(layer: &[Entity]) -> (f32, f } } } - Entity::Image(image, transform) => { - if image.is_some() { - for y in [0.0, 1.0] { - let transformed_y = transform.transform_y(y); - min_y = min_y.min(transformed_y); - max_y = max_y.max(transformed_y); - } + Entity::Image(_, transform) => { + for y in [0.0, 1.0] { + let transformed_y = transform.transform_y(y); + min_y = min_y.min(transformed_y); + max_y = max_y.max(transformed_y); } } Entity::Label(label, _, transform) => {