use waterui_core::layout::Size;
use vello::peniko;
pub struct CanvasImage {
image: peniko::ImageData,
width: u32,
height: u32,
}
impl CanvasImage {
pub fn from_rgba_pixels(width: u32, height: u32, pixels: &[u8]) -> Result<Self, ImageError> {
let expected_len = (width * height * 4) as usize;
if pixels.len() != expected_len {
return Err(ImageError::InvalidPixelData {
expected: expected_len,
got: pixels.len(),
});
}
let image = peniko::ImageData {
data: peniko::Blob::from(pixels.to_vec()),
format: peniko::ImageFormat::Rgba8,
alpha_type: peniko::ImageAlphaType::Alpha,
width,
height,
};
Ok(Self {
image,
width,
height,
})
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, ImageError> {
let img = image::load_from_memory(bytes).map_err(ImageError::DecodeError)?;
let rgba = img.to_rgba8();
let width = rgba.width();
let height = rgba.height();
let pixels = rgba.into_raw();
let image = peniko::ImageData {
data: peniko::Blob::from(pixels),
format: peniko::ImageFormat::Rgba8,
alpha_type: peniko::ImageAlphaType::Alpha,
width,
height,
};
Ok(Self {
image,
width,
height,
})
}
#[must_use]
pub const fn width(&self) -> u32 {
self.width
}
#[must_use]
pub const fn height(&self) -> u32 {
self.height
}
#[must_use]
#[allow(clippy::cast_precision_loss)]
pub const fn size(&self) -> Size {
Size::new(self.width as f32, self.height as f32)
}
#[must_use]
pub(crate) const fn inner(&self) -> &peniko::ImageData {
&self.image
}
}
impl core::fmt::Debug for CanvasImage {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("CanvasImage")
.field("width", &self.width)
.field("height", &self.height)
.finish_non_exhaustive()
}
}
#[derive(Debug)]
pub enum ImageError {
InvalidPixelData {
expected: usize,
got: usize,
},
DecodeError(image::ImageError),
}
impl core::fmt::Display for ImageError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::InvalidPixelData { expected, got } => {
write!(
f,
"Invalid pixel data: expected {expected} bytes, got {got}"
)
}
Self::DecodeError(err) => write!(f, "Failed to decode image: {err}"),
}
}
}
impl std::error::Error for ImageError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::InvalidPixelData { .. } => None,
Self::DecodeError(err) => Some(err),
}
}
}