#![allow(dead_code)]
use zune_core::bit_depth::BitType;
use zune_core::colorspace::ColorSpace;
use zune_image::errors::ImageErrors;
use zune_image::image::Image;
use zune_image::traits::OperationsTrait;
use crate::traits::NumOps;
pub struct ColorMatrix {
matrix: [[f32; 5]; 4]
}
impl ColorMatrix {
#[must_use]
pub fn new(matrix: [[f32; 5]; 4]) -> ColorMatrix {
ColorMatrix { matrix }
}
#[must_use]
pub fn try_from_slice(slice: &[f32]) -> Option<ColorMatrix> {
if slice.len() == 20 {
let mut matrix = [[0f32; 5]; 4];
let mut c = slice.chunks_exact(5);
matrix
.iter_mut()
.for_each(|x| x.copy_from_slice(c.next().unwrap()));
Some(ColorMatrix::new(matrix))
} else {
None
}
}
}
impl OperationsTrait for ColorMatrix {
fn name(&self) -> &'static str {
"Color Matrix"
}
#[allow(clippy::many_single_char_names)]
fn execute_impl(&self, image: &mut Image) -> Result<(), ImageErrors> {
let original_color = image.colorspace();
image.convert_color(ColorSpace::RGBA)?;
let depth = image.depth();
for frame in image.frames_mut() {
let channels = frame.channels_vec();
let (r, rest) = channels.split_at_mut(1);
let (g, rest) = rest.split_at_mut(1);
let (b, a) = rest.split_at_mut(1);
match depth.bit_type() {
BitType::U8 => color_matrix_component::<u8>(
r[0].reinterpret_as_mut()?,
g[0].reinterpret_as_mut()?,
b[0].reinterpret_as_mut()?,
a[0].reinterpret_as_mut()?,
&self.matrix
),
BitType::U16 => color_matrix_component::<u16>(
r[0].reinterpret_as_mut()?,
g[0].reinterpret_as_mut()?,
b[0].reinterpret_as_mut()?,
a[0].reinterpret_as_mut()?,
&self.matrix
),
BitType::F32 => color_matrix_component::<f32>(
r[0].reinterpret_as_mut()?,
g[0].reinterpret_as_mut()?,
b[0].reinterpret_as_mut()?,
a[0].reinterpret_as_mut()?,
&self.matrix
),
d => return Err(ImageErrors::ImageOperationNotImplemented(self.name(), d))
}
}
image.convert_color(original_color)?;
Ok(())
}
fn supported_types(&self) -> &'static [BitType] {
&[BitType::U8, BitType::U16, BitType::F32]
}
}
fn color_matrix_component<T: NumOps<T> + Copy>(
c1: &mut [T], c2: &mut [T], c3: &mut [T], alpha: &mut [T], color_matrix: &[[f32; 5]; 4]
) where
f32: From<T>
{
let max_t = f32::from(T::max_val());
for (((r, g), b), a) in c1
.iter_mut()
.zip(c2.iter_mut())
.zip(c3.iter_mut())
.zip(alpha.iter_mut())
{
let r_f32 = f32::from(*r);
let g_f32 = f32::from(*g);
let b_f32 = f32::from(*b);
let a_f32 = f32::from(*a);
let r_matrix = color_matrix[0];
let g_matrix = color_matrix[1];
let b_matrix = color_matrix[2];
let a_matrix = color_matrix[3];
let new_r = (r_f32 * r_matrix[0])
+ (g_f32 * r_matrix[1])
+ (b_f32 * r_matrix[2])
+ (a_f32 * r_matrix[3])
+ (max_t * r_matrix[4]);
let new_g = (r_f32 * g_matrix[0])
+ (g_f32 * g_matrix[1])
+ (b_f32 * g_matrix[2])
+ (a_f32 * g_matrix[3])
+ (max_t * g_matrix[4]);
let new_b = (r_f32 * b_matrix[0])
+ (g_f32 * b_matrix[1])
+ (b_f32 * b_matrix[2])
+ (a_f32 * b_matrix[3])
+ (max_t * b_matrix[4]);
let new_a = (r_f32 * a_matrix[0])
+ (g_f32 * a_matrix[1])
+ (b_f32 * a_matrix[2])
+ (a_f32 * a_matrix[3])
+ (max_t * a_matrix[4]);
*r = T::from_f32(new_r);
*g = T::from_f32(new_g);
*b = T::from_f32(new_b);
*a = T::from_f32(new_a);
}
}
#[cfg(feature = "benchmarks")]
#[cfg(test)]
mod benchmarks {
extern crate test;
use zune_core::colorspace::ColorSpace;
use zune_image::image::Image;
use zune_image::traits::OperationsTrait;
use crate::color_matrix::ColorMatrix;
#[bench]
fn bench_color_matrix_on_rgb_image(b: &mut test::Bencher) {
let width = 800;
let height = 800;
let mut image = Image::fill(0.5f32, ColorSpace::RGB, width, height);
let filter = ColorMatrix::new([
[0.2, 0.5, 0.3, 0.0, 0.0],
[0.2, 0.5, 0.3, 0.0, 0.0],
[0.2, 0.5, 0.3, 0.0, 0.0],
[0.0, 0.0, 0.0, 1.0, 0.0]
]);
b.iter(|| {
filter.execute(&mut image).unwrap();
});
}
}