use crate::error::{DecodeError, DecodedImage};
use crate::profile::Profile;
use std::sync::atomic::AtomicBool;
pub fn decode(src: &[u8], profile: &Profile, canceled: &AtomicBool) -> Result<DecodedImage, DecodeError> {
let (w, h) = crate::decoder_helpers::validate_dimensions(src, profile, "CL dimensions must be positive", 2)?;
let pixel_count = w * h;
let expected = pixel_count * 2;
let mut dst = vec![0u8; pixel_count * 4];
crate::pixel_utils::check_canceled(canceled, "cl decode canceled")?;
crate::simd::cl_row_to_bgra(&src[..expected], &mut dst);
#[allow(clippy::cast_possible_truncation)]
let out_w = w as u32;
#[allow(clippy::cast_possible_truncation)]
let out_h = h as u32;
Ok(DecodedImage {
data: dst,
width: out_w,
height: out_h,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::profile::Encoding;
use std::sync::atomic::AtomicBool;
fn make_profile(w: i32, h: i32) -> Profile {
Profile {
prefix: 0,
width: w,
height: h,
encoding: Encoding::Rgb565,
frame_byte_length: w * h * 2,
cl_chroma: true,
..Default::default()
}
}
#[test]
fn zero_width_returns_invalid_format() {
let profile = make_profile(0, 100);
let result = decode(b"", &profile, &AtomicBool::new(false));
assert!(matches!(result, Err(DecodeError::InvalidFormat(..))));
}
#[test]
fn zero_height_returns_invalid_format() {
let profile = make_profile(100, 0);
let result = decode(b"", &profile, &AtomicBool::new(false));
assert!(matches!(result, Err(DecodeError::InvalidFormat(..))));
}
#[test]
fn negative_width_returns_invalid_format() {
let profile = make_profile(-1, 100);
let result = decode(b"", &profile, &AtomicBool::new(false));
assert!(matches!(result, Err(DecodeError::InvalidFormat(..))));
}
#[test]
fn buffer_too_short_returns_error() {
let profile = make_profile(10, 10);
let result = decode(&[0u8; 10], &profile, &AtomicBool::new(false));
assert!(matches!(
result,
Err(DecodeError::BufferTooShort {
expected: 200,
actual: 10
})
));
}
#[test]
fn gray_pixel_neutral_chroma() {
let profile = make_profile(1, 1);
let img = decode(&[128, 0x88], &profile, &AtomicBool::new(false)).unwrap();
assert_eq!(img.data, [128, 128, 128, 255]);
assert_eq!(img.width, 1);
assert_eq!(img.height, 1);
}
#[test]
fn black_with_neutral_chroma() {
let profile = make_profile(1, 1);
let img = decode(&[0, 0x88], &profile, &AtomicBool::new(false)).unwrap();
assert_eq!(img.data, [0, 0, 0, 255]);
}
#[test]
fn white_with_neutral_chroma() {
let profile = make_profile(1, 1);
let img = decode(&[255, 0x88], &profile, &AtomicBool::new(false)).unwrap();
assert_eq!(img.data, [255, 255, 255, 255]);
}
#[test]
fn chroma_nibble_high_is_cr_low_is_cb() {
let profile = make_profile(1, 1);
let img = decode(&[128, 0xF0], &profile, &AtomicBool::new(false)).unwrap();
assert_eq!(img.data, [0, 92, 255, 255], "high nibble CB=0, CR=15");
}
#[test]
fn chroma_nibble_low_is_cb_high_is_cr() {
let profile = make_profile(1, 1);
let img = decode(&[128, 0x0F], &profile, &AtomicBool::new(false)).unwrap();
assert_eq!(img.data, [255, 182, 0, 255], "low nibble CB=15, CR=0");
}
#[test]
fn two_pixels_planar_layout() {
let profile = make_profile(2, 1);
let img = decode(&[128, 0, 0x88, 0x88], &profile, &AtomicBool::new(false)).unwrap();
assert_eq!(img.width, 2);
assert_eq!(img.height, 1);
assert_eq!(
img.data,
[
128, 128, 128, 255, 0, 0, 0, 255, ]
);
}
#[test]
fn two_by_two_grid() {
let profile = make_profile(2, 2);
let img = decode(
&[128, 128, 128, 128, 0x88, 0x88, 0x88, 0x88],
&profile,
&AtomicBool::new(false),
)
.unwrap();
assert_eq!(img.width, 2);
assert_eq!(img.height, 2);
let expected = vec![128u8, 128, 128, 255]; for y in 0..2 {
for x in 0..2 {
let off = (y * 2 + x) * 4;
assert_eq!(img.data[off..off + 4], expected, "pixel ({x},{y}) mismatch");
}
}
}
#[test]
fn chroma_nibbles_at_extremes() {
let profile = make_profile(1, 1);
let img = decode(&[255, 0xFF], &profile, &AtomicBool::new(false)).unwrap();
assert_eq!(img.data, [255, 137, 255, 255]);
}
#[test]
fn different_y_and_chroma_planes() {
let profile = make_profile(2, 1);
let img = decode(&[100, 200, 0x88, 0x88], &profile, &AtomicBool::new(false)).unwrap();
assert_eq!(img.data[0..4], [100, 100, 100, 255], "pixel 0 uses Y=100");
assert_eq!(img.data[4..8], [200, 200, 200, 255], "pixel 1 uses Y=200");
}
#[test]
#[allow(clippy::cast_possible_truncation)]
fn odd_width_3x3_decode_correct() {
let mut state: u32 = 0x9ABC_DEF0;
let n = 9;
let mut src = vec![0u8; n * 2];
for b in &mut src {
state = state.wrapping_mul(1_103_515_245).wrapping_add(12_345);
*b = (state >> 16) as u8;
}
let profile = make_profile(3, 3);
let img = decode(&src, &profile, &AtomicBool::new(false)).unwrap();
let (y, chroma) = src.split_at(n);
let mut expected = vec![0u8; n * 4];
for i in 0..n {
let cr = chroma[i] & 0xF0;
let cb = (chroma[i] & 0x0F) << 4;
let px = crate::yuv::yuv_to_bgra(y[i], cb, cr);
let o = i * 4;
expected[o..o + 4].copy_from_slice(&px);
}
assert_eq!(img.data, expected, "3×3 CL decode mismatch");
assert_eq!(img.width, 3);
assert_eq!(img.height, 3);
}
#[test]
#[allow(clippy::cast_possible_truncation)]
fn odd_width_7x3_decode_correct() {
let mut state: u32 = 0xFEDC_BA09;
let n = 21;
let mut src = vec![0u8; n * 2];
for b in &mut src {
state = state.wrapping_mul(1_103_515_245).wrapping_add(12_345);
*b = (state >> 16) as u8;
}
let profile = make_profile(7, 3);
let img = decode(&src, &profile, &AtomicBool::new(false)).unwrap();
let (y, chroma) = src.split_at(n);
let mut expected = vec![0u8; n * 4];
for i in 0..n {
let cr = chroma[i] & 0xF0;
let cb = (chroma[i] & 0x0F) << 4;
let px = crate::yuv::yuv_to_bgra(y[i], cb, cr);
let o = i * 4;
expected[o..o + 4].copy_from_slice(&px);
}
assert_eq!(img.data, expected, "7×3 CL decode mismatch");
}
}