use super::{MAX_DECODED, Result, bytes, check_time, dimensions, u16_le, u32_le};
use std::time::Instant;
#[allow(
clippy::too_many_lines,
clippy::cast_sign_loss,
clippy::cast_possible_truncation,
reason = "Validated positive dimensions fit 4096 pixels; bounded palette and channel arithmetic intentionally produces bytes"
)]
pub(super) fn normalize(data: &[u8], bmp: bool, started: Instant) -> Result<Option<Vec<u8>>> {
let base = if bmp { 14 } else { 0 };
if bmp && u32_le(data, 2)? as usize != data.len() {
return Err("invalid Inventor BMP size".into());
}
let header_size = u32_le(data, base)? as usize;
if ![40, 108, 124].contains(&header_size) {
return Ok(None);
}
let header = bytes(data, base, header_size)?;
let signed_width = i32::from_le_bytes(bytes(header, 4, 4)?.try_into()?);
let signed_height = i32::from_le_bytes(bytes(header, 8, 4)?.try_into()?);
if signed_width <= 0 {
return Err("invalid Inventor bitmap width".into());
}
let width = signed_width as usize;
let height = signed_height.unsigned_abs() as usize;
dimensions(width, height)?;
let depth = usize::from(u16_le(header, 14)?);
let compression = u32_le(header, 16)?;
if u16_le(header, 12)? != 1
|| ![1, 4, 8, 16, 24, 32].contains(&depth)
|| ![0, 3, 6].contains(&compression)
|| (compression != 0 && ![16, 32].contains(&depth))
{
return Ok(None);
}
let mut pixel_offset = base + header_size;
let mut masks = Vec::new();
if compression != 0 {
let count = if compression == 6 { 4 } else { 3 };
let values = if header_size == 40 {
let values = bytes(data, pixel_offset, count * 4)?;
pixel_offset += count * 4;
values
} else {
bytes(header, 40, 16)?
};
for mask in values.as_chunks::<4>().0 {
masks.push(u32::from_le_bytes(*mask));
}
} else if depth == 16 {
masks = vec![0x7c00, 0x03e0, 0x001f];
} else if depth == 32 {
masks = vec![0x00ff_0000, 0x0000_ff00, 0x0000_00ff];
}
let channels = mask_channels(masks, depth)?;
let mut palette = Vec::new();
let mut colors = u32_le(header, 32)? as usize;
if depth <= 8 {
if colors == 0 {
colors = 1 << depth;
}
if colors == 0 || colors > 1 << depth {
return Err("invalid Inventor bitmap palette size".into());
}
for color in bytes(data, pixel_offset, colors * 4)?.as_chunks::<4>().0 {
palette.push([color[2], color[1], color[0], 255]);
}
} else if colors > data.len().saturating_sub(pixel_offset) / 4 {
return Err("invalid Inventor bitmap color table".into());
}
pixel_offset += colors * 4;
if bmp {
let declared = u32_le(data, 10)? as usize;
if declared < pixel_offset {
return Err("invalid Inventor BMP pixel offset".into());
}
pixel_offset = declared;
}
let stride = (width * depth).div_ceil(32) * 4;
bytes(data, pixel_offset, stride * height)?;
if (width * 4 + 1) * height > MAX_DECODED {
return Err("Inventor decoded bitmap exceeds the 64 MiB limit".into());
}
let mut image = image::RgbaImage::new(width as u32, height as u32);
for y in 0..height {
check_time(started)?;
let source_y = if signed_height > 0 { height - y - 1 } else { y };
let row = bytes(data, pixel_offset + source_y * stride, stride)?;
for x in 0..width {
if x.is_multiple_of(256) {
check_time(started)?;
}
let rgba = if depth <= 8 {
let bit = x * depth;
let index = (row[bit / 8] >> (8 - depth - bit % 8)) & ((1_u16 << depth) - 1) as u8;
*palette
.get(usize::from(index))
.ok_or("invalid Inventor bitmap palette index")?
} else if depth == 24 {
[row[x * 3 + 2], row[x * 3 + 1], row[x * 3], 255]
} else {
let pixel = if depth == 16 {
u32::from(u16_le(row, x * 2)?)
} else {
u32_le(row, x * 4)?
};
let mut rgba = [255; 4];
for (index, &(mask, shift, maximum)) in channels.iter().enumerate() {
if mask != 0 {
rgba[index] =
(((u64::from(pixel & mask) >> shift) * 255) / u64::from(maximum)) as u8;
}
}
rgba
};
image.put_pixel(x as u32, y as u32, image::Rgba(rgba));
}
}
let mut output = Vec::new();
crate::render::write_png(&image, &mut output)?;
check_time(started)?;
Ok(Some(output))
}
fn mask_channels(mut masks: Vec<u32>, depth: usize) -> Result<Vec<(u32, u32, u32)>> {
if masks.is_empty() {
return Ok(Vec::new());
}
if masks.len() == 3 {
masks.push(0);
}
let mut used = 0;
let mut output = Vec::new();
for (index, mask) in masks.into_iter().enumerate() {
if mask == 0 {
if index != 3 {
return Err("invalid Inventor bitmap color mask".into());
}
output.push((0, 0, 0));
continue;
}
let shift = mask.trailing_zeros();
let maximum = mask >> shift;
if (depth < 32 && mask >> depth != 0)
|| mask & used != 0
|| maximum & maximum.wrapping_add(1) != 0
{
return Err("invalid Inventor bitmap color mask".into());
}
used |= mask;
output.push((mask, shift, maximum));
}
Ok(output)
}