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