use super::*;
use crate::{
frame::Pal8Frame,
raw::{Pal8, Pal8Row, pal8_to},
sinker::MixedSinker,
};
fn solid_palette(b: u8, g: u8, r: u8, a: u8) -> [[u8; 4]; 256] {
let mut p = [[0u8; 4]; 256];
for entry in p.iter_mut() {
*entry = [b, g, r, a];
}
p
}
fn identity_palette() -> [[u8; 4]; 256] {
let mut p = [[0u8; 4]; 256];
for (i, entry) in p.iter_mut().enumerate() {
let v = i as u8;
*entry = [v, v, v, 255];
}
p
}
fn make_frame<'a>(indices: &'a [u8], palette: &'a [[u8; 4]; 256], width: u32) -> Pal8Frame<'a> {
Pal8Frame::try_new(indices, palette, width, 1, width).unwrap()
}
#[test]
fn pal8_with_rgb_correct_channel_order() {
let palette = solid_palette(10, 20, 30, 40);
let indices = [0u8; 4];
let frame = make_frame(&indices, &palette, 4);
let mut rgb = std::vec![0u8; 4 * 3];
let mut sink = MixedSinker::<Pal8>::new(4, 1).with_rgb(&mut rgb).unwrap();
pal8_to(&frame, &mut sink).unwrap();
for i in 0..4 {
assert_eq!(rgb[i * 3], 30, "px {i} R");
assert_eq!(rgb[i * 3 + 1], 20, "px {i} G");
assert_eq!(rgb[i * 3 + 2], 10, "px {i} B");
}
}
#[test]
fn pal8_with_rgb_drops_alpha() {
let palette = solid_palette(0, 0, 0, 128);
let indices = [0u8; 4];
let frame = make_frame(&indices, &palette, 4);
let mut rgb = std::vec![0u8; 4 * 3];
let mut sink = MixedSinker::<Pal8>::new(4, 1).with_rgb(&mut rgb).unwrap();
pal8_to(&frame, &mut sink).unwrap();
assert_eq!(rgb.len(), 4 * 3);
assert!(rgb.iter().all(|&b| b == 0));
}
#[test]
fn pal8_with_rgba_passes_alpha() {
let palette = solid_palette(0, 0, 0, 200);
let indices = [0u8; 4];
let frame = make_frame(&indices, &palette, 4);
let mut rgba = std::vec![0u8; 4 * 4];
let mut sink = MixedSinker::<Pal8>::new(4, 1).with_rgba(&mut rgba).unwrap();
pal8_to(&frame, &mut sink).unwrap();
for i in 0..4 {
assert_eq!(rgba[i * 4 + 3], 200, "px {i} alpha");
}
}
#[test]
fn pal8_with_rgb_u16_full_range() {
let mut palette = [[0u8; 4]; 256];
palette[0] = [0, 0, 255, 255]; let indices = [0u8; 2];
let frame = make_frame(&indices, &palette, 2);
let mut rgb_u16 = std::vec![0u16; 2 * 3];
let mut sink = MixedSinker::<Pal8>::new(2, 1)
.with_rgb_u16(&mut rgb_u16)
.unwrap();
pal8_to(&frame, &mut sink).unwrap();
assert_eq!(rgb_u16[0], 0xFFFF, "R=255 → 0xFFFF");
assert_eq!(rgb_u16[1], 0x0000, "G=0 → 0x0000");
assert_eq!(rgb_u16[2], 0x0000, "B=0 → 0x0000");
assert_eq!(rgb_u16[3], 0xFFFF);
}
#[test]
fn pal8_with_rgba_u16_passes_alpha() {
let mut palette = [[0u8; 4]; 256];
palette[0] = [0, 0, 0, 128];
let indices = [0u8; 1];
let frame = make_frame(&indices, &palette, 1);
let mut rgba_u16 = std::vec![0u16; 4];
let mut sink = MixedSinker::<Pal8>::new(1, 1)
.with_rgba_u16(&mut rgba_u16)
.unwrap();
pal8_to(&frame, &mut sink).unwrap();
assert_eq!(rgba_u16[3], 0x8080, "A=128 → 0x8080");
}
#[test]
fn pal8_rgb_and_rgba_combo_strategy_a_plus() {
let mut palette = [[0u8; 4]; 256];
palette[0] = [10, 20, 30, 200]; palette[1] = [50, 100, 150, 80];
let indices = [0u8, 1u8, 0u8, 1u8];
let width = 4u32;
let frame = Pal8Frame::try_new(&indices, &palette, width, 1, width).unwrap();
let mut rgb_combo = std::vec![0u8; 4 * 3];
let mut rgba_combo = std::vec![0u8; 4 * 4];
let mut sink_combo = MixedSinker::<Pal8>::new(4, 1)
.with_rgb(&mut rgb_combo)
.unwrap()
.with_rgba(&mut rgba_combo)
.unwrap();
pal8_to(&frame, &mut sink_combo).unwrap();
let mut rgb_only = std::vec![0u8; 4 * 3];
let mut sink_rgb = MixedSinker::<Pal8>::new(4, 1)
.with_rgb(&mut rgb_only)
.unwrap();
pal8_to(&frame, &mut sink_rgb).unwrap();
let mut rgba_only = std::vec![0u8; 4 * 4];
let mut sink_rgba = MixedSinker::<Pal8>::new(4, 1)
.with_rgba(&mut rgba_only)
.unwrap();
pal8_to(&frame, &mut sink_rgba).unwrap();
assert_eq!(rgb_combo, rgb_only, "rgb combo must equal rgb-only");
assert_eq!(rgba_combo, rgba_only, "rgba combo must equal rgba-only");
}
#[test]
fn pal8_rgb_u16_and_rgba_u16_combo() {
let mut palette = [[0u8; 4]; 256];
palette[0] = [10, 20, 30, 200];
palette[1] = [50, 100, 150, 80];
let indices = [0u8, 1u8, 0u8, 1u8];
let width = 4u32;
let frame = Pal8Frame::try_new(&indices, &palette, width, 1, width).unwrap();
let mut rgb_u16_combo = std::vec![0u16; 4 * 3];
let mut rgba_u16_combo = std::vec![0u16; 4 * 4];
let mut sink_combo = MixedSinker::<Pal8>::new(4, 1)
.with_rgb_u16(&mut rgb_u16_combo)
.unwrap()
.with_rgba_u16(&mut rgba_u16_combo)
.unwrap();
pal8_to(&frame, &mut sink_combo).unwrap();
let mut rgb_u16_only = std::vec![0u16; 4 * 3];
let mut sink_rgb = MixedSinker::<Pal8>::new(4, 1)
.with_rgb_u16(&mut rgb_u16_only)
.unwrap();
pal8_to(&frame, &mut sink_rgb).unwrap();
let mut rgba_u16_only = std::vec![0u16; 4 * 4];
let mut sink_rgba = MixedSinker::<Pal8>::new(4, 1)
.with_rgba_u16(&mut rgba_u16_only)
.unwrap();
pal8_to(&frame, &mut sink_rgba).unwrap();
assert_eq!(
rgb_u16_combo, rgb_u16_only,
"rgb_u16 combo must equal rgb_u16-only"
);
assert_eq!(
rgba_u16_combo, rgba_u16_only,
"rgba_u16 combo must equal rgba_u16-only"
);
}
#[test]
fn pal8_with_luma_known_value() {
let mut palette = [[0u8; 4]; 256];
palette[0] = [0, 0, 255, 255]; let indices = [0u8; 4];
let frame = make_frame(&indices, &palette, 4);
let mut luma = std::vec![0u8; 4];
let mut sink = MixedSinker::<Pal8>::new(4, 1).with_luma(&mut luma).unwrap();
pal8_to(&frame, &mut sink).unwrap();
assert_eq!(luma[0], 54, "BT.709 luma for pure red = 54");
assert!(luma.iter().all(|&y| y == 54), "all pixels luma == 54");
}
#[test]
fn pal8_with_luma_u16_widening() {
let mut palette = [[0u8; 4]; 256];
palette[0] = [128, 128, 128, 255];
let indices = [0u8; 1];
let frame = make_frame(&indices, &palette, 1);
let mut luma_u16 = std::vec![0u16; 1];
let mut sink = MixedSinker::<Pal8>::new(1, 1)
.with_luma_u16(&mut luma_u16)
.unwrap();
pal8_to(&frame, &mut sink).unwrap();
assert_eq!(luma_u16[0], 0x8080, "luma=128 → 0x8080");
}
#[test]
fn pal8_with_hsv_saturated_red() {
let mut palette = [[0u8; 4]; 256];
palette[0] = [0, 0, 255, 255];
let indices = [0u8; 1];
let frame = make_frame(&indices, &palette, 1);
let mut h = std::vec![0u8; 1];
let mut s = std::vec![0u8; 1];
let mut v = std::vec![0u8; 1];
let mut sink = MixedSinker::<Pal8>::new(1, 1)
.with_hsv(&mut h, &mut s, &mut v)
.unwrap();
pal8_to(&frame, &mut sink).unwrap();
assert_eq!(s[0], 255, "S=255 for saturated red");
assert_eq!(v[0], 255, "V=255 for max-value red");
assert!(
h[0] <= 1 || h[0] >= 254,
"H for pure red should be near 0 or 255, got {}",
h[0]
);
}
#[test]
fn pal8_walker_row_index_ascending() {
use crate::{PixelSink, raw::Pal8Sink};
struct IndexRecorder {
indices: std::vec::Vec<usize>,
}
impl PixelSink for IndexRecorder {
type Input<'r> = Pal8Row<'r>;
type Error = std::convert::Infallible;
fn begin_frame(&mut self, _w: u32, _h: u32) -> Result<(), Self::Error> {
Ok(())
}
fn process(&mut self, row: Pal8Row<'_>) -> Result<(), Self::Error> {
self.indices.push(row.idx());
Ok(())
}
}
impl Pal8Sink for IndexRecorder {}
let palette = identity_palette();
let (w, h) = (4u32, 5u32);
let indices_data = std::vec![0u8; (w * h) as usize];
let frame = Pal8Frame::try_new(&indices_data, &palette, w, h, w).unwrap();
let mut rec = IndexRecorder {
indices: std::vec::Vec::new(),
};
pal8_to(&frame, &mut rec).unwrap();
let expected: std::vec::Vec<usize> = (0..h as usize).collect();
assert_eq!(
rec.indices, expected,
"row indices must be 0..height in order"
);
}
#[test]
fn pal8_walker_stride_slices_correctly() {
use crate::{PixelSink, raw::Pal8Sink};
struct RowLenRecorder {
lengths: std::vec::Vec<usize>,
}
impl PixelSink for RowLenRecorder {
type Input<'r> = Pal8Row<'r>;
type Error = std::convert::Infallible;
fn begin_frame(&mut self, _w: u32, _h: u32) -> Result<(), Self::Error> {
Ok(())
}
fn process(&mut self, row: Pal8Row<'_>) -> Result<(), Self::Error> {
self.lengths.push(row.row().len());
Ok(())
}
}
impl Pal8Sink for RowLenRecorder {}
let palette = identity_palette();
let (w, h, stride) = (4u32, 3u32, 8u32);
let indices_data = std::vec![0u8; (stride * h) as usize];
let frame = Pal8Frame::try_new(&indices_data, &palette, w, h, stride).unwrap();
let mut rec = RowLenRecorder {
lengths: std::vec::Vec::new(),
};
pal8_to(&frame, &mut rec).unwrap();
assert_eq!(rec.lengths.len(), h as usize, "one call per row");
for (i, &len) in rec.lengths.iter().enumerate() {
assert_eq!(len, w as usize, "row {i}: len must equal width");
}
}
#[test]
fn pal8_error_row_shape_mismatch() {
use crate::sinker::mixed::MixedSinkerError;
let palette = identity_palette();
let width = 4usize;
let mut rgb = std::vec![0u8; width * 3];
let mut sink = MixedSinker::<Pal8>::new(width, 1)
.with_rgb(&mut rgb)
.unwrap();
sink.begin_frame(width as u32, 1).unwrap();
let wrong_indices = [0u8; 2];
let row = Pal8Row::new(&wrong_indices, &palette, 0);
let err = sink.process(row).unwrap_err();
match err {
MixedSinkerError::RowShapeMismatch(e) => {
assert!(
e.which().is_pal_8_index_row(),
"which must be Pal8IndexRow, got {}",
e.which()
);
assert_eq!(e.expected(), width);
assert_eq!(e.actual(), 2);
}
other => panic!("expected RowShapeMismatch, got {other:?}"),
}
}