mirror of
https://github.com/storytold/craft-libs.git
synced 2026-10-31 07:39:23 +00:00
The crate denies panics, but asserts and slice indexing were reachable from arguments: pixel strides, buffers that are not whole pixels or a destination shorter than the source, single-colour slices shorter than the channel count, CLUT and pipeline dimensions, and LUT sizes. Every such check now returns CmsError::InvalidArgument. Buffer conversions validate once per call, before any pixel is written, so the per-pixel loops carry no extra checks; internal callers that already guarantee lengths use crate-private unchecked evaluation. Pipeline, Clut and Lut3d keep their fields private (read through accessors) so a validated value cannot be changed into one that panics. Pipeline::new checks matrix shapes and channel counts; Clut::new and Clut::sample check grid, outputs and the table size limit. API changes (unreleased crate): - Transform::eval, eval_fast, convert_u8/u16/f32, apply, convert_bytes_many and from_pipeline return Result. - Pipeline::new, extend and eval, Clut::new, sample, eval, eval_trilinear and eval_multilinear return Result. - GamutCheck::delta_e and out_of_gamut, Lut3d::from_transform, LutFile::identity and from_fn, synth::cmyk_profile and coated_cmyk return Result. - Pipeline, Clut and Lut3d fields are read through methods.
45 lines
1.9 KiB
Rust
45 lines
1.9 KiB
Rust
//! Regenerates the shipped synthetic CMYK profile when `CRAFT_CMS_REGEN_PROFILES=1`:
|
|
//!
|
|
//! ```sh
|
|
//! CRAFT_CMS_REGEN_PROFILES=1 cargo test -p craft-cms --release --test regen
|
|
//! ```
|
|
//!
|
|
//! Without the variable the test only checks that the generator's model still matches the
|
|
//! shipped AToB table (cheap), so model edits are not forgotten.
|
|
|
|
use craft_cms::synth::{CmykModel, CmykParams};
|
|
use craft_cms::{Builtin, Intent, Profile, Transform};
|
|
|
|
#[test]
|
|
fn regen_or_check_coated_cmyk() {
|
|
let path = concat!(env!("CARGO_MANIFEST_DIR"), "/profiles/photocraft-coated-cmyk.icc");
|
|
if std::env::var_os("CRAFT_CMS_REGEN_PROFILES").is_some() {
|
|
let t = std::time::Instant::now();
|
|
let p = craft_cms::synth::coated_cmyk().unwrap();
|
|
let bytes = p.to_bytes();
|
|
std::fs::write(path, &*bytes).unwrap();
|
|
eprintln!("wrote {} bytes in {:?}", bytes.len(), t.elapsed());
|
|
return;
|
|
}
|
|
// The shipped AToB (colorimetric) must reproduce the model.
|
|
let shipped = Profile::parse(&std::fs::read(path).unwrap()).unwrap();
|
|
let lab = Builtin::LabD50.profile();
|
|
let t = Transform::new(&shipped, lab, Intent::RelativeColorimetric, false).unwrap();
|
|
let model = CmykModel::coated(CmykParams::default().tvi);
|
|
let mut worst = 0.0f64;
|
|
for c in [0.0, 0.3, 0.7, 1.0] {
|
|
for m in [0.0, 0.45, 1.0] {
|
|
for y in [0.0, 0.55, 1.0] {
|
|
for k in [0.0, 0.5, 1.0] {
|
|
let mut o = [0.0f32; 3];
|
|
t.eval(&[c as f32, m as f32, y as f32, k as f32], &mut o).unwrap();
|
|
let got = [o[0] as f64 * 100.0, o[1] as f64 * 255.0 - 128.0, o[2] as f64 * 255.0 - 128.0];
|
|
let want = model.lab([c, m, y, k]);
|
|
worst = worst.max(craft_cms::math::delta_e76(got, want));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
assert!(worst < 1.0, "shipped CMYK profile is stale (max ΔE {worst}); regenerate it");
|
|
}
|