1//---------------------------------------------------------------------------------
2//
3//  Little Color Management System
4//  Copyright (c) 1998-2012 Marti Maria Saguer
5//
6// Permission is hereby granted, free of charge, to any person obtaining
7// a copy of this software and associated documentation files (the "Software"),
8// to deal in the Software without restriction, including without limitation
9// the rights to use, copy, modify, merge, publish, distribute, sublicense,
10// and/or sell copies of the Software, and to permit persons to whom the Software
11// is furnished to do so, subject to the following conditions:
12//
13// The above copyright notice and this permission notice shall be included in
14// all copies or substantial portions of the Software.
15//
16// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
18// THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23//
24//---------------------------------------------------------------------------------
25//
26
27#include "lcms2_internal.h"
28
29
30// Link several profiles to obtain a single LUT modelling the whole color transform. Intents, Black point
31// compensation and Adaptation parameters may vary across profiles. BPC and Adaptation refers to the PCS
32// after the profile. I.e, BPC[0] refers to connexion between profile(0) and profile(1)
33cmsPipeline* _cmsLinkProfiles(cmsContext     ContextID,
34                              cmsUInt32Number nProfiles,
35                              cmsUInt32Number Intents[],
36                              cmsHPROFILE     hProfiles[],
37                              cmsBool         BPC[],
38                              cmsFloat64Number AdaptationStates[],
39                              cmsUInt32Number dwFlags);
40
41//---------------------------------------------------------------------------------
42
43// This is the default routine for ICC-style intents. A user may decide to override it by using a plugin.
44// Supported intents are perceptual, relative colorimetric, saturation and ICC-absolute colorimetric
45static
46cmsPipeline* DefaultICCintents(cmsContext     ContextID,
47                               cmsUInt32Number nProfiles,
48                               cmsUInt32Number Intents[],
49                               cmsHPROFILE     hProfiles[],
50                               cmsBool         BPC[],
51                               cmsFloat64Number AdaptationStates[],
52                               cmsUInt32Number dwFlags);
53
54//---------------------------------------------------------------------------------
55
56// This is the entry for black-preserving K-only intents, which are non-ICC. Last profile have to be a output profile
57// to do the trick (no devicelinks allowed at that position)
58static
59cmsPipeline*  BlackPreservingKOnlyIntents(cmsContext     ContextID,
60                                          cmsUInt32Number nProfiles,
61                                          cmsUInt32Number Intents[],
62                                          cmsHPROFILE     hProfiles[],
63                                          cmsBool         BPC[],
64                                          cmsFloat64Number AdaptationStates[],
65                                          cmsUInt32Number dwFlags);
66
67//---------------------------------------------------------------------------------
68
69// This is the entry for black-plane preserving, which are non-ICC. Again, Last profile have to be a output profile
70// to do the trick (no devicelinks allowed at that position)
71static
72cmsPipeline*  BlackPreservingKPlaneIntents(cmsContext     ContextID,
73                                           cmsUInt32Number nProfiles,
74                                           cmsUInt32Number Intents[],
75                                           cmsHPROFILE     hProfiles[],
76                                           cmsBool         BPC[],
77                                           cmsFloat64Number AdaptationStates[],
78                                           cmsUInt32Number dwFlags);
79
80//---------------------------------------------------------------------------------
81
82
83// This is a structure holding implementations for all supported intents.
84typedef struct _cms_intents_list {
85
86    cmsUInt32Number Intent;
87    char            Description[256];
88    cmsIntentFn     Link;
89    struct _cms_intents_list*  Next;
90
91} cmsIntentsList;
92
93
94// Built-in intents
95static cmsIntentsList DefaultIntents[] = {
96
97    { INTENT_PERCEPTUAL,                            "Perceptual",                                   DefaultICCintents,            &DefaultIntents[1] },
98    { INTENT_RELATIVE_COLORIMETRIC,                 "Relative colorimetric",                        DefaultICCintents,            &DefaultIntents[2] },
99    { INTENT_SATURATION,                            "Saturation",                                   DefaultICCintents,            &DefaultIntents[3] },
100    { INTENT_ABSOLUTE_COLORIMETRIC,                 "Absolute colorimetric",                        DefaultICCintents,            &DefaultIntents[4] },
101    { INTENT_PRESERVE_K_ONLY_PERCEPTUAL,            "Perceptual preserving black ink",              BlackPreservingKOnlyIntents,  &DefaultIntents[5] },
102    { INTENT_PRESERVE_K_ONLY_RELATIVE_COLORIMETRIC, "Relative colorimetric preserving black ink",   BlackPreservingKOnlyIntents,  &DefaultIntents[6] },
103    { INTENT_PRESERVE_K_ONLY_SATURATION,            "Saturation preserving black ink",              BlackPreservingKOnlyIntents,  &DefaultIntents[7] },
104    { INTENT_PRESERVE_K_PLANE_PERCEPTUAL,           "Perceptual preserving black plane",            BlackPreservingKPlaneIntents, &DefaultIntents[8] },
105    { INTENT_PRESERVE_K_PLANE_RELATIVE_COLORIMETRIC,"Relative colorimetric preserving black plane", BlackPreservingKPlaneIntents, &DefaultIntents[9] },
106    { INTENT_PRESERVE_K_PLANE_SATURATION,           "Saturation preserving black plane",            BlackPreservingKPlaneIntents, NULL }
107};
108
109
110// A pointer to the begining of the list
111_cmsIntentsPluginChunkType _cmsIntentsPluginChunk = { NULL };
112
113// Duplicates the zone of memory used by the plug-in in the new context
114static
115void DupPluginIntentsList(struct _cmsContext_struct* ctx,
116                                               const struct _cmsContext_struct* src)
117{
118   _cmsIntentsPluginChunkType newHead = { NULL };
119   cmsIntentsList*  entry;
120   cmsIntentsList*  Anterior = NULL;
121   _cmsIntentsPluginChunkType* head = (_cmsIntentsPluginChunkType*) src->chunks[IntentPlugin];
122
123    // Walk the list copying all nodes
124   for (entry = head->Intents;
125        entry != NULL;
126        entry = entry ->Next) {
127
128            cmsIntentsList *newEntry = ( cmsIntentsList *) _cmsSubAllocDup(ctx ->MemPool, entry, sizeof(cmsIntentsList));
129
130            if (newEntry == NULL)
131                return;
132
133            // We want to keep the linked list order, so this is a little bit tricky
134            newEntry -> Next = NULL;
135            if (Anterior)
136                Anterior -> Next = newEntry;
137
138            Anterior = newEntry;
139
140            if (newHead.Intents == NULL)
141                newHead.Intents = newEntry;
142    }
143
144  ctx ->chunks[IntentPlugin] = _cmsSubAllocDup(ctx->MemPool, &newHead, sizeof(_cmsIntentsPluginChunkType));
145}
146
147void  _cmsAllocIntentsPluginChunk(struct _cmsContext_struct* ctx,
148                                         const struct _cmsContext_struct* src)
149{
150    if (src != NULL) {
151
152        // Copy all linked list
153        DupPluginIntentsList(ctx, src);
154    }
155    else {
156        static _cmsIntentsPluginChunkType IntentsPluginChunkType = { NULL };
157        ctx ->chunks[IntentPlugin] = _cmsSubAllocDup(ctx ->MemPool, &IntentsPluginChunkType, sizeof(_cmsIntentsPluginChunkType));
158    }
159}
160
161
162// Search the list for a suitable intent. Returns NULL if not found
163static
164cmsIntentsList* SearchIntent(cmsContext ContextID, cmsUInt32Number Intent)
165{
166    _cmsIntentsPluginChunkType* ctx = ( _cmsIntentsPluginChunkType*) _cmsContextGetClientChunk(ContextID, IntentPlugin);
167    cmsIntentsList* pt;
168
169    for (pt = ctx -> Intents; pt != NULL; pt = pt -> Next)
170        if (pt ->Intent == Intent) return pt;
171
172    for (pt = DefaultIntents; pt != NULL; pt = pt -> Next)
173        if (pt ->Intent == Intent) return pt;
174
175    return NULL;
176}
177
178// Black point compensation. Implemented as a linear scaling in XYZ. Black points
179// should come relative to the white point. Fills an matrix/offset element m
180// which is organized as a 4x4 matrix.
181static
182void ComputeBlackPointCompensation(const cmsCIEXYZ* BlackPointIn,
183                                   const cmsCIEXYZ* BlackPointOut,
184                                   cmsMAT3* m, cmsVEC3* off)
185{
186  cmsFloat64Number ax, ay, az, bx, by, bz, tx, ty, tz;
187
188   // Now we need to compute a matrix plus an offset m and of such of
189   // [m]*bpin + off = bpout
190   // [m]*D50  + off = D50
191   //
192   // This is a linear scaling in the form ax+b, where
193   // a = (bpout - D50) / (bpin - D50)
194   // b = - D50* (bpout - bpin) / (bpin - D50)
195
196   tx = BlackPointIn->X - cmsD50_XYZ()->X;
197   ty = BlackPointIn->Y - cmsD50_XYZ()->Y;
198   tz = BlackPointIn->Z - cmsD50_XYZ()->Z;
199
200   ax = (BlackPointOut->X - cmsD50_XYZ()->X) / tx;
201   ay = (BlackPointOut->Y - cmsD50_XYZ()->Y) / ty;
202   az = (BlackPointOut->Z - cmsD50_XYZ()->Z) / tz;
203
204   bx = - cmsD50_XYZ()-> X * (BlackPointOut->X - BlackPointIn->X) / tx;
205   by = - cmsD50_XYZ()-> Y * (BlackPointOut->Y - BlackPointIn->Y) / ty;
206   bz = - cmsD50_XYZ()-> Z * (BlackPointOut->Z - BlackPointIn->Z) / tz;
207
208   _cmsVEC3init(&m ->v[0], ax, 0,  0);
209   _cmsVEC3init(&m ->v[1], 0, ay,  0);
210   _cmsVEC3init(&m ->v[2], 0,  0,  az);
211   _cmsVEC3init(off, bx, by, bz);
212
213}
214
215
216// Approximate a blackbody illuminant based on CHAD information
217static
218cmsFloat64Number CHAD2Temp(const cmsMAT3* Chad)
219{
220    // Convert D50 across inverse CHAD to get the absolute white point
221    cmsVEC3 d, s;
222    cmsCIEXYZ Dest;
223    cmsCIExyY DestChromaticity;
224    cmsFloat64Number TempK;
225    cmsMAT3 m1, m2;
226
227    m1 = *Chad;
228    if (!_cmsMAT3inverse(&m1, &m2)) return FALSE;
229
230    s.n[VX] = cmsD50_XYZ() -> X;
231    s.n[VY] = cmsD50_XYZ() -> Y;
232    s.n[VZ] = cmsD50_XYZ() -> Z;
233
234    _cmsMAT3eval(&d, &m2, &s);
235
236    Dest.X = d.n[VX];
237    Dest.Y = d.n[VY];
238    Dest.Z = d.n[VZ];
239
240    cmsXYZ2xyY(&DestChromaticity, &Dest);
241
242    if (!cmsTempFromWhitePoint(&TempK, &DestChromaticity))
243        return -1.0;
244
245    return TempK;
246}
247
248// Compute a CHAD based on a given temperature
249static
250    void Temp2CHAD(cmsMAT3* Chad, cmsFloat64Number Temp)
251{
252    cmsCIEXYZ White;
253    cmsCIExyY ChromaticityOfWhite;
254
255    cmsWhitePointFromTemp(&ChromaticityOfWhite, Temp);
256    cmsxyY2XYZ(&White, &ChromaticityOfWhite);
257    _cmsAdaptationMatrix(Chad, NULL, &White, cmsD50_XYZ());
258}
259
260// Join scalings to obtain relative input to absolute and then to relative output.
261// Result is stored in a 3x3 matrix
262static
263cmsBool  ComputeAbsoluteIntent(cmsFloat64Number AdaptationState,
264                               const cmsCIEXYZ* WhitePointIn,
265                               const cmsMAT3* ChromaticAdaptationMatrixIn,
266                               const cmsCIEXYZ* WhitePointOut,
267                               const cmsMAT3* ChromaticAdaptationMatrixOut,
268                               cmsMAT3* m)
269{
270    cmsMAT3 Scale, m1, m2, m3, m4;
271
272    // Adaptation state
273    if (AdaptationState == 1.0) {
274
275        // Observer is fully adapted. Keep chromatic adaptation.
276        // That is the standard V4 behaviour
277        _cmsVEC3init(&m->v[0], WhitePointIn->X / WhitePointOut->X, 0, 0);
278        _cmsVEC3init(&m->v[1], 0, WhitePointIn->Y / WhitePointOut->Y, 0);
279        _cmsVEC3init(&m->v[2], 0, 0, WhitePointIn->Z / WhitePointOut->Z);
280
281    }
282    else  {
283
284        // Incomplete adaptation. This is an advanced feature.
285        _cmsVEC3init(&Scale.v[0], WhitePointIn->X / WhitePointOut->X, 0, 0);
286        _cmsVEC3init(&Scale.v[1], 0,  WhitePointIn->Y / WhitePointOut->Y, 0);
287        _cmsVEC3init(&Scale.v[2], 0, 0,  WhitePointIn->Z / WhitePointOut->Z);
288
289
290        if (AdaptationState == 0.0) {
291
292            m1 = *ChromaticAdaptationMatrixOut;
293            _cmsMAT3per(&m2, &m1, &Scale);
294            // m2 holds CHAD from output white to D50 times abs. col. scaling
295
296            // Observer is not adapted, undo the chromatic adaptation
297            _cmsMAT3per(m, &m2, ChromaticAdaptationMatrixOut);
298
299            m3 = *ChromaticAdaptationMatrixIn;
300            if (!_cmsMAT3inverse(&m3, &m4)) return FALSE;
301            _cmsMAT3per(m, &m2, &m4);
302
303        } else {
304
305            cmsMAT3 MixedCHAD;
306            cmsFloat64Number TempSrc, TempDest, Temp;
307
308            m1 = *ChromaticAdaptationMatrixIn;
309            if (!_cmsMAT3inverse(&m1, &m2)) return FALSE;
310            _cmsMAT3per(&m3, &m2, &Scale);
311            // m3 holds CHAD from input white to D50 times abs. col. scaling
312
313            TempSrc  = CHAD2Temp(ChromaticAdaptationMatrixIn);
314            TempDest = CHAD2Temp(ChromaticAdaptationMatrixOut);
315
316            if (TempSrc < 0.0 || TempDest < 0.0) return FALSE; // Something went wrong
317
318            if (_cmsMAT3isIdentity(&Scale) && fabs(TempSrc - TempDest) < 0.01) {
319
320                _cmsMAT3identity(m);
321                return TRUE;
322            }
323
324            Temp = (1.0 - AdaptationState) * TempDest + AdaptationState * TempSrc;
325
326            // Get a CHAD from whatever output temperature to D50. This replaces output CHAD
327            Temp2CHAD(&MixedCHAD, Temp);
328
329            _cmsMAT3per(m, &m3, &MixedCHAD);
330        }
331
332    }
333    return TRUE;
334
335}
336
337// Just to see if m matrix should be applied
338static
339cmsBool IsEmptyLayer(cmsMAT3* m, cmsVEC3* off)
340{
341    cmsFloat64Number diff = 0;
342    cmsMAT3 Ident;
343    int i;
344
345    if (m == NULL && off == NULL) return TRUE;  // NULL is allowed as an empty layer
346    if (m == NULL && off != NULL) return FALSE; // This is an internal error
347
348    _cmsMAT3identity(&Ident);
349
350    for (i=0; i < 3*3; i++)
351        diff += fabs(((cmsFloat64Number*)m)[i] - ((cmsFloat64Number*)&Ident)[i]);
352
353    for (i=0; i < 3; i++)
354        diff += fabs(((cmsFloat64Number*)off)[i]);
355
356
357    return (diff < 0.002);
358}
359
360
361// Compute the conversion layer
362static
363cmsBool ComputeConversion(int i, cmsHPROFILE hProfiles[],
364                                 cmsUInt32Number Intent,
365                                 cmsBool BPC,
366                                 cmsFloat64Number AdaptationState,
367                                 cmsMAT3* m, cmsVEC3* off)
368{
369
370    int k;
371
372    // m  and off are set to identity and this is detected latter on
373    _cmsMAT3identity(m);
374    _cmsVEC3init(off, 0, 0, 0);
375
376    // If intent is abs. colorimetric,
377    if (Intent == INTENT_ABSOLUTE_COLORIMETRIC) {
378
379        cmsCIEXYZ WhitePointIn, WhitePointOut;
380        cmsMAT3 ChromaticAdaptationMatrixIn, ChromaticAdaptationMatrixOut;
381
382        _cmsReadMediaWhitePoint(&WhitePointIn,  hProfiles[i-1]);
383        _cmsReadCHAD(&ChromaticAdaptationMatrixIn, hProfiles[i-1]);
384
385        _cmsReadMediaWhitePoint(&WhitePointOut,  hProfiles[i]);
386        _cmsReadCHAD(&ChromaticAdaptationMatrixOut, hProfiles[i]);
387
388        if (!ComputeAbsoluteIntent(AdaptationState,
389                                  &WhitePointIn,  &ChromaticAdaptationMatrixIn,
390                                  &WhitePointOut, &ChromaticAdaptationMatrixOut, m)) return FALSE;
391
392    }
393    else {
394        // Rest of intents may apply BPC.
395
396        if (BPC) {
397
398            cmsCIEXYZ BlackPointIn, BlackPointOut;
399
400            cmsDetectBlackPoint(&BlackPointIn,  hProfiles[i-1], Intent, 0);
401            cmsDetectDestinationBlackPoint(&BlackPointOut, hProfiles[i], Intent, 0);
402
403            // If black points are equal, then do nothing
404            if (BlackPointIn.X != BlackPointOut.X ||
405                BlackPointIn.Y != BlackPointOut.Y ||
406                BlackPointIn.Z != BlackPointOut.Z)
407                    ComputeBlackPointCompensation(&BlackPointIn, &BlackPointOut, m, off);
408        }
409    }
410
411    // Offset should be adjusted because the encoding. We encode XYZ normalized to 0..1.0,
412    // to do that, we divide by MAX_ENCODEABLE_XZY. The conversion stage goes XYZ -> XYZ so
413    // we have first to convert from encoded to XYZ and then convert back to encoded.
414    // y = Mx + Off
415    // x = x'c
416    // y = M x'c + Off
417    // y = y'c; y' = y / c
418    // y' = (Mx'c + Off) /c = Mx' + (Off / c)
419
420    for (k=0; k < 3; k++) {
421        off ->n[k] /= MAX_ENCODEABLE_XYZ;
422    }
423
424    return TRUE;
425}
426
427
428// Add a conversion stage if needed. If a matrix/offset m is given, it applies to XYZ space
429static
430cmsBool AddConversion(cmsPipeline* Result, cmsColorSpaceSignature InPCS, cmsColorSpaceSignature OutPCS, cmsMAT3* m, cmsVEC3* off)
431{
432    cmsFloat64Number* m_as_dbl = (cmsFloat64Number*) m;
433    cmsFloat64Number* off_as_dbl = (cmsFloat64Number*) off;
434
435    // Handle PCS mismatches. A specialized stage is added to the LUT in such case
436    switch (InPCS) {
437
438    case cmsSigXYZData: // Input profile operates in XYZ
439
440        switch (OutPCS) {
441
442        case cmsSigXYZData:  // XYZ -> XYZ
443            if (!IsEmptyLayer(m, off) &&
444                !cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl)))
445                return FALSE;
446            break;
447
448        case cmsSigLabData:  // XYZ -> Lab
449            if (!IsEmptyLayer(m, off) &&
450                !cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl)))
451                return FALSE;
452            if (!cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocXYZ2Lab(Result ->ContextID)))
453                return FALSE;
454            break;
455
456        default:
457            return FALSE;   // Colorspace mismatch
458        }
459        break;
460
461    case cmsSigLabData: // Input profile operates in Lab
462
463        switch (OutPCS) {
464
465        case cmsSigXYZData:  // Lab -> XYZ
466
467            if (!cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocLab2XYZ(Result ->ContextID)))
468                return FALSE;
469            if (!IsEmptyLayer(m, off) &&
470                !cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl)))
471                return FALSE;
472            break;
473
474        case cmsSigLabData:  // Lab -> Lab
475
476            if (!IsEmptyLayer(m, off)) {
477                if (!cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocLab2XYZ(Result ->ContextID)) ||
478                    !cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl)) ||
479                    !cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocXYZ2Lab(Result ->ContextID)))
480                    return FALSE;
481            }
482            break;
483
484        default:
485            return FALSE;  // Mismatch
486        }
487        break;
488
489        // On colorspaces other than PCS, check for same space
490    default:
491        if (InPCS != OutPCS) return FALSE;
492        break;
493    }
494
495    return TRUE;
496}
497
498
499// Is a given space compatible with another?
500static
501cmsBool ColorSpaceIsCompatible(cmsColorSpaceSignature a, cmsColorSpaceSignature b)
502{
503    // If they are same, they are compatible.
504    if (a == b) return TRUE;
505
506    // Check for MCH4 substitution of CMYK
507    if ((a == cmsSig4colorData) && (b == cmsSigCmykData)) return TRUE;
508    if ((a == cmsSigCmykData) && (b == cmsSig4colorData)) return TRUE;
509
510    // Check for XYZ/Lab. Those spaces are interchangeable as they can be computed one from other.
511    if ((a == cmsSigXYZData) && (b == cmsSigLabData)) return TRUE;
512    if ((a == cmsSigLabData) && (b == cmsSigXYZData)) return TRUE;
513
514    return FALSE;
515}
516
517
518// Default handler for ICC-style intents
519static
520cmsPipeline* DefaultICCintents(cmsContext       ContextID,
521                               cmsUInt32Number  nProfiles,
522                               cmsUInt32Number  TheIntents[],
523                               cmsHPROFILE      hProfiles[],
524                               cmsBool          BPC[],
525                               cmsFloat64Number AdaptationStates[],
526                               cmsUInt32Number  dwFlags)
527{
528    cmsPipeline* Lut = NULL;
529    cmsPipeline* Result;
530    cmsHPROFILE hProfile;
531    cmsMAT3 m;
532    cmsVEC3 off;
533    cmsColorSpaceSignature ColorSpaceIn, ColorSpaceOut, CurrentColorSpace;
534    cmsProfileClassSignature ClassSig;
535    cmsUInt32Number  i, Intent;
536
537    // For safety
538    if (nProfiles == 0) return NULL;
539
540    // Allocate an empty LUT for holding the result. 0 as channel count means 'undefined'
541    Result = cmsPipelineAlloc(ContextID, 0, 0);
542    if (Result == NULL) return NULL;
543
544    CurrentColorSpace = cmsGetColorSpace(hProfiles[0]);
545
546    for (i=0; i < nProfiles; i++) {
547
548        cmsBool  lIsDeviceLink, lIsInput;
549
550        hProfile      = hProfiles[i];
551        ClassSig      = cmsGetDeviceClass(hProfile);
552        lIsDeviceLink = (ClassSig == cmsSigLinkClass || ClassSig == cmsSigAbstractClass );
553
554        // First profile is used as input unless devicelink or abstract
555        if ((i == 0) && !lIsDeviceLink) {
556            lIsInput = TRUE;
557        }
558        else {
559          // Else use profile in the input direction if current space is not PCS
560        lIsInput      = (CurrentColorSpace != cmsSigXYZData) &&
561                        (CurrentColorSpace != cmsSigLabData);
562        }
563
564        Intent        = TheIntents[i];
565
566        if (lIsInput || lIsDeviceLink) {
567
568            ColorSpaceIn    = cmsGetColorSpace(hProfile);
569            ColorSpaceOut   = cmsGetPCS(hProfile);
570        }
571        else {
572
573            ColorSpaceIn    = cmsGetPCS(hProfile);
574            ColorSpaceOut   = cmsGetColorSpace(hProfile);
575        }
576
577        if (!ColorSpaceIsCompatible(ColorSpaceIn, CurrentColorSpace)) {
578
579            cmsSignalError(ContextID, cmsERROR_COLORSPACE_CHECK, "ColorSpace mismatch");
580            goto Error;
581        }
582
583        // If devicelink is found, then no custom intent is allowed and we can
584        // read the LUT to be applied. Settings don't apply here.
585        if (lIsDeviceLink || ((ClassSig == cmsSigNamedColorClass) && (nProfiles == 1))) {
586
587            // Get the involved LUT from the profile
588            Lut = _cmsReadDevicelinkLUT(hProfile, Intent);
589            if (Lut == NULL) goto Error;
590
591            // What about abstract profiles?
592             if (ClassSig == cmsSigAbstractClass && i > 0) {
593                if (!ComputeConversion(i, hProfiles, Intent, BPC[i], AdaptationStates[i], &m, &off)) goto Error;
594             }
595             else {
596                _cmsMAT3identity(&m);
597                _cmsVEC3init(&off, 0, 0, 0);
598             }
599
600
601            if (!AddConversion(Result, CurrentColorSpace, ColorSpaceIn, &m, &off)) goto Error;
602
603        }
604        else {
605
606            if (lIsInput) {
607                // Input direction means non-pcs connection, so proceed like devicelinks
608                Lut = _cmsReadInputLUT(hProfile, Intent);
609                if (Lut == NULL) goto Error;
610            }
611            else {
612
613                // Output direction means PCS connection. Intent may apply here
614                Lut = _cmsReadOutputLUT(hProfile, Intent);
615                if (Lut == NULL) goto Error;
616
617
618                if (!ComputeConversion(i, hProfiles, Intent, BPC[i], AdaptationStates[i], &m, &off)) goto Error;
619                if (!AddConversion(Result, CurrentColorSpace, ColorSpaceIn, &m, &off)) goto Error;
620
621            }
622        }
623
624        // Concatenate to the output LUT
625        if (!cmsPipelineCat(Result, Lut))
626            goto Error;
627
628        cmsPipelineFree(Lut);
629        Lut = NULL;
630
631        // Update current space
632        CurrentColorSpace = ColorSpaceOut;
633    }
634
635    return Result;
636
637Error:
638
639    if (Lut != NULL) cmsPipelineFree(Lut);
640    if (Result != NULL) cmsPipelineFree(Result);
641    return NULL;
642
643    cmsUNUSED_PARAMETER(dwFlags);
644}
645
646
647// Wrapper for DLL calling convention
648cmsPipeline*  CMSEXPORT _cmsDefaultICCintents(cmsContext     ContextID,
649                                              cmsUInt32Number nProfiles,
650                                              cmsUInt32Number TheIntents[],
651                                              cmsHPROFILE     hProfiles[],
652                                              cmsBool         BPC[],
653                                              cmsFloat64Number AdaptationStates[],
654                                              cmsUInt32Number dwFlags)
655{
656    return DefaultICCintents(ContextID, nProfiles, TheIntents, hProfiles, BPC, AdaptationStates, dwFlags);
657}
658
659// Black preserving intents ---------------------------------------------------------------------------------------------
660
661// Translate black-preserving intents to ICC ones
662static
663int TranslateNonICCIntents(int Intent)
664{
665    switch (Intent) {
666        case INTENT_PRESERVE_K_ONLY_PERCEPTUAL:
667        case INTENT_PRESERVE_K_PLANE_PERCEPTUAL:
668            return INTENT_PERCEPTUAL;
669
670        case INTENT_PRESERVE_K_ONLY_RELATIVE_COLORIMETRIC:
671        case INTENT_PRESERVE_K_PLANE_RELATIVE_COLORIMETRIC:
672            return INTENT_RELATIVE_COLORIMETRIC;
673
674        case INTENT_PRESERVE_K_ONLY_SATURATION:
675        case INTENT_PRESERVE_K_PLANE_SATURATION:
676            return INTENT_SATURATION;
677
678        default: return Intent;
679    }
680}
681
682// Sampler for Black-only preserving CMYK->CMYK transforms
683
684typedef struct {
685    cmsPipeline*    cmyk2cmyk;      // The original transform
686    cmsToneCurve*   KTone;          // Black-to-black tone curve
687
688} GrayOnlyParams;
689
690
691// Preserve black only if that is the only ink used
692static
693int BlackPreservingGrayOnlySampler(register const cmsUInt16Number In[], register cmsUInt16Number Out[], register void* Cargo)
694{
695    GrayOnlyParams* bp = (GrayOnlyParams*) Cargo;
696
697    // If going across black only, keep black only
698    if (In[0] == 0 && In[1] == 0 && In[2] == 0) {
699
700        // TAC does not apply because it is black ink!
701        Out[0] = Out[1] = Out[2] = 0;
702        Out[3] = cmsEvalToneCurve16(bp->KTone, In[3]);
703        return TRUE;
704    }
705
706    // Keep normal transform for other colors
707    bp ->cmyk2cmyk ->Eval16Fn(In, Out, bp ->cmyk2cmyk->Data);
708    return TRUE;
709}
710
711// This is the entry for black-preserving K-only intents, which are non-ICC
712static
713cmsPipeline*  BlackPreservingKOnlyIntents(cmsContext     ContextID,
714                                          cmsUInt32Number nProfiles,
715                                          cmsUInt32Number TheIntents[],
716                                          cmsHPROFILE     hProfiles[],
717                                          cmsBool         BPC[],
718                                          cmsFloat64Number AdaptationStates[],
719                                          cmsUInt32Number dwFlags)
720{
721    GrayOnlyParams  bp;
722    cmsPipeline*    Result;
723    cmsUInt32Number ICCIntents[256];
724    cmsStage*         CLUT;
725    cmsUInt32Number i, nGridPoints;
726
727
728    // Sanity check
729    if (nProfiles < 1 || nProfiles > 255) return NULL;
730
731    // Translate black-preserving intents to ICC ones
732    for (i=0; i < nProfiles; i++)
733        ICCIntents[i] = TranslateNonICCIntents(TheIntents[i]);
734
735    // Check for non-cmyk profiles
736    if (cmsGetColorSpace(hProfiles[0]) != cmsSigCmykData ||
737        cmsGetColorSpace(hProfiles[nProfiles-1]) != cmsSigCmykData)
738           return DefaultICCintents(ContextID, nProfiles, ICCIntents, hProfiles, BPC, AdaptationStates, dwFlags);
739
740    memset(&bp, 0, sizeof(bp));
741
742    // Allocate an empty LUT for holding the result
743    Result = cmsPipelineAlloc(ContextID, 4, 4);
744    if (Result == NULL) return NULL;
745
746    // Create a LUT holding normal ICC transform
747    bp.cmyk2cmyk = DefaultICCintents(ContextID,
748        nProfiles,
749        ICCIntents,
750        hProfiles,
751        BPC,
752        AdaptationStates,
753        dwFlags);
754
755    if (bp.cmyk2cmyk == NULL) goto Error;
756
757    // Now, compute the tone curve
758    bp.KTone = _cmsBuildKToneCurve(ContextID,
759        4096,
760        nProfiles,
761        ICCIntents,
762        hProfiles,
763        BPC,
764        AdaptationStates,
765        dwFlags);
766
767    if (bp.KTone == NULL) goto Error;
768
769
770    // How many gridpoints are we going to use?
771    nGridPoints = _cmsReasonableGridpointsByColorspace(cmsSigCmykData, dwFlags);
772
773    // Create the CLUT. 16 bits
774    CLUT = cmsStageAllocCLut16bit(ContextID, nGridPoints, 4, 4, NULL);
775    if (CLUT == NULL) goto Error;
776
777    // This is the one and only MPE in this LUT
778    if (!cmsPipelineInsertStage(Result, cmsAT_BEGIN, CLUT))
779        goto Error;
780
781    // Sample it. We cannot afford pre/post linearization this time.
782    if (!cmsStageSampleCLut16bit(CLUT, BlackPreservingGrayOnlySampler, (void*) &bp, 0))
783        goto Error;
784
785    // Get rid of xform and tone curve
786    cmsPipelineFree(bp.cmyk2cmyk);
787    cmsFreeToneCurve(bp.KTone);
788
789    return Result;
790
791Error:
792
793    if (bp.cmyk2cmyk != NULL) cmsPipelineFree(bp.cmyk2cmyk);
794    if (bp.KTone != NULL)  cmsFreeToneCurve(bp.KTone);
795    if (Result != NULL) cmsPipelineFree(Result);
796    return NULL;
797
798}
799
800// K Plane-preserving CMYK to CMYK ------------------------------------------------------------------------------------
801
802typedef struct {
803
804    cmsPipeline*     cmyk2cmyk;     // The original transform
805    cmsHTRANSFORM    hProofOutput;  // Output CMYK to Lab (last profile)
806    cmsHTRANSFORM    cmyk2Lab;      // The input chain
807    cmsToneCurve*    KTone;         // Black-to-black tone curve
808    cmsPipeline*     LabK2cmyk;     // The output profile
809    cmsFloat64Number MaxError;
810
811    cmsHTRANSFORM    hRoundTrip;
812    cmsFloat64Number MaxTAC;
813
814
815} PreserveKPlaneParams;
816
817
818// The CLUT will be stored at 16 bits, but calculations are performed at cmsFloat32Number precision
819static
820int BlackPreservingSampler(register const cmsUInt16Number In[], register cmsUInt16Number Out[], register void* Cargo)
821{
822    int i;
823    cmsFloat32Number Inf[4], Outf[4];
824    cmsFloat32Number LabK[4];
825    cmsFloat64Number SumCMY, SumCMYK, Error, Ratio;
826    cmsCIELab ColorimetricLab, BlackPreservingLab;
827    PreserveKPlaneParams* bp = (PreserveKPlaneParams*) Cargo;
828
829    // Convert from 16 bits to floating point
830    for (i=0; i < 4; i++)
831        Inf[i] = (cmsFloat32Number) (In[i] / 65535.0);
832
833    // Get the K across Tone curve
834    LabK[3] = cmsEvalToneCurveFloat(bp ->KTone, Inf[3]);
835
836    // If going across black only, keep black only
837    if (In[0] == 0 && In[1] == 0 && In[2] == 0) {
838
839        Out[0] = Out[1] = Out[2] = 0;
840        Out[3] = _cmsQuickSaturateWord(LabK[3] * 65535.0);
841        return TRUE;
842    }
843
844    // Try the original transform,
845    cmsPipelineEvalFloat( Inf, Outf, bp ->cmyk2cmyk);
846
847    // Store a copy of the floating point result into 16-bit
848    for (i=0; i < 4; i++)
849            Out[i] = _cmsQuickSaturateWord(Outf[i] * 65535.0);
850
851    // Maybe K is already ok (mostly on K=0)
852    if ( fabs(Outf[3] - LabK[3]) < (3.0 / 65535.0) ) {
853        return TRUE;
854    }
855
856    // K differ, mesure and keep Lab measurement for further usage
857    // this is done in relative colorimetric intent
858    cmsDoTransform(bp->hProofOutput, Out, &ColorimetricLab, 1);
859
860    // Is not black only and the transform doesn't keep black.
861    // Obtain the Lab of output CMYK. After that we have Lab + K
862    cmsDoTransform(bp ->cmyk2Lab, Outf, LabK, 1);
863
864    // Obtain the corresponding CMY using reverse interpolation
865    // (K is fixed in LabK[3])
866    if (!cmsPipelineEvalReverseFloat(LabK, Outf, Outf, bp ->LabK2cmyk)) {
867
868        // Cannot find a suitable value, so use colorimetric xform
869        // which is already stored in Out[]
870        return TRUE;
871    }
872
873    // Make sure to pass thru K (which now is fixed)
874    Outf[3] = LabK[3];
875
876    // Apply TAC if needed
877    SumCMY   = Outf[0]  + Outf[1] + Outf[2];
878    SumCMYK  = SumCMY + Outf[3];
879
880    if (SumCMYK > bp ->MaxTAC) {
881
882        Ratio = 1 - ((SumCMYK - bp->MaxTAC) / SumCMY);
883        if (Ratio < 0)
884            Ratio = 0;
885    }
886    else
887       Ratio = 1.0;
888
889    Out[0] = _cmsQuickSaturateWord(Outf[0] * Ratio * 65535.0);     // C
890    Out[1] = _cmsQuickSaturateWord(Outf[1] * Ratio * 65535.0);     // M
891    Out[2] = _cmsQuickSaturateWord(Outf[2] * Ratio * 65535.0);     // Y
892    Out[3] = _cmsQuickSaturateWord(Outf[3] * 65535.0);
893
894    // Estimate the error (this goes 16 bits to Lab DBL)
895    cmsDoTransform(bp->hProofOutput, Out, &BlackPreservingLab, 1);
896    Error = cmsDeltaE(&ColorimetricLab, &BlackPreservingLab);
897    if (Error > bp -> MaxError)
898        bp->MaxError = Error;
899
900    return TRUE;
901}
902
903// This is the entry for black-plane preserving, which are non-ICC
904static
905cmsPipeline* BlackPreservingKPlaneIntents(cmsContext     ContextID,
906                                          cmsUInt32Number nProfiles,
907                                          cmsUInt32Number TheIntents[],
908                                          cmsHPROFILE     hProfiles[],
909                                          cmsBool         BPC[],
910                                          cmsFloat64Number AdaptationStates[],
911                                          cmsUInt32Number dwFlags)
912{
913    PreserveKPlaneParams bp;
914    cmsPipeline*    Result = NULL;
915    cmsUInt32Number ICCIntents[256];
916    cmsStage*         CLUT;
917    cmsUInt32Number i, nGridPoints;
918    cmsHPROFILE hLab;
919
920    // Sanity check
921    if (nProfiles < 1 || nProfiles > 255) return NULL;
922
923    // Translate black-preserving intents to ICC ones
924    for (i=0; i < nProfiles; i++)
925        ICCIntents[i] = TranslateNonICCIntents(TheIntents[i]);
926
927    // Check for non-cmyk profiles
928    if (cmsGetColorSpace(hProfiles[0]) != cmsSigCmykData ||
929        !(cmsGetColorSpace(hProfiles[nProfiles-1]) == cmsSigCmykData ||
930        cmsGetDeviceClass(hProfiles[nProfiles-1]) == cmsSigOutputClass))
931           return  DefaultICCintents(ContextID, nProfiles, ICCIntents, hProfiles, BPC, AdaptationStates, dwFlags);
932
933    // Allocate an empty LUT for holding the result
934    Result = cmsPipelineAlloc(ContextID, 4, 4);
935    if (Result == NULL) return NULL;
936
937
938    memset(&bp, 0, sizeof(bp));
939
940    // We need the input LUT of the last profile, assuming this one is responsible of
941    // black generation. This LUT will be seached in inverse order.
942    bp.LabK2cmyk = _cmsReadInputLUT(hProfiles[nProfiles-1], INTENT_RELATIVE_COLORIMETRIC);
943    if (bp.LabK2cmyk == NULL) goto Cleanup;
944
945    // Get total area coverage (in 0..1 domain)
946    bp.MaxTAC = cmsDetectTAC(hProfiles[nProfiles-1]) / 100.0;
947    if (bp.MaxTAC <= 0) goto Cleanup;
948
949
950    // Create a LUT holding normal ICC transform
951    bp.cmyk2cmyk = DefaultICCintents(ContextID,
952                                         nProfiles,
953                                         ICCIntents,
954                                         hProfiles,
955                                         BPC,
956                                         AdaptationStates,
957                                         dwFlags);
958    if (bp.cmyk2cmyk == NULL) goto Cleanup;
959
960    // Now the tone curve
961    bp.KTone = _cmsBuildKToneCurve(ContextID, 4096, nProfiles,
962                                   ICCIntents,
963                                   hProfiles,
964                                   BPC,
965                                   AdaptationStates,
966                                   dwFlags);
967    if (bp.KTone == NULL) goto Cleanup;
968
969    // To measure the output, Last profile to Lab
970    hLab = cmsCreateLab4ProfileTHR(ContextID, NULL);
971    bp.hProofOutput = cmsCreateTransformTHR(ContextID, hProfiles[nProfiles-1],
972                                         CHANNELS_SH(4)|BYTES_SH(2), hLab, TYPE_Lab_DBL,
973                                         INTENT_RELATIVE_COLORIMETRIC,
974                                         cmsFLAGS_NOCACHE|cmsFLAGS_NOOPTIMIZE);
975    if ( bp.hProofOutput == NULL) goto Cleanup;
976
977    // Same as anterior, but lab in the 0..1 range
978    bp.cmyk2Lab = cmsCreateTransformTHR(ContextID, hProfiles[nProfiles-1],
979                                         FLOAT_SH(1)|CHANNELS_SH(4)|BYTES_SH(4), hLab,
980                                         FLOAT_SH(1)|CHANNELS_SH(3)|BYTES_SH(4),
981                                         INTENT_RELATIVE_COLORIMETRIC,
982                                         cmsFLAGS_NOCACHE|cmsFLAGS_NOOPTIMIZE);
983    if (bp.cmyk2Lab == NULL) goto Cleanup;
984    cmsCloseProfile(hLab);
985
986    // Error estimation (for debug only)
987    bp.MaxError = 0;
988
989    // How many gridpoints are we going to use?
990    nGridPoints = _cmsReasonableGridpointsByColorspace(cmsSigCmykData, dwFlags);
991
992
993    CLUT = cmsStageAllocCLut16bit(ContextID, nGridPoints, 4, 4, NULL);
994    if (CLUT == NULL) goto Cleanup;
995
996    if (!cmsPipelineInsertStage(Result, cmsAT_BEGIN, CLUT))
997        goto Cleanup;
998
999    cmsStageSampleCLut16bit(CLUT, BlackPreservingSampler, (void*) &bp, 0);
1000
1001Cleanup:
1002
1003    if (bp.cmyk2cmyk) cmsPipelineFree(bp.cmyk2cmyk);
1004    if (bp.cmyk2Lab) cmsDeleteTransform(bp.cmyk2Lab);
1005    if (bp.hProofOutput) cmsDeleteTransform(bp.hProofOutput);
1006
1007    if (bp.KTone) cmsFreeToneCurve(bp.KTone);
1008    if (bp.LabK2cmyk) cmsPipelineFree(bp.LabK2cmyk);
1009
1010    return Result;
1011}
1012
1013// Link routines ------------------------------------------------------------------------------------------------------
1014
1015// Chain several profiles into a single LUT. It just checks the parameters and then calls the handler
1016// for the first intent in chain. The handler may be user-defined. Is up to the handler to deal with the
1017// rest of intents in chain. A maximum of 255 profiles at time are supported, which is pretty reasonable.
1018cmsPipeline* _cmsLinkProfiles(cmsContext     ContextID,
1019                              cmsUInt32Number nProfiles,
1020                              cmsUInt32Number TheIntents[],
1021                              cmsHPROFILE     hProfiles[],
1022                              cmsBool         BPC[],
1023                              cmsFloat64Number AdaptationStates[],
1024                              cmsUInt32Number dwFlags)
1025{
1026    cmsUInt32Number i;
1027    cmsIntentsList* Intent;
1028
1029    // Make sure a reasonable number of profiles is provided
1030    if (nProfiles <= 0 || nProfiles > 255) {
1031         cmsSignalError(ContextID, cmsERROR_RANGE, "Couldn't link '%d' profiles", nProfiles);
1032        return NULL;
1033    }
1034
1035    for (i=0; i < nProfiles; i++) {
1036
1037        // Check if black point is really needed or allowed. Note that
1038        // following Adobe's document:
1039        // BPC does not apply to devicelink profiles, nor to abs colorimetric,
1040        // and applies always on V4 perceptual and saturation.
1041
1042        if (TheIntents[i] == INTENT_ABSOLUTE_COLORIMETRIC)
1043            BPC[i] = FALSE;
1044
1045        if (TheIntents[i] == INTENT_PERCEPTUAL || TheIntents[i] == INTENT_SATURATION) {
1046
1047            // Force BPC for V4 profiles in perceptual and saturation
1048            if (cmsGetProfileVersion(hProfiles[i]) >= 4.0)
1049                BPC[i] = TRUE;
1050        }
1051    }
1052
1053    // Search for a handler. The first intent in the chain defines the handler. That would
1054    // prevent using multiple custom intents in a multiintent chain, but the behaviour of
1055    // this case would present some issues if the custom intent tries to do things like
1056    // preserve primaries. This solution is not perfect, but works well on most cases.
1057
1058    Intent = SearchIntent(ContextID, TheIntents[0]);
1059    if (Intent == NULL) {
1060        cmsSignalError(ContextID, cmsERROR_UNKNOWN_EXTENSION, "Unsupported intent '%d'", TheIntents[0]);
1061        return NULL;
1062    }
1063
1064    // Call the handler
1065    return Intent ->Link(ContextID, nProfiles, TheIntents, hProfiles, BPC, AdaptationStates, dwFlags);
1066}
1067
1068// -------------------------------------------------------------------------------------------------
1069
1070// Get information about available intents. nMax is the maximum space for the supplied "Codes"
1071// and "Descriptions" the function returns the total number of intents, which may be greater
1072// than nMax, although the matrices are not populated beyond this level.
1073cmsUInt32Number CMSEXPORT cmsGetSupportedIntentsTHR(cmsContext ContextID, cmsUInt32Number nMax, cmsUInt32Number* Codes, char** Descriptions)
1074{
1075    _cmsIntentsPluginChunkType* ctx = ( _cmsIntentsPluginChunkType*) _cmsContextGetClientChunk(ContextID, IntentPlugin);
1076    cmsIntentsList* pt;
1077    cmsUInt32Number nIntents;
1078
1079
1080    for (nIntents=0, pt = ctx->Intents; pt != NULL; pt = pt -> Next)
1081    {
1082        if (nIntents < nMax) {
1083            if (Codes != NULL)
1084                Codes[nIntents] = pt ->Intent;
1085
1086            if (Descriptions != NULL)
1087                Descriptions[nIntents] = pt ->Description;
1088        }
1089
1090        nIntents++;
1091    }
1092
1093    for (nIntents=0, pt = DefaultIntents; pt != NULL; pt = pt -> Next)
1094    {
1095        if (nIntents < nMax) {
1096            if (Codes != NULL)
1097                Codes[nIntents] = pt ->Intent;
1098
1099            if (Descriptions != NULL)
1100                Descriptions[nIntents] = pt ->Description;
1101        }
1102
1103        nIntents++;
1104    }
1105    return nIntents;
1106}
1107
1108cmsUInt32Number CMSEXPORT cmsGetSupportedIntents(cmsUInt32Number nMax, cmsUInt32Number* Codes, char** Descriptions)
1109{
1110    return cmsGetSupportedIntentsTHR(NULL, nMax, Codes, Descriptions);
1111}
1112
1113// The plug-in registration. User can add new intents or override default routines
1114cmsBool  _cmsRegisterRenderingIntentPlugin(cmsContext id, cmsPluginBase* Data)
1115{
1116    _cmsIntentsPluginChunkType* ctx = ( _cmsIntentsPluginChunkType*) _cmsContextGetClientChunk(id, IntentPlugin);
1117    cmsPluginRenderingIntent* Plugin = (cmsPluginRenderingIntent*) Data;
1118    cmsIntentsList* fl;
1119
1120    // Do we have to reset the custom intents?
1121    if (Data == NULL) {
1122
1123        ctx->Intents = NULL;
1124        return TRUE;
1125    }
1126
1127    fl = (cmsIntentsList*) _cmsPluginMalloc(id, sizeof(cmsIntentsList));
1128    if (fl == NULL) return FALSE;
1129
1130
1131    fl ->Intent  = Plugin ->Intent;
1132    strncpy(fl ->Description, Plugin ->Description, sizeof(fl ->Description)-1);
1133    fl ->Description[sizeof(fl ->Description)-1] = 0;
1134
1135    fl ->Link    = Plugin ->Link;
1136
1137    fl ->Next = ctx ->Intents;
1138    ctx ->Intents = fl;
1139
1140    return TRUE;
1141}
1142
1143