use crate::{Result, formats::fcstd_container::DocumentArchive};
use image::{ImageFormat, ImageReader, RgbaImage};
use std::{io::Cursor, path::Path};
const THUMBNAIL: &str = "thumbnails/Thumbnail.png";
const MAX_PNG_BYTES: usize = 8 * 1024 * 1024;
const MAX_DIMENSION: u32 = 4096;
const MAX_PIXELS: u64 = 16_000_000;
pub fn extract(path: &Path) -> Result<Option<RgbaImage>> {
let mut archive = DocumentArchive::open(path)?;
archive
.read_optional(THUMBNAIL, MAX_PNG_BYTES)?
.as_deref()
.map(decode_png)
.transpose()
}
fn decode_png(bytes: &[u8]) -> Result<RgbaImage> {
if bytes.len() < 33
|| !bytes.starts_with(b"\x89PNG\r\n\x1a\n")
|| bytes[8..16] != [0, 0, 0, 13, b'I', b'H', b'D', b'R']
{
return Err("FreeCAD thumbnail is not a PNG with a valid IHDR carrier".into());
}
let width = u32::from_be_bytes(bytes[16..20].try_into()?);
let height = u32::from_be_bytes(bytes[20..24].try_into()?);
if width == 0
|| height == 0
|| width > MAX_DIMENSION
|| height > MAX_DIMENSION
|| u64::from(width) * u64::from(height) > MAX_PIXELS
{
return Err("FreeCAD saved PNG exceeds the dimension or pixel limit".into());
}
let mut reader = ImageReader::with_format(Cursor::new(bytes), ImageFormat::Png);
let mut limits = image::Limits::default();
limits.max_image_width = Some(MAX_DIMENSION);
limits.max_image_height = Some(MAX_DIMENSION);
limits.max_alloc = Some(64 * 1024 * 1024);
reader.limits(limits);
Ok(reader.decode()?.to_rgba8())
}