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 3 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
151 changes: 43 additions & 108 deletions core/src/image.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,38 @@
//! 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::hash::{Hash, Hasher};
use std::path::PathBuf;
use std::sync::Arc;

/// 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 {
/// File data
Path(PathBuf),

/// 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,
},
}

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()))
Self::Path(path.into())
}

/// Creates an image [`Handle`] containing the image pixels directly. This
Expand All @@ -29,13 +43,13 @@ impl Handle {
pub fn from_pixels(
width: u32,
height: u32,
pixels: impl AsRef<[u8]> + Send + Sync + 'static,
pixels: impl Into<Bytes>,
) -> Handle {
Self::from_data(Data::Rgba {
Self::Rgba {
width,
height,
pixels: Bytes::new(pixels),
})
pixels: pixels.into(),
}
}

/// Creates an image [`Handle`] containing the image data directly.
Expand All @@ -44,30 +58,26 @@ impl Handle {
///
/// 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)))
}

fn from_data(data: Data) -> Handle {
let mut hasher = FxHasher::default();
data.hash(&mut hasher);

Handle {
id: hasher.finish(),
data,
}
pub fn from_memory(bytes: impl Into<Bytes>) -> Handle {
Self::Bytes(bytes.into())
}

/// Returns the unique identifier of the [`Handle`].
pub fn id(&self) -> u64 {
self.id
let mut hasher = FxHasher::default();
self.hash(&mut hasher);

hasher.finish()
}
}

/// Returns a reference to the image [`Data`].
pub fn data(&self) -> &Data {
&self.data
impl Hash for Handle {
fn hash<H: Hasher>(&self, state: &mut H) {
match self {
Self::Path(path) => path.hash(state),
Self::Bytes(bytes) => bytes.as_ptr().hash(state),
Self::Rgba { pixels, .. } => pixels.as_ptr().hash(state),
}
}
}

Expand All @@ -80,87 +90,12 @@ 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 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.as_ref().as_ref().fmt(f)
}
}

impl std::hash::Hash for Bytes {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.0.as_ref().as_ref().hash(state);
}
}

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
}
}

impl Eq for Bytes {}

impl AsRef<[u8]> for Bytes {
fn as_ref(&self) -> &[u8] {
self.0.as_ref().as_ref()
}
}

impl std::ops::Deref for Bytes {
type Target = [u8];

fn deref(&self) -> &[u8] {
self.0.as_ref().as_ref()
}
}

/// The data of a raster image.
#[derive(Clone, PartialEq, Eq, Hash)]
pub enum Data {
/// File data
Path(PathBuf),

/// 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,
},
}

impl std::fmt::Debug for Data {
impl std::fmt::Debug for Handle {
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, .. } => {
Self::Path(path) => write!(f, "Path({path:?})"),
Self::Bytes(_) => write!(f, "Bytes(...)"),
Self::Rgba { width, height, .. } => {
write!(f, "Pixels({width} * {height})")
}
}
Expand Down
51 changes: 31 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,43 @@ 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
16 changes: 11 additions & 5 deletions runtime/src/window/screenshot.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
//! 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()`.
///
/// The `bytes` of this screenshot will always be ordered as `RGBA` in the `sRGB` color space.
#[derive(Clone)]
pub struct Screenshot {
/// The bytes of the [`Screenshot`].
pub bytes: Arc<Vec<u8>>,
pub bytes: Bytes,
/// The size of the [`Screenshot`].
pub size: Size<u32>,
}
Expand All @@ -28,9 +28,9 @@

impl Screenshot {
/// Creates a new [`Screenshot`].
pub fn new(bytes: Vec<u8>, size: Size<u32>) -> Self {
pub fn new(bytes: impl Into<Bytes>, size: Size<u32>) -> Self {
Self {
bytes: Arc::new(bytes),
bytes: bytes.into(),
size,
}
}
Expand Down Expand Up @@ -68,7 +68,7 @@
);

Ok(Self {
bytes: Arc::new(chopped),
bytes: Bytes::from(chopped),
size: Size::new(region.width, region.height),
})
}
Expand All @@ -80,6 +80,12 @@
}
}

impl Into<Bytes> for Screenshot {

Check failure on line 83 in runtime/src/window/screenshot.rs

View workflow job for this annotation

GitHub Actions / all

an implementation of `From` is preferred since it gives you `Into<_>` for free where the reverse isn't true
fn into(self) -> Bytes {
self.bytes
}
}

#[derive(Debug, thiserror::Error)]
/// Errors that can occur when cropping a [`Screenshot`].
pub enum CropError {
Expand Down
2 changes: 1 addition & 1 deletion tiny_skia/src/raster.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down
4 changes: 2 additions & 2 deletions wgpu/src/image/raster.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use rustc_hash::{FxHashMap, FxHashSet};
#[derive(Debug)]
pub enum Memory {
/// Image data on host
Host(image_rs::ImageBuffer<image_rs::Rgba<u8>, Vec<u8>>),
Host(image_rs::ImageBuffer<image_rs::Rgba<u8>, image::Bytes>),
/// Storage entry
Device(atlas::Entry),
/// Image not found
Expand Down Expand Up @@ -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,
};
Expand Down
Loading