use super::error::{BioFormatsError, Result};
pub fn decompress_lzw(data: &[u8]) -> Result<Vec<u8>> {
use weezl::{decode::Decoder, BitOrder};
let mut decoder = Decoder::with_tiff_size_switch(BitOrder::Msb, 8);
decoder
.decode(data)
.map_err(|e| BioFormatsError::Codec(e.to_string()))
}
pub fn decompress_deflate(data: &[u8]) -> Result<Vec<u8>> {
use flate2::read::ZlibDecoder;
use std::io::Read;
let mut decoder = ZlibDecoder::new(data);
let mut out = Vec::new();
decoder.read_to_end(&mut out).map_err(BioFormatsError::Io)?;
Ok(out)
}
pub fn decompress_deflate_raw(data: &[u8]) -> Result<Vec<u8>> {
use flate2::read::DeflateDecoder;
use std::io::Read;
let mut decoder = DeflateDecoder::new(data);
let mut out = Vec::new();
decoder.read_to_end(&mut out).map_err(BioFormatsError::Io)?;
Ok(out)
}
pub fn decompress_packbits(data: &[u8]) -> Result<Vec<u8>> {
let mut out = Vec::new();
let mut i = 0;
while i < data.len() {
let header = data[i] as i8;
i += 1;
if header >= 0 {
let count = (header as usize) + 1;
if i + count > data.len() {
return Err(BioFormatsError::InvalidData(
"PackBits: literal run overruns input".into(),
));
}
out.extend_from_slice(&data[i..i + count]);
i += count;
} else if header != -128 {
let count = (-header as usize) + 1;
if i >= data.len() {
return Err(BioFormatsError::InvalidData(
"PackBits: repeat run missing byte".into(),
));
}
let byte = data[i];
i += 1;
for _ in 0..count {
out.push(byte);
}
}
}
Ok(out)
}
pub fn decompress_jpeg(data: &[u8]) -> Result<Vec<u8>> {
let mut decoder = jpeg_decoder::Decoder::new(data);
decoder
.decode()
.map_err(|e| BioFormatsError::Codec(e.to_string()))
}
pub fn decompress_zstd(data: &[u8]) -> Result<Vec<u8>> {
zstd::decode_all(data).map_err(BioFormatsError::Io)
}
pub fn decompress_png(data: &[u8]) -> Result<Vec<u8>> {
decode_image_memory(data, image::ImageFormat::Png)
}
pub fn decompress_bmp(data: &[u8]) -> Result<Vec<u8>> {
decode_image_memory(data, image::ImageFormat::Bmp)
}
fn decode_image_memory(data: &[u8], format: image::ImageFormat) -> Result<Vec<u8>> {
let img = image::load_from_memory_with_format(data, format)
.map_err(|e| BioFormatsError::Codec(e.to_string()))?;
Ok(match img {
image::DynamicImage::ImageLuma8(b) => b.into_raw(),
image::DynamicImage::ImageLumaA8(b) => b.into_raw(),
image::DynamicImage::ImageRgb8(b) => b.into_raw(),
image::DynamicImage::ImageRgba8(b) => b.into_raw(),
image::DynamicImage::ImageLuma16(b) => {
b.into_raw().iter().flat_map(|v| v.to_le_bytes()).collect()
}
image::DynamicImage::ImageLumaA16(b) => {
b.into_raw().iter().flat_map(|v| v.to_le_bytes()).collect()
}
image::DynamicImage::ImageRgb16(b) => {
b.into_raw().iter().flat_map(|v| v.to_le_bytes()).collect()
}
image::DynamicImage::ImageRgba16(b) => {
b.into_raw().iter().flat_map(|v| v.to_le_bytes()).collect()
}
other => other.to_rgb8().into_raw(),
})
}
pub fn decompress_jpeg2000(data: &[u8]) -> Result<Vec<u8>> {
use jpeg2k::Image as J2kImage;
let image = J2kImage::from_bytes(data)
.map_err(|e| BioFormatsError::Codec(format!("JPEG 2000: {e}")))?;
let components = image.components();
if components.is_empty() {
return Err(BioFormatsError::Codec("JPEG 2000: no components".into()));
}
let width = components[0].width() as usize;
let height = components[0].height() as usize;
let n_components = components.len();
let prec = components[0].precision() as usize;
let bps = if prec <= 8 {
1
} else if prec <= 16 {
2
} else {
4
};
let mut out = Vec::with_capacity(width * height * n_components * bps);
for y in 0..height {
for x in 0..width {
for c in 0..n_components {
let val = components[c].data()[y * width + x];
let bytes = val.to_le_bytes();
out.extend_from_slice(&bytes[..bps]);
}
}
}
Ok(out)
}
#[cfg(feature = "jpegxr")]
pub fn decompress_jpegxr(data: &[u8]) -> Result<Vec<u8>> {
use std::io::Cursor;
let cursor = Cursor::new(data);
let mut decoder = jpegxr::ImageDecode::with_reader(cursor)
.map_err(|e| BioFormatsError::Codec(format!("JPEG-XR: {e}")))?;
let (width, height) = decoder
.get_size()
.map_err(|e| BioFormatsError::Codec(format!("JPEG-XR size: {e}")))?;
let format = decoder
.get_pixel_format()
.map_err(|e| BioFormatsError::Codec(format!("JPEG-XR format: {e}")))?;
let bpp: usize = match format {
jpegxr::PixelFormat::PixelFormat8bppGray => 1,
jpegxr::PixelFormat::PixelFormat16bppGray => 2,
jpegxr::PixelFormat::PixelFormat32bppGrayFloat => 4,
jpegxr::PixelFormat::PixelFormat24bppRGB => 3,
jpegxr::PixelFormat::PixelFormat24bppBGR => 3,
jpegxr::PixelFormat::PixelFormat32bppBGRA => 4,
jpegxr::PixelFormat::PixelFormat32bppRGBA => 4,
jpegxr::PixelFormat::PixelFormat48bppRGB => 6,
jpegxr::PixelFormat::PixelFormat64bppRGBA => 8,
_ => 3, };
let row_bytes = width as usize * bpp;
let stride = (row_bytes + 3) & !3; let mut buf = vec![0u8; stride * height as usize];
decoder
.copy_all(&mut buf, stride)
.map_err(|e| BioFormatsError::Codec(format!("JPEG-XR decode: {e}")))?;
if stride != row_bytes {
let mut out = Vec::with_capacity(row_bytes * height as usize);
for y in 0..height as usize {
out.extend_from_slice(&buf[y * stride..y * stride + row_bytes]);
}
Ok(out)
} else {
Ok(buf)
}
}
#[cfg(not(feature = "jpegxr"))]
pub fn decompress_jpegxr(_data: &[u8]) -> Result<Vec<u8>> {
Err(BioFormatsError::UnsupportedFormat(
"JPEG-XR support requires the 'jpegxr' feature: cargo build --features jpegxr".into(),
))
}
pub fn decompress_ccitt_group3(data: &[u8], width: u32, height: u32) -> Result<Vec<u8>> {
let width = width as usize;
let height = height as usize;
if width == 0 || height == 0 {
return Ok(Vec::new());
}
let row_bytes = width.div_ceil(8);
let mut out = vec![0u8; row_bytes * height];
let mut bits = MsbBitReader::new(data);
for row in 0..height {
bits.skip_ccitt_eols();
let mut x = 0usize;
let mut black = false;
while x < width {
let mut run = 0usize;
loop {
match decode_ccitt_run(&mut bits, black)? {
CcittCode::Run(len) => {
run += len as usize;
if len < 64 {
break;
}
}
CcittCode::Eol => {
if x == 0 {
continue;
}
return Err(BioFormatsError::InvalidData(
"CCITT Group 3: EOL before row is complete".into(),
));
}
}
}
if x + run > width {
return Err(BioFormatsError::InvalidData(
"CCITT Group 3: run exceeds row width".into(),
));
}
if black {
for px in x..x + run {
out[row * row_bytes + px / 8] |= 0x80 >> (px % 8);
}
}
x += run;
black = !black;
}
}
Ok(out)
}
pub fn decompress_ccitt_group4(data: &[u8], width: u32, height: u32) -> Result<Vec<u8>> {
let width = width as usize;
let height = height as usize;
if width == 0 || height == 0 {
return Ok(Vec::new());
}
let row_bytes = width.div_ceil(8);
let mut out = vec![0u8; row_bytes * height];
let mut bits = MsbBitReader::new(data);
let mut reference = vec![false; width];
for row in 0..height {
let mut coding = vec![false; width];
let mut x = 0usize;
let mut black = false;
while x < width {
match decode_group4_mode(&mut bits)? {
Group4Mode::Pass => {
let (_, b2) = group4_reference_changing_elements(&reference, x, black);
if b2 < x {
return Err(BioFormatsError::InvalidData(
"CCITT Group 4: pass mode moved backwards".into(),
));
}
if black {
coding[x..b2].fill(true);
}
x = b2;
}
Group4Mode::Horizontal => {
let run1 = decode_ccitt_run_length(&mut bits, black, "CCITT Group 4")?;
let mid = x.checked_add(run1).ok_or_else(|| {
BioFormatsError::InvalidData(
"CCITT Group 4: horizontal run overflows".into(),
)
})?;
if mid > width {
return Err(BioFormatsError::InvalidData(
"CCITT Group 4: horizontal run exceeds row width".into(),
));
}
if black {
coding[x..mid].fill(true);
}
let run2 = decode_ccitt_run_length(&mut bits, !black, "CCITT Group 4")?;
let next = mid.checked_add(run2).ok_or_else(|| {
BioFormatsError::InvalidData(
"CCITT Group 4: horizontal run overflows".into(),
)
})?;
if next > width {
return Err(BioFormatsError::InvalidData(
"CCITT Group 4: horizontal run exceeds row width".into(),
));
}
if !black {
coding[mid..next].fill(true);
}
if next == x {
return Err(BioFormatsError::InvalidData(
"CCITT Group 4: horizontal mode made no progress".into(),
));
}
x = next;
}
Group4Mode::Vertical(offset) => {
let (b1, _) = group4_reference_changing_elements(&reference, x, black);
let a1 = b1 as isize + offset as isize;
if a1 < x as isize || a1 > width as isize {
return Err(BioFormatsError::InvalidData(
"CCITT Group 4: vertical run exceeds row width".into(),
));
}
let next = a1 as usize;
if black {
coding[x..next].fill(true);
}
x = next;
black = !black;
}
}
}
for (px, &is_black) in coding.iter().enumerate() {
if is_black {
out[row * row_bytes + px / 8] |= 0x80 >> (px % 8);
}
}
reference = coding;
}
Ok(out)
}
const MAX_VIDEO_DECODE_BYTES: usize = 512 * 1024 * 1024;
fn checked_video_output_len(
codec: &str,
width: usize,
height: usize,
channels: usize,
) -> Result<usize> {
let len = width
.checked_mul(height)
.and_then(|n| n.checked_mul(channels))
.ok_or_else(|| {
BioFormatsError::InvalidData(format!("{codec}: output byte count overflows"))
})?;
if len > MAX_VIDEO_DECODE_BYTES {
return Err(BioFormatsError::InvalidData(format!(
"{codec}: decoded frame is too large"
)));
}
Ok(len)
}
pub fn decompress_msrle(data: &[u8], width: u32, height: u32) -> Result<Vec<u8>> {
let width = width as usize;
let height = height as usize;
if width == 0 || height == 0 {
return Err(BioFormatsError::InvalidData(
"MSRLE: width and height must be non-zero".into(),
));
}
let output_len = checked_video_output_len("MSRLE", width, height, 1)?;
let mut out = vec![0u8; output_len];
let mut x = 0usize;
let mut y = height - 1;
let mut i = 0usize;
while i + 1 < data.len() {
let count = data[i] as usize;
let value = data[i + 1];
i += 2;
if count != 0 {
if x + count > width {
return Err(BioFormatsError::InvalidData(
"MSRLE: encoded run exceeds row width".into(),
));
}
let row = y * width;
for px in &mut out[row + x..row + x + count] {
*px = value;
}
x += count;
continue;
}
match value {
0 => {
x = 0;
if y == 0 {
break;
}
y -= 1;
}
1 => break,
2 => {
if i + 1 >= data.len() {
return Err(BioFormatsError::InvalidData(
"MSRLE: delta command missing offsets".into(),
));
}
let dx = data[i] as usize;
let dy = data[i + 1] as usize;
i += 2;
x = x.checked_add(dx).ok_or_else(|| {
BioFormatsError::InvalidData("MSRLE: delta x overflows".into())
})?;
y = y.checked_sub(dy).ok_or_else(|| {
BioFormatsError::InvalidData("MSRLE: delta y moves before first row".into())
})?;
if x > width {
return Err(BioFormatsError::InvalidData(
"MSRLE: delta moves past row width".into(),
));
}
}
n => {
let n = n as usize;
if i + n > data.len() {
return Err(BioFormatsError::InvalidData(
"MSRLE: absolute run overruns input".into(),
));
}
if x + n > width {
return Err(BioFormatsError::InvalidData(
"MSRLE: absolute run exceeds row width".into(),
));
}
let row = y * width;
out[row + x..row + x + n].copy_from_slice(&data[i..i + n]);
x += n;
i += n;
if n & 1 == 1 {
if i >= data.len() {
return Err(BioFormatsError::InvalidData(
"MSRLE: absolute run missing pad byte".into(),
));
}
i += 1;
}
}
}
}
Ok(out)
}
pub fn decompress_msvideo(data: &[u8], width: u32, height: u32, bpp: u32) -> Result<Vec<u8>> {
let width = width as usize;
let height = height as usize;
if width == 0 || height == 0 {
return Err(BioFormatsError::InvalidData(
"MSVideo: width and height must be non-zero".into(),
));
}
let out_channels = match bpp {
1 => 1usize,
2 => 3usize,
other => {
return Err(BioFormatsError::UnsupportedFormat(format!(
"MSVideo: unsupported bit depth {} (only 8-bit and 16-bit are supported)",
other * 8
)))
}
};
let sixteen_bit = bpp == 2;
let output_len = checked_video_output_len("MSVideo", width, height, out_channels)?;
let mut out = vec![0u8; output_len];
let blocks_wide = width.div_ceil(4);
let blocks_high = height.div_ceil(4);
let total_blocks = blocks_wide
.checked_mul(blocks_high)
.ok_or_else(|| BioFormatsError::InvalidData("MSVideo: block count overflows".into()))?;
let put = |out: &mut [u8], px: usize, py: usize, color: u16| {
if px >= width || py >= height {
return;
}
if sixteen_bit {
let r = (((color >> 10) & 0x1f) as u32 * 255 / 31) as u8;
let g = (((color >> 5) & 0x1f) as u32 * 255 / 31) as u8;
let b = ((color & 0x1f) as u32 * 255 / 31) as u8;
let off = (py * width + px) * 3;
out[off] = r;
out[off + 1] = g;
out[off + 2] = b;
} else {
out[py * width + px] = color as u8;
}
};
let put_2color = |out: &mut [u8], base_x: usize, base_y: usize, flags: u16, ca: u16, cb: u16| {
for bit in 0..16usize {
let col = bit % 4;
let row_from_bottom = bit / 4;
let px = base_x + col;
let py = base_y + (3 - row_from_bottom);
let color = if (flags >> bit) & 1 == 1 { ca } else { cb };
put(out, px, py, color);
}
};
let mut i = 0usize;
let mut block_index = 0usize;
while block_index < total_blocks {
if i + 2 > data.len() {
break;
}
let byte_a = data[i];
let byte_b = data[i + 1];
let flags = u16::from_le_bytes([byte_a, byte_b]);
i += 2;
let block_row_from_bottom = block_index / blocks_wide;
let block_col = block_index % blocks_wide;
let block_row = blocks_high - 1 - block_row_from_bottom;
let base_x = block_col * 4;
let base_y = block_row * 4;
if byte_a == 0 && byte_b == 0 {
break;
}
if (0x84..=0x87).contains(&byte_b) {
let skip = (byte_b as usize - 0x84) * 256 + byte_a as usize;
block_index += skip.max(1);
continue;
}
if sixteen_bit {
if byte_b < 0x80 {
if i + 2 > data.len() {
return Err(BioFormatsError::InvalidData(
"MSVideo: 16-bit block missing color".into(),
));
}
let first = u16::from_le_bytes([data[i], data[i + 1]]);
if first & 0x8000 != 0 {
if i + 16 > data.len() {
return Err(BioFormatsError::InvalidData(
"MSVideo: 16-bit 8-color block overruns input".into(),
));
}
let mut colors = [0u16; 8];
for c in &mut colors {
*c = u16::from_le_bytes([data[i], data[i + 1]]) & 0x7fff;
i += 2;
}
put_8color(&mut out, base_x, base_y, flags, &colors, &put);
} else {
if i + 4 > data.len() {
return Err(BioFormatsError::InvalidData(
"MSVideo: 16-bit 2-color block overruns input".into(),
));
}
let ca = u16::from_le_bytes([data[i], data[i + 1]]);
let cb = u16::from_le_bytes([data[i + 2], data[i + 3]]);
i += 4;
put_2color(&mut out, base_x, base_y, flags, ca, cb);
}
} else {
let color = flags & 0x7fff;
for bit in 0..16usize {
put(&mut out, base_x + bit % 4, base_y + (3 - bit / 4), color);
}
}
block_index += 1;
continue;
}
if byte_b < 0x80 {
if i + 2 > data.len() {
return Err(BioFormatsError::InvalidData(
"MSVideo: 8-bit 2-color block overruns input".into(),
));
}
let ca = data[i] as u16;
let cb = data[i + 1] as u16;
i += 2;
put_2color(&mut out, base_x, base_y, flags, ca, cb);
} else if byte_b >= 0x90 {
if i + 8 > data.len() {
return Err(BioFormatsError::InvalidData(
"MSVideo: 8-bit 8-color block overruns input".into(),
));
}
let mut colors = [0u16; 8];
for c in &mut colors {
*c = data[i] as u16;
i += 1;
}
put_8color(&mut out, base_x, base_y, flags, &colors, &put);
} else {
let color = byte_a as u16;
for bit in 0..16usize {
put(&mut out, base_x + bit % 4, base_y + (3 - bit / 4), color);
}
}
block_index += 1;
}
Ok(out)
}
fn put_8color<F: Fn(&mut [u8], usize, usize, u16)>(
out: &mut [u8],
base_x: usize,
base_y: usize,
flags: u16,
colors: &[u16; 8],
put: &F,
) {
for bit in 0..16usize {
let col = bit % 4;
let row_from_bottom = bit / 4;
let quad = (if row_from_bottom < 2 { 0 } else { 2 }) + usize::from(col >= 2);
let pair = quad * 2;
let color = if (flags >> bit) & 1 == 1 {
colors[pair + 1]
} else {
colors[pair]
};
let px = base_x + col;
let py = base_y + (3 - row_from_bottom);
put(out, px, py, color);
}
}
pub fn decompress_mjpb(_data: &[u8]) -> Result<Vec<u8>> {
Err(BioFormatsError::UnsupportedFormat(
"Motion JPEG-B codec not implemented: this checkout has no MJPB bitstream parser, ome-codecs source, or known-output fixture".into(),
))
}
pub fn decompress_qtrle(data: &[u8], width: u32, height: u32, bpp: u32) -> Result<Vec<u8>> {
let bytes_per_pixel = match bpp {
8 => 1usize,
24 => 3usize,
_ => {
return Err(BioFormatsError::UnsupportedFormat(format!(
"QuickTime RLE: unsupported {bpp} bpp; only 8 and 24 bpp are implemented"
)));
}
};
let width = width as usize;
let height = height as usize;
if width == 0 || height == 0 {
return Ok(Vec::new());
}
let row_bytes = width.checked_mul(bytes_per_pixel).ok_or_else(|| {
BioFormatsError::InvalidData("QuickTime RLE: row byte count overflows".into())
})?;
let out_len = row_bytes.checked_mul(height).ok_or_else(|| {
BioFormatsError::InvalidData("QuickTime RLE: output byte count overflows".into())
})?;
if data.len() < 6 {
return Err(BioFormatsError::InvalidData(
"QuickTime RLE: chunk header is truncated".into(),
));
}
let chunk_size = u32::from_be_bytes([data[0], data[1], data[2], data[3]]) as usize;
if chunk_size < 6 || chunk_size > data.len() {
return Err(BioFormatsError::InvalidData(
"QuickTime RLE: invalid chunk size".into(),
));
}
let mut i = 4usize;
let header = read_qtrle_be_u16(data, &mut i)?;
if header & !0x0008 != 0 {
return Err(BioFormatsError::UnsupportedFormat(format!(
"QuickTime RLE: unsupported header flags 0x{header:04x}"
)));
}
let (start_line, changed_lines) = if header & 0x0008 != 0 {
let start_line = read_qtrle_be_u16(data, &mut i)? as usize;
i = i.checked_add(2).ok_or_else(|| {
BioFormatsError::InvalidData("QuickTime RLE: header offset overflows".into())
})?;
if i > chunk_size {
return Err(BioFormatsError::InvalidData(
"QuickTime RLE: changed-line header is truncated".into(),
));
}
let changed_lines = read_qtrle_be_u16(data, &mut i)? as usize;
i = i.checked_add(2).ok_or_else(|| {
BioFormatsError::InvalidData("QuickTime RLE: header offset overflows".into())
})?;
if i > chunk_size {
return Err(BioFormatsError::InvalidData(
"QuickTime RLE: changed-line header is truncated".into(),
));
}
(start_line, changed_lines)
} else {
(0, height)
};
let end_line = start_line.checked_add(changed_lines).ok_or_else(|| {
BioFormatsError::InvalidData("QuickTime RLE: changed-line range overflows".into())
})?;
if end_line > height {
return Err(BioFormatsError::InvalidData(
"QuickTime RLE: changed-line range exceeds image height".into(),
));
}
let mut out = vec![0u8; out_len];
for y in start_line..end_line {
let initial_skip = read_qtrle_u8(data, &mut i, chunk_size)? as usize;
if initial_skip == 0 {
return Err(BioFormatsError::InvalidData(
"QuickTime RLE: line skip underflows".into(),
));
}
let mut x = initial_skip - 1;
loop {
let opcode = read_qtrle_i8(data, &mut i, chunk_size)?;
match opcode {
-1 => break,
0 => {
let skip = read_qtrle_u8(data, &mut i, chunk_size)? as usize;
if skip == 0 {
return Err(BioFormatsError::InvalidData(
"QuickTime RLE: skip opcode underflows".into(),
));
}
x = x.checked_add(skip - 1).ok_or_else(|| {
BioFormatsError::InvalidData("QuickTime RLE: skip overflows".into())
})?;
if x > width {
return Err(BioFormatsError::InvalidData(
"QuickTime RLE: skip exceeds row width".into(),
));
}
}
n if n < 0 => {
let count = (-n) as usize;
if i + bytes_per_pixel > chunk_size {
return Err(BioFormatsError::InvalidData(
"QuickTime RLE: repeat pixel overruns input".into(),
));
}
write_qtrle_pixels(
&mut out,
y,
&mut x,
width,
row_bytes,
bytes_per_pixel,
count,
&data[i..i + bytes_per_pixel],
)?;
i += bytes_per_pixel;
}
n => {
let count = n as usize;
let byte_count = count.checked_mul(bytes_per_pixel).ok_or_else(|| {
BioFormatsError::InvalidData(
"QuickTime RLE: literal byte count overflows".into(),
)
})?;
if i + byte_count > chunk_size {
return Err(BioFormatsError::InvalidData(
"QuickTime RLE: literal run overruns input".into(),
));
}
if x + count > width {
return Err(BioFormatsError::InvalidData(
"QuickTime RLE: literal run exceeds row width".into(),
));
}
let dst = y * row_bytes + x * bytes_per_pixel;
out[dst..dst + byte_count].copy_from_slice(&data[i..i + byte_count]);
i += byte_count;
x += count;
}
}
}
}
Ok(out)
}
pub fn decompress_rpza(data: &[u8], width: u32, height: u32) -> Result<Vec<u8>> {
let width = width as usize;
let height = height as usize;
if width == 0 || height == 0 {
return Ok(Vec::new());
}
if data.len() < 4 {
return Err(BioFormatsError::InvalidData(
"RPZA: chunk header is truncated".into(),
));
}
let chunk_len = ((data[1] as usize) << 16) | ((data[2] as usize) << 8) | data[3] as usize;
if chunk_len > data.len() {
return Err(BioFormatsError::InvalidData(
"RPZA: chunk length exceeds input".into(),
));
}
let output_len = checked_video_output_len("RPZA", width, height, 3)?;
let mut out = vec![0u8; output_len];
let blocks_wide = width.div_ceil(4);
let blocks_high = height.div_ceil(4);
let total_blocks = blocks_wide
.checked_mul(blocks_high)
.ok_or_else(|| BioFormatsError::InvalidData("RPZA: block count overflows".into()))?;
let mut block = 0usize;
let mut i = 4usize;
let end = chunk_len.max(4);
while block < total_blocks && i < end {
let opcode = data[i];
i += 1;
if opcode < 0x80 {
i -= 1;
if i + 32 > end {
return Err(BioFormatsError::InvalidData(
"RPZA: literal block overruns input".into(),
));
}
let mut colors = [0u16; 16];
for color in &mut colors {
*color = read_rpza_be_u16(data, &mut i, end)?;
}
rpza_write_literal_block(&mut out, width, height, blocks_wide, block, &colors);
block += 1;
continue;
}
let count = ((opcode & 0x1f) as usize) + 1;
if block + count > total_blocks {
return Err(BioFormatsError::InvalidData(
"RPZA: block run exceeds frame".into(),
));
}
match opcode & 0xe0 {
0x80 => {
block += count;
}
0xa0 => {
let color = read_rpza_be_u16(data, &mut i, end)?;
for _ in 0..count {
rpza_write_solid_block(&mut out, width, height, blocks_wide, block, color);
block += 1;
}
}
0xc0 => {
let color_a = read_rpza_be_u16(data, &mut i, end)?;
let color_b = read_rpza_be_u16(data, &mut i, end)?;
let colors = rpza_four_color_table(color_a, color_b);
for _ in 0..count {
if i + 4 > end {
return Err(BioFormatsError::InvalidData(
"RPZA: four-color block overruns input".into(),
));
}
let indices = [data[i], data[i + 1], data[i + 2], data[i + 3]];
i += 4;
rpza_write_indexed_block(
&mut out,
width,
height,
blocks_wide,
block,
&colors,
&indices,
);
block += 1;
}
}
_ => {
return Err(BioFormatsError::InvalidData(format!(
"RPZA: unsupported opcode 0x{opcode:02x}"
)));
}
}
}
Ok(out)
}
pub fn decompress_nikon(data: &[u8], width: u32, height: u32, bpp: u32) -> Result<Vec<u8>> {
Err(BioFormatsError::UnsupportedFormat(format!(
"Nikon NEF compression 34713 requires Nikon maker-note IFD tag 150 metadata \
(vPredictor, curve, split, lossless flag, and compressed strip byte count/maxBytes) \
plus a dedicated decoder; generic TIFF strip geometry is insufficient for \
{width}x{height} at {bpp} bpp with {} compressed bytes",
data.len()
)))
}
pub fn decompress_lzo(data: &[u8]) -> Result<Vec<u8>> {
let mut decoder = Lzo1xDecoder::new(data);
decoder.decode()
}
pub fn codec_base64_decode(data: &[u8]) -> Result<Vec<u8>> {
let mut out = Vec::with_capacity(data.len() * 3 / 4);
let mut buf: u32 = 0;
let mut bits: u32 = 0;
for &b in data {
let val = match b {
b'A'..=b'Z' => b - b'A',
b'a'..=b'z' => b - b'a' + 26,
b'0'..=b'9' => b - b'0' + 52,
b'+' => 62,
b'/' => 63,
b'=' | b'\n' | b'\r' | b' ' => continue,
_ => continue,
};
buf = (buf << 6) | val as u32;
bits += 6;
if bits >= 8 {
bits -= 8;
out.push((buf >> bits) as u8);
buf &= (1 << bits) - 1;
}
}
Ok(out)
}
pub fn decompress_huffman(_data: &[u8]) -> Result<Vec<u8>> {
Err(BioFormatsError::UnsupportedFormat(
"Standalone Huffman codec not yet implemented".into(),
))
}
fn read_qtrle_be_u16(data: &[u8], i: &mut usize) -> Result<u16> {
if *i + 2 > data.len() {
return Err(BioFormatsError::InvalidData(
"QuickTime RLE: truncated 16-bit field".into(),
));
}
let value = u16::from_be_bytes([data[*i], data[*i + 1]]);
*i += 2;
Ok(value)
}
fn read_qtrle_u8(data: &[u8], i: &mut usize, limit: usize) -> Result<u8> {
if *i >= limit {
return Err(BioFormatsError::InvalidData(
"QuickTime RLE: packet overruns chunk".into(),
));
}
let value = data[*i];
*i += 1;
Ok(value)
}
fn read_qtrle_i8(data: &[u8], i: &mut usize, limit: usize) -> Result<i8> {
Ok(read_qtrle_u8(data, i, limit)? as i8)
}
struct Lzo1xDecoder<'a> {
input: &'a [u8],
ip: usize,
output: Vec<u8>,
}
impl<'a> Lzo1xDecoder<'a> {
fn new(input: &'a [u8]) -> Self {
Self {
input,
ip: 0,
output: Vec::new(),
}
}
fn decode(&mut self) -> Result<Vec<u8>> {
if self.input.is_empty() {
return Ok(Vec::new());
}
let mut token = self.read_u8()? as usize;
if token > 17 {
let literal_len = token - 17;
self.copy_literals(literal_len)?;
token = self.read_u8()? as usize;
if token < 16 {
return Err(BioFormatsError::InvalidData(
"LZO1X: invalid token after initial literal run".into(),
));
}
}
loop {
if token < 16 {
let literal_len = if token == 0 {
self.extended_len(15)?
} else {
token
} + 3;
self.copy_literals(literal_len)?;
token = self.read_u8()? as usize;
if token < 16 {
let offset = 0x0801 + (token >> 2) + ((self.read_u8()? as usize) << 2);
self.copy_match(offset, 3)?;
token = self.copy_trailing_literals(token & 0x03)?;
continue;
}
}
let trailing = if token >= 64 {
let len = (token >> 5) + 1;
let offset = 1 + ((token >> 2) & 0x07) + ((self.read_u8()? as usize) << 3);
self.copy_match(offset, len)?;
token & 0x03
} else if token >= 32 {
let len = token & 0x1f;
let len = if len == 0 {
self.extended_len(31)?
} else {
len
} + 2;
let pair = self.read_le_u16()? as usize;
let offset = 1 + (pair >> 2);
self.copy_match(offset, len)?;
pair & 0x03
} else {
let len = token & 0x07;
let len = if len == 0 { self.extended_len(7)? } else { len } + 2;
let pair = self.read_le_u16()? as usize;
let offset = 0x4000 + ((token & 0x08) << 11) + (pair >> 2);
if offset == 0x4000 {
if self.ip == self.input.len() {
return Ok(std::mem::take(&mut self.output));
}
return Err(BioFormatsError::InvalidData(
"LZO1X: trailing data after end marker".into(),
));
}
self.copy_match(offset + 1, len)?;
pair & 0x03
};
token = self.copy_trailing_literals(trailing)?;
}
}
fn read_u8(&mut self) -> Result<u8> {
if self.ip >= self.input.len() {
return Err(BioFormatsError::InvalidData(
"LZO1X: truncated input".into(),
));
}
let value = self.input[self.ip];
self.ip += 1;
Ok(value)
}
fn read_le_u16(&mut self) -> Result<u16> {
if self.ip + 2 > self.input.len() {
return Err(BioFormatsError::InvalidData(
"LZO1X: truncated 16-bit match offset".into(),
));
}
let value = u16::from_le_bytes([self.input[self.ip], self.input[self.ip + 1]]);
self.ip += 2;
Ok(value)
}
fn extended_len(&mut self, base: usize) -> Result<usize> {
let mut len = base;
while self.ip < self.input.len() && self.input[self.ip] == 0 {
len = len.checked_add(255).ok_or_else(|| {
BioFormatsError::InvalidData("LZO1X: length overflows usize".into())
})?;
self.ip += 1;
}
let extra = self.read_u8()? as usize;
len.checked_add(extra)
.ok_or_else(|| BioFormatsError::InvalidData("LZO1X: length overflows usize".into()))
}
fn copy_literals(&mut self, len: usize) -> Result<()> {
if self.ip + len > self.input.len() {
return Err(BioFormatsError::InvalidData(
"LZO1X: literal run overruns input".into(),
));
}
self.output
.extend_from_slice(&self.input[self.ip..self.ip + len]);
self.ip += len;
Ok(())
}
fn copy_match(&mut self, offset: usize, len: usize) -> Result<()> {
if offset == 0 || offset > self.output.len() {
return Err(BioFormatsError::InvalidData(
"LZO1X: invalid match back-reference".into(),
));
}
let start = self.output.len() - offset;
for i in 0..len {
let value = self.output[start + i];
self.output.push(value);
}
Ok(())
}
fn copy_trailing_literals(&mut self, len: usize) -> Result<usize> {
self.copy_literals(len)?;
Ok(self.read_u8()? as usize)
}
}
fn write_qtrle_pixels(
out: &mut [u8],
y: usize,
x: &mut usize,
width: usize,
row_bytes: usize,
bytes_per_pixel: usize,
count: usize,
pixel: &[u8],
) -> Result<()> {
if *x + count > width {
return Err(BioFormatsError::InvalidData(
"QuickTime RLE: repeat run exceeds row width".into(),
));
}
let mut dst = y * row_bytes + *x * bytes_per_pixel;
for _ in 0..count {
out[dst..dst + bytes_per_pixel].copy_from_slice(pixel);
dst += bytes_per_pixel;
}
*x += count;
Ok(())
}
fn read_rpza_be_u16(data: &[u8], i: &mut usize, limit: usize) -> Result<u16> {
if *i + 2 > limit {
return Err(BioFormatsError::InvalidData(
"RPZA: truncated 16-bit color".into(),
));
}
let value = u16::from_be_bytes([data[*i], data[*i + 1]]);
*i += 2;
Ok(value)
}
fn rpza_rgb555_to_rgb24(color: u16) -> [u8; 3] {
let r = ((color >> 10) & 0x1f) as u8;
let g = ((color >> 5) & 0x1f) as u8;
let b = (color & 0x1f) as u8;
[
(r << 3) | (r >> 2),
(g << 3) | (g >> 2),
(b << 3) | (b >> 2),
]
}
fn rpza_four_color_table(color_a: u16, color_b: u16) -> [[u8; 3]; 4] {
let mut colors = [[0u8; 3]; 4];
colors[0] = rpza_rgb555_to_rgb24(color_a);
colors[3] = rpza_rgb555_to_rgb24(color_b);
for component in 0..3 {
let shift = 10 - component * 5;
let a = ((color_a >> shift) & 0x1f) as u32;
let b = ((color_b >> shift) & 0x1f) as u32;
let c1 = ((11 * a + 21 * b) >> 5) as u8;
let c2 = ((21 * a + 11 * b) >> 5) as u8;
colors[1][component] = (c1 << 3) | (c1 >> 2);
colors[2][component] = (c2 << 3) | (c2 >> 2);
}
colors
}
fn rpza_write_rgb(
out: &mut [u8],
width: usize,
height: usize,
block_x: usize,
block_y: usize,
px: usize,
py: usize,
rgb: [u8; 3],
) {
let x = block_x * 4 + px;
let y = block_y * 4 + py;
if x >= width || y >= height {
return;
}
let offset = (y * width + x) * 3;
out[offset..offset + 3].copy_from_slice(&rgb);
}
fn rpza_write_solid_block(
out: &mut [u8],
width: usize,
height: usize,
blocks_wide: usize,
block: usize,
color: u16,
) {
let block_x = block % blocks_wide;
let block_y = block / blocks_wide;
let rgb = rpza_rgb555_to_rgb24(color);
for py in 0..4 {
for px in 0..4 {
rpza_write_rgb(out, width, height, block_x, block_y, px, py, rgb);
}
}
}
fn rpza_write_literal_block(
out: &mut [u8],
width: usize,
height: usize,
blocks_wide: usize,
block: usize,
colors: &[u16; 16],
) {
let block_x = block % blocks_wide;
let block_y = block / blocks_wide;
for py in 0..4 {
for px in 0..4 {
let rgb = rpza_rgb555_to_rgb24(colors[py * 4 + px]);
rpza_write_rgb(out, width, height, block_x, block_y, px, py, rgb);
}
}
}
fn rpza_write_indexed_block(
out: &mut [u8],
width: usize,
height: usize,
blocks_wide: usize,
block: usize,
colors: &[[u8; 3]; 4],
indices: &[u8; 4],
) {
let block_x = block % blocks_wide;
let block_y = block / blocks_wide;
for (py, &row) in indices.iter().enumerate() {
for px in 0..4 {
let index = ((row >> (6 - px * 2)) & 0x03) as usize;
rpza_write_rgb(out, width, height, block_x, block_y, px, py, colors[index]);
}
}
}
#[derive(Clone)]
struct MsbBitReader<'a> {
data: &'a [u8],
bit_pos: usize,
}
impl<'a> MsbBitReader<'a> {
fn new(data: &'a [u8]) -> Self {
Self { data, bit_pos: 0 }
}
fn read_bit(&mut self) -> Result<u16> {
if self.bit_pos >= self.data.len() * 8 {
return Err(BioFormatsError::InvalidData(
"CCITT Group 3: truncated bitstream".into(),
));
}
let byte = self.data[self.bit_pos / 8];
let bit = (byte >> (7 - (self.bit_pos % 8))) & 1;
self.bit_pos += 1;
Ok(bit as u16)
}
fn peek_bits(&self, len: usize) -> Option<u16> {
if self.bit_pos + len > self.data.len() * 8 {
return None;
}
let mut code = 0u16;
for bit in 0..len {
let pos = self.bit_pos + bit;
let byte = self.data[pos / 8];
code = (code << 1) | ((byte >> (7 - (pos % 8))) & 1) as u16;
}
Some(code)
}
fn skip_bits(&mut self, len: usize) {
self.bit_pos += len;
}
fn skip_ccitt_eols(&mut self) {
while self.peek_bits(12) == Some(0b0000_0000_0001) {
self.skip_bits(12);
}
}
}
enum CcittCode {
Run(u16),
Eol,
}
enum Group4Mode {
Pass,
Horizontal,
Vertical(i8),
}
fn decode_group4_mode(bits: &mut MsbBitReader<'_>) -> Result<Group4Mode> {
if bits.peek_bits(1) == Some(0b1) {
bits.skip_bits(1);
return Ok(Group4Mode::Vertical(0));
}
if bits.peek_bits(3) == Some(0b011) {
bits.skip_bits(3);
return Ok(Group4Mode::Vertical(1));
}
if bits.peek_bits(3) == Some(0b010) {
bits.skip_bits(3);
return Ok(Group4Mode::Vertical(-1));
}
if bits.peek_bits(3) == Some(0b001) {
bits.skip_bits(3);
return Ok(Group4Mode::Horizontal);
}
if bits.peek_bits(4) == Some(0b0001) {
bits.skip_bits(4);
return Ok(Group4Mode::Pass);
}
if bits.peek_bits(6) == Some(0b000011) {
bits.skip_bits(6);
return Ok(Group4Mode::Vertical(2));
}
if bits.peek_bits(6) == Some(0b000010) {
bits.skip_bits(6);
return Ok(Group4Mode::Vertical(-2));
}
if bits.peek_bits(7) == Some(0b0000011) {
bits.skip_bits(7);
return Ok(Group4Mode::Vertical(3));
}
if bits.peek_bits(7) == Some(0b0000010) {
bits.skip_bits(7);
return Ok(Group4Mode::Vertical(-3));
}
Err(BioFormatsError::InvalidData(
"CCITT Group 4: invalid two-dimensional mode code".into(),
))
}
fn group4_reference_changing_elements(
reference: &[bool],
x: usize,
current_black: bool,
) -> (usize, usize) {
let b1 = group4_next_transition_to(reference, x, !current_black);
let b2 = if b1 >= reference.len() {
reference.len()
} else {
group4_next_transition_to(reference, b1 + 1, current_black)
};
(b1, b2)
}
fn group4_next_transition_to(reference: &[bool], start: usize, color: bool) -> usize {
if start >= reference.len() {
return reference.len();
}
let mut previous = if start == 0 {
false
} else {
reference[start - 1]
};
for (offset, &pixel) in reference[start..].iter().enumerate() {
if pixel != previous && pixel == color {
return start + offset;
}
previous = pixel;
}
reference.len()
}
fn decode_ccitt_run_length(
bits: &mut MsbBitReader<'_>,
black: bool,
context: &str,
) -> Result<usize> {
let mut run = 0usize;
loop {
match decode_ccitt_run(bits, black)? {
CcittCode::Run(len) => {
run += len as usize;
if len < 64 {
return Ok(run);
}
}
CcittCode::Eol => {
return Err(BioFormatsError::InvalidData(format!(
"{context}: unexpected EOL code"
)));
}
}
}
}
fn decode_ccitt_run(bits: &mut MsbBitReader<'_>, black: bool) -> Result<CcittCode> {
let table = if black {
CCITT_BLACK_CODES
} else {
CCITT_WHITE_CODES
};
let mut code = 0u16;
for len in 1..=13 {
code = (code << 1) | bits.read_bit()?;
if len == 12 && code == 0b0000_0000_0001 {
return Ok(CcittCode::Eol);
}
if let Some((_, _, run)) = table
.iter()
.find(|(code_len, table_code, _)| *code_len == len && *table_code == code)
{
return Ok(CcittCode::Run(*run));
}
}
Err(BioFormatsError::InvalidData(
"CCITT Group 3: invalid Huffman code".into(),
))
}
const CCITT_WHITE_CODES: &[(u8, u16, u16)] = &[
(8, 0b00110101, 0),
(6, 0b000111, 1),
(4, 0b0111, 2),
(4, 0b1000, 3),
(4, 0b1011, 4),
(4, 0b1100, 5),
(4, 0b1110, 6),
(4, 0b1111, 7),
(5, 0b10011, 8),
(5, 0b10100, 9),
(5, 0b00111, 10),
(5, 0b01000, 11),
(6, 0b001000, 12),
(6, 0b000011, 13),
(6, 0b110100, 14),
(6, 0b110101, 15),
(6, 0b101010, 16),
(6, 0b101011, 17),
(7, 0b0100111, 18),
(7, 0b0001100, 19),
(7, 0b0001000, 20),
(7, 0b0010111, 21),
(7, 0b0000011, 22),
(7, 0b0000100, 23),
(7, 0b0101000, 24),
(7, 0b0101011, 25),
(7, 0b0010011, 26),
(7, 0b0100100, 27),
(7, 0b0011000, 28),
(8, 0b00000010, 29),
(8, 0b00000011, 30),
(8, 0b00011010, 31),
(8, 0b00011011, 32),
(8, 0b00010010, 33),
(8, 0b00010011, 34),
(8, 0b00010100, 35),
(8, 0b00010101, 36),
(8, 0b00010110, 37),
(8, 0b00010111, 38),
(8, 0b00101000, 39),
(8, 0b00101001, 40),
(8, 0b00101010, 41),
(8, 0b00101011, 42),
(8, 0b00101100, 43),
(8, 0b00101101, 44),
(8, 0b00000100, 45),
(8, 0b00000101, 46),
(8, 0b00001010, 47),
(8, 0b00001011, 48),
(8, 0b01010010, 49),
(8, 0b01010011, 50),
(8, 0b01010100, 51),
(8, 0b01010101, 52),
(8, 0b00100100, 53),
(8, 0b00100101, 54),
(8, 0b01011000, 55),
(8, 0b01011001, 56),
(8, 0b01011010, 57),
(8, 0b01011011, 58),
(8, 0b01001010, 59),
(8, 0b01001011, 60),
(8, 0b00110010, 61),
(8, 0b00110011, 62),
(8, 0b00110100, 63),
(5, 0b11011, 64),
(5, 0b10010, 128),
(6, 0b010111, 192),
(7, 0b0110111, 256),
(8, 0b00110110, 320),
(8, 0b00110111, 384),
(8, 0b01100100, 448),
(8, 0b01100101, 512),
(8, 0b01101000, 576),
(8, 0b01100111, 640),
(9, 0b011001100, 704),
(9, 0b011001101, 768),
(9, 0b011010010, 832),
(9, 0b011010011, 896),
(9, 0b011010100, 960),
(9, 0b011010101, 1024),
(9, 0b011010110, 1088),
(9, 0b011010111, 1152),
(9, 0b011011000, 1216),
(9, 0b011011001, 1280),
(9, 0b011011010, 1344),
(9, 0b011011011, 1408),
(9, 0b010011000, 1472),
(9, 0b010011001, 1536),
(9, 0b010011010, 1600),
(6, 0b011000, 1664),
(9, 0b010011011, 1728),
];
const CCITT_BLACK_CODES: &[(u8, u16, u16)] = &[
(10, 0b0000110111, 0),
(3, 0b010, 1),
(2, 0b11, 2),
(2, 0b10, 3),
(3, 0b011, 4),
(4, 0b0011, 5),
(4, 0b0010, 6),
(5, 0b00011, 7),
(6, 0b000101, 8),
(6, 0b000100, 9),
(7, 0b0000100, 10),
(7, 0b0000101, 11),
(7, 0b0000111, 12),
(8, 0b00000100, 13),
(8, 0b00000111, 14),
(9, 0b000011000, 15),
(10, 0b0000010111, 16),
(10, 0b0000011000, 17),
(10, 0b0000001000, 18),
(11, 0b00001100111, 19),
(11, 0b00001101000, 20),
(11, 0b00001101100, 21),
(11, 0b00000110111, 22),
(11, 0b00000101000, 23),
(11, 0b00000010111, 24),
(11, 0b00000011000, 25),
(12, 0b000011001010, 26),
(12, 0b000011001011, 27),
(12, 0b000011001100, 28),
(12, 0b000011001101, 29),
(12, 0b000001101000, 30),
(12, 0b000001101001, 31),
(12, 0b000001101010, 32),
(12, 0b000001101011, 33),
(12, 0b000011010010, 34),
(12, 0b000011010011, 35),
(12, 0b000011010100, 36),
(12, 0b000011010101, 37),
(12, 0b000011010110, 38),
(12, 0b000011010111, 39),
(12, 0b000001101100, 40),
(12, 0b000001101101, 41),
(12, 0b000011011010, 42),
(12, 0b000011011011, 43),
(12, 0b000001010100, 44),
(12, 0b000001010101, 45),
(12, 0b000001010110, 46),
(12, 0b000001010111, 47),
(12, 0b000001100100, 48),
(12, 0b000001100101, 49),
(12, 0b000001010010, 50),
(12, 0b000001010011, 51),
(12, 0b000000100100, 52),
(12, 0b000000110111, 53),
(12, 0b000000111000, 54),
(12, 0b000000100111, 55),
(12, 0b000000101000, 56),
(12, 0b000001011000, 57),
(12, 0b000001011001, 58),
(12, 0b000000101011, 59),
(12, 0b000000101100, 60),
(12, 0b000001011010, 61),
(12, 0b000001100110, 62),
(12, 0b000001100111, 63),
(10, 0b0000001111, 64),
(12, 0b000011001000, 128),
(12, 0b000011001001, 192),
(12, 0b000001011011, 256),
(12, 0b000000110011, 320),
(12, 0b000000110100, 384),
(12, 0b000000110101, 448),
(13, 0b0000001101100, 512),
(13, 0b0000001101101, 576),
(13, 0b0000001001010, 640),
(13, 0b0000001001011, 704),
(13, 0b0000001001100, 768),
(13, 0b0000001001101, 832),
(13, 0b0000001110010, 896),
(13, 0b0000001110011, 960),
(13, 0b0000001110100, 1024),
(13, 0b0000001110101, 1088),
(13, 0b0000001110110, 1152),
(13, 0b0000001110111, 1216),
(13, 0b0000001010010, 1280),
(13, 0b0000001010011, 1344),
(13, 0b0000001010100, 1408),
(13, 0b0000001010101, 1472),
(13, 0b0000001011010, 1536),
(13, 0b0000001011011, 1600),
(13, 0b0000001100100, 1664),
(13, 0b0000001100101, 1728),
];
pub fn undo_horizontal_differencing(data: &mut [u8], samples_per_pixel: usize) {
if samples_per_pixel == 0 || data.len() < samples_per_pixel * 2 {
return;
}
for i in samples_per_pixel..data.len() {
data[i] = data[i].wrapping_add(data[i - samples_per_pixel]);
}
}
pub fn undo_horizontal_differencing_u16(data: &mut [u16], samples_per_pixel: usize) {
if samples_per_pixel == 0 || data.len() < samples_per_pixel * 2 {
return;
}
for i in samples_per_pixel..data.len() {
data[i] = data[i].wrapping_add(data[i - samples_per_pixel]);
}
}
#[derive(Clone)]
struct CinepakCodebook {
entries: Vec<[[u8; 3]; 4]>,
}
impl CinepakCodebook {
fn new() -> Self {
CinepakCodebook {
entries: vec![[[0u8; 3]; 4]; 256],
}
}
}
#[inline]
fn cinepak_yuv_to_rgb(y: u8, u: i8, v: i8) -> [u8; 3] {
let y = y as i32;
let u = u as i32;
let v = v as i32;
let r = (y + (v * 2)).clamp(0, 255) as u8;
let g = (y - (u / 2) - v).clamp(0, 255) as u8;
let b = (y + (u * 2)).clamp(0, 255) as u8;
[r, g, b]
}
#[inline]
fn rd_be_u16(data: &[u8], i: usize) -> Result<u16> {
data.get(i..i + 2)
.map(|b| u16::from_be_bytes([b[0], b[1]]))
.ok_or_else(|| BioFormatsError::Codec("Cinepak: truncated stream".into()))
}
fn cinepak_read_codebook(
book: &mut CinepakCodebook,
data: &[u8],
mut pos: usize,
end: usize,
grayscale: bool,
detail: bool,
) -> Result<()> {
let entry_size = if grayscale { 4 } else { 6 };
let mut index = 0usize;
let mut flag = 0u32;
let mut flag_bits = 0u32;
while index < 256 && pos < end {
let update = if detail {
if flag_bits == 0 {
if pos + 4 > end {
break;
}
flag = u32::from_be_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]);
pos += 4;
flag_bits = 32;
}
flag_bits -= 1;
(flag >> flag_bits) & 1 == 1
} else {
true
};
if update {
if pos + entry_size > end {
break;
}
let y = [data[pos], data[pos + 1], data[pos + 2], data[pos + 3]];
let (u, v) = if grayscale {
(0i8, 0i8)
} else {
(data[pos + 4] as i8, data[pos + 5] as i8)
};
pos += entry_size;
book.entries[index] = [
cinepak_yuv_to_rgb(y[0], u, v),
cinepak_yuv_to_rgb(y[1], u, v),
cinepak_yuv_to_rgb(y[2], u, v),
cinepak_yuv_to_rgb(y[3], u, v),
];
}
index += 1;
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn cinepak_put_v1(
out: &mut [u8],
width: usize,
height: usize,
channels: usize,
mb_x: usize,
mb_y: usize,
e: &[[u8; 3]; 4],
) {
for q in 0..4 {
let (qx, qy) = ((q & 1) * 2, (q >> 1) * 2);
let rgb = e[q];
for dy in 0..2usize {
for dx in 0..2usize {
cinepak_set_pixel(out, width, height, channels, mb_x + qx + dx, mb_y + qy + dy, rgb);
}
}
}
}
#[allow(clippy::too_many_arguments)]
fn cinepak_put_v4(
out: &mut [u8],
width: usize,
height: usize,
channels: usize,
mb_x: usize,
mb_y: usize,
e0: &[[u8; 3]; 4],
e1: &[[u8; 3]; 4],
e2: &[[u8; 3]; 4],
e3: &[[u8; 3]; 4],
) {
let books = [e0, e1, e2, e3];
for q in 0..4 {
let (qx, qy) = ((q & 1) * 2, (q >> 1) * 2);
let e = books[q];
cinepak_set_pixel(out, width, height, channels, mb_x + qx, mb_y + qy, e[0]);
cinepak_set_pixel(out, width, height, channels, mb_x + qx + 1, mb_y + qy, e[1]);
cinepak_set_pixel(out, width, height, channels, mb_x + qx, mb_y + qy + 1, e[2]);
cinepak_set_pixel(out, width, height, channels, mb_x + qx + 1, mb_y + qy + 1, e[3]);
}
}
#[inline]
fn cinepak_set_pixel(
out: &mut [u8],
width: usize,
height: usize,
channels: usize,
x: usize,
y: usize,
rgb: [u8; 3],
) {
if x >= width || y >= height {
return;
}
let off = (y * width + x) * channels;
if channels == 1 {
if off < out.len() {
out[off] = rgb[0];
}
} else if off + 2 < out.len() {
out[off] = rgb[0];
out[off + 1] = rgb[1];
out[off + 2] = rgb[2];
}
}
pub fn decompress_cinepak(
data: &[u8],
width: u32,
height: u32,
bpp: u32,
prev: &[u8],
) -> Result<Vec<u8>> {
let width = width as usize;
let height = height as usize;
if width == 0 || height == 0 {
return Err(BioFormatsError::InvalidData(
"Cinepak: width and height must be non-zero".into(),
));
}
let grayscale = bpp == 8;
let channels = if grayscale { 1 } else { 3 };
let output_len = checked_video_output_len("Cinepak", width, height, channels)?;
let mut out = vec![0u8; output_len];
if prev.len() == output_len {
out.copy_from_slice(prev);
}
if data.len() < 10 {
return Err(BioFormatsError::Codec("Cinepak: frame header too short".into()));
}
let num_strips = rd_be_u16(data, 8)? as usize;
let mut pos = 10usize;
let mut v1 = CinepakCodebook::new();
let mut v4 = CinepakCodebook::new();
let mut strip_y0 = 0usize;
for _ in 0..num_strips {
if pos + 12 > data.len() {
break;
}
let _strip_id = rd_be_u16(data, pos)?;
let strip_size = rd_be_u16(data, pos + 2)? as usize;
let top = rd_be_u16(data, pos + 4)? as usize;
let _left = rd_be_u16(data, pos + 6)? as usize;
let bottom = rd_be_u16(data, pos + 8)? as usize;
let _right = rd_be_u16(data, pos + 10)? as usize;
let strip_data_start = pos + 12;
let strip_end = (pos + strip_size).min(data.len()).max(strip_data_start);
pos = strip_data_start;
let strip_top = if bottom > top { top } else { strip_y0 };
let strip_bottom = if bottom > top {
bottom
} else {
(strip_y0 + (bottom)).max(strip_y0)
};
let _ = strip_bottom;
cinepak_decode_strip(
&mut out, width, height, channels, prev, grayscale, &mut v1, &mut v4, data, pos,
strip_end, strip_top,
)?;
strip_y0 = strip_top
+ {
let h = bottom.saturating_sub(top);
if h > 0 {
h
} else {
0
}
};
pos = strip_end;
}
Ok(out)
}
#[allow(clippy::too_many_arguments)]
fn cinepak_decode_strip(
out: &mut [u8],
width: usize,
height: usize,
channels: usize,
prev: &[u8],
grayscale: bool,
v1: &mut CinepakCodebook,
v4: &mut CinepakCodebook,
data: &[u8],
mut pos: usize,
strip_end: usize,
strip_top: usize,
) -> Result<()> {
let mb_per_row = width.div_ceil(4);
while pos + 4 <= strip_end {
let chunk_id = rd_be_u16(data, pos)?;
let chunk_size = rd_be_u16(data, pos + 2)? as usize;
let chunk_data = pos + 4;
let chunk_end = (pos + chunk_size).min(strip_end).max(chunk_data);
pos = chunk_end;
match chunk_id {
0x2000 => cinepak_read_codebook(v4, data, chunk_data, chunk_end, grayscale, false)?,
0x2200 => cinepak_read_codebook(v4, data, chunk_data, chunk_end, grayscale, true)?,
0x2100 => cinepak_read_codebook(v1, data, chunk_data, chunk_end, grayscale, false)?,
0x2300 => cinepak_read_codebook(v1, data, chunk_data, chunk_end, grayscale, true)?,
0x3000 | 0x3100 => {
let inter = chunk_id == 0x3100;
cinepak_decode_vectors(
out, width, height, channels, prev, v1, v4, data, chunk_data, chunk_end,
strip_top, mb_per_row, inter,
)?;
}
0x3200 => {
cinepak_decode_v1_only(
out, width, height, channels, v1, data, chunk_data, chunk_end, strip_top,
mb_per_row,
)?;
}
_ => {
}
}
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn cinepak_decode_vectors(
out: &mut [u8],
width: usize,
height: usize,
channels: usize,
prev: &[u8],
v1: &CinepakCodebook,
v4: &CinepakCodebook,
data: &[u8],
mut pos: usize,
end: usize,
strip_top: usize,
mb_per_row: usize,
inter: bool,
) -> Result<()> {
let strip_mb_rows = height.saturating_sub(strip_top).div_ceil(4).max(1);
let mut mb_index = 0usize;
let total_mb = mb_per_row * strip_mb_rows;
let mut flag = 0u32;
let mut flag_bits = 0u32;
let mut next_flag = |pos: &mut usize| -> Option<bool> {
if flag_bits == 0 {
if *pos + 4 > end {
return None;
}
flag = u32::from_be_bytes([data[*pos], data[*pos + 1], data[*pos + 2], data[*pos + 3]]);
*pos += 4;
flag_bits = 32;
}
flag_bits -= 1;
Some((flag >> flag_bits) & 1 == 1)
};
while pos < end && mb_index < total_mb {
let mb_x = (mb_index % mb_per_row) * 4;
let mb_y = strip_top + (mb_index / mb_per_row) * 4;
if mb_y >= height {
break;
}
let coded = if inter {
match next_flag(&mut pos) {
Some(b) => b,
None => break,
}
} else {
true
};
if !coded {
let _ = prev;
mb_index += 1;
continue;
}
let is_v4 = match next_flag(&mut pos) {
Some(b) => b,
None => break,
};
if is_v4 {
if pos + 4 > end {
break;
}
let i0 = data[pos] as usize;
let i1 = data[pos + 1] as usize;
let i2 = data[pos + 2] as usize;
let i3 = data[pos + 3] as usize;
pos += 4;
cinepak_put_v4(
out,
width,
height,
channels,
mb_x,
mb_y,
&v4.entries[i0],
&v4.entries[i1],
&v4.entries[i2],
&v4.entries[i3],
);
} else {
if pos >= end {
break;
}
let i = data[pos] as usize;
pos += 1;
cinepak_put_v1(out, width, height, channels, mb_x, mb_y, &v1.entries[i]);
}
mb_index += 1;
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn cinepak_decode_v1_only(
out: &mut [u8],
width: usize,
height: usize,
channels: usize,
v1: &CinepakCodebook,
data: &[u8],
mut pos: usize,
end: usize,
strip_top: usize,
mb_per_row: usize,
) -> Result<()> {
let mut mb_index = 0usize;
while pos < end {
let mb_x = (mb_index % mb_per_row) * 4;
let mb_y = strip_top + (mb_index / mb_per_row) * 4;
if mb_y >= height {
break;
}
let i = data[pos] as usize;
pos += 1;
cinepak_put_v1(out, width, height, channels, mb_x, mb_y, &v1.entries[i]);
mb_index += 1;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
fn bits_to_bytes(bits: &str) -> Vec<u8> {
let mut out = Vec::new();
let mut byte = 0u8;
let mut used = 0usize;
for bit in bits.bytes().filter(|b| *b == b'0' || *b == b'1') {
byte = (byte << 1) | (bit - b'0');
used += 1;
if used == 8 {
out.push(byte);
byte = 0;
used = 0;
}
}
if used != 0 {
out.push(byte << (8 - used));
}
out
}
#[test]
fn ccitt_group3_decodes_all_white_row_with_eol() {
let data = bits_to_bytes(
"000000000001\
10011",
);
let out = decompress_ccitt_group3(&data, 8, 1).expect("CCITT Group 3 decode");
assert_eq!(out, vec![0x00]);
}
#[test]
fn ccitt_group3_decodes_mixed_runs() {
let data = bits_to_bytes(
"000000000001\
0111\
0011\
1000",
);
let out = decompress_ccitt_group3(&data, 10, 1).expect("CCITT Group 3 decode");
assert_eq!(out, vec![0b0011_1110, 0x00]);
}
#[test]
fn ccitt_group3_rejects_overlong_run() {
let data = bits_to_bytes("10100");
let err = decompress_ccitt_group3(&data, 8, 1).expect_err("overlong run must fail");
assert!(matches!(
err,
BioFormatsError::InvalidData(message) if message.contains("run exceeds row width")
));
}
#[test]
fn ccitt_group4_decodes_all_white_rows_with_vertical_mode() {
let data = bits_to_bytes("11");
let out = decompress_ccitt_group4(&data, 8, 2).expect("CCITT Group 4 decode");
assert_eq!(out, vec![0x00, 0x00]);
}
#[test]
fn ccitt_group4_decodes_horizontal_then_vertical_mixed_rows() {
let data = bits_to_bytes(
"001\
0111\
011\
1\
111",
);
let out = decompress_ccitt_group4(&data, 10, 2).expect("CCITT Group 4 decode");
assert_eq!(out, vec![0b0011_1100, 0x00, 0b0011_1100, 0x00]);
}
#[test]
fn ccitt_group4_decodes_pass_mode() {
let data = bits_to_bytes(
"001\
0111\
011\
1\
0001\
1",
);
let out = decompress_ccitt_group4(&data, 10, 2).expect("CCITT Group 4 decode");
assert_eq!(out, vec![0b0011_1100, 0x00, 0x00, 0x00]);
}
#[test]
fn msrle_decodes_encoded_absolute_delta_and_bottom_up_rows() {
let data = [
3, 7, 0, 0, 0, 3, 1, 2, 3, 0, 0, 1, ];
let out = decompress_msrle(&data, 3, 2).expect("MSRLE decode");
assert_eq!(out, vec![1, 2, 3, 7, 7, 7]);
}
#[test]
fn msrle_decodes_delta_offsets() {
let data = [
1, 9, 0, 2, 1, 1, 1, 5, 0, 1, ];
let out = decompress_msrle(&data, 3, 2).expect("MSRLE decode");
assert_eq!(out, vec![0, 0, 5, 9, 0, 0]);
}
#[test]
fn msvideo_8bit_decodes_two_color_block() {
let data = [
0x0f, 0x00, 100, 200, ];
let out = decompress_msvideo(&data, 4, 4, 1).expect("MSVideo 8-bit decode");
let mut expected = vec![200u8; 16];
for px in expected.iter_mut().skip(12) {
*px = 100;
}
assert_eq!(out, expected);
}
#[test]
fn msvideo_8bit_decodes_solid_block() {
let data = [42u8, 0x80];
let out = decompress_msvideo(&data, 4, 4, 1).expect("MSVideo 8-bit solid");
assert_eq!(out, vec![42u8; 16]);
}
#[test]
fn msvideo_8bit_skip_leaves_previous_frame_zero() {
let data = [0x01u8, 0x84];
let out = decompress_msvideo(&data, 4, 4, 1).expect("MSVideo 8-bit skip");
assert_eq!(out, vec![0u8; 16]);
}
#[test]
fn msvideo_16bit_decodes_solid_rgb555() {
let data = [0xff, 0xff];
let out = decompress_msvideo(&data, 4, 4, 2).expect("MSVideo 16-bit solid");
assert_eq!(out.len(), 16 * 3);
assert!(out.iter().all(|&b| b == 255));
}
#[test]
fn msvideo_rejects_unsupported_depth() {
let err = decompress_msvideo(&[0, 0], 4, 4, 3).expect_err("unsupported depth must fail");
assert!(matches!(err, BioFormatsError::UnsupportedFormat(_)));
}
#[test]
fn msrle_rejects_overlong_runs() {
let err = decompress_msrle(&[4, 1], 3, 1).expect_err("overlong run must fail");
assert!(matches!(
err,
BioFormatsError::InvalidData(message) if message.contains("exceeds row width")
));
}
#[test]
fn msrle_rejects_oversized_dimensions_before_allocation() {
let err = decompress_msrle(&[0, 1], u32::MAX, u32::MAX)
.expect_err("oversized frame must fail before allocation");
assert!(matches!(
err,
BioFormatsError::InvalidData(message)
if message.contains("output byte count overflows")
|| message.contains("decoded frame is too large")
));
}
#[test]
fn mjpb_reports_missing_local_implementation_sources_and_fixture() {
let err = decompress_mjpb(&[]).expect_err("MJPB remains unsupported without fixtures");
assert!(matches!(
err,
BioFormatsError::UnsupportedFormat(message)
if message.contains("MJPB bitstream parser")
&& message.contains("ome-codecs source")
&& message.contains("known-output fixture")
));
}
#[test]
fn standalone_huffman_reports_missing_decoder_contract() {
let err = decompress_huffman(&[]).expect_err("standalone Huffman remains unsupported");
assert!(matches!(
err,
BioFormatsError::UnsupportedFormat(message)
if message.contains("Standalone Huffman codec not yet implemented")
));
}
#[test]
fn nikon_reports_missing_decoder_contract() {
let err = decompress_nikon(&[], 17, 23, 36).expect_err("Nikon remains unsupported");
assert!(matches!(
err,
BioFormatsError::UnsupportedFormat(message)
if message.contains("Nikon NEF compression 34713")
&& message.contains("maker-note IFD tag 150")
&& message.contains("vPredictor")
&& message.contains("curve")
&& message.contains("split")
&& message.contains("lossless flag")
&& message.contains("maxBytes")
&& message.contains("17x23")
&& message.contains("36 bpp")
&& message.contains("0 compressed bytes")
));
}
#[test]
fn lzo1x_decodes_initial_literal_run() {
let data = [
22, b'h', b'e', b'l', b'l', b'o', 0x11, 0x00, 0x00, ];
let out = decompress_lzo(&data).expect("LZO1X decode");
assert_eq!(out, b"hello");
}
#[test]
fn lzo1x_decodes_near_match() {
let data = [
20, b'a', b'b', b'c', 0x48, 0x00, 0x11, 0x00, 0x00, ];
let out = decompress_lzo(&data).expect("LZO1X decode");
assert_eq!(out, b"abcabc");
}
#[test]
fn lzo1x_decodes_extended_literal_run() {
let mut data = vec![0x00, 0x06];
data.extend_from_slice(&[0x5a; 24]);
data.extend_from_slice(&[0x11, 0x00, 0x00]);
let out = decompress_lzo(&data).expect("LZO1X decode");
assert_eq!(out, vec![0x5a; 24]);
}
#[test]
fn lzo1x_rejects_invalid_back_reference() {
let data = [
20, b'a', b'b', b'c', 0x40, 0x00, b'd', 0x10, 0x00, 0x00, ];
let err = decompress_lzo(&data).expect_err("invalid back-reference must fail");
assert!(matches!(
err,
BioFormatsError::InvalidData(message)
if message.contains("invalid match back-reference")
));
}
#[test]
fn lzo1x_rejects_truncated_literal_run() {
let err = decompress_lzo(&[22, b'h']).expect_err("truncated literal run must fail");
assert!(matches!(
err,
BioFormatsError::InvalidData(message) if message.contains("literal run overruns input")
));
}
#[test]
fn qtrle_decodes_8bit_literal_and_repeat_rows() {
let data = [
0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 1, 5, 1, 2, 3, 4, 5, 0xff, 1, 0xfb, 9, 0xff, ];
let out = decompress_qtrle(&data, 5, 2, 8).expect("QTRLE decode");
assert_eq!(out, vec![1, 2, 3, 4, 5, 9, 9, 9, 9, 9]);
}
#[test]
fn qtrle_decodes_24bit_changed_line_window() {
let data = [
0x00, 0x00, 0x00, 0x17, 0x00, 0x08, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 2, 2, 255, 0, 0, 0, 255, 0, 0xff,
];
let out = decompress_qtrle(&data, 4, 3, 24).expect("QTRLE decode");
assert_eq!(
out,
vec![
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]
);
}
#[test]
fn qtrle_rejects_unsupported_depth() {
let err = decompress_qtrle(&[0, 0, 0, 6, 0, 0], 1, 1, 16)
.expect_err("16 bpp is outside the implemented subset");
assert!(matches!(
err,
BioFormatsError::UnsupportedFormat(message)
if message.contains("only 8 and 24 bpp are implemented")
));
}
#[test]
fn rpza_decodes_solid_color_run() {
let data = [
0xe1, 0x00, 0x00, 0x07, 0xa0, 0x7c, 0x00, ];
let out = decompress_rpza(&data, 4, 4).expect("RPZA decode");
assert_eq!(out, vec![255, 0, 0].repeat(16));
}
#[test]
fn rpza_decodes_literal_block_and_clips_edges() {
let data = [
0xe1, 0x00, 0x00, 0x24, 0x7c, 0x00, 0x03, 0xe0, 0x00, 0x1f, 0x7f, 0xff, 0x00, 0x00, 0x7c, 0x00, 0x03, 0xe0, 0x00, 0x1f, 0x7f, 0xff, 0x00, 0x00, 0x7c, 0x00, 0x03, 0xe0, 0x00, 0x1f, 0x7f, 0xff, 0x00, 0x00, 0x7c, 0x00, ];
let out = decompress_rpza(&data, 2, 2).expect("RPZA decode");
assert_eq!(
out,
vec![
255, 0, 0, 0, 255, 0, 0, 0, 0, 255, 0, 0, ]
);
}
#[test]
fn rpza_decodes_four_color_block() {
let data = [
0xe1, 0x00, 0x00, 0x0d, 0xc0, 0x7c, 0x00, 0x00, 0x00, 0x1b, 0x1b, 0x1b, 0x1b, ];
let out = decompress_rpza(&data, 4, 4).expect("RPZA decode");
assert_eq!(out, vec![255, 0, 0, 82, 0, 0, 165, 0, 0, 0, 0, 0].repeat(4));
}
#[test]
fn rpza_decodes_skipped_blocks_as_black() {
let data = [
0xe1, 0x00, 0x00, 0x08, 0xa0, 0x7c, 0x00, 0x80, ];
let out = decompress_rpza(&data, 8, 4).expect("RPZA decode");
let mut expected = Vec::new();
for _ in 0..4 {
expected.extend_from_slice(&[255, 0, 0].repeat(4));
expected.extend_from_slice(&[0, 0, 0].repeat(4));
}
assert_eq!(out, expected);
}
#[test]
fn rpza_rejects_oversized_dimensions_before_allocation() {
let data = [0xe1, 0x00, 0x00, 0x04];
let err = decompress_rpza(&data, u32::MAX, u32::MAX)
.expect_err("oversized frame must fail before allocation");
assert!(matches!(
err,
BioFormatsError::InvalidData(message)
if message.contains("output byte count overflows")
|| message.contains("decoded frame is too large")
));
}
fn be16(v: u16) -> [u8; 2] {
v.to_be_bytes()
}
fn cinepak_frame(width: u16, height: u16, chunks: &[(u16, Vec<u8>)]) -> Vec<u8> {
let mut strip_body = Vec::new();
for (id, body) in chunks {
strip_body.extend_from_slice(&be16(*id));
strip_body.extend_from_slice(&be16((body.len() + 4) as u16));
strip_body.extend_from_slice(body);
}
let strip_size = strip_body.len() + 12;
let mut frame = Vec::new();
frame.push(0x00); frame.extend_from_slice(&[0, 0, 0]); frame.extend_from_slice(&be16(width));
frame.extend_from_slice(&be16(height));
frame.extend_from_slice(&be16(1)); frame.extend_from_slice(&be16(0x1000)); frame.extend_from_slice(&be16(strip_size as u16));
frame.extend_from_slice(&be16(0)); frame.extend_from_slice(&be16(0)); frame.extend_from_slice(&be16(height)); frame.extend_from_slice(&be16(width)); frame.extend_from_slice(&strip_body);
frame
}
#[test]
fn cinepak_v1_grayscale_block_upscales_quadrants() {
let v1 = vec![10u8, 20, 30, 40]; let vectors = vec![0u8]; let frame = cinepak_frame(4, 4, &[(0x2100, v1), (0x3200, vectors)]);
let out = decompress_cinepak(&frame, 4, 4, 8, &[]).expect("decode");
assert_eq!(out.len(), 16);
let px = |x: usize, y: usize| out[y * 4 + x];
for y in 0..2 {
for x in 0..2 {
assert_eq!(px(x, y), 10, "TL");
assert_eq!(px(x + 2, y), 20, "TR");
assert_eq!(px(x, y + 2), 30, "BL");
assert_eq!(px(x + 2, y + 2), 40, "BR");
}
}
}
#[test]
fn cinepak_v4_rgb_block_maps_four_codebook_entries() {
let mut v4 = Vec::new();
for i in 0..4u8 {
let y = (i + 1) * 30;
v4.extend_from_slice(&[y, y, y, y, 0, 0]); }
let mut vectors = Vec::new();
vectors.extend_from_slice(&[0x80, 0x00, 0x00, 0x00]); vectors.extend_from_slice(&[0, 1, 2, 3]); let frame = cinepak_frame(4, 4, &[(0x2000, v4), (0x3000, vectors)]);
let out = decompress_cinepak(&frame, 4, 4, 24, &[]).expect("decode");
assert_eq!(out.len(), 4 * 4 * 3);
let px = |x: usize, y: usize| out[(y * 4 + x) * 3];
assert_eq!(px(0, 0), 30); assert_eq!(px(2, 0), 60); assert_eq!(px(0, 2), 90); assert_eq!(px(2, 2), 120); }
#[test]
fn cinepak_rejects_zero_dimensions() {
let err = decompress_cinepak(&[0u8; 10], 0, 4, 24, &[])
.expect_err("zero width must fail");
assert!(matches!(err, BioFormatsError::InvalidData(_)));
}
#[test]
fn cinepak_inter_frame_copies_previous_when_uncoded() {
let prev = vec![50u8; 16];
let mut vectors = Vec::new();
vectors.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]); let mut frame = cinepak_frame(4, 4, &[(0x3100, vectors)]);
frame[10] = 0x11;
let out = decompress_cinepak(&frame, 4, 4, 8, &prev).expect("decode");
assert_eq!(out, prev, "uncoded inter MB should copy previous frame");
}
fn encode_rgb(pixels: &[u8], w: u32, h: u32, format: image::ImageFormat) -> Vec<u8> {
let img = image::RgbImage::from_raw(w, h, pixels.to_vec()).expect("rgb image");
let mut buf = std::io::Cursor::new(Vec::new());
image::DynamicImage::ImageRgb8(img)
.write_to(&mut buf, format)
.expect("encode");
buf.into_inner()
}
#[test]
fn png_tile_decodes_to_interleaved_rgb() {
let pixels = vec![
10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, ];
let encoded = encode_rgb(&pixels, 2, 2, image::ImageFormat::Png);
let out = decompress_png(&encoded).expect("PNG decode");
assert_eq!(out, pixels, "PNG tile must decode to interleaved RGB bytes");
}
#[test]
fn bmp_tile_decodes_to_interleaved_rgb() {
let pixels = vec![
10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120,
];
let encoded = encode_rgb(&pixels, 2, 2, image::ImageFormat::Bmp);
let out = decompress_bmp(&encoded).expect("BMP decode");
assert_eq!(out, pixels, "BMP tile must decode to interleaved RGB bytes");
}
#[test]
fn png_decode_rejects_garbage() {
let err = decompress_png(&[0u8; 16]).expect_err("garbage must fail");
assert!(matches!(err, BioFormatsError::Codec(_)));
}
}