use smix_error::{ExpectationFailure, FailureCode, FailureInit};
use smix_simctl::surface_capture::CapturedFrame;
pub(crate) enum GraySampler<'a> {
Decoded(GrayView),
Bgra {
data: &'a [u8],
w: usize,
h: usize,
},
}
impl GraySampler<'_> {
pub(crate) fn dims(&self) -> (usize, usize) {
match self {
GraySampler::Decoded(g) => (g.w, g.h),
GraySampler::Bgra { w, h, .. } => (*w, *h),
}
}
pub(crate) fn gray(&self, x: usize, y: usize) -> u8 {
match self {
GraySampler::Decoded(g) => g.gray(x, y),
GraySampler::Bgra { data, w, h } => {
if x >= *w || y >= *h {
return 0;
}
let idx = (y * w + x) * 4;
let b = data[idx] as u16;
let g = data[idx + 1] as u16;
let r = data[idx + 2] as u16;
((b + g + r) / 3) as u8
}
}
}
}
pub(crate) fn sampler_for(frame: &CapturedFrame) -> Result<GraySampler<'_>, ExpectationFailure> {
match frame {
CapturedFrame::Png(bytes) => Ok(GraySampler::Decoded(decode_gray(bytes)?)),
CapturedFrame::Bgra {
width,
height,
data,
} => {
let w = *width as usize;
let h = *height as usize;
if w == 0 || h == 0 {
return Err(ExpectationFailure::new(FailureInit {
code: Some(FailureCode::DriverError),
message: format!("BGRA frame has zero dimension: {w}×{h}"),
..Default::default()
}));
}
if data.len() < w * h * 4 {
return Err(ExpectationFailure::new(FailureInit {
code: Some(FailureCode::DriverError),
message: format!(
"BGRA frame too short: {} bytes for {w}×{h} (need {})",
data.len(),
w * h * 4
),
..Default::default()
}));
}
Ok(GraySampler::Bgra { data, w, h })
}
}
}
pub(crate) struct GrayView {
raw: Vec<u8>,
channels: usize,
pub(crate) w: usize,
pub(crate) h: usize,
}
impl GrayView {
pub(crate) fn gray(&self, x: usize, y: usize) -> u8 {
if x >= self.w || y >= self.h {
return 0;
}
let idx = (y * self.w + x) * self.channels;
match self.channels {
1 | 2 => self.raw[idx],
3 | 4 => {
let r = self.raw[idx] as u16;
let g = self.raw[idx + 1] as u16;
let b = self.raw[idx + 2] as u16;
((r + g + b) / 3) as u8
}
_ => 0,
}
}
}
pub(crate) fn decode_gray(png_bytes: &[u8]) -> Result<GrayView, ExpectationFailure> {
let decoder = png::Decoder::new(png_bytes);
let mut reader = decoder.read_info().map_err(|e| {
ExpectationFailure::new(FailureInit {
code: Some(FailureCode::DriverError),
message: format!("PNG decode (read_info) failed: {e}"),
..Default::default()
})
})?;
let mut buf = vec![0u8; reader.output_buffer_size()];
let info = reader.next_frame(&mut buf).map_err(|e| {
ExpectationFailure::new(FailureInit {
code: Some(FailureCode::DriverError),
message: format!("PNG decode (next_frame) failed: {e}"),
..Default::default()
})
})?;
let channels = match info.color_type {
png::ColorType::Rgb => 3usize,
png::ColorType::Rgba => 4usize,
png::ColorType::Grayscale => 1usize,
png::ColorType::GrayscaleAlpha => 2usize,
other => {
return Err(ExpectationFailure::new(FailureInit {
code: Some(FailureCode::DriverError),
message: format!("unsupported PNG color type: {other:?}"),
..Default::default()
}));
}
};
let w = info.width as usize;
let h = info.height as usize;
if w == 0 || h == 0 {
return Err(ExpectationFailure::new(FailureInit {
code: Some(FailureCode::DriverError),
message: format!("PNG has zero dimension: {w}×{h}"),
..Default::default()
}));
}
let size = info.buffer_size();
buf.truncate(size);
Ok(GrayView {
raw: buf,
channels,
w,
h,
})
}