use std::marker::PhantomData;
use crate::error::{Error, Result};
use crate::format::{Gray8, Gray16, PixelFormat, Rgba8, Rgba16, Sample, Srgb8, Srgb16};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BitDepth {
Eight,
Sixteen,
}
impl BitDepth {
pub fn max_value(self) -> f64 {
match self {
BitDepth::Eight => 255.0,
BitDepth::Sixteen => 65_535.0,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Channels {
Gray,
Rgb,
Rgba,
}
impl Channels {
pub fn count(self) -> usize {
match self {
Channels::Gray => 1,
Channels::Rgb => 3,
Channels::Rgba => 4,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ColorSpace {
Srgb,
Grayscale,
}
#[derive(Debug, Clone)]
pub struct Image<F: PixelFormat> {
width: u32,
height: u32,
data: Vec<F::Sample>,
_format: PhantomData<F>,
}
impl<F: PixelFormat> Image<F> {
fn from_samples(width: u32, height: u32, data: Vec<F::Sample>) -> Result<Self> {
let expected = width as usize * height as usize * F::CHANNELS.count();
if data.len() != expected {
return Err(Error::BufferSize {
expected,
actual: data.len(),
});
}
Ok(Self {
width,
height,
data,
_format: PhantomData,
})
}
pub fn width(&self) -> u32 {
self.width
}
pub fn height(&self) -> u32 {
self.height
}
pub fn dimensions(&self) -> (u32, u32) {
(self.width, self.height)
}
pub fn channels(&self) -> Channels {
F::CHANNELS
}
pub fn bit_depth(&self) -> BitDepth {
F::BIT_DEPTH
}
pub fn color_space(&self) -> ColorSpace {
F::COLOR_SPACE
}
pub fn samples(&self) -> &[F::Sample] {
&self.data
}
pub fn sample_count(&self) -> usize {
self.data.len()
}
pub(crate) fn sample_at(&self, x: u32, y: u32, c: usize) -> f64 {
let channels = F::CHANNELS.count();
let index = (y as usize * self.width as usize + x as usize) * channels + c;
self.data[index].into()
}
#[cfg_attr(not(feature = "ssimulacra2"), allow(dead_code))]
pub(crate) fn to_rgb_f32_normalized(&self) -> Vec<f32> {
let max = <F::Sample as Sample>::MAX;
let pixels = self.width as usize * self.height as usize;
let mut out = Vec::with_capacity(pixels * 3);
for y in 0..self.height {
for x in 0..self.width {
let (r, g, b) = match F::CHANNELS {
Channels::Gray => {
let v = self.sample_at(x, y, 0);
(v, v, v)
}
Channels::Rgb | Channels::Rgba => (
self.sample_at(x, y, 0),
self.sample_at(x, y, 1),
self.sample_at(x, y, 2),
),
};
out.push((r / max) as f32);
out.push((g / max) as f32);
out.push((b / max) as f32);
}
}
out
}
}
impl Image<Srgb8> {
pub fn srgb8(width: u32, height: u32, data: Vec<u8>) -> Result<Self> {
Self::from_samples(width, height, data)
}
}
impl Image<Srgb16> {
pub fn srgb16(width: u32, height: u32, data: Vec<u16>) -> Result<Self> {
Self::from_samples(width, height, data)
}
}
impl Image<Gray8> {
pub fn gray8(width: u32, height: u32, data: Vec<u8>) -> Result<Self> {
Self::from_samples(width, height, data)
}
}
impl Image<Gray16> {
pub fn gray16(width: u32, height: u32, data: Vec<u16>) -> Result<Self> {
Self::from_samples(width, height, data)
}
}
impl Image<Rgba8> {
pub fn rgba8(width: u32, height: u32, data: Vec<u8>) -> Result<Self> {
Self::from_samples(width, height, data)
}
}
impl Image<Rgba16> {
pub fn rgba16(width: u32, height: u32, data: Vec<u16>) -> Result<Self> {
Self::from_samples(width, height, data)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rejects_wrong_buffer_size() {
let err = Image::srgb8(2, 2, vec![0; 10]).unwrap_err();
assert!(matches!(
err,
Error::BufferSize {
expected: 12,
actual: 10
}
));
}
#[test]
fn sixteen_bit_samples_round_trip() {
let img = Image::gray16(1, 1, vec![258]).unwrap();
assert_eq!(img.samples(), &[258]);
assert_eq!(img.sample_at(0, 0, 0), 258.0);
}
#[test]
fn to_rgb_f32_replicates_gray_and_normalizes() {
let img = Image::gray8(1, 1, vec![255]).unwrap();
assert_eq!(img.to_rgb_f32_normalized(), vec![1.0, 1.0, 1.0]);
}
#[test]
fn accessors_reflect_the_format() {
let img = Image::rgba16(2, 3, vec![0; 24]).unwrap();
assert_eq!(img.dimensions(), (2, 3));
assert_eq!(img.channels(), Channels::Rgba);
assert_eq!(img.bit_depth(), BitDepth::Sixteen);
assert_eq!(img.color_space(), ColorSpace::Srgb);
assert_eq!(img.sample_count(), 24);
}
}