use super::Image;
use super::buffer::{invalid, sample_count, with_capacity};
use crate::{error::Result, transform::Transform};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ToRgb;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ToRgba;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ToGray;
impl Transform<Image<u8>> for ToRgb {
type Output = Image<u8>;
fn apply(&self, _index: usize, image: Image<u8>) -> Result<Image<u8>> {
convert(image, 3)
}
}
impl Transform<Image<u8>> for ToRgba {
type Output = Image<u8>;
fn apply(&self, _index: usize, image: Image<u8>) -> Result<Image<u8>> {
convert(image, 4)
}
}
impl Transform<Image<u8>> for ToGray {
type Output = Image<u8>;
fn apply(&self, _index: usize, image: Image<u8>) -> Result<Image<u8>> {
convert(image, 1)
}
}
fn luma(r: u8, g: u8, b: u8) -> u8 {
let sum = 19595 * u32::from(r) + 38470 * u32::from(g) + 7471 * u32::from(b) + 0x8000;
(sum >> 16) as u8
}
fn convert(image: Image<u8>, to: usize) -> Result<Image<u8>> {
let from = image.channels();
if from == to {
return Ok(image);
}
let (width, height) = (image.width(), image.height());
let mut out = with_capacity(sample_count(width, height, to)?)?;
let pixels = image.data().chunks_exact(from);
match (from, to) {
(1, 3) => out.extend(pixels.flat_map(|p| [p[0], p[0], p[0]])),
(2, 3) => out.extend(pixels.flat_map(|p| [p[0], p[0], p[0]])),
(4, 3) => out.extend(pixels.flat_map(|p| [p[0], p[1], p[2]])),
(1, 4) => out.extend(pixels.flat_map(|p| [p[0], p[0], p[0], 255])),
(2, 4) => out.extend(pixels.flat_map(|p| [p[0], p[0], p[0], p[1]])),
(3, 4) => out.extend(pixels.flat_map(|p| [p[0], p[1], p[2], 255])),
(2, 1) => out.extend(pixels.map(|p| p[0])),
(3 | 4, 1) => out.extend(pixels.map(|p| luma(p[0], p[1], p[2]))),
_ => {
return Err(invalid(format!("cannot convert {from} channels to {to}")));
}
}
Image::new(width, height, to, out)
}
#[cfg(test)]
mod tests {
use super::{ToGray, ToRgb, ToRgba};
use crate::image::Image;
use crate::transform::Transform;
fn image(channels: usize, data: &[u8]) -> Image<u8> {
Image::new(1, data.len() / channels, channels, data.to_vec()).unwrap()
}
#[test]
fn every_channel_count_converts() {
let gray = image(1, &[7]);
let la = image(2, &[7, 9]);
let rgb = image(3, &[10, 20, 30]);
let rgba = image(4, &[10, 20, 30, 40]);
let rgb_of = |i: &Image<u8>| ToRgb.apply(0, i.clone()).unwrap().into_vec();
let rgba_of = |i: &Image<u8>| ToRgba.apply(0, i.clone()).unwrap().into_vec();
let gray_of = |i: &Image<u8>| ToGray.apply(0, i.clone()).unwrap().into_vec();
assert_eq!(rgb_of(&gray), [7, 7, 7]);
assert_eq!(rgb_of(&la), [7, 7, 7]);
assert_eq!(rgb_of(&rgb), [10, 20, 30]);
assert_eq!(rgb_of(&rgba), [10, 20, 30]);
assert_eq!(rgba_of(&gray), [7, 7, 7, 255]);
assert_eq!(rgba_of(&la), [7, 7, 7, 9]);
assert_eq!(rgba_of(&rgb), [10, 20, 30, 255]);
assert_eq!(gray_of(&la), [7]);
assert_eq!(gray_of(&rgb), [18]);
assert_eq!(gray_of(&rgba), [18]);
assert_eq!(gray_of(&image(3, &[255, 255, 255])), [255]);
}
#[test]
fn only_rgb_gray_and_rgba_are_targets() {
let err = super::convert(image(3, &[1, 2, 3]), 2).unwrap_err();
assert!(err.to_string().contains("cannot convert 3 channels to 2"));
}
}