use crate::color::{Rgb, adobe_cmyk_to_srgb};
use crate::image::{BitImage, Pixels, Samples, Unpacked};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Row<'a, P>(&'a [P]);
impl<'a, P> Row<'a, P> {
#[must_use]
pub const fn pixels(self) -> &'a [P] {
self.0
}
}
pub trait Rows {
type Pixel;
fn next(&mut self) -> Option<Row<'_, Self::Pixel>>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[repr(C)]
pub struct Rgba8(pub [u8; 4]);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[repr(C)]
pub struct Rgb8(pub [u8; 3]);
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Palette(Box<[Rgb8]>);
impl Palette {
#[must_use]
pub fn new(entries: &[Rgb]) -> Self {
Self(entries.iter().map(|c| Rgb8(c.to_bytes())).collect())
}
#[must_use]
pub fn get(&self, index: u8) -> Rgb8 {
self.0
.get(usize::from(index))
.copied()
.unwrap_or(Rgb8([0, 0, 0]))
}
}
#[derive(Debug)]
pub struct Source<'a> {
kind: SourceKind<'a>,
width: usize,
height: u32,
y: u32,
}
#[derive(Debug)]
enum SourceKind<'a> {
Stencil {
bits: &'a BitImage,
scratch: Vec<u8>,
},
Packed {
rows: Unpacked<'a>,
components: usize,
},
Gray(&'a [u8]),
Rgb(&'a [u8]),
Cmyk(&'a [u8]),
Indexed(&'a [u8]),
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum SampleRow<'a> {
Gray(&'a [u8]),
Rgb(&'a [u8]),
Cmyk(&'a [u8]),
Indexed(&'a [u8]),
}
impl<'a> Source<'a> {
#[must_use]
pub fn new(samples: &'a Samples, width: u32, height: u32) -> Self {
let width = width as usize;
let kind = match samples {
Samples::Packed(p) => SourceKind::Packed {
rows: Unpacked::new(p),
components: p.components(),
},
Samples::Whole(Pixels::Stencil(bits)) => SourceKind::Stencil {
bits,
scratch: vec![0_u8; width],
},
Samples::Whole(Pixels::Gray8(d)) => SourceKind::Gray(d),
Samples::Whole(Pixels::Rgb8(d)) => SourceKind::Rgb(d),
Samples::Whole(Pixels::Cmyk8(d)) => SourceKind::Cmyk(d),
Samples::Whole(Pixels::Indexed { indices, .. }) => SourceKind::Indexed(indices),
};
Self {
kind,
width,
height,
y: 0,
}
}
#[cfg(test)]
pub(crate) fn at_row(samples: &'a Samples, width: u32, y: u32) -> Self {
let mut source = Self::new(samples, width, y.saturating_add(1));
match &mut source.kind {
SourceKind::Packed { rows, .. } => {
for _ in 0..y {
if rows.next_row().is_none() {
break;
}
}
source.y = y;
}
_ => source.y = y,
}
source
}
pub(crate) fn next_row(&mut self) -> Option<SampleRow<'_>> {
if self.y >= self.height {
return None;
}
let y = self.y as usize;
self.y += 1;
let width = self.width;
let span = |components: usize, len: usize| -> Option<(usize, usize)> {
let stride = width.checked_mul(components)?;
let at = y.checked_mul(stride)?;
let end = at.checked_add(stride)?.min(len).max(at);
let whole = (end - at) / components * components;
Some((at.min(len), at.min(len).checked_add(whole)?))
};
match &mut self.kind {
SourceKind::Stencil { bits, scratch } => {
for (x, slot) in scratch.iter_mut().enumerate() {
let set = u32::try_from(x).is_ok_and(|x| bits.pixel(x, self.y - 1));
*slot = if set { 0 } else { 255 };
}
Some(SampleRow::Gray(scratch))
}
SourceKind::Gray(d) => {
let (a, b) = span(1, d.len())?;
Some(SampleRow::Gray(d.get(a..b)?))
}
SourceKind::Rgb(d) => {
let (a, b) = span(3, d.len())?;
Some(SampleRow::Rgb(d.get(a..b)?))
}
SourceKind::Cmyk(d) => {
let (a, b) = span(4, d.len())?;
Some(SampleRow::Cmyk(d.get(a..b)?))
}
SourceKind::Indexed(d) => {
let (a, b) = span(1, d.len())?;
Some(SampleRow::Indexed(d.get(a..b)?))
}
SourceKind::Packed { rows, components } => {
let row = rows.next_row()?;
Some(match *components {
1 => SampleRow::Gray(row),
4 => SampleRow::Cmyk(row),
_ => SampleRow::Rgb(row),
})
}
}
}
}
#[derive(Debug)]
pub struct Converted<'a> {
source: Source<'a>,
palette: Option<Palette>,
buf: Vec<Rgba8>,
}
impl<'a> Converted<'a> {
#[must_use]
pub fn new(source: Source<'a>, palette: Option<Palette>) -> Self {
let width = source.width;
Self {
source,
palette,
buf: vec![Rgba8::default(); width],
}
}
pub fn next_row_with(&mut self, finish: impl FnOnce(&mut [Rgba8])) -> Option<Row<'_, Rgba8>> {
let samples = self.source.next_row()?;
convert_row(samples, self.palette.as_ref(), &mut self.buf);
finish(&mut self.buf);
Some(Row(&self.buf))
}
}
impl Rows for Converted<'_> {
type Pixel = Rgba8;
fn next(&mut self) -> Option<Row<'_, Rgba8>> {
self.next_row_with(|_| ())
}
}
fn convert_row(samples: SampleRow<'_>, palette: Option<&Palette>, dst: &mut [Rgba8]) {
let converted = match samples {
SampleRow::Gray(src) => {
for (slot, &v) in dst.iter_mut().zip(src) {
*slot = Rgba8([v, v, v, 255]);
}
src.len()
}
SampleRow::Rgb(src) => {
for (slot, px) in dst.iter_mut().zip(src.as_chunks::<3>().0) {
let [red, green, blue] = *px;
*slot = Rgba8([red, green, blue, 255]);
}
src.len() / 3
}
SampleRow::Cmyk(src) => {
for (slot, px) in dst.iter_mut().zip(src.as_chunks::<4>().0) {
let [cyan, magenta, yellow, black] = *px;
let rgb = adobe_cmyk_to_srgb(cyan, magenta, yellow, black);
*slot = Rgba8([rgb[0], rgb[1], rgb[2], 255]);
}
src.len() / 4
}
SampleRow::Indexed(src) => {
for (slot, &index) in dst.iter_mut().zip(src) {
let Rgb8(rgb) = palette.map_or(Rgb8([0, 0, 0]), |p| p.get(index));
*slot = Rgba8([rgb[0], rgb[1], rgb[2], 255]);
}
src.len()
}
};
if let Some(tail) = dst.get_mut(converted..) {
let black = match samples {
SampleRow::Indexed(_) => {
let Rgb8(rgb) = palette.map_or(Rgb8([0, 0, 0]), |p| p.get(0));
Rgba8([rgb[0], rgb[1], rgb[2], 255])
}
SampleRow::Cmyk(_) => {
let rgb = adobe_cmyk_to_srgb(0, 0, 0, 0);
Rgba8([rgb[0], rgb[1], rgb[2], 255])
}
SampleRow::Gray(_) | SampleRow::Rgb(_) => Rgba8([0, 0, 0, 255]),
};
tail.fill(black);
}
}
#[cfg(test)]
mod tests {
use super::*;
fn gray(data: &[u8]) -> Samples {
Samples::Whole(Pixels::Gray8(data.into()))
}
fn packed_gray(data: &[u8], width: u32, height: u32) -> Samples {
Samples::Packed(crate::image::Packed::new(
data.into(),
crate::image::Depth::Eight,
1,
width as usize,
width,
height,
&crate::color::ColorSpace::DeviceGray,
None,
))
}
#[test]
fn a_grey_row_widens_to_opaque_rgba() {
let px = gray(&[0, 128, 255, 7]);
let mut c = Converted::new(Source::new(&px, 2, 2), None);
let first = c.next().expect("first row").pixels().to_vec();
assert_eq!(
first,
vec![Rgba8([0, 0, 0, 255]), Rgba8([128, 128, 128, 255])]
);
let second = c.next().expect("second row").pixels().to_vec();
assert_eq!(
second,
vec![Rgba8([255, 255, 255, 255]), Rgba8([7, 7, 7, 255])]
);
assert!(c.next().is_none());
}
#[test]
fn an_rgb_row_keeps_its_component_order() {
let px = Samples::Whole(Pixels::Rgb8(Box::new([1, 2, 3, 4, 5, 6])));
let mut c = Converted::new(Source::new(&px, 2, 1), None);
let row = c.next().expect("row").pixels().to_vec();
assert_eq!(row, vec![Rgba8([1, 2, 3, 255]), Rgba8([4, 5, 6, 255])]);
}
#[test]
fn an_indexed_row_reads_its_palette_and_falls_back_to_black() {
let px = Samples::Whole(Pixels::Indexed {
indices: Box::new([0, 1, 9]),
palette: Box::new([]),
});
let palette = Palette::new(&[
Rgb {
r: 1.0,
g: 0.0,
b: 0.0,
},
Rgb {
r: 0.0,
g: 1.0,
b: 0.0,
},
]);
let mut c = Converted::new(Source::new(&px, 3, 1), Some(palette));
let row = c.next().expect("row").pixels().to_vec();
assert_eq!(
row,
vec![
Rgba8([255, 0, 0, 255]),
Rgba8([0, 255, 0, 255]),
Rgba8([0, 0, 0, 255]),
]
);
}
#[test]
fn a_stencils_set_bit_is_ink_and_its_clear_bit_is_paper() {
let bits = BitImage {
width: 4,
height: 1,
row_bytes: 1,
bits: vec![0b1010_0000],
};
let px = Samples::Whole(Pixels::Stencil(bits));
let mut c = Converted::new(Source::new(&px, 4, 1), None);
let row = c.next().expect("row").pixels().to_vec();
assert_eq!(
row,
vec![
Rgba8([0, 0, 0, 255]),
Rgba8([255, 255, 255, 255]),
Rgba8([0, 0, 0, 255]),
Rgba8([255, 255, 255, 255]),
]
);
}
#[test]
fn a_short_buffer_paints_black_past_its_end() {
let px = gray(&[1, 2, 3, 4, 5]);
let mut c = Converted::new(Source::new(&px, 2, 4), None);
let black = Rgba8([0, 0, 0, 255]);
let row = |v: &[u8]| -> Vec<Rgba8> { v.iter().map(|&b| Rgba8([b, b, b, 255])).collect() };
assert_eq!(c.next().expect("row 0").pixels(), row(&[1, 2]));
assert_eq!(c.next().expect("row 1").pixels(), row(&[3, 4]));
assert_eq!(
c.next().expect("row 2").pixels(),
vec![Rgba8([5, 5, 5, 255]), black]
);
assert_eq!(c.next().expect("row 3").pixels(), vec![black, black]);
assert!(c.next().is_none());
}
#[test]
fn a_packed_source_yields_what_an_unpacked_one_does() {
let data: Vec<u8> = (0..12u8).map(|i| i.wrapping_mul(23)).collect();
let eager = gray(&data);
let lazy = packed_gray(&data, 3, 4);
let mut a = Converted::new(Source::new(&eager, 3, 4), None);
let mut b = Converted::new(Source::new(&lazy, 3, 4), None);
for _ in 0..4 {
let want = a.next().expect("an eager row").pixels().to_vec();
let got = b.next().expect("a lazy row").pixels().to_vec();
assert_eq!(want, got);
}
assert!(a.next().is_none());
assert!(b.next().is_none());
}
}