use core::ops::Range;
use zenpixels::{PixelDescriptor, PixelSlice};
use zenpixels_convert::RowConverter;
pub struct RowStream<'a> {
inner: Inner<'a>,
width: u32,
height: u32,
primaries: zenpixels::ColorPrimaries,
scratch: Vec<u8>,
}
enum Inner<'a> {
Native(PixelSlice<'a>),
StripAlpha8 {
slice: PixelSlice<'a>,
rgb_idx: [u8; 3],
},
Convert {
slice: PixelSlice<'a>,
converter: RowConverter,
},
}
impl<'a> RowStream<'a> {
pub fn new(slice: PixelSlice<'a>) -> Result<Self, String> {
let width = slice.width();
let height = slice.rows();
let desc = slice.descriptor();
let rgb8_compat = desc.layout_compatible(PixelDescriptor::RGB8);
let strip_idx = strip_alpha_indices(desc.format);
let inner = if rgb8_compat {
Inner::Native(slice)
} else if let Some(rgb_idx) = strip_idx {
Inner::StripAlpha8 { slice, rgb_idx }
} else {
let converter = RowConverter::new(desc, PixelDescriptor::RGB8_SRGB)
.map_err(|e| format!("RowConverter::new failed: {:?}", e))?;
Inner::Convert { slice, converter }
};
let scratch_len = (width as usize).saturating_mul(3);
Ok(Self {
inner,
width,
height,
primaries: desc.primaries,
scratch: vec![0u8; scratch_len],
})
}
#[inline]
pub fn primaries(&self) -> zenpixels::ColorPrimaries {
self.primaries
}
#[inline]
pub fn width(&self) -> u32 {
self.width
}
#[inline]
pub fn height(&self) -> u32 {
self.height
}
pub fn fetch_into(&mut self, y: u32, dst: &mut [u8]) {
assert!(
y < self.height,
"row {y} out of bounds (height={})",
self.height
);
let len = self.width as usize * 3;
assert!(dst.len() >= len, "dst too small for one RGB8 row");
match &mut self.inner {
Inner::Native(slice) => {
let row = slice.row(y);
dst[..len].copy_from_slice(&row[..len]);
}
Inner::StripAlpha8 { slice, rgb_idx } => {
strip_alpha_row(slice.row(y), self.width as usize, *rgb_idx, &mut dst[..len]);
}
Inner::Convert { slice, converter } => {
let src = slice.row(y);
converter.convert_row(src, &mut dst[..len], self.width);
}
}
}
pub fn borrow_row(&mut self, y: u32) -> &[u8] {
assert!(
y < self.height,
"row {y} out of bounds (height={})",
self.height
);
let len = self.width as usize * 3;
match &mut self.inner {
Inner::Native(slice) => &slice.row(y)[..len],
Inner::StripAlpha8 { slice, rgb_idx } => {
strip_alpha_row(slice.row(y), self.width as usize, *rgb_idx, &mut self.scratch[..len]);
&self.scratch[..len]
}
Inner::Convert { slice, converter } => {
let src = slice.row(y);
converter.convert_row(src, &mut self.scratch[..len], self.width);
&self.scratch[..len]
}
}
}
pub fn fetch_range(&mut self, range: Range<u32>, dst: &mut [u8]) {
let row_bytes = self.width as usize * 3;
let need = range.len() * row_bytes;
assert!(dst.len() >= need, "dst too small for {} rows", range.len());
for (i, y) in range.enumerate() {
self.fetch_into(y, &mut dst[i * row_bytes..(i + 1) * row_bytes]);
}
}
}
fn strip_alpha_indices(format: zenpixels::PixelFormat) -> Option<[u8; 3]> {
use zenpixels::PixelFormat;
match format {
PixelFormat::Rgba8 | PixelFormat::Rgbx8 => Some([0, 1, 2]),
PixelFormat::Bgra8 | PixelFormat::Bgrx8 => Some([2, 1, 0]),
_ => None,
}
}
#[inline]
fn strip_alpha_row(src: &[u8], width: usize, rgb_idx: [u8; 3], dst: &mut [u8]) {
let need_src = width * 4;
let need_dst = width * 3;
debug_assert!(src.len() >= need_src);
debug_assert!(dst.len() >= need_dst);
let src_row = &src[..need_src];
let dst_row = &mut dst[..need_dst];
match rgb_idx {
[0, 1, 2] => {
garb::bytes::rgba_to_rgb(src_row, dst_row).unwrap();
}
[2, 1, 0] => {
garb::bytes::bgra_to_rgb(src_row, dst_row).unwrap();
}
_ => {
let r = rgb_idx[0] as usize;
let g = rgb_idx[1] as usize;
let b = rgb_idx[2] as usize;
for (s, d) in src_row.chunks_exact(4).zip(dst_row.chunks_exact_mut(3)) {
d[0] = s[r];
d[1] = s[g];
d[2] = s[b];
}
}
}
}
#[cfg(test)]
mod strip_tests {
use super::*;
use zenpixels::{PixelDescriptor, PixelSlice};
#[test]
fn strip_alpha_path_fires_for_rgba8() {
let w: u32 = 8;
let h: u32 = 4;
let mut rgba = Vec::with_capacity((w * h * 4) as usize);
for i in 0..(w * h) {
let v = (i & 0xFF) as u8;
rgba.extend_from_slice(&[v, v.wrapping_add(1), v.wrapping_add(2), 0xFF]);
}
let s = PixelSlice::new(&rgba, w, h, (w * 4) as usize, PixelDescriptor::RGBA8_SRGB)
.unwrap();
let mut stream = RowStream::new(s).unwrap();
let row = stream.borrow_row(0);
assert_eq!(row.len(), (w * 3) as usize);
for px in 0..(w as usize) {
assert_eq!(row[px * 3], (px & 0xFF) as u8);
assert_eq!(row[px * 3 + 1], (px as u8).wrapping_add(1));
assert_eq!(row[px * 3 + 2], (px as u8).wrapping_add(2));
}
}
#[test]
fn strip_alpha_path_swaps_channels_for_bgra8() {
let w: u32 = 4;
let h: u32 = 1;
let bgra: Vec<u8> = vec![10, 20, 30, 0xFF, 11, 21, 31, 0xFF, 12, 22, 32, 0xFF, 13, 23, 33, 0xFF];
let s = PixelSlice::new(&bgra, w, h, (w * 4) as usize, PixelDescriptor::BGRA8_SRGB)
.unwrap();
let mut stream = RowStream::new(s).unwrap();
let row = stream.borrow_row(0);
assert_eq!(row, &[30, 20, 10, 31, 21, 11, 32, 22, 12, 33, 23, 13]);
}
}