diff --git a/Cargo.toml b/Cargo.toml index 2b6a0d0333..60773da01d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -137,6 +137,7 @@ iced_winit = { version = "0.13.0-dev", path = "winit" } async-std = "1.0" bitflags = "2.0" bytemuck = { version = "1.0", features = ["derive"] } +bytes = "1.6" cosmic-text = "0.10" dark-light = "1.0" futures = "0.3" diff --git a/core/Cargo.toml b/core/Cargo.toml index 7bd37021ec..3c557bca0f 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -19,6 +19,7 @@ advanced = [] [dependencies] bitflags.workspace = true +bytes.workspace = true glam.workspace = true log.workspace = true num-traits.workspace = true diff --git a/core/src/image.rs b/core/src/image.rs index dc74e5c1ea..c38239bcad 100644 --- a/core/src/image.rs +++ b/core/src/image.rs @@ -1,16 +1,45 @@ //! Load and draw raster graphics. +pub use bytes::Bytes; + use crate::{Rectangle, Size}; use rustc_hash::FxHasher; -use std::hash::{Hash, Hasher as _}; -use std::path::PathBuf; -use std::sync::Arc; +use std::hash::{Hash, Hasher}; +use std::path::{Path, PathBuf}; /// A handle of some image data. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Handle { - id: u64, - data: Data, +#[derive(Clone, PartialEq, Eq)] +pub enum Handle { + /// A file handle. The image data will be read + /// from the file path. + /// + /// Use [`from_path`] to create this variant. + /// + /// [`from_path`]: Self::from_path + Path(Id, PathBuf), + + /// A handle pointing to some encoded image bytes in-memory. + /// + /// Use [`from_bytes`] to create this variant. + /// + /// [`from_bytes`]: Self::from_bytes + Bytes(Id, Bytes), + + /// A handle pointing to decoded image pixels in RGBA format. + /// + /// Use [`from_rgba`] to create this variant. + /// + /// [`from_rgba`]: Self::from_rgba + Rgba { + /// The id of this handle. + id: Id, + /// The width of the image. + width: u32, + /// The height of the image. + height: u32, + /// The pixels. + pixels: Bytes, + }, } impl Handle { @@ -18,56 +47,48 @@ impl Handle { /// /// Makes an educated guess about the image format by examining the data in the file. pub fn from_path>(path: T) -> Handle { - Self::from_data(Data::Path(path.into())) - } + let path = path.into(); - /// Creates an image [`Handle`] containing the image pixels directly. This - /// function expects the input data to be provided as a `Vec` of RGBA - /// pixels. - /// - /// This is useful if you have already decoded your image. - pub fn from_pixels( - width: u32, - height: u32, - pixels: impl AsRef<[u8]> + Send + Sync + 'static, - ) -> Handle { - Self::from_data(Data::Rgba { - width, - height, - pixels: Bytes::new(pixels), - }) + Self::Path(Id::path(&path), path) } - /// Creates an image [`Handle`] containing the image data directly. + /// Creates an image [`Handle`] containing the encoded image data directly. /// /// Makes an educated guess about the image format by examining the given data. /// /// This is useful if you already have your image loaded in-memory, maybe /// because you downloaded or generated it procedurally. - pub fn from_memory( - bytes: impl AsRef<[u8]> + Send + Sync + 'static, - ) -> Handle { - Self::from_data(Data::Bytes(Bytes::new(bytes))) + pub fn from_bytes(bytes: impl Into) -> Handle { + Self::Bytes(Id::unique(), bytes.into()) } - fn from_data(data: Data) -> Handle { - let mut hasher = FxHasher::default(); - data.hash(&mut hasher); - - Handle { - id: hasher.finish(), - data, + /// Creates an image [`Handle`] containing the decoded image pixels directly. + /// + /// This function expects the pixel data to be provided as a collection of [`Bytes`] + /// of RGBA pixels. Therefore, the length of the pixel data should always be + /// `width * height * 4`. + /// + /// This is useful if you have already decoded your image. + pub fn from_rgba( + width: u32, + height: u32, + pixels: impl Into, + ) -> Handle { + Self::Rgba { + id: Id::unique(), + width, + height, + pixels: pixels.into(), } } /// Returns the unique identifier of the [`Handle`]. - pub fn id(&self) -> u64 { - self.id - } - - /// Returns a reference to the image [`Data`]. - pub fn data(&self) -> &Data { - &self.data + pub fn id(&self) -> Id { + match self { + Handle::Path(id, _) + | Handle::Bytes(id, _) + | Handle::Rgba { id, .. } => *id, + } } } @@ -80,90 +101,46 @@ where } } -impl Hash for Handle { - fn hash(&self, state: &mut H) { - self.id.hash(state); - } -} - -/// A wrapper around raw image data. -/// -/// It behaves like a `&[u8]`. -#[derive(Clone)] -pub struct Bytes(Arc + Send + Sync + 'static>); - -impl Bytes { - /// Creates new [`Bytes`] around `data`. - pub fn new(data: impl AsRef<[u8]> + Send + Sync + 'static) -> Self { - Self(Arc::new(data)) - } -} - -impl std::fmt::Debug for Bytes { +impl std::fmt::Debug for Handle { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.0.as_ref().as_ref().fmt(f) + match self { + Self::Path(_, path) => write!(f, "Path({path:?})"), + Self::Bytes(_, _) => write!(f, "Bytes(...)"), + Self::Rgba { width, height, .. } => { + write!(f, "Pixels({width} * {height})") + } + } } } -impl std::hash::Hash for Bytes { - fn hash(&self, state: &mut H) { - self.0.as_ref().as_ref().hash(state); - } -} +/// The unique identifier of some [`Handle`] data. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Id(_Id); -impl PartialEq for Bytes { - fn eq(&self, other: &Self) -> bool { - let a = self.as_ref(); - let b = other.as_ref(); - core::ptr::eq(a, b) || a == b - } +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +enum _Id { + Unique(u64), + Hash(u64), } -impl Eq for Bytes {} - -impl AsRef<[u8]> for Bytes { - fn as_ref(&self) -> &[u8] { - self.0.as_ref().as_ref() - } -} +impl Id { + fn unique() -> Self { + use std::sync::atomic::{self, AtomicU64}; -impl std::ops::Deref for Bytes { - type Target = [u8]; + static NEXT_ID: AtomicU64 = AtomicU64::new(0); - fn deref(&self) -> &[u8] { - self.0.as_ref().as_ref() + Self(_Id::Unique(NEXT_ID.fetch_add(1, atomic::Ordering::Relaxed))) } -} -/// The data of a raster image. -#[derive(Clone, PartialEq, Eq, Hash)] -pub enum Data { - /// File data - Path(PathBuf), + fn path(path: impl AsRef) -> Self { + let hash = { + let mut hasher = FxHasher::default(); + path.as_ref().hash(&mut hasher); - /// In-memory data - Bytes(Bytes), - - /// Decoded image pixels in RGBA format. - Rgba { - /// The width of the image. - width: u32, - /// The height of the image. - height: u32, - /// The pixels. - pixels: Bytes, - }, -} + hasher.finish() + }; -impl std::fmt::Debug for Data { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Data::Path(path) => write!(f, "Path({path:?})"), - Data::Bytes(_) => write!(f, "Bytes(...)"), - Data::Rgba { width, height, .. } => { - write!(f, "Pixels({width} * {height})") - } - } + Self(_Id::Hash(hash)) } } @@ -184,7 +161,7 @@ pub trait Renderer: crate::Renderer { /// The image Handle to be displayed. Iced exposes its own default implementation of a [`Handle`] /// /// [`Handle`]: Self::Handle - type Handle: Clone + Hash; + type Handle: Clone; /// Returns the dimensions of an image for the given [`Handle`]. fn measure_image(&self, handle: &Self::Handle) -> Size; diff --git a/examples/pokedex/src/main.rs b/examples/pokedex/src/main.rs index 0811c08d99..35f2323642 100644 --- a/examples/pokedex/src/main.rs +++ b/examples/pokedex/src/main.rs @@ -188,7 +188,7 @@ impl Pokemon { { let bytes = reqwest::get(&url).await?.bytes().await?; - Ok(image::Handle::from_memory(bytes)) + Ok(image::Handle::from_bytes(bytes)) } #[cfg(target_arch = "wasm32")] diff --git a/examples/screenshot/src/main.rs b/examples/screenshot/src/main.rs index d887c41b50..5c175ccc0a 100644 --- a/examples/screenshot/src/main.rs +++ b/examples/screenshot/src/main.rs @@ -109,7 +109,7 @@ impl Example { fn view(&self) -> Element<'_, Message> { let image: Element = if let Some(screenshot) = &self.screenshot { - image(image::Handle::from_pixels( + image(image::Handle::from_rgba( screenshot.size.width, screenshot.size.height, screenshot.clone(), diff --git a/graphics/src/image.rs b/graphics/src/image.rs index c6135e9ec9..04c4505755 100644 --- a/graphics/src/image.rs +++ b/graphics/src/image.rs @@ -50,7 +50,8 @@ impl Image { /// [`Handle`]: image::Handle pub fn load( handle: &image::Handle, -) -> ::image::ImageResult<::image::DynamicImage> { +) -> ::image::ImageResult<::image::ImageBuffer<::image::Rgba, image::Bytes>> +{ use bitflags::bitflags; bitflags! { @@ -100,8 +101,8 @@ pub fn load( } } - match handle.data() { - image::Data::Path(path) => { + let (width, height, pixels) = match handle { + image::Handle::Path(_, path) => { let image = ::image::open(path)?; let operation = std::fs::File::open(path) @@ -110,33 +111,44 @@ pub fn load( .and_then(|mut reader| Operation::from_exif(&mut reader).ok()) .unwrap_or_else(Operation::empty); - Ok(operation.perform(image)) + let rgba = operation.perform(image).into_rgba8(); + + ( + rgba.width(), + rgba.height(), + image::Bytes::from(rgba.into_raw()), + ) } - image::Data::Bytes(bytes) => { + image::Handle::Bytes(_, bytes) => { let image = ::image::load_from_memory(bytes)?; let operation = Operation::from_exif(&mut std::io::Cursor::new(bytes)) .ok() .unwrap_or_else(Operation::empty); - Ok(operation.perform(image)) + let rgba = operation.perform(image).into_rgba8(); + + ( + rgba.width(), + rgba.height(), + image::Bytes::from(rgba.into_raw()), + ) } - image::Data::Rgba { + image::Handle::Rgba { width, height, pixels, - } => { - if let Some(image) = - ::image::ImageBuffer::from_vec(*width, *height, pixels.to_vec()) - { - Ok(::image::DynamicImage::ImageRgba8(image)) - } else { - Err(::image::error::ImageError::Limits( - ::image::error::LimitError::from_kind( - ::image::error::LimitErrorKind::DimensionError, - ), - )) - } - } + .. + } => (*width, *height, pixels.clone()), + }; + + if let Some(image) = ::image::ImageBuffer::from_raw(width, height, pixels) { + Ok(image) + } else { + Err(::image::error::ImageError::Limits( + ::image::error::LimitError::from_kind( + ::image::error::LimitErrorKind::DimensionError, + ), + )) } } diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 2150346202..703c3ed955 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -18,6 +18,7 @@ debug = [] multi-window = [] [dependencies] +bytes.workspace = true iced_core.workspace = true iced_futures.workspace = true iced_futures.features = ["thread-pool"] diff --git a/runtime/src/window/screenshot.rs b/runtime/src/window/screenshot.rs index 21e0471888..fb31811023 100644 --- a/runtime/src/window/screenshot.rs +++ b/runtime/src/window/screenshot.rs @@ -1,8 +1,8 @@ //! Take screenshots of a window. use crate::core::{Rectangle, Size}; +use bytes::Bytes; use std::fmt::{Debug, Formatter}; -use std::sync::Arc; /// Data of a screenshot, captured with `window::screenshot()`. /// @@ -10,7 +10,7 @@ use std::sync::Arc; #[derive(Clone)] pub struct Screenshot { /// The bytes of the [`Screenshot`]. - pub bytes: Arc>, + pub bytes: Bytes, /// The size of the [`Screenshot`]. pub size: Size, } @@ -28,9 +28,9 @@ impl Debug for Screenshot { impl Screenshot { /// Creates a new [`Screenshot`]. - pub fn new(bytes: Vec, size: Size) -> Self { + pub fn new(bytes: impl Into, size: Size) -> Self { Self { - bytes: Arc::new(bytes), + bytes: bytes.into(), size, } } @@ -68,7 +68,7 @@ impl Screenshot { ); Ok(Self { - bytes: Arc::new(chopped), + bytes: Bytes::from(chopped), size: Size::new(region.width, region.height), }) } @@ -80,6 +80,12 @@ impl AsRef<[u8]> for Screenshot { } } +impl From for Bytes { + fn from(screenshot: Screenshot) -> Self { + screenshot.bytes + } +} + #[derive(Debug, thiserror::Error)] /// Errors that can occur when cropping a [`Screenshot`]. pub enum CropError { diff --git a/tiny_skia/src/raster.rs b/tiny_skia/src/raster.rs index 176b0da92d..907fce7c40 100644 --- a/tiny_skia/src/raster.rs +++ b/tiny_skia/src/raster.rs @@ -71,8 +71,8 @@ impl Pipeline { #[derive(Debug, Default)] struct Cache { - entries: FxHashMap>, - hits: FxHashSet, + entries: FxHashMap>, + hits: FxHashSet, } impl Cache { @@ -83,7 +83,7 @@ impl Cache { let id = handle.id(); if let hash_map::Entry::Vacant(entry) = self.entries.entry(id) { - let image = graphics::image::load(handle).ok()?.into_rgba8(); + let image = graphics::image::load(handle).ok()?; let mut buffer = vec![0u32; image.width() as usize * image.height() as usize]; diff --git a/wgpu/src/image/raster.rs b/wgpu/src/image/raster.rs index 7a837f2898..4d3c3125b8 100644 --- a/wgpu/src/image/raster.rs +++ b/wgpu/src/image/raster.rs @@ -10,7 +10,7 @@ use rustc_hash::{FxHashMap, FxHashSet}; #[derive(Debug)] pub enum Memory { /// Image data on host - Host(image_rs::ImageBuffer, Vec>), + Host(image_rs::ImageBuffer, image::Bytes>), /// Storage entry Device(atlas::Entry), /// Image not found @@ -38,8 +38,8 @@ impl Memory { /// Caches image raster data #[derive(Debug, Default)] pub struct Cache { - map: FxHashMap, - hits: FxHashSet, + map: FxHashMap, + hits: FxHashSet, should_trim: bool, } @@ -51,7 +51,7 @@ impl Cache { } let memory = match graphics::image::load(handle) { - Ok(image) => Memory::Host(image.to_rgba8()), + Ok(image) => Memory::Host(image), Err(image_rs::error::ImageError::IoError(_)) => Memory::NotFound, Err(_) => Memory::Invalid, }; diff --git a/widget/src/image.rs b/widget/src/image.rs index f673c7b3bb..21d371b700 100644 --- a/widget/src/image.rs +++ b/widget/src/image.rs @@ -11,8 +11,6 @@ use crate::core::{ ContentFit, Element, Layout, Length, Rectangle, Size, Vector, Widget, }; -use std::hash::Hash; - pub use image::{FilterMethod, Handle}; /// Creates a new [`Viewer`] with the given image `Handle`. @@ -128,7 +126,7 @@ pub fn draw( filter_method: FilterMethod, ) where Renderer: image::Renderer, - Handle: Clone + Hash, + Handle: Clone, { let Size { width, height } = renderer.measure_image(handle); let image_size = Size::new(width as f32, height as f32); @@ -167,7 +165,7 @@ impl Widget for Image where Renderer: image::Renderer, - Handle: Clone + Hash, + Handle: Clone, { fn size(&self) -> Size { Size { @@ -216,7 +214,7 @@ impl<'a, Message, Theme, Renderer, Handle> From> for Element<'a, Message, Theme, Renderer> where Renderer: image::Renderer, - Handle: Clone + Hash + 'a, + Handle: Clone + 'a, { fn from(image: Image) -> Element<'a, Message, Theme, Renderer> { Element::new(image) diff --git a/widget/src/image/viewer.rs b/widget/src/image/viewer.rs index 75d73b1927..214cb996b9 100644 --- a/widget/src/image/viewer.rs +++ b/widget/src/image/viewer.rs @@ -10,8 +10,6 @@ use crate::core::{ Vector, Widget, }; -use std::hash::Hash; - /// A frame that displays an image with the ability to zoom in/out and pan. #[allow(missing_debug_implementations)] pub struct Viewer { @@ -94,7 +92,7 @@ impl Widget for Viewer where Renderer: image::Renderer, - Handle: Clone + Hash, + Handle: Clone, { fn tag(&self) -> tree::Tag { tree::Tag::of::() @@ -401,7 +399,7 @@ impl<'a, Message, Theme, Renderer, Handle> From> where Renderer: 'a + image::Renderer, Message: 'a, - Handle: Clone + Hash + 'a, + Handle: Clone + 'a, { fn from(viewer: Viewer) -> Element<'a, Message, Theme, Renderer> { Element::new(viewer)