mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-13 22:12:12 +00:00
Export common raster codec facade
This commit is contained in:
6 files changed
+7
-98
No files matched your search
Generated
-8
@@ -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"
|
||||
|
||||
@@ -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"] }
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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 }));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user