use zeroize::Zeroize;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ColorSpace {
Rgb8,
Rgb16,
Rgba8,
Luma8,
}
impl ColorSpace {
pub fn bytes_per_pixel(&self) -> usize {
match self {
ColorSpace::Rgb8 => 3,
ColorSpace::Rgb16 => 6,
ColorSpace::Rgba8 => 4,
ColorSpace::Luma8 => 1,
}
}
pub fn has_explicit_red_channel(&self) -> bool {
match self {
ColorSpace::Rgb8 | ColorSpace::Rgb16 | ColorSpace::Rgba8 => true,
ColorSpace::Luma8 => false,
}
}
}
pub trait CoverSource {
fn dimensions(&self) -> (u32, u32);
fn pixels(&self) -> &[u8];
fn pixels_mut(&mut self) -> &mut [u8];
fn color_space(&self) -> ColorSpace;
fn pixel_count(&self) -> usize {
let (width, height) = self.dimensions();
width as usize * height as usize
}
fn pixel_offset(&self, x: u32, y: u32) -> usize {
let (width, _) = self.dimensions();
(y as usize * width as usize + x as usize) * self.color_space().bytes_per_pixel()
}
}
#[derive(Debug)]
pub struct ImageBuffer {
pixels: Vec<u8>,
width: u32,
height: u32,
color_space: ColorSpace,
}
impl ImageBuffer {
pub(crate) fn new(pixels: Vec<u8>, width: u32, height: u32, color_space: ColorSpace) -> Self {
Self {
pixels,
width,
height,
color_space,
}
}
}
impl CoverSource for ImageBuffer {
fn dimensions(&self) -> (u32, u32) {
(self.width, self.height)
}
fn pixels(&self) -> &[u8] {
&self.pixels
}
fn pixels_mut(&mut self) -> &mut [u8] {
&mut self.pixels
}
fn color_space(&self) -> ColorSpace {
self.color_space
}
}
impl Zeroize for ImageBuffer {
fn zeroize(&mut self) {
self.pixels.zeroize();
}
}
#[cfg(test)]
mod tests {
use super::*;
const LAYOUTS: [(ColorSpace, usize); 4] = [
(ColorSpace::Rgb8, 3),
(ColorSpace::Rgb16, 6),
(ColorSpace::Rgba8, 4),
(ColorSpace::Luma8, 1),
];
#[test]
fn each_layout_reports_its_own_stride() {
for (layout, stride) in LAYOUTS {
assert_eq!(layout.bytes_per_pixel(), stride, "layout {layout:?}");
}
}
#[test]
fn only_colour_layouts_carry_a_red_channel() {
for (layout, _) in LAYOUTS {
assert_eq!(
layout.has_explicit_red_channel(),
layout != ColorSpace::Luma8,
"layout {layout:?}"
);
}
}
#[test]
fn offsets_follow_row_major_order() {
let image = ImageBuffer::new(vec![0u8; 6 * 4 * 3], 6, 4, ColorSpace::Rgb8);
assert_eq!(image.dimensions(), (6, 4));
assert_eq!(image.pixel_count(), 24);
assert_eq!(image.pixel_offset(0, 0), 0);
assert_eq!(image.pixel_offset(1, 0), 3);
assert_eq!(image.pixel_offset(1, 1), 21);
}
#[test]
fn zeroizing_clears_the_samples_and_keeps_the_geometry() {
let mut image = ImageBuffer::new(vec![0xAAu8; 12], 4, 3, ColorSpace::Luma8);
image.pixels_mut()[0] = 0xFF;
assert!(image.pixels().iter().any(|&sample| sample != 0));
image.zeroize();
assert!(image.pixels().iter().all(|&sample| sample == 0));
assert_eq!(image.dimensions(), (4, 3));
assert_eq!(image.color_space(), ColorSpace::Luma8);
}
}