Add explicit error handling to image loading

This commit is contained in:
Héctor Ramón Jiménez 2025-10-28 21:19:25 +01:00
parent 7c11ccb046
commit 867fe819c0
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
17 changed files with 357 additions and 118 deletions

View file

@ -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<Memory>);
/// Some memory taken by an [`Allocation`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Memory(Handle);
pub struct Memory {
handle: Handle,
size: Size<u32>,
}
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<u32> {
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<u32>) -> 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<Allocation, Error>;
/// Returns the dimensions of an image for the given [`Handle`].
fn measure_image(&self, handle: &Self::Handle) -> Size<u32>;
///
/// 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<Size<u32>>;
/// 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<Self::Handle>,
@ -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<dyn std::error::Error + Send + Sync>),
/// The image file was not found.
#[error("the image file could not be opened: {0}")]
Inaccessible(Arc<io::Error>),
/// 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,
}

View file

@ -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<image::Allocation, image::Error>)
+ Send
+ 'static,
);
}

View file

@ -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<image::Allocation, image::Error>)
+ 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<u32> {
Size::default()
fn load_image(
&self,
handle: &Self::Handle,
) -> Result<image::Allocation, image::Error> {
#[allow(unsafe_code)]
Ok(unsafe { image::allocate(handle, Size::new(100, 100)) })
}
fn measure_image(&self, _handle: &Self::Handle) -> Option<Size<u32>> {
Some(Size::new(100, 100))
}
fn draw_image(

View file

@ -166,7 +166,7 @@ pub enum Error {
RequestFailed(Arc<reqwest::Error>),
IOFailed(Arc<io::Error>),
JoinFailed(Arc<task::JoinError>),
ImageDecodingFailed(Arc<image::ImageError>),
ImageDecodingFailed,
BlurhashDecodingFailed(Arc<blurhash::Error>),
}
@ -188,12 +188,6 @@ impl From<task::JoinError> for Error {
}
}
impl From<image::ImageError> for Error {
fn from(error: image::ImageError) -> Self {
Self::ImageDecodingFailed(Arc::new(error))
}
}
impl From<blurhash::Error> for Error {
fn from(error: blurhash::Error) -> Self {
Self::BlurhashDecodingFailed(Arc::new(error))

View file

@ -49,7 +49,7 @@ enum Message {
ImagePoppedOut(Id),
ImageDownloaded(Result<image::Allocation, Error>),
ThumbnailDownloaded(Id, Result<Bytes, Error>),
ThumbnailAllocated(Id, image::Allocation),
ThumbnailAllocated(Id, Result<image::Allocation, image::Error>),
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()
}
}

View file

@ -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<u8>, 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<u8>, image::Bytes>>
{
pub fn load(handle: &image::Handle) -> Result<Buffer, image::Error> {
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)),
}
}

View file

@ -73,7 +73,9 @@ where
fn allocate_image(
&mut self,
handle: &image::Handle,
callback: impl FnOnce(image::Allocation) + Send + 'static,
callback: impl FnOnce(Result<image::Allocation, image::Error>)
+ 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<u32> {
fn load_image(
&self,
handle: &Self::Handle,
) -> Result<image::Allocation, image::Error> {
delegate!(self, renderer, renderer.load_image(handle))
}
fn measure_image(&self, handle: &Self::Handle) -> Option<Size<u32>> {
delegate!(self, renderer, renderer.measure_image(handle))
}

View file

@ -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<Allocation>),
Allocate(Handle, oneshot::Sender<Result<Allocation, Error>>),
}
/// 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<Handle>) -> Task<Allocation> {
pub fn allocate(handle: impl Into<Handle>) -> Task<Result<Allocation, Error>> {
task::oneshot(|sender| {
crate::Action::Image(Action::Allocate(handle.into(), sender))
})

View file

@ -390,6 +390,20 @@ impl<T, E> Task<Result<T, E>> {
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<E2>(
self,
f: impl Fn(E) -> E2 + MaybeSend + 'static,
) -> Task<Result<T, E2>>
where
T: MaybeSend + 'static,
E: MaybeSend + 'static,
E2: MaybeSend + 'static,
{
self.map(move |result| result.map_err(&f))
}
}
impl<T> Default for Task<T> {

View file

@ -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::*;
}

View file

@ -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<core::image::Allocation, core::image::Error>)
+ 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<u32> {
fn load_image(
&self,
handle: &Self::Handle,
) -> Result<core::image::Allocation, core::image::Error> {
self.engine.raster_pipeline.load(handle)
}
fn measure_image(
&self,
handle: &Self::Handle,
) -> Option<crate::core::Size<u32>> {
self.engine.raster_pipeline.dimensions(handle)
}

View file

@ -18,12 +18,24 @@ impl Pipeline {
}
}
pub fn dimensions(&self, handle: &raster::Handle) -> Size<u32> {
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<raster::Allocation, raster::Error> {
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<Size<u32>> {
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<tiny_skia::PixmapRef<'_>> {
) -> Result<tiny_skia::PixmapRef<'_>, 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) {

View file

@ -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<core::image::Allocation, core::image::Error>)
+ 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<u32> {
pub fn load_image(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
handle: &core::image::Handle,
) -> Result<core::image::Allocation, core::image::Error> {
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<Size<u32>> {
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<core::image::Id, Vec<Callback>>,
belt: wgpu::util::StagingBelt,
}
#[cfg(feature = "image")]
type Callback = Box<dyn FnOnce(core::image::Allocation) + Send>;
type Callback =
Box<dyn FnOnce(Result<core::image::Allocation, core::image::Error>) + 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,
},
}

View file

@ -20,24 +20,14 @@ pub enum Memory {
bind_group: Option<Arc<wgpu::BindGroup>>,
allocation: Option<Weak<image::Memory>>,
},
/// 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<Image> {
match self {
Memory::Host(image) => Some(image.clone()),
Memory::Device { .. } | Memory::NotFound | Memory::Invalid => None,
Memory::Device { .. } | Memory::Error(_) => None,
}
}
}

View file

@ -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<core::image::Allocation, core::image::Error>)
+ 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<u32> {
fn load_image(
&self,
handle: &Self::Handle,
) -> Result<core::image::Allocation, core::image::Error> {
self.image_cache.borrow_mut().load_image(
&self.engine.device,
&self.engine.queue,
handle,
)
}
fn measure_image(&self, handle: &Self::Handle) -> Option<core::Size<u32>> {
self.image_cache.borrow_mut().measure_image(handle)
}

View file

@ -197,7 +197,8 @@ where
Renderer: image::Renderer<Handle = Handle>,
{
// 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<Renderer, Handle>(
where
Renderer: image::Renderer<Handle = Handle>,
{
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());

View file

@ -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<Renderer>(
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);