Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Utilize bytes::Bytes for images #2356

Merged
merged 7 commits into from
May 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ advanced = []

[dependencies]
bitflags.workspace = true
bytes.workspace = true
glam.workspace = true
log.workspace = true
num-traits.workspace = true
Expand Down
207 changes: 92 additions & 115 deletions core/src/image.rs
Original file line number Diff line number Diff line change
@@ -1,73 +1,94 @@
//! 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 {
/// Creates an image [`Handle`] pointing to the image of the given path.
///
/// Makes an educated guess about the image format by examining the data in the file.
pub fn from_path<T: Into<PathBuf>>(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<u8>` 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<Bytes>) -> 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<Bytes>,
) -> 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,
}
}
}

Expand All @@ -80,90 +101,46 @@ where
}
}

impl Hash for Handle {
fn hash<H: std::hash::Hasher>(&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<dyn AsRef<[u8]> + 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<H: std::hash::Hasher>(&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 {
Bajix marked this conversation as resolved.
Show resolved Hide resolved
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<Path>) -> 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))
}
}

Expand All @@ -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<u32>;
Expand Down
2 changes: 1 addition & 1 deletion examples/pokedex/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down
2 changes: 1 addition & 1 deletion examples/screenshot/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ impl Example {
fn view(&self) -> Element<'_, Message> {
let image: Element<Message> = if let Some(screenshot) = &self.screenshot
{
image(image::Handle::from_pixels(
image(image::Handle::from_rgba(
screenshot.size.width,
screenshot.size.height,
screenshot.clone(),
Expand Down
52 changes: 32 additions & 20 deletions graphics/src/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u8>, image::Bytes>>
{
use bitflags::bitflags;

bitflags! {
Expand Down Expand Up @@ -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)
Expand All @@ -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,
),
))
}
}
1 change: 1 addition & 0 deletions runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ debug = []
multi-window = []

[dependencies]
bytes.workspace = true
iced_core.workspace = true
iced_futures.workspace = true
iced_futures.features = ["thread-pool"]
Expand Down
Loading
Loading