From 867fe819c098a1d583e0d3e46ddf7eb1f292c2be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 28 Oct 2025 21:19:25 +0100 Subject: [PATCH] Add explicit error handling to image loading --- core/src/image.rs | 56 +++++++++++-- core/src/renderer.rs | 4 +- core/src/renderer/null.rs | 18 ++++- examples/gallery/src/civitai.rs | 8 +- examples/gallery/src/main.rs | 11 ++- graphics/src/image.rs | 28 +++++-- renderer/src/fallback.rs | 13 +++- runtime/src/image.rs | 6 +- runtime/src/task.rs | 14 ++++ src/lib.rs | 2 +- tiny_skia/src/lib.rs | 24 +++++- tiny_skia/src/raster.rs | 108 +++++++++++++++---------- wgpu/src/image/cache.rs | 134 +++++++++++++++++++++++++++----- wgpu/src/image/raster.rs | 19 +---- wgpu/src/lib.rs | 17 +++- widget/src/image.rs | 5 +- widget/src/image/viewer.rs | 8 +- 17 files changed, 357 insertions(+), 118 deletions(-) diff --git a/core/src/image.rs b/core/src/image.rs index 65342dae..9540140c 100644 --- a/core/src/image.rs +++ b/core/src/image.rs @@ -7,6 +7,7 @@ use crate::{Radians, Rectangle, Size}; use rustc_hash::FxHasher; use std::hash::{Hash, Hasher}; +use std::io; use std::path::{Path, PathBuf}; use std::sync::{Arc, Weak}; @@ -258,7 +259,10 @@ pub struct Allocation(Arc); /// Some memory taken by an [`Allocation`]. #[derive(Debug, Clone, PartialEq, Eq)] -pub struct Memory(Handle); +pub struct Memory { + handle: Handle, + size: Size, +} impl Allocation { /// Returns a weak reference to the [`Memory`] of the [`Allocation`]. @@ -273,7 +277,12 @@ impl Allocation { /// Returns the [`Handle`] of this [`Allocation`]. pub fn handle(&self) -> &Handle { - &self.0.0 + &self.0.handle + } + + /// Returns the [`Size`] of the image of this [`Allocation`]. + pub fn size(&self) -> Size { + self.0.size } } @@ -284,8 +293,11 @@ impl Allocation { /// # Safety /// Must only be created once the [`Handle`] is allocated in memory. #[allow(unsafe_code)] -pub unsafe fn allocate(handle: &Handle) -> Allocation { - Allocation(Arc::new(Memory(handle.clone()))) +pub unsafe fn allocate(handle: &Handle, size: Size) -> Allocation { + Allocation(Arc::new(Memory { + handle: handle.clone(), + size, + })) } /// A [`Renderer`] that can render raster graphics. @@ -297,10 +309,27 @@ pub trait Renderer: crate::Renderer { /// [`Handle`]: Self::Handle type Handle: Clone; + /// Loads an image and returns an explicit [`Allocation`] to it. + /// + /// If the image is not already loaded, this method will block! You should + /// generally not use it in drawing logic if you want to avoid frame drops. + fn load_image(&self, handle: &Self::Handle) -> Result; + /// Returns the dimensions of an image for the given [`Handle`]. - fn measure_image(&self, handle: &Self::Handle) -> Size; + /// + /// If the image is not already loaded, the [`Renderer`] may choose to return + /// `None`, load the image in the background, and then trigger a relayout. + /// + /// If you need a measurement right away, consider using [`Renderer::load_image`]. + fn measure_image(&self, handle: &Self::Handle) -> Option>; /// Draws an [`Image`] inside the provided `bounds`. + /// + /// If the image is not already loaded, the [`Renderer`] may choose to render + /// nothing, load the image in the background, and then trigger a redraw. + /// + /// If you need to draw an image right away, consider using [`Renderer::load_image`] + /// and hold on to an [`Allocation`] first. fn draw_image( &mut self, image: Image, @@ -308,3 +337,20 @@ pub trait Renderer: crate::Renderer { clip_bounds: Rectangle, ); } + +/// An image loading error. +#[derive(Debug, Clone, thiserror::Error)] +pub enum Error { + /// The image data was invalid or could not be decoded. + #[error("the image data was invalid or could not be decoded: {0}")] + Invalid(Arc), + /// The image file was not found. + #[error("the image file could not be opened: {0}")] + Inaccessible(Arc), + /// Loading images is unsupported. + #[error("loading images is unsupported")] + Unsupported, + /// Not enough memory to allocate the image. + #[error("not enough memory to allocate the image")] + OutOfMemory, +} diff --git a/core/src/renderer.rs b/core/src/renderer.rs index 6e24f661..89157c56 100644 --- a/core/src/renderer.rs +++ b/core/src/renderer.rs @@ -68,7 +68,9 @@ pub trait Renderer { fn allocate_image( &mut self, handle: &image::Handle, - callback: impl FnOnce(image::Allocation) + Send + 'static, + callback: impl FnOnce(Result) + + Send + + 'static, ); } diff --git a/core/src/renderer/null.rs b/core/src/renderer/null.rs index 87b6ed19..3c17265d 100644 --- a/core/src/renderer/null.rs +++ b/core/src/renderer/null.rs @@ -28,10 +28,12 @@ impl Renderer for () { fn allocate_image( &mut self, handle: &image::Handle, - callback: impl FnOnce(image::Allocation) + Send + 'static, + callback: impl FnOnce(Result) + + Send + + 'static, ) { #[allow(unsafe_code)] - callback(unsafe { image::allocate(handle) }); + callback(Ok(unsafe { image::allocate(handle, Size::new(100, 100)) })); } } @@ -213,8 +215,16 @@ impl text::Editor for () { impl image::Renderer for () { type Handle = image::Handle; - fn measure_image(&self, _handle: &Self::Handle) -> Size { - Size::default() + fn load_image( + &self, + handle: &Self::Handle, + ) -> Result { + #[allow(unsafe_code)] + Ok(unsafe { image::allocate(handle, Size::new(100, 100)) }) + } + + fn measure_image(&self, _handle: &Self::Handle) -> Option> { + Some(Size::new(100, 100)) } fn draw_image( diff --git a/examples/gallery/src/civitai.rs b/examples/gallery/src/civitai.rs index 94ca69c4..f7475525 100644 --- a/examples/gallery/src/civitai.rs +++ b/examples/gallery/src/civitai.rs @@ -166,7 +166,7 @@ pub enum Error { RequestFailed(Arc), IOFailed(Arc), JoinFailed(Arc), - ImageDecodingFailed(Arc), + ImageDecodingFailed, BlurhashDecodingFailed(Arc), } @@ -188,12 +188,6 @@ impl From for Error { } } -impl From for Error { - fn from(error: image::ImageError) -> Self { - Self::ImageDecodingFailed(Arc::new(error)) - } -} - impl From for Error { fn from(error: blurhash::Error) -> Self { Self::BlurhashDecodingFailed(Arc::new(error)) diff --git a/examples/gallery/src/main.rs b/examples/gallery/src/main.rs index 99bd2cca..f6ed0077 100644 --- a/examples/gallery/src/main.rs +++ b/examples/gallery/src/main.rs @@ -49,7 +49,7 @@ enum Message { ImagePoppedOut(Id), ImageDownloaded(Result), ThumbnailDownloaded(Id, Result), - ThumbnailAllocated(Id, image::Allocation), + ThumbnailAllocated(Id, Result), ThumbnailHovered(Id, bool), BlurhashDecoded(Id, civitai::Blurhash), Open(Id), @@ -175,7 +175,7 @@ impl Gallery { .then(image::allocate) .map(Message::ThumbnailAllocated.with(id)) } - Message::ThumbnailAllocated(id, allocation) => { + Message::ThumbnailAllocated(id, Ok(allocation)) => { if !self.visible.contains(&id) { return Task::none(); } @@ -221,7 +221,7 @@ impl Gallery { Task::future(image.download(Size::Original)) .and_then(|bytes| { image::allocate(image::Handle::from_bytes(bytes)) - .map(Ok) + .map_err(|_| Error::ImageDecodingFailed) }) .map(Message::ImageDownloaded) } @@ -236,6 +236,11 @@ impl Gallery { | Message::ThumbnailDownloaded(_, Err(error)) => { dbg!(error); + Task::none() + } + Message::ThumbnailAllocated(_, Err(error)) => { + dbg!(error); + Task::none() } } diff --git a/graphics/src/image.rs b/graphics/src/image.rs index 6f74b721..c844aa9e 100644 --- a/graphics/src/image.rs +++ b/graphics/src/image.rs @@ -6,6 +6,8 @@ use crate::core::Rectangle; use crate::core::image; use crate::core::svg; +use std::sync::Arc; + /// A raster or vector image. #[allow(missing_docs)] #[derive(Debug, Clone, PartialEq)] @@ -37,14 +39,14 @@ impl Image { } } +/// An image buffer. +pub type Buffer = ::image::ImageBuffer<::image::Rgba, image::Bytes>; + #[cfg(feature = "image")] /// Tries to load an image by its [`Handle`]. /// /// [`Handle`]: image::Handle -pub fn load( - handle: &image::Handle, -) -> ::image::ImageResult<::image::ImageBuffer<::image::Rgba, image::Bytes>> -{ +pub fn load(handle: &image::Handle) -> Result { use bitflags::bitflags; bitflags! { @@ -96,7 +98,7 @@ pub fn load( let (width, height, pixels) = match handle { image::Handle::Path(_, path) => { - let image = ::image::open(path)?; + let image = ::image::open(path).map_err(to_error)?; let operation = std::fs::File::open(path) .ok() @@ -113,7 +115,8 @@ pub fn load( ) } image::Handle::Bytes(_, bytes) => { - let image = ::image::load_from_memory(bytes)?; + let image = ::image::load_from_memory(bytes).map_err(to_error)?; + let operation = Operation::from_exif(&mut std::io::Cursor::new(bytes)) .ok() @@ -138,10 +141,19 @@ pub fn load( if let Some(image) = ::image::ImageBuffer::from_raw(width, height, pixels) { Ok(image) } else { - Err(::image::error::ImageError::Limits( + Err(to_error(::image::error::ImageError::Limits( ::image::error::LimitError::from_kind( ::image::error::LimitErrorKind::DimensionError, ), - )) + ))) + } +} + +fn to_error(error: ::image::ImageError) -> image::Error { + match error { + ::image::ImageError::IoError(error) => { + image::Error::Inaccessible(Arc::new(error)) + } + error => image::Error::Invalid(Arc::new(error)), } } diff --git a/renderer/src/fallback.rs b/renderer/src/fallback.rs index 01e49390..16cec4a3 100644 --- a/renderer/src/fallback.rs +++ b/renderer/src/fallback.rs @@ -73,7 +73,9 @@ where fn allocate_image( &mut self, handle: &image::Handle, - callback: impl FnOnce(image::Allocation) + Send + 'static, + callback: impl FnOnce(Result) + + Send + + 'static, ) { delegate!(self, renderer, renderer.allocate_image(handle, callback)); } @@ -154,7 +156,14 @@ where { type Handle = A::Handle; - fn measure_image(&self, handle: &Self::Handle) -> Size { + fn load_image( + &self, + handle: &Self::Handle, + ) -> Result { + delegate!(self, renderer, renderer.load_image(handle)) + } + + fn measure_image(&self, handle: &Self::Handle) -> Option> { delegate!(self, renderer, renderer.measure_image(handle)) } diff --git a/runtime/src/image.rs b/runtime/src/image.rs index 2e2cfa69..53b736cc 100644 --- a/runtime/src/image.rs +++ b/runtime/src/image.rs @@ -3,13 +3,13 @@ use crate::core::image::Handle; use crate::futures::futures::channel::oneshot; use crate::task::{self, Task}; -pub use crate::core::image::Allocation; +pub use crate::core::image::{Allocation, Error}; /// An image action. #[derive(Debug)] pub enum Action { /// Allocates the given [`Handle`]. - Allocate(Handle, oneshot::Sender), + Allocate(Handle, oneshot::Sender>), } /// Allocates an image [`Handle`]. @@ -17,7 +17,7 @@ pub enum Action { /// When you obtain an [`Allocation`] explicitly, you get the guarantee /// that using a [`Handle`] will draw the corresponding image immediately /// in the next frame. -pub fn allocate(handle: impl Into) -> Task { +pub fn allocate(handle: impl Into) -> Task> { task::oneshot(|sender| { crate::Action::Image(Action::Allocate(handle.into(), sender)) }) diff --git a/runtime/src/task.rs b/runtime/src/task.rs index 97f64b61..209c1176 100644 --- a/runtime/src/task.rs +++ b/runtime/src/task.rs @@ -390,6 +390,20 @@ impl Task> { result.map_or_else(|error| Task::done(Err(error)), &f) }) } + + /// Maps the error type of this [`Task`] to a different one using the given + /// function. + pub fn map_err( + self, + f: impl Fn(E) -> E2 + MaybeSend + 'static, + ) -> Task> + where + T: MaybeSend + 'static, + E: MaybeSend + 'static, + E2: MaybeSend + 'static, + { + self.map(move |result| result.map_err(&f)) + } } impl Default for Task { diff --git a/src/lib.rs b/src/lib.rs index 88d26ee1..b3933670 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -628,7 +628,7 @@ pub mod widget { #[cfg(feature = "image")] pub mod image { //! Images display raster graphics in different formats (PNG, JPG, etc.). - pub use iced_runtime::image::{Allocation, allocate}; + pub use iced_runtime::image::{Allocation, Error, allocate}; pub use iced_widget::image::*; } diff --git a/tiny_skia/src/lib.rs b/tiny_skia/src/lib.rs index 159f8155..0d04c033 100644 --- a/tiny_skia/src/lib.rs +++ b/tiny_skia/src/lib.rs @@ -232,11 +232,17 @@ impl core::Renderer for Renderer { fn allocate_image( &mut self, handle: &core::image::Handle, - callback: impl FnOnce(core::image::Allocation) + Send + 'static, + callback: impl FnOnce(Result) + + Send + + 'static, ) { - // TODO: Concurrency + #[cfg(feature = "image")] #[allow(unsafe_code)] - callback(unsafe { core::image::allocate(handle) }); + // TODO: Concurrency + callback(self.engine.raster_pipeline.load(handle)); + + #[cfg(not(feature = "image"))] + callback(Err(core::image::Error::Unsupported)) } } @@ -360,7 +366,17 @@ impl graphics::mesh::Renderer for Renderer { impl core::image::Renderer for Renderer { type Handle = core::image::Handle; - fn measure_image(&self, handle: &Self::Handle) -> crate::core::Size { + fn load_image( + &self, + handle: &Self::Handle, + ) -> Result { + self.engine.raster_pipeline.load(handle) + } + + fn measure_image( + &self, + handle: &Self::Handle, + ) -> Option> { self.engine.raster_pipeline.dimensions(handle) } diff --git a/tiny_skia/src/raster.rs b/tiny_skia/src/raster.rs index c40f55b2..56ed8c47 100644 --- a/tiny_skia/src/raster.rs +++ b/tiny_skia/src/raster.rs @@ -18,12 +18,24 @@ impl Pipeline { } } - pub fn dimensions(&self, handle: &raster::Handle) -> Size { - if let Some(image) = self.cache.borrow_mut().allocate(handle) { - Size::new(image.width(), image.height()) - } else { - Size::new(0, 0) - } + pub fn load( + &self, + handle: &raster::Handle, + ) -> Result { + let mut cache = self.cache.borrow_mut(); + let image = cache.allocate(handle)?; + + #[allow(unsafe_code)] + Ok(unsafe { + raster::allocate(handle, Size::new(image.width(), image.height())) + }) + } + + pub fn dimensions(&self, handle: &raster::Handle) -> Option> { + let mut cache = self.cache.borrow_mut(); + let image = cache.allocate(handle).ok()?; + + Some(Size::new(image.width(), image.height())) } pub fn draw( @@ -36,34 +48,34 @@ impl Pipeline { transform: tiny_skia::Transform, clip_mask: Option<&tiny_skia::Mask>, ) { - if let Some(image) = self.cache.borrow_mut().allocate(handle) { - let width_scale = bounds.width / image.width() as f32; - let height_scale = bounds.height / image.height() as f32; + let mut cache = self.cache.borrow_mut(); - let transform = transform.pre_scale(width_scale, height_scale); + let Ok(image) = cache.allocate(handle) else { + return; + }; - let quality = match filter_method { - raster::FilterMethod::Linear => { - tiny_skia::FilterQuality::Bilinear - } - raster::FilterMethod::Nearest => { - tiny_skia::FilterQuality::Nearest - } - }; + let width_scale = bounds.width / image.width() as f32; + let height_scale = bounds.height / image.height() as f32; - pixels.draw_pixmap( - (bounds.x / width_scale) as i32, - (bounds.y / height_scale) as i32, - image, - &tiny_skia::PixmapPaint { - quality, - opacity, - ..Default::default() - }, - transform, - clip_mask, - ); - } + let transform = transform.pre_scale(width_scale, height_scale); + + let quality = match filter_method { + raster::FilterMethod::Linear => tiny_skia::FilterQuality::Bilinear, + raster::FilterMethod::Nearest => tiny_skia::FilterQuality::Nearest, + }; + + pixels.draw_pixmap( + (bounds.x / width_scale) as i32, + (bounds.y / height_scale) as i32, + image, + &tiny_skia::PixmapPaint { + quality, + opacity, + ..Default::default() + }, + transform, + clip_mask, + ); } pub fn trim_cache(&mut self) { @@ -81,11 +93,18 @@ impl Cache { pub fn allocate( &mut self, handle: &raster::Handle, - ) -> Option> { + ) -> Result, raster::Error> { let id = handle.id(); if let hash_map::Entry::Vacant(entry) = self.entries.entry(id) { - let image = graphics::image::load(handle).ok()?; + let image = match graphics::image::load(handle) { + Ok(image) => image, + Err(error) => { + let _ = entry.insert(None); + + return Err(error); + } + }; let mut buffer = vec![0u32; image.width() as usize * image.height() as usize]; @@ -106,14 +125,21 @@ impl Cache { } let _ = self.hits.insert(id); - self.entries.get(&id).unwrap().as_ref().map(|entry| { - tiny_skia::PixmapRef::from_bytes( - bytemuck::cast_slice(&entry.pixels), - entry.width, - entry.height, - ) - .expect("Build pixmap from image bytes") - }) + + Ok(self + .entries + .get(&id) + .unwrap() + .as_ref() + .map(|entry| { + tiny_skia::PixmapRef::from_bytes( + bytemuck::cast_slice(&entry.pixels), + entry.width, + entry.height, + ) + .expect("Build pixmap from image bytes") + }) + .expect("Image should be allocated")) } fn trim(&mut self) { diff --git a/wgpu/src/image/cache.rs b/wgpu/src/image/cache.rs index 4ac46708..f832b239 100644 --- a/wgpu/src/image/cache.rs +++ b/wgpu/src/image/cache.rs @@ -38,6 +38,7 @@ impl Cache { raster: Raster { cache: crate::image::raster::Cache::default(), pending: HashMap::new(), + belt: wgpu::util::StagingBelt::new(2 * 1024 * 1024), }, #[cfg(feature = "svg")] vector: crate::image::vector::Cache::default(), @@ -50,7 +51,9 @@ impl Cache { pub fn allocate_image( &mut self, handle: &core::image::Handle, - callback: impl FnOnce(core::image::Allocation) + Send + 'static, + callback: impl FnOnce(Result) + + Send + + 'static, ) { use crate::image::raster::Memory; @@ -61,21 +64,22 @@ impl Cache { return; } - if let Some(Memory::Device { allocation, .. }) = - self.raster.cache.get_mut(handle) + if let Some(Memory::Device { + allocation, entry, .. + }) = self.raster.cache.get_mut(handle) { if let Some(allocation) = allocation .as_ref() .and_then(core::image::Allocation::upgrade) { - callback(allocation); + callback(Ok(allocation)); return; } #[allow(unsafe_code)] - let new = unsafe { core::image::allocate(handle) }; + let new = unsafe { core::image::allocate(handle, entry.size()) }; *allocation = Some(new.downgrade()); - callback(new); + callback(Ok(new)); return; } @@ -87,21 +91,104 @@ impl Cache { } #[cfg(feature = "image")] - pub fn measure_image(&mut self, handle: &core::image::Handle) -> Size { + pub fn load_image( + &mut self, + device: &wgpu::Device, + queue: &wgpu::Queue, + handle: &core::image::Handle, + ) -> Result { + use crate::image::raster::Memory; + + if !self.raster.cache.contains(handle) { + self.raster.cache.insert(handle, Memory::load(handle)); + } + + match self.raster.cache.get_mut(handle).unwrap() { + Memory::Host(image) => { + let mut encoder = device.create_command_encoder( + &wgpu::CommandEncoderDescriptor { + label: Some("raster image upload"), + }, + ); + + let entry = self.atlas.upload( + device, + &mut encoder, + &mut self.raster.belt, + image.width(), + image.height(), + image, + ); + + self.raster.belt.finish(); + let submission = queue.submit([encoder.finish()]); + self.raster.belt.recall(); + + let Some(entry) = entry else { + return Err(core::image::Error::OutOfMemory); + }; + + let _ = device + .poll(wgpu::PollType::WaitForSubmissionIndex(submission)); + + #[allow(unsafe_code)] + let allocation = unsafe { + core::image::allocate( + handle, + Size::new(image.width(), image.height()), + ) + }; + + self.raster.cache.insert( + handle, + Memory::Device { + entry, + bind_group: None, + allocation: Some(allocation.downgrade()), + }, + ); + + Ok(allocation) + } + Memory::Device { + entry, allocation, .. + } => { + if let Some(allocation) = allocation + .as_ref() + .and_then(core::image::Allocation::upgrade) + { + return Ok(allocation); + } + + #[allow(unsafe_code)] + let new = + unsafe { core::image::allocate(handle, entry.size()) }; + + *allocation = Some(new.downgrade()); + + Ok(new) + } + Memory::Error(error) => Err(error.clone()), + } + } + + #[cfg(feature = "image")] + pub fn measure_image( + &mut self, + handle: &core::image::Handle, + ) -> Option> { self.receive(); - if let Some(memory) = load_image( + let image = load_image( &mut self.raster.cache, &mut self.raster.pending, #[cfg(not(target_arch = "wasm32"))] &self.worker, handle, None, - ) { - return memory.dimensions(); - } + )?; - Size::new(0, 0) + Some(image.dimensions()) } #[cfg(feature = "svg")] @@ -230,13 +317,14 @@ impl Cache { let allocation = if let Some(callbacks) = callbacks { #[allow(unsafe_code)] - let allocation = - unsafe { core::image::allocate(&handle) }; + let allocation = unsafe { + core::image::allocate(&handle, entry.size()) + }; let reference = allocation.downgrade(); for callback in callbacks { - callback(allocation.clone()); + callback(Ok(allocation.clone())); } Some(reference) @@ -254,7 +342,15 @@ impl Cache { ); } worker::Work::Error { handle, error } => { - self.raster.cache.insert(&handle, Memory::error(error)); + let callbacks = self.raster.pending.remove(&handle.id()); + + if let Some(callbacks) = callbacks { + for callback in callbacks { + callback(Err(error.clone())); + } + } + + self.raster.cache.insert(&handle, Memory::Error(error)); } } } @@ -272,10 +368,12 @@ impl Drop for Cache { struct Raster { cache: crate::image::raster::Cache, pending: HashMap>, + belt: wgpu::util::StagingBelt, } #[cfg(feature = "image")] -type Callback = Box; +type Callback = + Box) + Send>; #[cfg(feature = "image")] fn load_image<'a>( @@ -418,7 +516,7 @@ mod worker { }, Error { handle: image::Handle, - error: crate::graphics::image::image_rs::error::ImageError, + error: image::Error, }, } diff --git a/wgpu/src/image/raster.rs b/wgpu/src/image/raster.rs index 4300e935..5320a3c1 100644 --- a/wgpu/src/image/raster.rs +++ b/wgpu/src/image/raster.rs @@ -20,24 +20,14 @@ pub enum Memory { bind_group: Option>, allocation: Option>, }, - /// Image not found - NotFound, - /// Invalid image data - Invalid, + Error(image::Error), } impl Memory { pub fn load(handle: &image::Handle) -> Self { match graphics::image::load(handle) { Ok(image) => Self::Host(image), - Err(error) => Self::error(error), - } - } - - pub fn error(error: image_rs::error::ImageError) -> Self { - match error { - image_rs::error::ImageError::IoError(_) => Self::NotFound, - _ => Self::Invalid, + Err(error) => Self::Error(error), } } @@ -49,15 +39,14 @@ impl Memory { Size::new(width, height) } Memory::Device { entry, .. } => entry.size(), - Memory::NotFound => Size::new(1, 1), - Memory::Invalid => Size::new(1, 1), + Memory::Error(_) => Size::new(1, 1), } } pub fn host(&self) -> Option { match self { Memory::Host(image) => Some(image.clone()), - Memory::Device { .. } | Memory::NotFound | Memory::Invalid => None, + Memory::Device { .. } | Memory::Error(_) => None, } } } diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index e0a7e969..01de8800 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -702,7 +702,9 @@ impl core::Renderer for Renderer { fn allocate_image( &mut self, _handle: &core::image::Handle, - _callback: impl FnOnce(core::image::Allocation) + Send + 'static, + _callback: impl FnOnce(Result) + + Send + + 'static, ) { #[cfg(feature = "image")] self.image_cache @@ -773,7 +775,18 @@ impl core::text::Renderer for Renderer { impl core::image::Renderer for Renderer { type Handle = core::image::Handle; - fn measure_image(&self, handle: &Self::Handle) -> core::Size { + fn load_image( + &self, + handle: &Self::Handle, + ) -> Result { + self.image_cache.borrow_mut().load_image( + &self.engine.device, + &self.engine.queue, + handle, + ) + } + + fn measure_image(&self, handle: &Self::Handle) -> Option> { self.image_cache.borrow_mut().measure_image(handle) } diff --git a/widget/src/image.rs b/widget/src/image.rs index e40c9b6d..c5108d81 100644 --- a/widget/src/image.rs +++ b/widget/src/image.rs @@ -197,7 +197,8 @@ where Renderer: image::Renderer, { // The raw w/h of the underlying image - let image_size = crop(renderer.measure_image(handle), region); + let image_size = + crop(renderer.measure_image(handle).unwrap_or_default(), region); // The rotated size of the image let rotated_size = rotation.apply(image_size); @@ -239,7 +240,7 @@ fn drawing_bounds( where Renderer: image::Renderer, { - let original_size = renderer.measure_image(handle); + let original_size = renderer.measure_image(handle).unwrap_or_default(); let image_size = crop(original_size, region); let rotated_size = rotation.apply(image_size); let adjusted_fit = content_fit.fit(rotated_size, bounds.size()); diff --git a/widget/src/image/viewer.rs b/widget/src/image/viewer.rs index 2fab9013..209b3728 100644 --- a/widget/src/image/viewer.rs +++ b/widget/src/image/viewer.rs @@ -123,7 +123,9 @@ where limits: &layout::Limits, ) -> layout::Node { // The raw w/h of the underlying image - let image_size = renderer.measure_image(&self.handle); + let image_size = + renderer.measure_image(&self.handle).unwrap_or_default(); + let image_size = Size::new(image_size.width as f32, image_size.height as f32); @@ -436,7 +438,9 @@ pub fn scaled_image_size( where Renderer: image::Renderer, { - let Size { width, height } = renderer.measure_image(handle); + let Size { width, height } = + renderer.measure_image(handle).unwrap_or_default(); + let image_size = Size::new(width as f32, height as f32); let adjusted_fit = content_fit.fit(image_size, bounds);