use crate::element::Value;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
static NEXT_ID: AtomicU64 = AtomicU64::new(1);
#[derive(Clone)]
pub struct Image {
data: Arc<ImageData>,
}
struct ImageData {
id: u64,
width: u32,
height: u32,
pixels: Box<[u8]>,
}
impl Image {
pub fn from_premultiplied_rgba(
width: u32,
height: u32,
mut pixels: Vec<u8>,
) -> Result<Image, ImageError> {
check_size(width, height, pixels.len())?;
for pixel in pixels.as_chunks_mut::<4>().0 {
let alpha = pixel[3];
for channel in &mut pixel[..3] {
*channel = (*channel).min(alpha);
}
}
Ok(Image::new(width, height, pixels))
}
pub fn from_straight_rgba(
width: u32,
height: u32,
mut pixels: Vec<u8>,
) -> Result<Image, ImageError> {
check_size(width, height, pixels.len())?;
for pixel in pixels.as_chunks_mut::<4>().0 {
let alpha = u32::from(pixel[3]);
for channel in &mut pixel[..3] {
*channel = ((u32::from(*channel) * alpha + 127) / 255) as u8;
}
}
Ok(Image::new(width, height, pixels))
}
fn new(width: u32, height: u32, pixels: Vec<u8>) -> Image {
Image {
data: Arc::new(ImageData {
id: NEXT_ID.fetch_add(1, Ordering::Relaxed),
width,
height,
pixels: pixels.into_boxed_slice(),
}),
}
}
pub fn width(&self) -> u32 {
self.data.width
}
pub fn height(&self) -> u32 {
self.data.height
}
pub fn pixels(&self) -> &[u8] {
&self.data.pixels
}
pub fn id(&self) -> u64 {
self.data.id
}
}
impl PartialEq for Image {
fn eq(&self, other: &Self) -> bool {
self.data.id == other.data.id
}
}
impl From<Option<Image>> for Value<Option<Image>> {
fn from(value: Option<Image>) -> Value<Option<Image>> {
Value::Static(value)
}
}
impl std::fmt::Debug for Image {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Image")
.field("id", &self.data.id)
.field("width", &self.data.width)
.field("height", &self.data.height)
.finish()
}
}
fn check_size(width: u32, height: u32, found: usize) -> Result<(), ImageError> {
if width == 0 || height == 0 {
return Err(ImageError::ZeroSize { width, height });
}
let expected = u64::from(width)
.checked_mul(u64::from(height))
.and_then(|count| count.checked_mul(4))
.and_then(|bytes| usize::try_from(bytes).ok());
match expected {
Some(expected) if expected == found => Ok(()),
expected => Err(ImageError::PixelCount {
expected: expected.unwrap_or(usize::MAX),
found,
}),
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum ImageError {
ZeroSize {
width: u32,
height: u32,
},
PixelCount {
expected: usize,
found: usize,
},
}
impl std::fmt::Display for ImageError {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ImageError::ZeroSize { width, height } => {
write!(formatter, "an image cannot be {width}x{height} pixels")
}
ImageError::PixelCount { expected, found } => write!(
formatter,
"the pixel buffer holds {found} bytes, but the size calls for {expected}"
),
}
}
}
impl std::error::Error for ImageError {}
#[cfg(test)]
#[path = "image_tests.rs"]
mod tests;