Files
craft-libs/crates/cms/tests/regen.rs
aiongg d0107d1130 craft-cms: return errors instead of panicking on caller input
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.
2026-10-13 01:33:07 +00:00

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");
}