use std::hash::{Hash, Hasher};
use std::path::Path;
use std::sync::Arc;
pub struct ImageData {
pub width: u32,
pub height: u32,
pub pixels: Vec<u8>,
}
#[derive(Clone)]
pub struct ImageHandle(Arc<ImageData>);
#[derive(Debug)]
pub struct ImageError(pub String);
impl std::fmt::Display for ImageError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
impl std::error::Error for ImageError {}
impl ImageHandle {
pub fn from_bytes(bytes: &[u8]) -> Result<Self, ImageError> {
let img = image::load_from_memory(bytes)
.map_err(|e| ImageError(e.to_string()))?
.to_rgba8();
Ok(Self(Arc::new(ImageData {
width: img.width(),
height: img.height(),
pixels: img.into_raw(),
})))
}
pub fn from_path(path: impl AsRef<Path>) -> Result<Self, ImageError> {
let bytes = std::fs::read(path).map_err(|e| ImageError(e.to_string()))?;
Self::from_bytes(&bytes)
}
pub fn width(&self) -> u32 {
self.0.width
}
pub fn height(&self) -> u32 {
self.0.height
}
pub fn pixels(&self) -> &[u8] {
&self.0.pixels
}
pub fn from_arc(data: Arc<ImageData>) -> Self {
Self(data)
}
}
impl PartialEq for ImageHandle {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.0, &other.0)
}
}
impl Eq for ImageHandle {}
impl Hash for ImageHandle {
fn hash<H: Hasher>(&self, state: &mut H) {
(Arc::as_ptr(&self.0) as usize).hash(state);
}
}
impl std::fmt::Debug for ImageHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "ImageHandle({}x{})", self.0.width, self.0.height)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn minimal_rgba_png() -> Vec<u8> {
use image::{ImageBuffer, ImageFormat, Rgba};
let img: ImageBuffer<Rgba<u8>, Vec<u8>> =
ImageBuffer::from_pixel(1, 1, Rgba([255u8, 255, 255, 255]));
let mut buf = Vec::new();
img.write_to(&mut std::io::Cursor::new(&mut buf), ImageFormat::Png)
.expect("in-memory PNG encode should not fail");
buf
}
#[test]
fn from_bytes_decodes_png() {
let handle = ImageHandle::from_bytes(&minimal_rgba_png())
.expect("hand-crafted 1×1 RGBA PNG should decode successfully");
assert_eq!(handle.width(), 1);
assert_eq!(handle.height(), 1);
assert_eq!(handle.pixels().len(), 4, "1×1 RGBA image has 4 bytes");
}
#[test]
fn from_bytes_invalid_returns_err() {
let result = ImageHandle::from_bytes(b"not a png");
assert!(result.is_err());
}
#[test]
fn two_handles_from_same_arc_are_equal() {
let a = ImageHandle(Arc::new(ImageData {
width: 1,
height: 1,
pixels: vec![255, 0, 0, 255],
}));
let b = a.clone();
assert_eq!(a, b);
}
#[test]
fn two_independent_handles_are_not_equal() {
let make = || {
ImageHandle(Arc::new(ImageData {
width: 1,
height: 1,
pixels: vec![255, 0, 0, 255],
}))
};
assert_ne!(make(), make());
}
#[test]
fn width_height_accessors() {
let h = ImageHandle(Arc::new(ImageData {
width: 42,
height: 7,
pixels: vec![0u8; 42 * 7 * 4],
}));
assert_eq!(h.width(), 42);
assert_eq!(h.height(), 7);
}
}