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