diff --git a/Cargo.lock b/Cargo.lock index 4489cddd5..8fdfd50b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1719,6 +1719,7 @@ name = "designcraft-cli" version = "0.20.1" dependencies = [ "craft-file-formats", + "craft-image-codecs", "designcraft-color", "designcraft-compose", "designcraft-doc", diff --git a/apps/designcraft-cli/Cargo.toml b/apps/designcraft-cli/Cargo.toml index edebc5e29..d214b9d84 100644 --- a/apps/designcraft-cli/Cargo.toml +++ b/apps/designcraft-cli/Cargo.toml @@ -23,5 +23,6 @@ designcraft-format = { workspace = true } designcraft-pdf = { workspace = true } designcraft-mcp = { workspace = true } craft-file-formats = { workspace = true } +craft-image-codecs = { workspace = true } image = { workspace = true } serde_json = { workspace = true } diff --git a/apps/designcraft-cli/src/main.rs b/apps/designcraft-cli/src/main.rs index 23817bbe7..1e39e906e 100644 --- a/apps/designcraft-cli/src/main.rs +++ b/apps/designcraft-cli/src/main.rs @@ -319,16 +319,17 @@ fn names_the_same_file(input: &std::path::Path, out: &std::path::Path) -> bool { /// as it is; it is decoded anyway, so a broken one is an error rather than an unreadable thumbnail. fn fit_png(png: Vec, size: Option) -> Result, String> { // The preview comes from the file: it may claim any size. - let mut limits = image::Limits::default(); - limits.max_image_width = Some(4096); - limits.max_image_height = Some(4096); - let mut reader = image::ImageReader::with_format(std::io::Cursor::new(&png), image::ImageFormat::Png); - reader.limits(limits); - let img = reader.decode().map_err(|e| e.to_string())?; - let Some(size) = size.filter(|s| img.width().max(img.height()) > *s) else { return Ok(png) }; - let mut out = std::io::Cursor::new(Vec::new()); - img.resize(size, size, image::imageops::FilterType::Triangle).write_to(&mut out, image::ImageFormat::Png).map_err(|e| e.to_string())?; - Ok(out.into_inner()) + let info = craft_image_codecs::inspect_raster(&png).map_err(|e| e.to_string())?.ok_or("invalid PNG preview")?; + if info.width > 4096 || info.height > 4096 { + return Err("preview is larger than 4096 pixels".into()); + } + let decoded = craft_image_codecs::decode(&png).map_err(|e| e.to_string())?; + let Some(size) = size.filter(|s| decoded.width().max(decoded.height()) > *s) else { return Ok(png) }; + let rgba = image::RgbaImage::from_raw(decoded.width(), decoded.height(), decoded.to_rgba8()).ok_or("invalid PNG pixel data")?; + let resized = image::DynamicImage::ImageRgba8(rgba).resize(size, size, image::imageops::FilterType::Triangle).to_rgba8(); + let encoded = craft_image_codecs::Image::from_u8(resized.width(), resized.height(), craft_image_codecs::ChannelLayout::Rgba, resized.into_raw()) + .map_err(|e| e.to_string())?; + craft_image_codecs::encode(&encoded, craft_image_codecs::Format::Png, &Default::default()).map_err(|e| e.to_string()) } /// `mcp` (headless, in-process engine) or `mcp --connect PORT|HOST:PORT` (drive a running app