Export common raster codec facade

This commit is contained in:
Dylan M. Taylor committed 2026-11-13 00:03:41 -05:00
1 parent 8c41139369
commit 4ecdcd78da
6 files changed
+7 -98

No files matched your search

Generated
-8
View File
@@ -2344,14 +2344,6 @@ dependencies = [
"kurbo 0.13.1",
]
[[package]]
name = "craft-versioned-json"
version = "0.1.0"
dependencies = [
"serde_json",
"thiserror 2.0.21",
]
[[package]]
name = "craft-video"
version = "0.1.0"
-1
View File
@@ -17,7 +17,6 @@ craft-aep = { path = "crates/aep" }
craft-affinity = { path = "crates/affinity" }
craft-ai-core = { path = "crates/ai-core" }
craft-pdf = { path = "crates/pdf", version = "=0.1.0" }
craft-versioned-json = { path = "crates/versioned-json" }
craft-pdn = { path = "crates/pdn" }
craft-plate-test = { path = "crates/plate-test" }
krilla = { version = "=0.8.2", default-features = false, features = ["raster-images"] }
+5 -11
View File
@@ -24,8 +24,7 @@ pub struct DecodeLimits {
impl Default for DecodeLimits {
fn default() -> Self {
Self { max_file_bytes: 64 * 1024 * 1024, max_width: 10_000, max_height: 10_000,
max_pixels: 16_000_000, max_alloc_bytes: 256 * 1024 * 1024 }
Self { max_file_bytes: 64 * 1024 * 1024, max_width: 10_000, max_height: 10_000, max_pixels: 16_000_000, max_alloc_bytes: 256 * 1024 * 1024 }
}
}
@@ -38,8 +37,7 @@ pub fn decode_rgba(bytes: &[u8], limits: DecodeLimits) -> Result<RgbaImage, Code
return Err(CodecError::unsupported("image", "HEIC/HEIF is not supported by the enabled codecs"));
}
let reader = || -> Result<ImageReader<Cursor<&[u8]>>, CodecError> {
let mut reader = ImageReader::new(Cursor::new(bytes)).with_guessed_format()
.map_err(|e| CodecError::malformed("image", e))?;
let mut reader = ImageReader::new(Cursor::new(bytes)).with_guessed_format().map_err(|e| CodecError::malformed("image", e))?;
if reader.format().is_none() {
return Err(CodecError::unsupported("image", "unrecognised image format"));
}
@@ -71,22 +69,18 @@ pub fn encode_jpeg(pixels: &RgbaImage, quality: u8) -> Result<Vec<u8>, CodecErro
}
let rgb = image::DynamicImage::ImageRgba8(pixels.clone()).to_rgb8();
let mut out = Cursor::new(Vec::new());
image::codecs::jpeg::JpegEncoder::new_with_quality(&mut out, quality)
.encode_image(&rgb).map_err(|e| CodecError::encode("JPEG", e))?;
image::codecs::jpeg::JpegEncoder::new_with_quality(&mut out, quality).encode_image(&rgb).map_err(|e| CodecError::encode("JPEG", e))?;
Ok(out.into_inner())
}
fn is_heif(bytes: &[u8]) -> bool {
bytes.get(4..8) == Some(b"ftyp")
&& matches!(bytes.get(8..12), Some(b"heic" | b"heix" | b"hevc" | b"mif1" | b"msf1" | b"avif"))
bytes.get(4..8) == Some(b"ftyp") && matches!(bytes.get(8..12), Some(b"heic" | b"heix" | b"hevc" | b"mif1" | b"msf1" | b"avif"))
}
fn check_dimensions(width: u32, height: u32, limits: DecodeLimits) -> Result<(), CodecError> {
let pixels = u64::from(width).checked_mul(u64::from(height))
.ok_or_else(|| CodecError::LimitExceeded("image dimensions overflow".into()))?;
let pixels = u64::from(width).checked_mul(u64::from(height)).ok_or_else(|| CodecError::LimitExceeded("image dimensions overflow".into()))?;
if width == 0 || height == 0 || width > limits.max_width || height > limits.max_height || pixels > limits.max_pixels {
return Err(CodecError::LimitExceeded(format!("image dimensions {width}x{height} exceed configured limits")));
}
Ok(())
}
+2
View File
@@ -18,6 +18,8 @@
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
/// Common bounded decoding and PNG/JPEG encoding for common image formats.
pub mod common;
pub mod dds;
mod dds_bc;
pub mod dicom;
-15
View File
@@ -1,15 +0,0 @@
[package]
name = "craft-versioned-json"
description = "Reusable named and versioned JSON document envelopes"
version.workspace = true
edition.workspace = true
license.workspace = true
rust-version.workspace = true
repository.workspace = true
[dependencies]
serde_json = { workspace = true }
thiserror = { workspace = true }
[lints]
workspace = true
-63
View File
@@ -1,63 +0,0 @@
//! Helpers for named, versioned JSON documents whose payload is an object.
//!
//! The library checks and writes the common envelope. Each application owns its payload schema,
//! migrations and validation.
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
use serde_json::{Map, Value};
/// A recognized document header.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Header {
pub name: String,
pub version: u64,
}
/// Header errors, kept independent of an application's schema and error type.
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum Error {
#[error("missing or invalid `{0}` header")]
Missing(String),
#[error("expected format `{expected}`, found `{found}`")]
WrongName { expected: String, found: String },
#[error("format version {found} is unsupported; this build reads {expected}")]
UnsupportedVersion { found: u64, expected: u64 },
}
/// Checks `root[header_key]` for the expected name and exact supported version.
pub fn read_header(root: &Value, header_key: &str, expected_name: &str, expected_version: u64) -> Result<Header, Error> {
let header = root.get(header_key).ok_or_else(|| Error::Missing(header_key.to_owned()))?;
let name = header.get("name").and_then(Value::as_str).ok_or_else(|| Error::Missing(header_key.to_owned()))?;
let version = header.get("version").and_then(Value::as_u64).ok_or_else(|| Error::Missing(header_key.to_owned()))?;
if name != expected_name {
return Err(Error::WrongName { expected: expected_name.to_owned(), found: name.to_owned() });
}
if version != expected_version {
return Err(Error::UnsupportedVersion { found: version, expected: expected_version });
}
Ok(Header { name: name.to_owned(), version })
}
/// Adds a named/versioned header to a JSON object, creating the object if needed.
pub fn write_header(root: &mut Value, header_key: &str, name: &str, version: u64) -> Result<(), Error> {
if !root.is_object() {
*root = Value::Object(Map::new());
}
let object = root.as_object_mut().ok_or_else(|| Error::Missing(header_key.to_owned()))?;
object.insert(header_key.to_owned(), serde_json::json!({ "name": name, "version": version }));
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn reads_and_writes_headers() {
let mut root = serde_json::json!({ "payload": true });
write_header(&mut root, "format", "example", 3).unwrap();
assert_eq!(read_header(&root, "format", "example", 3).unwrap().version, 3);
assert_eq!(read_header(&root, "format", "example", 4), Err(Error::UnsupportedVersion { found: 3, expected: 4 }));
}
}