Home | History | Annotate | Download | only in src
      1 //---------------------------------------------------------------------------------
      2 //
      3 //  Little Color Management System
      4 //  Copyright (c) 1998-2016 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)
     33 cmsPipeline* _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
     45 static
     46 cmsPipeline* 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)
     58 static
     59 cmsPipeline*  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)
     71 static
     72 cmsPipeline*  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.
     84 typedef 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
     95 static 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 beginning of the list
    111 _cmsIntentsPluginChunkType _cmsIntentsPluginChunk = { NULL };
    112 
    113 // Duplicates the zone of memory used by the plug-in in the new context
    114 static
    115 void 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 
    147 void  _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
    163 static
    164 cmsIntentsList* 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.
    181 static
    182 void 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
    217 static
    218 cmsFloat64Number 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
    249 static
    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
    262 static
    263 cmsBool  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     // TODO: Follow Marc Mahy's recommendation to check if CHAD is same by using M1*M2 == M2*M1. If so, do nothing.
    273     // TODO: Add support for ArgyllArts tag
    274 
    275     // Adaptation state
    276     if (AdaptationState == 1.0) {
    277 
    278         // Observer is fully adapted. Keep chromatic adaptation.
    279         // That is the standard V4 behaviour
    280         _cmsVEC3init(&m->v[0], WhitePointIn->X / WhitePointOut->X, 0, 0);
    281         _cmsVEC3init(&m->v[1], 0, WhitePointIn->Y / WhitePointOut->Y, 0);
    282         _cmsVEC3init(&m->v[2], 0, 0, WhitePointIn->Z / WhitePointOut->Z);
    283 
    284     }
    285     else  {
    286 
    287         // Incomplete adaptation. This is an advanced feature.
    288         _cmsVEC3init(&Scale.v[0], WhitePointIn->X / WhitePointOut->X, 0, 0);
    289         _cmsVEC3init(&Scale.v[1], 0,  WhitePointIn->Y / WhitePointOut->Y, 0);
    290         _cmsVEC3init(&Scale.v[2], 0, 0,  WhitePointIn->Z / WhitePointOut->Z);
    291 
    292 
    293         if (AdaptationState == 0.0) {
    294 
    295             m1 = *ChromaticAdaptationMatrixOut;
    296             _cmsMAT3per(&m2, &m1, &Scale);
    297             // m2 holds CHAD from output white to D50 times abs. col. scaling
    298 
    299             // Observer is not adapted, undo the chromatic adaptation
    300             _cmsMAT3per(m, &m2, ChromaticAdaptationMatrixOut);
    301 
    302             m3 = *ChromaticAdaptationMatrixIn;
    303             if (!_cmsMAT3inverse(&m3, &m4)) return FALSE;
    304             _cmsMAT3per(m, &m2, &m4);
    305 
    306         } else {
    307 
    308             cmsMAT3 MixedCHAD;
    309             cmsFloat64Number TempSrc, TempDest, Temp;
    310 
    311             m1 = *ChromaticAdaptationMatrixIn;
    312             if (!_cmsMAT3inverse(&m1, &m2)) return FALSE;
    313             _cmsMAT3per(&m3, &m2, &Scale);
    314             // m3 holds CHAD from input white to D50 times abs. col. scaling
    315 
    316             TempSrc  = CHAD2Temp(ChromaticAdaptationMatrixIn);
    317             TempDest = CHAD2Temp(ChromaticAdaptationMatrixOut);
    318 
    319             if (TempSrc < 0.0 || TempDest < 0.0) return FALSE; // Something went wrong
    320 
    321             if (_cmsMAT3isIdentity(&Scale) && fabs(TempSrc - TempDest) < 0.01) {
    322 
    323                 _cmsMAT3identity(m);
    324                 return TRUE;
    325             }
    326 
    327             Temp = (1.0 - AdaptationState) * TempDest + AdaptationState * TempSrc;
    328 
    329             // Get a CHAD from whatever output temperature to D50. This replaces output CHAD
    330             Temp2CHAD(&MixedCHAD, Temp);
    331 
    332             _cmsMAT3per(m, &m3, &MixedCHAD);
    333         }
    334 
    335     }
    336     return TRUE;
    337 
    338 }
    339 
    340 // Just to see if m matrix should be applied
    341 static
    342 cmsBool IsEmptyLayer(cmsMAT3* m, cmsVEC3* off)
    343 {
    344     cmsFloat64Number diff = 0;
    345     cmsMAT3 Ident;
    346     int i;
    347 
    348     if (m == NULL && off == NULL) return TRUE;  // NULL is allowed as an empty layer
    349     if (m == NULL && off != NULL) return FALSE; // This is an internal error
    350 
    351     _cmsMAT3identity(&Ident);
    352 
    353     for (i=0; i < 3*3; i++)
    354         diff += fabs(((cmsFloat64Number*)m)[i] - ((cmsFloat64Number*)&Ident)[i]);
    355 
    356     for (i=0; i < 3; i++)
    357         diff += fabs(((cmsFloat64Number*)off)[i]);
    358 
    359 
    360     return (diff < 0.002);
    361 }
    362 
    363 
    364 // Compute the conversion layer
    365 static
    366 cmsBool ComputeConversion(int i, cmsHPROFILE hProfiles[],
    367                                  cmsUInt32Number Intent,
    368                                  cmsBool BPC,
    369                                  cmsFloat64Number AdaptationState,
    370                                  cmsMAT3* m, cmsVEC3* off)
    371 {
    372 
    373     int k;
    374 
    375     // m  and off are set to identity and this is detected latter on
    376     _cmsMAT3identity(m);
    377     _cmsVEC3init(off, 0, 0, 0);
    378 
    379     // If intent is abs. colorimetric,
    380     if (Intent == INTENT_ABSOLUTE_COLORIMETRIC) {
    381 
    382         cmsCIEXYZ WhitePointIn, WhitePointOut;
    383         cmsMAT3 ChromaticAdaptationMatrixIn, ChromaticAdaptationMatrixOut;
    384 
    385         _cmsReadMediaWhitePoint(&WhitePointIn,  hProfiles[i-1]);
    386         _cmsReadCHAD(&ChromaticAdaptationMatrixIn, hProfiles[i-1]);
    387 
    388         _cmsReadMediaWhitePoint(&WhitePointOut,  hProfiles[i]);
    389         _cmsReadCHAD(&ChromaticAdaptationMatrixOut, hProfiles[i]);
    390 
    391         if (!ComputeAbsoluteIntent(AdaptationState,
    392                                   &WhitePointIn,  &ChromaticAdaptationMatrixIn,
    393                                   &WhitePointOut, &ChromaticAdaptationMatrixOut, m)) return FALSE;
    394 
    395     }
    396     else {
    397         // Rest of intents may apply BPC.
    398 
    399         if (BPC) {
    400 
    401             cmsCIEXYZ BlackPointIn, BlackPointOut;
    402 
    403             cmsDetectBlackPoint(&BlackPointIn,  hProfiles[i-1], Intent, 0);
    404             cmsDetectDestinationBlackPoint(&BlackPointOut, hProfiles[i], Intent, 0);
    405 
    406             // If black points are equal, then do nothing
    407             if (BlackPointIn.X != BlackPointOut.X ||
    408                 BlackPointIn.Y != BlackPointOut.Y ||
    409                 BlackPointIn.Z != BlackPointOut.Z)
    410                     ComputeBlackPointCompensation(&BlackPointIn, &BlackPointOut, m, off);
    411         }
    412     }
    413 
    414     // Offset should be adjusted because the encoding. We encode XYZ normalized to 0..1.0,
    415     // to do that, we divide by MAX_ENCODEABLE_XZY. The conversion stage goes XYZ -> XYZ so
    416     // we have first to convert from encoded to XYZ and then convert back to encoded.
    417     // y = Mx + Off
    418     // x = x'c
    419     // y = M x'c + Off
    420     // y = y'c; y' = y / c
    421     // y' = (Mx'c + Off) /c = Mx' + (Off / c)
    422 
    423     for (k=0; k < 3; k++) {
    424         off ->n[k] /= MAX_ENCODEABLE_XYZ;
    425     }
    426 
    427     return TRUE;
    428 }
    429 
    430 
    431 // Add a conversion stage if needed. If a matrix/offset m is given, it applies to XYZ space
    432 static
    433 cmsBool AddConversion(cmsPipeline* Result, cmsColorSpaceSignature InPCS, cmsColorSpaceSignature OutPCS, cmsMAT3* m, cmsVEC3* off)
    434 {
    435     cmsFloat64Number* m_as_dbl = (cmsFloat64Number*) m;
    436     cmsFloat64Number* off_as_dbl = (cmsFloat64Number*) off;
    437 
    438     // Handle PCS mismatches. A specialized stage is added to the LUT in such case
    439     switch (InPCS) {
    440 
    441     case cmsSigXYZData: // Input profile operates in XYZ
    442 
    443         switch (OutPCS) {
    444 
    445         case cmsSigXYZData:  // XYZ -> XYZ
    446             if (!IsEmptyLayer(m, off) &&
    447                 !cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl)))
    448                 return FALSE;
    449             break;
    450 
    451         case cmsSigLabData:  // XYZ -> Lab
    452             if (!IsEmptyLayer(m, off) &&
    453                 !cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl)))
    454                 return FALSE;
    455             if (!cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocXYZ2Lab(Result ->ContextID)))
    456                 return FALSE;
    457             break;
    458 
    459         default:
    460             return FALSE;   // Colorspace mismatch
    461         }
    462         break;
    463 
    464     case cmsSigLabData: // Input profile operates in Lab
    465 
    466         switch (OutPCS) {
    467 
    468         case cmsSigXYZData:  // Lab -> XYZ
    469 
    470             if (!cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocLab2XYZ(Result ->ContextID)))
    471                 return FALSE;
    472             if (!IsEmptyLayer(m, off) &&
    473                 !cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl)))
    474                 return FALSE;
    475             break;
    476 
    477         case cmsSigLabData:  // Lab -> Lab
    478 
    479             if (!IsEmptyLayer(m, off)) {
    480                 if (!cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocLab2XYZ(Result ->ContextID)) ||
    481                     !cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl)) ||
    482                     !cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocXYZ2Lab(Result ->ContextID)))
    483                     return FALSE;
    484             }
    485             break;
    486 
    487         default:
    488             return FALSE;  // Mismatch
    489         }
    490         break;
    491 
    492         // On colorspaces other than PCS, check for same space
    493     default:
    494         if (InPCS != OutPCS) return FALSE;
    495         break;
    496     }
    497 
    498     return TRUE;
    499 }
    500 
    501 
    502 // Is a given space compatible with another?
    503 static
    504 cmsBool ColorSpaceIsCompatible(cmsColorSpaceSignature a, cmsColorSpaceSignature b)
    505 {
    506     // If they are same, they are compatible.
    507     if (a == b) return TRUE;
    508 
    509     // Check for MCH4 substitution of CMYK
    510     if ((a == cmsSig4colorData) && (b == cmsSigCmykData)) return TRUE;
    511     if ((a == cmsSigCmykData) && (b == cmsSig4colorData)) return TRUE;
    512 
    513     // Check for XYZ/Lab. Those spaces are interchangeable as they can be computed one from other.
    514     if ((a == cmsSigXYZData) && (b == cmsSigLabData)) return TRUE;
    515     if ((a == cmsSigLabData) && (b == cmsSigXYZData)) return TRUE;
    516 
    517     return FALSE;
    518 }
    519 
    520 
    521 // Default handler for ICC-style intents
    522 static
    523 cmsPipeline* DefaultICCintents(cmsContext       ContextID,
    524                                cmsUInt32Number  nProfiles,
    525                                cmsUInt32Number  TheIntents[],
    526                                cmsHPROFILE      hProfiles[],
    527                                cmsBool          BPC[],
    528                                cmsFloat64Number AdaptationStates[],
    529                                cmsUInt32Number  dwFlags)
    530 {
    531     cmsPipeline* Lut = NULL;
    532     cmsPipeline* Result;
    533     cmsHPROFILE hProfile;
    534     cmsMAT3 m;
    535     cmsVEC3 off;
    536     cmsColorSpaceSignature ColorSpaceIn, ColorSpaceOut = cmsSigLabData, CurrentColorSpace;
    537     cmsProfileClassSignature ClassSig;
    538     cmsUInt32Number  i, Intent;
    539 
    540     // For safety
    541     if (nProfiles == 0) return NULL;
    542 
    543     // Allocate an empty LUT for holding the result. 0 as channel count means 'undefined'
    544     Result = cmsPipelineAlloc(ContextID, 0, 0);
    545     if (Result == NULL) return NULL;
    546 
    547     CurrentColorSpace = cmsGetColorSpace(hProfiles[0]);
    548 
    549     for (i=0; i < nProfiles; i++) {
    550 
    551         cmsBool  lIsDeviceLink, lIsInput;
    552 
    553         hProfile      = hProfiles[i];
    554         ClassSig      = cmsGetDeviceClass(hProfile);
    555         lIsDeviceLink = (ClassSig == cmsSigLinkClass || ClassSig == cmsSigAbstractClass );
    556 
    557         // First profile is used as input unless devicelink or abstract
    558         if ((i == 0) && !lIsDeviceLink) {
    559             lIsInput = TRUE;
    560         }
    561         else {
    562           // Else use profile in the input direction if current space is not PCS
    563         lIsInput      = (CurrentColorSpace != cmsSigXYZData) &&
    564                         (CurrentColorSpace != cmsSigLabData);
    565         }
    566 
    567         Intent        = TheIntents[i];
    568 
    569         if (lIsInput || lIsDeviceLink) {
    570 
    571             ColorSpaceIn    = cmsGetColorSpace(hProfile);
    572             ColorSpaceOut   = cmsGetPCS(hProfile);
    573         }
    574         else {
    575 
    576             ColorSpaceIn    = cmsGetPCS(hProfile);
    577             ColorSpaceOut   = cmsGetColorSpace(hProfile);
    578         }
    579 
    580         if (!ColorSpaceIsCompatible(ColorSpaceIn, CurrentColorSpace)) {
    581 
    582             cmsSignalError(ContextID, cmsERROR_COLORSPACE_CHECK, "ColorSpace mismatch");
    583             goto Error;
    584         }
    585 
    586         // If devicelink is found, then no custom intent is allowed and we can
    587         // read the LUT to be applied. Settings don't apply here.
    588         if (lIsDeviceLink || ((ClassSig == cmsSigNamedColorClass) && (nProfiles == 1))) {
    589 
    590             // Get the involved LUT from the profile
    591             Lut = _cmsReadDevicelinkLUT(hProfile, Intent);
    592             if (Lut == NULL) goto Error;
    593 
    594             // What about abstract profiles?
    595              if (ClassSig == cmsSigAbstractClass && i > 0) {
    596                 if (!ComputeConversion(i, hProfiles, Intent, BPC[i], AdaptationStates[i], &m, &off)) goto Error;
    597              }
    598              else {
    599                 _cmsMAT3identity(&m);
    600                 _cmsVEC3init(&off, 0, 0, 0);
    601              }
    602 
    603 
    604             if (!AddConversion(Result, CurrentColorSpace, ColorSpaceIn, &m, &off)) goto Error;
    605 
    606         }
    607         else {
    608 
    609             if (lIsInput) {
    610                 // Input direction means non-pcs connection, so proceed like devicelinks
    611                 Lut = _cmsReadInputLUT(hProfile, Intent);
    612                 if (Lut == NULL) goto Error;
    613             }
    614             else {
    615 
    616                 // Output direction means PCS connection. Intent may apply here
    617                 Lut = _cmsReadOutputLUT(hProfile, Intent);
    618                 if (Lut == NULL) goto Error;
    619 
    620 
    621                 if (!ComputeConversion(i, hProfiles, Intent, BPC[i], AdaptationStates[i], &m, &off)) goto Error;
    622                 if (!AddConversion(Result, CurrentColorSpace, ColorSpaceIn, &m, &off)) goto Error;
    623 
    624             }
    625         }
    626 
    627         // Concatenate to the output LUT
    628         if (!cmsPipelineCat(Result, Lut))
    629             goto Error;
    630 
    631         cmsPipelineFree(Lut);
    632         Lut = NULL;
    633 
    634         // Update current space
    635         CurrentColorSpace = ColorSpaceOut;
    636     }
    637 
    638     // Check for non-negatives clip
    639     if (dwFlags & cmsFLAGS_NONEGATIVES) {
    640 
    641            if (ColorSpaceOut == cmsSigGrayData ||
    642                   ColorSpaceOut == cmsSigRgbData ||
    643                   ColorSpaceOut == cmsSigCmykData) {
    644 
    645                   cmsStage* clip = _cmsStageClipNegatives(Result->ContextID, cmsChannelsOf(ColorSpaceOut));
    646                   if (clip == NULL) goto Error;
    647 
    648                   if (!cmsPipelineInsertStage(Result, cmsAT_END, clip))
    649                          goto Error;
    650            }
    651 
    652     }
    653 
    654     return Result;
    655 
    656 Error:
    657 
    658     if (Lut != NULL) cmsPipelineFree(Lut);
    659     if (Result != NULL) cmsPipelineFree(Result);
    660     return NULL;
    661 
    662     cmsUNUSED_PARAMETER(dwFlags);
    663 }
    664 
    665 
    666 // Wrapper for DLL calling convention
    667 cmsPipeline*  CMSEXPORT _cmsDefaultICCintents(cmsContext     ContextID,
    668                                               cmsUInt32Number nProfiles,
    669                                               cmsUInt32Number TheIntents[],
    670                                               cmsHPROFILE     hProfiles[],
    671                                               cmsBool         BPC[],
    672                                               cmsFloat64Number AdaptationStates[],
    673                                               cmsUInt32Number dwFlags)
    674 {
    675     return DefaultICCintents(ContextID, nProfiles, TheIntents, hProfiles, BPC, AdaptationStates, dwFlags);
    676 }
    677 
    678 // Black preserving intents ---------------------------------------------------------------------------------------------
    679 
    680 // Translate black-preserving intents to ICC ones
    681 static
    682 int TranslateNonICCIntents(int Intent)
    683 {
    684     switch (Intent) {
    685         case INTENT_PRESERVE_K_ONLY_PERCEPTUAL:
    686         case INTENT_PRESERVE_K_PLANE_PERCEPTUAL:
    687             return INTENT_PERCEPTUAL;
    688 
    689         case INTENT_PRESERVE_K_ONLY_RELATIVE_COLORIMETRIC:
    690         case INTENT_PRESERVE_K_PLANE_RELATIVE_COLORIMETRIC:
    691             return INTENT_RELATIVE_COLORIMETRIC;
    692 
    693         case INTENT_PRESERVE_K_ONLY_SATURATION:
    694         case INTENT_PRESERVE_K_PLANE_SATURATION:
    695             return INTENT_SATURATION;
    696 
    697         default: return Intent;
    698     }
    699 }
    700 
    701 // Sampler for Black-only preserving CMYK->CMYK transforms
    702 
    703 typedef struct {
    704     cmsPipeline*    cmyk2cmyk;      // The original transform
    705     cmsToneCurve*   KTone;          // Black-to-black tone curve
    706 
    707 } GrayOnlyParams;
    708 
    709 
    710 // Preserve black only if that is the only ink used
    711 static
    712 int BlackPreservingGrayOnlySampler(register const cmsUInt16Number In[], register cmsUInt16Number Out[], register void* Cargo)
    713 {
    714     GrayOnlyParams* bp = (GrayOnlyParams*) Cargo;
    715 
    716     // If going across black only, keep black only
    717     if (In[0] == 0 && In[1] == 0 && In[2] == 0) {
    718 
    719         // TAC does not apply because it is black ink!
    720         Out[0] = Out[1] = Out[2] = 0;
    721         Out[3] = cmsEvalToneCurve16(bp->KTone, In[3]);
    722         return TRUE;
    723     }
    724 
    725     // Keep normal transform for other colors
    726     bp ->cmyk2cmyk ->Eval16Fn(In, Out, bp ->cmyk2cmyk->Data);
    727     return TRUE;
    728 }
    729 
    730 // This is the entry for black-preserving K-only intents, which are non-ICC
    731 static
    732 cmsPipeline*  BlackPreservingKOnlyIntents(cmsContext     ContextID,
    733                                           cmsUInt32Number nProfiles,
    734                                           cmsUInt32Number TheIntents[],
    735                                           cmsHPROFILE     hProfiles[],
    736                                           cmsBool         BPC[],
    737                                           cmsFloat64Number AdaptationStates[],
    738                                           cmsUInt32Number dwFlags)
    739 {
    740     GrayOnlyParams  bp;
    741     cmsPipeline*    Result;
    742     cmsUInt32Number ICCIntents[256];
    743     cmsStage*         CLUT;
    744     cmsUInt32Number i, nGridPoints;
    745 
    746 
    747     // Sanity check
    748     if (nProfiles < 1 || nProfiles > 255) return NULL;
    749 
    750     // Translate black-preserving intents to ICC ones
    751     for (i=0; i < nProfiles; i++)
    752         ICCIntents[i] = TranslateNonICCIntents(TheIntents[i]);
    753 
    754     // Check for non-cmyk profiles
    755     if (cmsGetColorSpace(hProfiles[0]) != cmsSigCmykData ||
    756         cmsGetColorSpace(hProfiles[nProfiles-1]) != cmsSigCmykData)
    757            return DefaultICCintents(ContextID, nProfiles, ICCIntents, hProfiles, BPC, AdaptationStates, dwFlags);
    758 
    759     memset(&bp, 0, sizeof(bp));
    760 
    761     // Allocate an empty LUT for holding the result
    762     Result = cmsPipelineAlloc(ContextID, 4, 4);
    763     if (Result == NULL) return NULL;
    764 
    765     // Create a LUT holding normal ICC transform
    766     bp.cmyk2cmyk = DefaultICCintents(ContextID,
    767         nProfiles,
    768         ICCIntents,
    769         hProfiles,
    770         BPC,
    771         AdaptationStates,
    772         dwFlags);
    773 
    774     if (bp.cmyk2cmyk == NULL) goto Error;
    775 
    776     // Now, compute the tone curve
    777     bp.KTone = _cmsBuildKToneCurve(ContextID,
    778         4096,
    779         nProfiles,
    780         ICCIntents,
    781         hProfiles,
    782         BPC,
    783         AdaptationStates,
    784         dwFlags);
    785 
    786     if (bp.KTone == NULL) goto Error;
    787 
    788 
    789     // How many gridpoints are we going to use?
    790     nGridPoints = _cmsReasonableGridpointsByColorspace(cmsSigCmykData, dwFlags);
    791 
    792     // Create the CLUT. 16 bits
    793     CLUT = cmsStageAllocCLut16bit(ContextID, nGridPoints, 4, 4, NULL);
    794     if (CLUT == NULL) goto Error;
    795 
    796     // This is the one and only MPE in this LUT
    797     if (!cmsPipelineInsertStage(Result, cmsAT_BEGIN, CLUT))
    798         goto Error;
    799 
    800     // Sample it. We cannot afford pre/post linearization this time.
    801     if (!cmsStageSampleCLut16bit(CLUT, BlackPreservingGrayOnlySampler, (void*) &bp, 0))
    802         goto Error;
    803 
    804     // Get rid of xform and tone curve
    805     cmsPipelineFree(bp.cmyk2cmyk);
    806     cmsFreeToneCurve(bp.KTone);
    807 
    808     return Result;
    809 
    810 Error:
    811 
    812     if (bp.cmyk2cmyk != NULL) cmsPipelineFree(bp.cmyk2cmyk);
    813     if (bp.KTone != NULL)  cmsFreeToneCurve(bp.KTone);
    814     if (Result != NULL) cmsPipelineFree(Result);
    815     return NULL;
    816 
    817 }
    818 
    819 // K Plane-preserving CMYK to CMYK ------------------------------------------------------------------------------------
    820 
    821 typedef struct {
    822 
    823     cmsPipeline*     cmyk2cmyk;     // The original transform
    824     cmsHTRANSFORM    hProofOutput;  // Output CMYK to Lab (last profile)
    825     cmsHTRANSFORM    cmyk2Lab;      // The input chain
    826     cmsToneCurve*    KTone;         // Black-to-black tone curve
    827     cmsPipeline*     LabK2cmyk;     // The output profile
    828     cmsFloat64Number MaxError;
    829 
    830     cmsHTRANSFORM    hRoundTrip;
    831     cmsFloat64Number MaxTAC;
    832 
    833 
    834 } PreserveKPlaneParams;
    835 
    836 
    837 // The CLUT will be stored at 16 bits, but calculations are performed at cmsFloat32Number precision
    838 static
    839 int BlackPreservingSampler(register const cmsUInt16Number In[], register cmsUInt16Number Out[], register void* Cargo)
    840 {
    841     int i;
    842     cmsFloat32Number Inf[4], Outf[4];
    843     cmsFloat32Number LabK[4];
    844     cmsFloat64Number SumCMY, SumCMYK, Error, Ratio;
    845     cmsCIELab ColorimetricLab, BlackPreservingLab;
    846     PreserveKPlaneParams* bp = (PreserveKPlaneParams*) Cargo;
    847 
    848     // Convert from 16 bits to floating point
    849     for (i=0; i < 4; i++)
    850         Inf[i] = (cmsFloat32Number) (In[i] / 65535.0);
    851 
    852     // Get the K across Tone curve
    853     LabK[3] = cmsEvalToneCurveFloat(bp ->KTone, Inf[3]);
    854 
    855     // If going across black only, keep black only
    856     if (In[0] == 0 && In[1] == 0 && In[2] == 0) {
    857 
    858         Out[0] = Out[1] = Out[2] = 0;
    859         Out[3] = _cmsQuickSaturateWord(LabK[3] * 65535.0);
    860         return TRUE;
    861     }
    862 
    863     // Try the original transform,
    864     cmsPipelineEvalFloat( Inf, Outf, bp ->cmyk2cmyk);
    865 
    866     // Store a copy of the floating point result into 16-bit
    867     for (i=0; i < 4; i++)
    868             Out[i] = _cmsQuickSaturateWord(Outf[i] * 65535.0);
    869 
    870     // Maybe K is already ok (mostly on K=0)
    871     if ( fabs(Outf[3] - LabK[3]) < (3.0 / 65535.0) ) {
    872         return TRUE;
    873     }
    874 
    875     // K differ, mesure and keep Lab measurement for further usage
    876     // this is done in relative colorimetric intent
    877     cmsDoTransform(bp->hProofOutput, Out, &ColorimetricLab, 1);
    878 
    879     // Is not black only and the transform doesn't keep black.
    880     // Obtain the Lab of output CMYK. After that we have Lab + K
    881     cmsDoTransform(bp ->cmyk2Lab, Outf, LabK, 1);
    882 
    883     // Obtain the corresponding CMY using reverse interpolation
    884     // (K is fixed in LabK[3])
    885     if (!cmsPipelineEvalReverseFloat(LabK, Outf, Outf, bp ->LabK2cmyk)) {
    886 
    887         // Cannot find a suitable value, so use colorimetric xform
    888         // which is already stored in Out[]
    889         return TRUE;
    890     }
    891 
    892     // Make sure to pass through K (which now is fixed)
    893     Outf[3] = LabK[3];
    894 
    895     // Apply TAC if needed
    896     SumCMY   = Outf[0]  + Outf[1] + Outf[2];
    897     SumCMYK  = SumCMY + Outf[3];
    898 
    899     if (SumCMYK > bp ->MaxTAC) {
    900 
    901         Ratio = 1 - ((SumCMYK - bp->MaxTAC) / SumCMY);
    902         if (Ratio < 0)
    903             Ratio = 0;
    904     }
    905     else
    906        Ratio = 1.0;
    907 
    908     Out[0] = _cmsQuickSaturateWord(Outf[0] * Ratio * 65535.0);     // C
    909     Out[1] = _cmsQuickSaturateWord(Outf[1] * Ratio * 65535.0);     // M
    910     Out[2] = _cmsQuickSaturateWord(Outf[2] * Ratio * 65535.0);     // Y
    911     Out[3] = _cmsQuickSaturateWord(Outf[3] * 65535.0);
    912 
    913     // Estimate the error (this goes 16 bits to Lab DBL)
    914     cmsDoTransform(bp->hProofOutput, Out, &BlackPreservingLab, 1);
    915     Error = cmsDeltaE(&ColorimetricLab, &BlackPreservingLab);
    916     if (Error > bp -> MaxError)
    917         bp->MaxError = Error;
    918 
    919     return TRUE;
    920 }
    921 
    922 // This is the entry for black-plane preserving, which are non-ICC
    923 static
    924 cmsPipeline* BlackPreservingKPlaneIntents(cmsContext     ContextID,
    925                                           cmsUInt32Number nProfiles,
    926                                           cmsUInt32Number TheIntents[],
    927                                           cmsHPROFILE     hProfiles[],
    928                                           cmsBool         BPC[],
    929                                           cmsFloat64Number AdaptationStates[],
    930                                           cmsUInt32Number dwFlags)
    931 {
    932     PreserveKPlaneParams bp;
    933     cmsPipeline*    Result = NULL;
    934     cmsUInt32Number ICCIntents[256];
    935     cmsStage*         CLUT;
    936     cmsUInt32Number i, nGridPoints;
    937     cmsHPROFILE hLab;
    938 
    939     // Sanity check
    940     if (nProfiles < 1 || nProfiles > 255) return NULL;
    941 
    942     // Translate black-preserving intents to ICC ones
    943     for (i=0; i < nProfiles; i++)
    944         ICCIntents[i] = TranslateNonICCIntents(TheIntents[i]);
    945 
    946     // Check for non-cmyk profiles
    947     if (cmsGetColorSpace(hProfiles[0]) != cmsSigCmykData ||
    948         !(cmsGetColorSpace(hProfiles[nProfiles-1]) == cmsSigCmykData ||
    949         cmsGetDeviceClass(hProfiles[nProfiles-1]) == cmsSigOutputClass))
    950            return  DefaultICCintents(ContextID, nProfiles, ICCIntents, hProfiles, BPC, AdaptationStates, dwFlags);
    951 
    952     // Allocate an empty LUT for holding the result
    953     Result = cmsPipelineAlloc(ContextID, 4, 4);
    954     if (Result == NULL) return NULL;
    955 
    956 
    957     memset(&bp, 0, sizeof(bp));
    958 
    959     // We need the input LUT of the last profile, assuming this one is responsible of
    960     // black generation. This LUT will be searched in inverse order.
    961     bp.LabK2cmyk = _cmsReadInputLUT(hProfiles[nProfiles-1], INTENT_RELATIVE_COLORIMETRIC);
    962     if (bp.LabK2cmyk == NULL) goto Cleanup;
    963 
    964     // Get total area coverage (in 0..1 domain)
    965     bp.MaxTAC = cmsDetectTAC(hProfiles[nProfiles-1]) / 100.0;
    966     if (bp.MaxTAC <= 0) goto Cleanup;
    967 
    968 
    969     // Create a LUT holding normal ICC transform
    970     bp.cmyk2cmyk = DefaultICCintents(ContextID,
    971                                          nProfiles,
    972                                          ICCIntents,
    973                                          hProfiles,
    974                                          BPC,
    975                                          AdaptationStates,
    976                                          dwFlags);
    977     if (bp.cmyk2cmyk == NULL) goto Cleanup;
    978 
    979     // Now the tone curve
    980     bp.KTone = _cmsBuildKToneCurve(ContextID, 4096, nProfiles,
    981                                    ICCIntents,
    982                                    hProfiles,
    983                                    BPC,
    984                                    AdaptationStates,
    985                                    dwFlags);
    986     if (bp.KTone == NULL) goto Cleanup;
    987 
    988     // To measure the output, Last profile to Lab
    989     hLab = cmsCreateLab4ProfileTHR(ContextID, NULL);
    990     bp.hProofOutput = cmsCreateTransformTHR(ContextID, hProfiles[nProfiles-1],
    991                                          CHANNELS_SH(4)|BYTES_SH(2), hLab, TYPE_Lab_DBL,
    992                                          INTENT_RELATIVE_COLORIMETRIC,
    993                                          cmsFLAGS_NOCACHE|cmsFLAGS_NOOPTIMIZE);
    994     if ( bp.hProofOutput == NULL) goto Cleanup;
    995 
    996     // Same as anterior, but lab in the 0..1 range
    997     bp.cmyk2Lab = cmsCreateTransformTHR(ContextID, hProfiles[nProfiles-1],
    998                                          FLOAT_SH(1)|CHANNELS_SH(4)|BYTES_SH(4), hLab,
    999                                          FLOAT_SH(1)|CHANNELS_SH(3)|BYTES_SH(4),
   1000                                          INTENT_RELATIVE_COLORIMETRIC,
   1001                                          cmsFLAGS_NOCACHE|cmsFLAGS_NOOPTIMIZE);
   1002     if (bp.cmyk2Lab == NULL) goto Cleanup;
   1003     cmsCloseProfile(hLab);
   1004 
   1005     // Error estimation (for debug only)
   1006     bp.MaxError = 0;
   1007 
   1008     // How many gridpoints are we going to use?
   1009     nGridPoints = _cmsReasonableGridpointsByColorspace(cmsSigCmykData, dwFlags);
   1010 
   1011 
   1012     CLUT = cmsStageAllocCLut16bit(ContextID, nGridPoints, 4, 4, NULL);
   1013     if (CLUT == NULL) goto Cleanup;
   1014 
   1015     if (!cmsPipelineInsertStage(Result, cmsAT_BEGIN, CLUT))
   1016         goto Cleanup;
   1017 
   1018     cmsStageSampleCLut16bit(CLUT, BlackPreservingSampler, (void*) &bp, 0);
   1019 
   1020 Cleanup:
   1021 
   1022     if (bp.cmyk2cmyk) cmsPipelineFree(bp.cmyk2cmyk);
   1023     if (bp.cmyk2Lab) cmsDeleteTransform(bp.cmyk2Lab);
   1024     if (bp.hProofOutput) cmsDeleteTransform(bp.hProofOutput);
   1025 
   1026     if (bp.KTone) cmsFreeToneCurve(bp.KTone);
   1027     if (bp.LabK2cmyk) cmsPipelineFree(bp.LabK2cmyk);
   1028 
   1029     return Result;
   1030 }
   1031 
   1032 // Link routines ------------------------------------------------------------------------------------------------------
   1033 
   1034 // Chain several profiles into a single LUT. It just checks the parameters and then calls the handler
   1035 // for the first intent in chain. The handler may be user-defined. Is up to the handler to deal with the
   1036 // rest of intents in chain. A maximum of 255 profiles at time are supported, which is pretty reasonable.
   1037 cmsPipeline* _cmsLinkProfiles(cmsContext     ContextID,
   1038                               cmsUInt32Number nProfiles,
   1039                               cmsUInt32Number TheIntents[],
   1040                               cmsHPROFILE     hProfiles[],
   1041                               cmsBool         BPC[],
   1042                               cmsFloat64Number AdaptationStates[],
   1043                               cmsUInt32Number dwFlags)
   1044 {
   1045     cmsUInt32Number i;
   1046     cmsIntentsList* Intent;
   1047 
   1048     // Make sure a reasonable number of profiles is provided
   1049     if (nProfiles <= 0 || nProfiles > 255) {
   1050          cmsSignalError(ContextID, cmsERROR_RANGE, "Couldn't link '%d' profiles", nProfiles);
   1051         return NULL;
   1052     }
   1053 
   1054     for (i=0; i < nProfiles; i++) {
   1055 
   1056         // Check if black point is really needed or allowed. Note that
   1057         // following Adobe's document:
   1058         // BPC does not apply to devicelink profiles, nor to abs colorimetric,
   1059         // and applies always on V4 perceptual and saturation.
   1060 
   1061         if (TheIntents[i] == INTENT_ABSOLUTE_COLORIMETRIC)
   1062             BPC[i] = FALSE;
   1063 
   1064         if (TheIntents[i] == INTENT_PERCEPTUAL || TheIntents[i] == INTENT_SATURATION) {
   1065 
   1066             // Force BPC for V4 profiles in perceptual and saturation
   1067             if (cmsGetEncodedICCversion(hProfiles[i]) >= 0x4000000)
   1068                 BPC[i] = TRUE;
   1069         }
   1070     }
   1071 
   1072     // Search for a handler. The first intent in the chain defines the handler. That would
   1073     // prevent using multiple custom intents in a multiintent chain, but the behaviour of
   1074     // this case would present some issues if the custom intent tries to do things like
   1075     // preserve primaries. This solution is not perfect, but works well on most cases.
   1076 
   1077     Intent = SearchIntent(ContextID, TheIntents[0]);
   1078     if (Intent == NULL) {
   1079         cmsSignalError(ContextID, cmsERROR_UNKNOWN_EXTENSION, "Unsupported intent '%d'", TheIntents[0]);
   1080         return NULL;
   1081     }
   1082 
   1083     // Call the handler
   1084     return Intent ->Link(ContextID, nProfiles, TheIntents, hProfiles, BPC, AdaptationStates, dwFlags);
   1085 }
   1086 
   1087 // -------------------------------------------------------------------------------------------------
   1088 
   1089 // Get information about available intents. nMax is the maximum space for the supplied "Codes"
   1090 // and "Descriptions" the function returns the total number of intents, which may be greater
   1091 // than nMax, although the matrices are not populated beyond this level.
   1092 cmsUInt32Number CMSEXPORT cmsGetSupportedIntentsTHR(cmsContext ContextID, cmsUInt32Number nMax, cmsUInt32Number* Codes, char** Descriptions)
   1093 {
   1094     _cmsIntentsPluginChunkType* ctx = ( _cmsIntentsPluginChunkType*) _cmsContextGetClientChunk(ContextID, IntentPlugin);
   1095     cmsIntentsList* pt;
   1096     cmsUInt32Number nIntents;
   1097 
   1098 
   1099     for (nIntents=0, pt = ctx->Intents; pt != NULL; pt = pt -> Next)
   1100     {
   1101         if (nIntents < nMax) {
   1102             if (Codes != NULL)
   1103                 Codes[nIntents] = pt ->Intent;
   1104 
   1105             if (Descriptions != NULL)
   1106                 Descriptions[nIntents] = pt ->Description;
   1107         }
   1108 
   1109         nIntents++;
   1110     }
   1111 
   1112     for (nIntents=0, pt = DefaultIntents; pt != NULL; pt = pt -> Next)
   1113     {
   1114         if (nIntents < nMax) {
   1115             if (Codes != NULL)
   1116                 Codes[nIntents] = pt ->Intent;
   1117 
   1118             if (Descriptions != NULL)
   1119                 Descriptions[nIntents] = pt ->Description;
   1120         }
   1121 
   1122         nIntents++;
   1123     }
   1124     return nIntents;
   1125 }
   1126 
   1127 cmsUInt32Number CMSEXPORT cmsGetSupportedIntents(cmsUInt32Number nMax, cmsUInt32Number* Codes, char** Descriptions)
   1128 {
   1129     return cmsGetSupportedIntentsTHR(NULL, nMax, Codes, Descriptions);
   1130 }
   1131 
   1132 // The plug-in registration. User can add new intents or override default routines
   1133 cmsBool  _cmsRegisterRenderingIntentPlugin(cmsContext id, cmsPluginBase* Data)
   1134 {
   1135     _cmsIntentsPluginChunkType* ctx = ( _cmsIntentsPluginChunkType*) _cmsContextGetClientChunk(id, IntentPlugin);
   1136     cmsPluginRenderingIntent* Plugin = (cmsPluginRenderingIntent*) Data;
   1137     cmsIntentsList* fl;
   1138 
   1139     // Do we have to reset the custom intents?
   1140     if (Data == NULL) {
   1141 
   1142         ctx->Intents = NULL;
   1143         return TRUE;
   1144     }
   1145 
   1146     fl = (cmsIntentsList*) _cmsPluginMalloc(id, sizeof(cmsIntentsList));
   1147     if (fl == NULL) return FALSE;
   1148 
   1149 
   1150     fl ->Intent  = Plugin ->Intent;
   1151     strncpy(fl ->Description, Plugin ->Description, sizeof(fl ->Description)-1);
   1152     fl ->Description[sizeof(fl ->Description)-1] = 0;
   1153 
   1154     fl ->Link    = Plugin ->Link;
   1155 
   1156     fl ->Next = ctx ->Intents;
   1157     ctx ->Intents = fl;
   1158 
   1159     return TRUE;
   1160 }
   1161 
   1162