* craft-cms: the changes PhotoCraft made to its copy since extraction
PhotoCraft kept editing crates/cms after craft-cms was extracted from it at
storytold/photocraft@b3222ee8. This ports its 41 commits up to
storytold/photocraft@0143b148 with a three-way merge per file, keeping the
changes craft-cms made on its own (Result-returning API, white preservation,
FOGRA39, named colours, profile ID).
Ported: the .3dl shaper, Mesh header, UTF-16 and .look fixes in lutfile; the
checked ICC offsets and the per-profile curve sample budget; same_bytes and the
byte-confirmed transform cache; the blocked matrix/TRC path and the 8-bit
first-stage table; GpuMatrixShaper; cicp_rgb; the ACEScg and Dot Gain 20 %
built-ins (the latter now the default gray); bisection for a table's flat ends;
PhotoCraft spelled with a capital C in the built-in descriptions, with the
shipped coated CMYK profile's bytes as PhotoCraft regenerated them.
Not taken, because craft-cms already does it another way: Curve::inverse_table
(inverse64_many), the effective-dimension loop in multilinear interpolation,
the curve-count and mluc checks, and the .cube DOMAIN writer.
The 8-bit first-stage table checks device white as the exact path does.
Refs #779
* craft-cms: Lut3d::from_fn
PhotoCraft's proof and HDR display tables are built from a chain of several
transforms, which it wrote into Lut3d's fields. Those are private here, so
from_fn samples any RGB function, with the same size bounds as from_transform.
Refs #779
* craft-cms: README names Lut3d::from_fn
The parser reads and validates DOMAIN_MIN/DOMAIN_MAX and the type
documents them as meaningful ('the input domain the table expects'), but
the writer never emitted them: a custom-domain .cube re-saved through
parse → write_cube came back with the default domain, passed the
domain_is_default gate that used to reject it, and every row silently
mapped colours over the wrong input range.
Emit DOMAIN_MIN/DOMAIN_MAX when the domain is not the default (parse_cube
matches keywords line by line and accepts them anywhere; {} on f32 prints
the shortest roundtripping form). Regression test:
cube_domain_survives_a_write — fails today (the written table loses the
domain), round-trips exactly with the emission.
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.