use std::borrow::Cow;
use crate::{Error, err};
#[derive(
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
derive_more::Display,
derive_more::Debug,
)]
#[display("RgbShifts({}, {}, {})", r, g, b)]
#[debug("{}", self)]
pub struct RgbShifts {
pub r: u8,
pub g: u8,
pub b: u8,
}
impl RgbShifts {
pub fn from_rgb<S: Subpixel>(&self, r: S, g: S, b: S) -> u32 {
(u32::from(r.to_u8()) << self.r) |
(u32::from(g.to_u8()) << self.g) |
(u32::from(b.to_u8()) << self.b)
}
pub fn from_luma<S: Subpixel>(&self, luma: S) -> u32 {
u32::from(luma.to_u8()) * 0x0101_0101
}
}
pub trait Subpixel: bytemuck::Pod {
fn to_u8(self) -> u8;
}
impl Subpixel for u8 {
fn to_u8(self) -> u8 { self }
}
impl Subpixel for u16 {
fn to_u8(self) -> u8 { (self >> 8) as u8 }
}
impl Subpixel for f32 {
fn to_u8(self) -> u8 {
#[allow(clippy::neg_cmp_op_on_partial_ord)]
if !(self < 1.0) {
255
} else if self <= 0.0 {
0
} else {
(self * 255.0).round() as u8
}
}
}
pub trait IntoXBuffer<'a> {
type Buffer: AsRef<[u8]>;
fn dimensions(&self) -> Result<(u16, u16), err::ImageTooLarge>;
fn into_x_buffer(
self,
rgb_shifts: RgbShifts,
) -> crate::Result<Self::Buffer>;
}
pub fn new_dimensions<T: Copy + Into<u32> + TryInto<u16>>(
dimensions: (T, T),
) -> Result<(u16, u16), err::ImageTooLarge> {
let (width, height) = dimensions;
width
.try_into()
.ok()
.zip(height.try_into().ok())
.ok_or_else(|| err::ImageTooLarge(width.into(), height.into()))
}
#[derive(Clone)]
struct InnerImage<'a, S: Clone> {
dimensions: (u16, u16),
data: Cow<'a, [S]>,
}
impl<'a, S: Clone> InnerImage<'a, S> {
pub fn new(
width: u32,
height: u32,
data: Cow<'a, [S]>,
channels: usize,
) -> Result<Self, Error> {
let (width, height) = new_dimensions((width, height))?;
if usize::from(width) * usize::from(height) * channels == data.len() {
Ok(Self { dimensions: (width, height), data })
} else {
let len = data.len() * core::mem::size_of::<S>();
Err(Error::BadBufferSize(len, width, height))
}
}
}
#[derive(Clone, derive_more::AsRef, derive_more::Deref)]
#[as_ref(Vec<u32>, [u32])]
pub struct XBuffer(Vec<u32>);
impl AsRef<[u8]> for XBuffer {
fn as_ref(&self) -> &[u8] { bytemuck::must_cast_slice(self.0.as_slice()) }
}
macro_rules! replace_expr {
($_t:tt $sub:expr) => {
$sub
};
}
macro_rules! count_tts {
($($tts:tt)*) => {0usize $(+ replace_expr!($tts 1usize))*};
}
macro_rules! make_image_type {
($(#[doc = $doc:expr])* $Image:ident; |[$($ch:ident),*], $rgb_shifts:ident| $body:expr) => {
$(#[doc = $doc])*
#[derive(Clone)]
pub struct $Image<'a, S: Clone>(InnerImage<'a, S>);
impl<'a, S: Clone> $Image<'a, S> {
pub fn new(
width: u32,
height: u32,
data: Cow<'a, [S]>,
) -> Result<Self, Error> {
let channels = count_tts!($($ch)*);
InnerImage::new(width, height, data, channels).map(Self)
}
}
impl<'a, S: Subpixel> IntoXBuffer<'a> for $Image<'a, S> {
type Buffer = XBuffer;
fn dimensions(&self) -> Result<(u16, u16), err::ImageTooLarge> { Ok(self.0.dimensions) }
fn into_x_buffer(self, $rgb_shifts: RgbShifts) -> crate::Result<Self::Buffer> {
let (chunks, remainder) = self.0.data.as_chunks();
assert_eq!(0, remainder.len());
Ok(XBuffer(chunks.iter().map(|&[$($ch),*]| $body).collect()))
}
}
}
}
make_image_type! {
RgbImage; |[r, g, b], rgb_shifts| rgb_shifts.from_rgb(r, g, b)
}
make_image_type! {
RgbaImage; |[r, g, b, _alpha], rgb_shifts| rgb_shifts.from_rgb(r, g, b)
}
make_image_type! {
LumaImage; |[y], rgb_shifts| rgb_shifts.from_luma(y)
}
make_image_type! {
LumaAImage; |[y, _alpha], rgb_shifts| rgb_shifts.from_luma(y)
}
#[test]
fn test_buffer_size_mismatch() {
let data: [u8; 13] = [0; 13];
let res = RgbImage::new(2, 2, (&data[..11]).into());
assert!(matches!(res, Err(Error::BadBufferSize(11, 2, 2))));
let res = RgbImage::new(2, 2, (&data[..13]).into());
assert!(matches!(res, Err(Error::BadBufferSize(13, 2, 2))));
RgbImage::new(2, 2, (&data[..12]).into()).unwrap();
}
#[cfg(feature = "image")]
impl IntoXBuffer<'static> for image::DynamicImage {
type Buffer = Vec<u8>;
fn dimensions(&self) -> Result<(u16, u16), err::ImageTooLarge> {
new_dimensions(image::GenericImageView::dimensions(self))
}
fn into_x_buffer(
mut self,
rgb_shifts: RgbShifts,
) -> crate::Result<Self::Buffer> {
if let Some(rgba) = self.as_mut_rgba8() {
rgba.apply_color_space(
image::metadata::Cicp::SRGB,
Default::default(),
)?;
Ok(fix_channel_order(self.into_rgba8().into_vec(), rgb_shifts))
} else {
(&self).into_x_buffer(rgb_shifts)
}
}
}
#[cfg(feature = "image")]
impl IntoXBuffer<'static> for &image::DynamicImage {
type Buffer = Vec<u8>;
fn dimensions(&self) -> Result<(u16, u16), err::ImageTooLarge> {
new_dimensions(image::GenericImageView::dimensions(*self))
}
fn into_x_buffer(
self,
rgb_shifts: RgbShifts,
) -> crate::Result<Self::Buffer> {
let (width, height) = image::GenericImageView::dimensions(self);
let img = image::RgbaImage::new(width, height);
let mut img = image::DynamicImage::ImageRgba8(img);
img.copy_from_color_space(self, Default::default())?;
Ok(fix_channel_order(img.into_rgba8().into_vec(), rgb_shifts))
}
}
#[cfg(feature = "image")]
fn fix_channel_order(mut data: Vec<u8>, rgb_shifts: RgbShifts) -> Vec<u8> {
if rgb_shifts.from_rgb(1u8, 2u8, 3u8).to_ne_bytes() != [1u8, 2, 3, 0] {
let (chunks, remainder) = data.as_chunks_mut();
assert_eq!(0, remainder.len());
for chunk in chunks {
let [r, g, b, _] = *chunk;
*chunk = rgb_shifts.from_rgb(r, g, b).to_ne_bytes();
}
}
data
}