1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
//! Color Management System (CMS) traits.
//!
//! Defines the interface for ICC profile-based color transforms. When a CMS
//! feature is enabled (e.g., `cms-moxcms`, `cms-lcms2`), the implementation
//! provides ICC-to-ICC transforms. Named profile conversions (sRGB, P3,
//! BT.2020) use hardcoded matrices and don't require a CMS.
//!
//! # When codecs need a CMS
//!
//! Most codecs don't need to interact with the CMS directly.
//! [`finalize_for_output`](super::finalize_for_output) handles CMS transforms
//! internally when the [`OutputProfile`](super::OutputProfile) requires one.
//!
//! A codec needs CMS awareness only when:
//!
//! - **Decoding** an image with an embedded ICC profile that doesn't match
//! any known CICP combination. The decoder extracts the ICC bytes and
//! stores them on [`ColorContext`](crate::ColorContext). The CMS is used
//! later (at encode or processing time), not during decode.
//!
//! - **Encoding** with `OutputProfile::Icc(custom_profile)`. The CMS builds
//! a source→destination transform, which `finalize_for_output` applies
//! row-by-row via [`RowTransform`].
//!
//! # Implementing a CMS backend
//!
//! To add a new CMS backend (e.g., wrapping Little CMS 2):
//!
//! 1. Implement [`ColorManagement`] on your backend struct.
//! 2. `build_transform` should parse both ICC profiles, create an internal
//! transform object, and return it as `Box<dyn RowTransform>`.
//! 3. `identify_profile` should check if an ICC profile matches a known
//! standard (sRGB, Display P3, etc.) and return the corresponding
//! [`Cicp`](crate::Cicp). This enables the fast path: if both source
//! and destination are known profiles, hardcoded matrices are used
//! instead of the CMS.
//! 4. Feature-gate your implementation behind a cargo feature
//! (e.g., `cms-lcms2`).
//!
//! ```rust,ignore
//! struct MyLcms2;
//!
//! impl ColorManagement for MyLcms2 {
//! type Error = lcms2::Error;
//!
//! fn build_transform(
//! &self,
//! src_icc: &[u8],
//! dst_icc: &[u8],
//! ) -> Result<Box<dyn RowTransform>, Self::Error> {
//! let src = lcms2::Profile::new_icc(src_icc)?;
//! let dst = lcms2::Profile::new_icc(dst_icc)?;
//! let xform = lcms2::Transform::new(&src, &dst, ...)?;
//! Ok(Box::new(Lcms2RowTransform(xform)))
//! }
//!
//! fn identify_profile(&self, icc: &[u8]) -> Option<Cicp> {
//! // Fast: check MD5 hash against known profiles
//! // Slow: parse TRC+matrix, compare within tolerance
//! None
//! }
//! }
//! ```
//!
//! # No-op CMS
//!
//! Codecs that don't need ICC support can provide a no-op CMS whose
//! `build_transform` always returns an error. This satisfies the type
//! system while making it clear that ICC transforms are unsupported.
use cratePixelFormat;
use Box;
/// ICC rendering intent — controls how colors outside the destination gamut
/// are handled during a profile-to-profile transform.
///
/// # Which intent to use
///
/// For **display-to-display** workflows (web images, app thumbnails, photo
/// export): use [`RelativeColorimetric`](Self::RelativeColorimetric). It
/// preserves in-gamut colors exactly and is the de facto standard for screen
/// output.
///
/// For **photographic print** with a profile that has a perceptual table:
/// use [`Perceptual`](Self::Perceptual). It compresses the full source gamut
/// smoothly instead of clipping.
///
/// For **soft-proofing** ("what will this print look like on screen"): use
/// [`AbsoluteColorimetric`](Self::AbsoluteColorimetric) to simulate the
/// paper white.
///
/// [`Saturation`](Self::Saturation) is for business graphics (pie charts,
/// logos). It is almost never correct for photographic images.
///
/// # Interaction with ICC profiles
///
/// An ICC profile may contain up to four LUTs (AToB0–AToB3), one per intent.
/// **Most display profiles only ship a single LUT** (relative colorimetric).
/// When you request an intent whose LUT is absent, the CMS silently falls
/// back to the profile's default — usually relative colorimetric. This means
/// `Perceptual` and `RelativeColorimetric` produce **identical output** for
/// the vast majority of display profiles (sRGB IEC 61966-2.1, Display P3,
/// etc.). The distinction only matters for print/press profiles that include
/// dedicated perceptual gamut-mapping tables.
///
/// # Bugs and pitfalls
///
/// - **Perceptual on display profiles is a no-op.** Requesting `Perceptual`
/// doesn't add gamut mapping when the profile lacks a perceptual table —
/// it silently degrades to clipping. If you need actual gamut mapping
/// between display profiles, you must supply a profile that contains
/// perceptual intent tables (e.g., a proofing profile or a carefully
/// authored display profile).
///
/// - **AbsoluteColorimetric tints whites.** Source white is preserved
/// literally, so a D50 source on a D65 display shows yellowish whites.
/// Never use this for final output — only for proofing previews.
///
/// - **Saturation may shift hues.** The ICC spec allows saturation-intent
/// tables to sacrifice hue accuracy for vividness. Photographs will look
/// wrong.
/// Controls which transfer function metadata the CMS trusts when building
/// a transform.
///
/// ICC profiles store transfer response curves (TRC) as `curv` or `para`
/// tags — lookup tables or parametric curves baked into the profile. Modern
/// container formats (JPEG XL, HEIF/AVIF, AV1) also carry CICP transfer
/// characteristics — an integer code that names an exact mathematical
/// transfer function (sRGB, PQ, HLG, etc.).
///
/// When both are present, they should agree — but in practice, the ICC TRC
/// may be a reduced-precision approximation of the CICP function (limited
/// by `curv` table size or `para` parameter quantization). The question is
/// which source of truth to prefer.
///
/// # Which priority to use
///
/// - **Standard ICC workflows** (JPEG, PNG, TIFF, WebP): use
/// [`PreferIcc`](Self::PreferIcc). These formats don't carry CICP metadata;
/// the ICC profile is the sole authority.
///
/// - **CICP-native formats** (JPEG XL, HEIF, AVIF): use
/// [`PreferCicp`](Self::PreferCicp). The CICP code is the authoritative
/// description; the ICC profile exists for backwards compatibility with
/// older software.
///
/// # Bugs and pitfalls
///
/// - **CICP ≠ ICC is a real bug.** Some encoders embed a generic sRGB ICC
/// profile alongside a PQ or HLG CICP code. Using `PreferCicp` is correct
/// here — the ICC profile is wrong (or at best, a tone-mapped fallback).
/// Using `PreferIcc` would silently apply the wrong transfer function.
///
/// - **`PreferIcc` for CICP-native formats loses precision.** If the ICC
/// profile's `curv` table is a 1024-entry LUT approximating the sRGB
/// function, you get quantization steps in dark tones. The CICP code
/// gives the exact closed-form function — no quantization, no table
/// interpolation error.
///
/// - **`PreferCicp` for pure-ICC formats is harmless but pointless.** If
/// the profile has no embedded CICP metadata, the CMS ignores this flag
/// and falls back to the TRC. No wrong output, just a wasted branch.
///
/// - **Advisory vs. authoritative.** The ICC Votable Proposal on CICP
/// metadata in ICC profiles designates the CICP fields as *advisory*.
/// The profile's actual TRC tags remain the normative description.
/// `PreferIcc` follows this interpretation. `PreferCicp` overrides it
/// for formats where the container's CICP is known to be authoritative.
/// Shareable, stateless row-level color transform.
///
/// Takes `&self` — the same instance can be held behind `Arc<dyn RowTransform>`
/// and reused across threads, converters, or cached for batch workloads.
/// Appropriate when the transform carries no per-call mutable state: pure
/// matrix/LUT math, moxcms `TransformExecutor` (whose `transform(&self, ...)`
/// is already `&self`), or any stateless formula-based conversion.
///
/// When the transform needs scratch buffers or per-call state, use
/// [`RowTransformMut`] instead.
/// Owned, stateful row-level color transform.
///
/// Takes `&mut self` — each [`RowConverter`] owns its own `Box<dyn
/// RowTransformMut>`, so implementations can reuse scratch buffers and
/// update internal state per call without interior mutability.
///
/// When the transform is stateless and could be shared, use
/// [`RowTransform`] instead — [`PluggableCms`] can offer both paths via
/// [`build_shared_source_transform`](PluggableCms::build_shared_source_transform).
///
/// [`RowConverter`]: crate::RowConverter
/// Color management system interface.
///
/// Abstracts over CMS backends (moxcms, lcms2, etc.) to provide
/// ICC profile transforms and profile identification.
///
/// # Feature-gated
///
/// The trait is always available for trait bounds and generic code.
/// Concrete implementations are provided by feature-gated modules
/// (e.g., `cms-moxcms`).
///
/// # Deprecated
///
/// Prefer [`PluggableCms`] for new code. `ColorManagement` is generic,
/// not dyn-safe, and takes raw ICC byte pairs; `PluggableCms` is
/// dyn-safe, accepts [`ColorProfileSource`] (ICC / CICP / named /
/// primaries+transfer), carries [`ConvertOptions`], and composes into
/// the dispatch chain used by
/// [`RowConverter::new_explicit_with_cms`](crate::RowConverter::new_explicit_with_cms).
///
/// [`ColorProfileSource`]: crate::ColorProfileSource
/// [`ConvertOptions`]: crate::policy::ConvertOptions
/// Dyn-compatible CMS plugin interface for overriding gamut/profile
/// conversions inside a [`ConvertPlan`](crate::ConvertPlan).
///
/// When a `PluggableCms` is passed to
/// [`RowConverter::new_explicit_with_cms`](crate::RowConverter::new_explicit_with_cms)
/// and the source and destination profiles differ, the plan asks the
/// plugin whether it will handle the exact `(src_format, dst_format)`
/// pair. If the plugin returns a transform, the plan collapses to a
/// single external-transform step that drives the row end-to-end —
/// built-in linearize → gamut-matrix → encode steps (and their fused
/// matlut kernels) are bypassed for that conversion. If the plugin
/// returns `None`, the plan falls back to the built-in path.
///
/// `PluggableCms` is intentionally narrower than [`ColorManagement`]:
/// - It accepts [`ColorProfileSource`] instead of raw ICC bytes, so
/// plugins can use primaries/transfer shortcuts, named profiles, CICP,
/// or ICC without forcing the caller to serialize to ICC.
/// - It receives [`ConvertOptions`] so plugins can honor
/// `clip_out_of_gamut` and future fields like rendering intent.
/// - It is dyn-compatible (no associated `Error` type; no generics).
/// This is what lets it live behind `&dyn PluggableCms` in API
/// signatures without forcing every caller to monomorphize.
///
/// # Decline vs. fail
///
/// Plugin methods return `Option<Result<T, CmsPluginError>>` with three
/// outcomes:
/// - `None` — declined ("not my problem"). The dispatch chain continues
/// to the next plugin (typically `ZenCmsLite`) or falls through to the
/// built-in path.
/// - `Some(Ok(transform))` — accepted. The dispatch chain stops here.
/// - `Some(Err(e))` — tried-and-failed. The error propagates immediately;
/// **the chain does not continue**. If a plugin took ownership of a
/// conversion and failed, we surface that rather than silently producing
/// different output from a fallback backend.
///
/// [`ColorProfileSource`]: crate::ColorProfileSource
/// [`ConvertOptions`]: crate::policy::ConvertOptions
/// Error produced by a [`PluggableCms`] when a plugin recognized a
/// conversion pair but failed to build a transform.
///
/// Type-erased wrapper over any `core::error::Error + Send + Sync`. Use
/// [`CmsPluginError::new`] or [`From`] to construct.
;