Add explicit error handling to image loading
This commit is contained in:
parent
7c11ccb046
commit
867fe819c0
17 changed files with 357 additions and 118 deletions
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
|
|
|||
|
|
@ -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::*;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue