text: /*
   * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   *
   * This code is free software; you can redistribute it and/or modify it
   * under the terms of the GNU General Public License version 2 only, as
   * published by the Free Software Foundation.  Oracle designates this
   * particular file as subject to the "Classpath" exception as provided
   * by Oracle in the LICENSE file that accompanied this code.
   *
   * This code is distributed in the hope that it will be useful, but WITHOUT
   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
   * version 2 for more details (a copy is included in the LICENSE file that
   * accompanied this code).
   *
   * You should have received a copy of the GNU General Public License version
   * 2 along with this work; if not, write to the Free Software Foundation,
   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
   *
   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
   * or visit www.oracle.com if you need additional information or have any
   * questions.
   */
  
  // This file is available under and governed by the GNU General Public
  // License version 2 only, as published by the Free Software Foundation.
  // However, the following notice accompanied the original version of this
  // file:
  //
  //---------------------------------------------------------------------------------
  //
  //  Little Color Management System
  //  Copyright (c) 1998-2012 Marti Maria Saguer
  //
  // Permission is hereby granted, free of charge, to any person obtaining
  // a copy of this software and associated documentation files (the "Software"),
  // to deal in the Software without restriction, including without limitation
  // the rights to use, copy, modify, merge, publish, distribute, sublicense,
  // and/or sell copies of the Software, and to permit persons to whom the Software
  // is furnished to do so, subject to the following conditions:
  //
  // The above copyright notice and this permission notice shall be included in
  // all copies or substantial portions of the Software.
  //
  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
  // THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  //
  //---------------------------------------------------------------------------------
  //
  
  #include "lcms2_internal.h"
  
  
  // Link several profiles to obtain a single LUT modelling the whole color transform. Intents, Black point
  // compensation and Adaptation parameters may vary across profiles. BPC and Adaptation refers to the PCS
  // after the profile. I.e, BPC[0] refers to connexion between profile(0) and profile(1)
  cmsPipeline* _cmsLinkProfiles(cmsContext     ContextID,
                                cmsUInt32Number nProfiles,
                                cmsUInt32Number Intents[],
                                cmsHPROFILE     hProfiles[],
                                cmsBool         BPC[],
                                cmsFloat64Number AdaptationStates[],
                                cmsUInt32Number dwFlags);
  
  //---------------------------------------------------------------------------------
  
  // This is the default routine for ICC-style intents. A user may decide to override it by using a plugin.
  // Supported intents are perceptual, relative colorimetric, saturation and ICC-absolute colorimetric
  static
  cmsPipeline* DefaultICCintents(cmsContext     ContextID,
                                 cmsUInt32Number nProfiles,
                                 cmsUInt32Number Intents[],
                                 cmsHPROFILE     hProfiles[],
                                 cmsBool         BPC[],
                                 cmsFloat64Number AdaptationStates[],
                                 cmsUInt32Number dwFlags);
  
  //---------------------------------------------------------------------------------
  
  // This is the entry for black-preserving K-only intents, which are non-ICC. Last profile have to be a output profile
  // to do the trick (no devicelinks allowed at that position)
  static
  cmsPipeline*  BlackPreservingKOnlyIntents(cmsContext     ContextID,
                                            cmsUInt32Number nProfiles,
                                            cmsUInt32Number Intents[],
                                            cmsHPROFILE     hProfiles[],
                                            cmsBool         BPC[],
                                            cmsFloat64Number AdaptationStates[],
                                            cmsUInt32Number dwFlags);
  
  //---------------------------------------------------------------------------------
  
  // This is the entry for black-plane preserving, which are non-ICC. Again, Last profile have to be a output profile
  // to do the trick (no devicelinks allowed at that position)
  static
  cmsPipeline*  BlackPreservingKPlaneIntents(cmsContext     ContextID,
                                             cmsUInt32Number nProfiles,
                                             cmsUInt32Number Intents[],
                                             cmsHPROFILE     hProfiles[],
                                             cmsBool         BPC[],
                                             cmsFloat64Number AdaptationStates[],
                                             cmsUInt32Number dwFlags);
  
  //---------------------------------------------------------------------------------
  
  
  // This is a structure holding implementations for all supported intents.
  typedef struct _cms_intents_list {
  
      cmsUInt32Number Intent;
      char            Description[256];
      cmsIntentFn     Link;
      struct _cms_intents_list*  Next;
  
  } cmsIntentsList;
  
  
  // Built-in intents
  static cmsIntentsList DefaultIntents[] = {
  
      { INTENT_PERCEPTUAL,                            "Perceptual",                                   DefaultICCintents,            &DefaultIntents[1] },
      { INTENT_RELATIVE_COLORIMETRIC,                 "Relative colorimetric",                        DefaultICCintents,            &DefaultIntents[2] },
      { INTENT_SATURATION,                            "Saturation",                                   DefaultICCintents,            &DefaultIntents[3] },
      { INTENT_ABSOLUTE_COLORIMETRIC,                 "Absolute colorimetric",                        DefaultICCintents,            &DefaultIntents[4] },
      { INTENT_PRESERVE_K_ONLY_PERCEPTUAL,            "Perceptual preserving black ink",              BlackPreservingKOnlyIntents,  &DefaultIntents[5] },
      { INTENT_PRESERVE_K_ONLY_RELATIVE_COLORIMETRIC, "Relative colorimetric preserving black ink",   BlackPreservingKOnlyIntents,  &DefaultIntents[6] },
      { INTENT_PRESERVE_K_ONLY_SATURATION,            "Saturation preserving black ink",              BlackPreservingKOnlyIntents,  &DefaultIntents[7] },
      { INTENT_PRESERVE_K_PLANE_PERCEPTUAL,           "Perceptual preserving black plane",            BlackPreservingKPlaneIntents, &DefaultIntents[8] },
      { INTENT_PRESERVE_K_PLANE_RELATIVE_COLORIMETRIC,"Relative colorimetric preserving black plane", BlackPreservingKPlaneIntents, &DefaultIntents[9] },
      { INTENT_PRESERVE_K_PLANE_SATURATION,           "Saturation preserving black plane",            BlackPreservingKPlaneIntents, NULL }
  };
  
  
  // A pointer to the begining of the list
  static cmsIntentsList *Intents = DefaultIntents;
  
  // Search the list for a suitable intent. Returns NULL if not found
  static
  cmsIntentsList* SearchIntent(cmsUInt32Number Intent)
  {
      cmsIntentsList* pt;
  
      for (pt = Intents; pt != NULL; pt = pt -> Next)
          if (pt ->Intent == Intent) return pt;
  
      return NULL;
  }
  
  // Black point compensation. Implemented as a linear scaling in XYZ. Black points
  // should come relative to the white point. Fills an matrix/offset element m
  // which is organized as a 4x4 matrix.
  static
  void ComputeBlackPointCompensation(const cmsCIEXYZ* BlackPointIn,
                                     const cmsCIEXYZ* BlackPointOut,
                                     cmsMAT3* m, cmsVEC3* off)
  {
    cmsFloat64Number ax, ay, az, bx, by, bz, tx, ty, tz;
  
     // Now we need to compute a matrix plus an offset m and of such of
     // [m]*bpin + off = bpout
     // [m]*D50  + off = D50
     //
     // This is a linear scaling in the form ax+b, where
     // a = (bpout - D50) / (bpin - D50)
     // b = - D50* (bpout - bpin) / (bpin - D50)
  
     tx = BlackPointIn->X - cmsD50_XYZ()->X;
     ty = BlackPointIn->Y - cmsD50_XYZ()->Y;
     tz = BlackPointIn->Z - cmsD50_XYZ()->Z;
  
     ax = (BlackPointOut->X - cmsD50_XYZ()->X) / tx;
     ay = (BlackPointOut->Y - cmsD50_XYZ()->Y) / ty;
     az = (BlackPointOut->Z - cmsD50_XYZ()->Z) / tz;
  
     bx = - cmsD50_XYZ()-> X * (BlackPointOut->X - BlackPointIn->X) / tx;
     by = - cmsD50_XYZ()-> Y * (BlackPointOut->Y - BlackPointIn->Y) / ty;
     bz = - cmsD50_XYZ()-> Z * (BlackPointOut->Z - BlackPointIn->Z) / tz;
  
     _cmsVEC3init(&m ->v[0], ax, 0,  0);
     _cmsVEC3init(&m ->v[1], 0, ay,  0);
     _cmsVEC3init(&m ->v[2], 0,  0,  az);
     _cmsVEC3init(off, bx, by, bz);
  
  }
  
  
  // Approximate a blackbody illuminant based on CHAD information
  static
  cmsFloat64Number CHAD2Temp(const cmsMAT3* Chad)
  {
      // Convert D50 across inverse CHAD to get the absolute white point
      cmsVEC3 d, s;
      cmsCIEXYZ Dest;
      cmsCIExyY DestChromaticity;
      cmsFloat64Number TempK;
      cmsMAT3 m1, m2;
  
      m1 = *Chad;
      if (!_cmsMAT3inverse(&m1, &m2)) return FALSE;
  
      s.n[VX] = cmsD50_XYZ() -> X;
      s.n[VY] = cmsD50_XYZ() -> Y;
      s.n[VZ] = cmsD50_XYZ() -> Z;
  
      _cmsMAT3eval(&d, &m2, &s);
  
      Dest.X = d.n[VX];
      Dest.Y = d.n[VY];
      Dest.Z = d.n[VZ];
  
      cmsXYZ2xyY(&DestChromaticity, &Dest);
  
      if (!cmsTempFromWhitePoint(&TempK, &DestChromaticity))
          return -1.0;
  
      return TempK;
  }
  
  // Compute a CHAD based on a given temperature
  static
      void Temp2CHAD(cmsMAT3* Chad, cmsFloat64Number Temp)
  {
      cmsCIEXYZ White;
      cmsCIExyY ChromaticityOfWhite;
  
      cmsWhitePointFromTemp(&ChromaticityOfWhite, Temp);
      cmsxyY2XYZ(&White, &ChromaticityOfWhite);
      _cmsAdaptationMatrix(Chad, NULL, &White, cmsD50_XYZ());
  }
  
  // Join scalings to obtain relative input to absolute and then to relative output.
  // Result is stored in a 3x3 matrix
  static
  cmsBool  ComputeAbsoluteIntent(cmsFloat64Number AdaptationState,
                                 const cmsCIEXYZ* WhitePointIn,
                                 const cmsMAT3* ChromaticAdaptationMatrixIn,
                                 const cmsCIEXYZ* WhitePointOut,
                                 const cmsMAT3* ChromaticAdaptationMatrixOut,
                                 cmsMAT3* m)
  {
      cmsMAT3 Scale, m1, m2, m3, m4;
  
      // Adaptation state
      if (AdaptationState == 1.0) {
  
          // Observer is fully adapted. Keep chromatic adaptation.
          // That is the standard V4 behaviour
          _cmsVEC3init(&m->v[0], WhitePointIn->X / WhitePointOut->X, 0, 0);
          _cmsVEC3init(&m->v[1], 0, WhitePointIn->Y / WhitePointOut->Y, 0);
          _cmsVEC3init(&m->v[2], 0, 0, WhitePointIn->Z / WhitePointOut->Z);
  
      }
      else  {
  
          // Incomplete adaptation. This is an advanced feature.
          _cmsVEC3init(&Scale.v[0], WhitePointIn->X / WhitePointOut->X, 0, 0);
          _cmsVEC3init(&Scale.v[1], 0,  WhitePointIn->Y / WhitePointOut->Y, 0);
          _cmsVEC3init(&Scale.v[2], 0, 0,  WhitePointIn->Z / WhitePointOut->Z);
  
  
          if (AdaptationState == 0.0) {
  
              m1 = *ChromaticAdaptationMatrixOut;
              _cmsMAT3per(&m2, &m1, &Scale);
              // m2 holds CHAD from output white to D50 times abs. col. scaling
  
              // Observer is not adapted, undo the chromatic adaptation
              _cmsMAT3per(m, &m3, ChromaticAdaptationMatrixOut);
  
              m3 = *ChromaticAdaptationMatrixIn;
              if (!_cmsMAT3inverse(&m3, &m4)) return FALSE;
              _cmsMAT3per(m, &m2, &m4);
  
          } else {
  
              cmsMAT3 MixedCHAD;
              cmsFloat64Number TempSrc, TempDest, Temp;
  
              m1 = *ChromaticAdaptationMatrixIn;
              if (!_cmsMAT3inverse(&m1, &m2)) return FALSE;
              _cmsMAT3per(&m3, &m2, &Scale);
              // m3 holds CHAD from input white to D50 times abs. col. scaling
  
              TempSrc  = CHAD2Temp(ChromaticAdaptationMatrixIn);
              TempDest = CHAD2Temp(ChromaticAdaptationMatrixOut);
  
              if (TempSrc < 0.0 || TempDest < 0.0) return FALSE; // Something went wrong
  
              if (_cmsMAT3isIdentity(&Scale) && fabs(TempSrc - TempDest) < 0.01) {
  
                  _cmsMAT3identity(m);
                  return TRUE;
              }
  
              Temp = (1.0 - AdaptationState) * TempDest + AdaptationState * TempSrc;
  
              // Get a CHAD from whatever output temperature to D50. This replaces output CHAD
              Temp2CHAD(&MixedCHAD, Temp);
  
              _cmsMAT3per(m, &m3, &MixedCHAD);
          }
  
      }
      return TRUE;
  
  }
  
  // Just to see if m matrix should be applied
  static
  cmsBool IsEmptyLayer(cmsMAT3* m, cmsVEC3* off)
  {
      cmsFloat64Number diff = 0;
      cmsMAT3 Ident;
      int i;
  
      if (m == NULL && off == NULL) return TRUE;  // NULL is allowed as an empty layer
      if (m == NULL && off != NULL) return FALSE; // This is an internal error
  
      _cmsMAT3identity(&Ident);
  
      for (i=0; i < 3*3; i++)
          diff += fabs(((cmsFloat64Number*)m)[i] - ((cmsFloat64Number*)&Ident)[i]);
  
      for (i=0; i < 3; i++)
          diff += fabs(((cmsFloat64Number*)off)[i]);
  
  
      return (diff < 0.002);
  }
  
  
  // Compute the conversion layer
  static
  cmsBool ComputeConversion(int i, cmsHPROFILE hProfiles[],
                                   cmsUInt32Number Intent,
                                   cmsBool BPC,
                                   cmsFloat64Number AdaptationState,
                                   cmsMAT3* m, cmsVEC3* off)
  {
  
      int k;
  
      // m  and off are set to identity and this is detected latter on
      _cmsMAT3identity(m);
      _cmsVEC3init(off, 0, 0, 0);
  
      // If intent is abs. colorimetric,
      if (Intent == INTENT_ABSOLUTE_COLORIMETRIC) {
  
          cmsCIEXYZ WhitePointIn, WhitePointOut;
          cmsMAT3 ChromaticAdaptationMatrixIn, ChromaticAdaptationMatrixOut;
  
          _cmsReadMediaWhitePoint(&WhitePointIn,  hProfiles[i-1]);
          _cmsReadCHAD(&ChromaticAdaptationMatrixIn, hProfiles[i-1]);
  
          _cmsReadMediaWhitePoint(&WhitePointOut,  hProfiles[i]);
          _cmsReadCHAD(&ChromaticAdaptationMatrixOut, hProfiles[i]);
  
          if (!ComputeAbsoluteIntent(AdaptationState,
                                    &WhitePointIn,  &ChromaticAdaptationMatrixIn,
                                    &WhitePointOut, &ChromaticAdaptationMatrixOut, m)) return FALSE;
  
      }
      else {
          // Rest of intents may apply BPC.
  
          if (BPC) {
  
              cmsCIEXYZ BlackPointIn, BlackPointOut;
  
              cmsDetectBlackPoint(&BlackPointIn,  hProfiles[i-1], Intent, 0);
              cmsDetectDestinationBlackPoint(&BlackPointOut, hProfiles[i], Intent, 0);
  
              // If black points are equal, then do nothing
              if (BlackPointIn.X != BlackPointOut.X ||
                  BlackPointIn.Y != BlackPointOut.Y ||
                  BlackPointIn.Z != BlackPointOut.Z)
                      ComputeBlackPointCompensation(&BlackPointIn, &BlackPointOut, m, off);
          }
      }
  
      // Offset should be adjusted because the encoding. We encode XYZ normalized to 0..1.0,
      // to do that, we divide by MAX_ENCODEABLE_XZY. The conversion stage goes XYZ -> XYZ so
      // we have first to convert from encoded to XYZ and then convert back to encoded.
      // y = Mx + Off
      // x = x'c
      // y = M x'c + Off
      // y = y'c; y' = y / c
      // y' = (Mx'c + Off) /c = Mx' + (Off / c)
  
      for (k=0; k < 3; k++) {
          off ->n[k] /= MAX_ENCODEABLE_XYZ;
      }
  
      return TRUE;
  }
  
  
  // Add a conversion stage if needed. If a matrix/offset m is given, it applies to XYZ space
  static
  cmsBool AddConversion(cmsPipeline* Result, cmsColorSpaceSignature InPCS, cmsColorSpaceSignature OutPCS, cmsMAT3* m, cmsVEC3* off)
  {
      cmsFloat64Number* m_as_dbl = (cmsFloat64Number*) m;
      cmsFloat64Number* off_as_dbl = (cmsFloat64Number*) off;
  
      // Handle PCS mismatches. A specialized stage is added to the LUT in such case
      switch (InPCS) {
  
          case cmsSigXYZData: // Input profile operates in XYZ
  
              switch (OutPCS) {
  
              case cmsSigXYZData:  // XYZ -> XYZ
                  if (!IsEmptyLayer(m, off))
                      cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl));
                  break;
  
              case cmsSigLabData:  // XYZ -> Lab
                  if (!IsEmptyLayer(m, off))
                      cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl));
                  cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocXYZ2Lab(Result ->ContextID));
                  break;
  
              default:
                  return FALSE;   // Colorspace mismatch
                  }
                  break;
  
  
          case cmsSigLabData: // Input profile operates in Lab
  
              switch (OutPCS) {
  
              case cmsSigXYZData:  // Lab -> XYZ
  
                  cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocLab2XYZ(Result ->ContextID));
                  if (!IsEmptyLayer(m, off))
                      cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl));
                  break;
  
              case cmsSigLabData:  // Lab -> Lab
  
                  if (!IsEmptyLayer(m, off)) {
                      cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocLab2XYZ(Result ->ContextID));
                      cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl));
                      cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocXYZ2Lab(Result ->ContextID));
                  }
                  break;
  
              default:
                  return FALSE;  // Mismatch
              }
              break;
  
  
              // On colorspaces other than PCS, check for same space
          default:
              if (InPCS != OutPCS) return FALSE;
              break;
      }
  
      return TRUE;
  }
  
  
  // Is a given space compatible with another?
  static
  cmsBool ColorSpaceIsCompatible(cmsColorSpaceSignature a, cmsColorSpaceSignature b)
  {
      // If they are same, they are compatible.
      if (a == b) return TRUE;
  
      // Check for MCH4 substitution of CMYK
      if ((a == cmsSig4colorData) && (b == cmsSigCmykData)) return TRUE;
      if ((a == cmsSigCmykData) && (b == cmsSig4colorData)) return TRUE;
  
      // Check for XYZ/Lab. Those spaces are interchangeable as they can be computed one from other.
      if ((a == cmsSigXYZData) && (b == cmsSigLabData)) return TRUE;
      if ((a == cmsSigLabData) && (b == cmsSigXYZData)) return TRUE;
  
      return FALSE;
  }
  
  
  // Default handler for ICC-style intents
  static
  cmsPipeline* DefaultICCintents(cmsContext       ContextID,
                                 cmsUInt32Number  nProfiles,
                                 cmsUInt32Number  TheIntents[],
                                 cmsHPROFILE      hProfiles[],
                                 cmsBool          BPC[],
                                 cmsFloat64Number AdaptationStates[],
                                 cmsUInt32Number  dwFlags)
  {
      cmsPipeline* Lut, *Result;
      cmsHPROFILE hProfile;
      cmsMAT3 m;
      cmsVEC3 off;
      cmsColorSpaceSignature ColorSpaceIn, ColorSpaceOut, CurrentColorSpace;
      cmsProfileClassSignature ClassSig;
      cmsUInt32Number  i, Intent;
  
      // For safety
      if (nProfiles == 0) return NULL;
  
      // Allocate an empty LUT for holding the result. 0 as channel count means 'undefined'
      Result = cmsPipelineAlloc(ContextID, 0, 0);
      if (Result == NULL) return NULL;
  
      CurrentColorSpace = cmsGetColorSpace(hProfiles[0]);
  
      for (i=0; i < nProfiles; i++) {
  
          cmsBool  lIsDeviceLink, lIsInput;
  
          hProfile      = hProfiles[i];
          ClassSig      = cmsGetDeviceClass(hProfile);
          lIsDeviceLink = (ClassSig == cmsSigLinkClass || ClassSig == cmsSigAbstractClass );
  
          // First profile is used as input unless devicelink or abstract
          if ((i == 0) && !lIsDeviceLink) {
              lIsInput = TRUE;
          }
          else {
            // Else use profile in the input direction if current space is not PCS
          lIsInput      = (CurrentColorSpace != cmsSigXYZData) &&
                          (CurrentColorSpace != cmsSigLabData);
          }
  
          Intent        = TheIntents[i];
  
          if (lIsInput || lIsDeviceLink) {
  
              ColorSpaceIn    = cmsGetColorSpace(hProfile);
              ColorSpaceOut   = cmsGetPCS(hProfile);
          }
          else {
  
              ColorSpaceIn    = cmsGetPCS(hProfile);
              ColorSpaceOut   = cmsGetColorSpace(hProfile);
          }
  
          if (!ColorSpaceIsCompatible(ColorSpaceIn, CurrentColorSpace)) {
  
              cmsSignalError(ContextID, cmsERROR_COLORSPACE_CHECK, "ColorSpace mismatch");
              goto Error;
          }
  
          // If devicelink is found, then no custom intent is allowed and we can
          // read the LUT to be applied. Settings don't apply here.
          if (lIsDeviceLink || ((ClassSig == cmsSigNamedColorClass) && (nProfiles == 1))) {
  
              // Get the involved LUT from the profile
              Lut = _cmsReadDevicelinkLUT(hProfile, Intent);
              if (Lut == NULL) goto Error;
  
              // What about abstract profiles?
               if (ClassSig == cmsSigAbstractClass && i > 0) {
                  if (!ComputeConversion(i, hProfiles, Intent, BPC[i], AdaptationStates[i], &m, &off)) goto Error;
               }
               else {
                  _cmsMAT3identity(&m);
                  _cmsVEC3init(&off, 0, 0, 0);
               }
  
  
              if (!AddConversion(Result, CurrentColorSpace, ColorSpaceIn, &m, &off)) goto Error;
  
          }
          else {
  
              if (lIsInput) {
                  // Input direction means non-pcs connection, so proceed like devicelinks
                  Lut = _cmsReadInputLUT(hProfile, Intent);
                  if (Lut == NULL) goto Error;
              }
              else {
  
                  // Output direction means PCS connection. Intent may apply here
                  Lut = _cmsReadOutputLUT(hProfile, Intent);
                  if (Lut == NULL) goto Error;
  
  
                  if (!ComputeConversion(i, hProfiles, Intent, BPC[i], AdaptationStates[i], &m, &off)) goto Error;
                  if (!AddConversion(Result, CurrentColorSpace, ColorSpaceIn, &m, &off)) goto Error;
  
              }
          }
  
          // Concatenate to the output LUT
          cmsPipelineCat(Result, Lut);
          cmsPipelineFree(Lut);
  
          // Update current space
          CurrentColorSpace = ColorSpaceOut;
      }
  
      return Result;
  
  Error:
  
      if (Result != NULL) cmsPipelineFree(Result);
      return NULL;
  
      cmsUNUSED_PARAMETER(dwFlags);
  }
  
  
  // Wrapper for DLL calling convention
  cmsPipeline*  CMSEXPORT _cmsDefaultICCintents(cmsContext     ContextID,
                                                cmsUInt32Number nProfiles,
                                                cmsUInt32Number TheIntents[],
                                                cmsHPROFILE     hProfiles[],
                                                cmsBool         BPC[],
                                                cmsFloat64Number AdaptationStates[],
                                                cmsUInt32Number dwFlags)
  {
      return DefaultICCintents(ContextID, nProfiles, TheIntents, hProfiles, BPC, AdaptationStates, dwFlags);
  }
  
  // Black preserving intents ---------------------------------------------------------------------------------------------
  
  // Translate black-preserving intents to ICC ones
  static
  int TranslateNonICCIntents(int Intent)
  {
      switch (Intent) {
          case INTENT_PRESERVE_K_ONLY_PERCEPTUAL:
          case INTENT_PRESERVE_K_PLANE_PERCEPTUAL:
              return INTENT_PERCEPTUAL;
  
          case INTENT_PRESERVE_K_ONLY_RELATIVE_COLORIMETRIC:
          case INTENT_PRESERVE_K_PLANE_RELATIVE_COLORIMETRIC:
              return INTENT_RELATIVE_COLORIMETRIC;
  
          case INTENT_PRESERVE_K_ONLY_SATURATION:
          case INTENT_PRESERVE_K_PLANE_SATURATION:
              return INTENT_SATURATION;
  
          default: return Intent;
      }
  }
  
  // Sampler for Black-only preserving CMYK->CMYK transforms
  
  typedef struct {
      cmsPipeline*    cmyk2cmyk;      // The original transform
      cmsToneCurve*   KTone;          // Black-to-black tone curve
  
  } GrayOnlyParams;
  
  
  // Preserve black only if that is the only ink used
  static
  int BlackPreservingGrayOnlySampler(register const cmsUInt16Number In[], register cmsUInt16Number Out[], register void* Cargo)
  {
      GrayOnlyParams* bp = (GrayOnlyParams*) Cargo;
  
      // If going across black only, keep black only
      if (In[0] == 0 && In[1] == 0 && In[2] == 0) {
  
          // TAC does not apply because it is black ink!
          Out[0] = Out[1] = Out[2] = 0;
          Out[3] = cmsEvalToneCurve16(bp->KTone, In[3]);
          return TRUE;
      }
  
      // Keep normal transform for other colors
      bp ->cmyk2cmyk ->Eval16Fn(In, Out, bp ->cmyk2cmyk->Data);
      return TRUE;
  }
  
  // This is the entry for black-preserving K-only intents, which are non-ICC
  static
  cmsPipeline*  BlackPreservingKOnlyIntents(cmsContext     ContextID,
                                            cmsUInt32Number nProfiles,
                                            cmsUInt32Number TheIntents[],
                                            cmsHPROFILE     hProfiles[],
                                            cmsBool         BPC[],
                                            cmsFloat64Number AdaptationStates[],
                                            cmsUInt32Number dwFlags)
  {
      GrayOnlyParams  bp;
      cmsPipeline*    Result;
      cmsUInt32Number ICCIntents[256];
      cmsStage*         CLUT;
      cmsUInt32Number i, nGridPoints;
  
  
      // Sanity check
      if (nProfiles < 1 || nProfiles > 255) return NULL;
  
      // Translate black-preserving intents to ICC ones
      for (i=0; i < nProfiles; i++)
          ICCIntents[i] = TranslateNonICCIntents(TheIntents[i]);
  
      // Check for non-cmyk profiles
      if (cmsGetColorSpace(hProfiles[0]) != cmsSigCmykData ||
          cmsGetColorSpace(hProfiles[nProfiles-1]) != cmsSigCmykData)
             return DefaultICCintents(ContextID, nProfiles, ICCIntents, hProfiles, BPC, AdaptationStates, dwFlags);
  
      memset(&bp, 0, sizeof(bp));
  
      // Allocate an empty LUT for holding the result
      Result = cmsPipelineAlloc(ContextID, 4, 4);
      if (Result == NULL) return NULL;
  
      // Create a LUT holding normal ICC transform
      bp.cmyk2cmyk = DefaultICCintents(ContextID,
          nProfiles,
          ICCIntents,
          hProfiles,
          BPC,
          AdaptationStates,
          dwFlags);
  
      if (bp.cmyk2cmyk == NULL) goto Error;
  
      // Now, compute the tone curve
      bp.KTone = _cmsBuildKToneCurve(ContextID,
          4096,
          nProfiles,
          ICCIntents,
          hProfiles,
          BPC,
          AdaptationStates,
          dwFlags);
  
      if (bp.KTone == NULL) goto Error;
  
  
      // How many gridpoints are we going to use?
      nGridPoints = _cmsReasonableGridpointsByColorspace(cmsSigCmykData, dwFlags);
  
      // Create the CLUT. 16 bits
      CLUT = cmsStageAllocCLut16bit(ContextID, nGridPoints, 4, 4, NULL);
      if (CLUT == NULL) goto Error;
  
      // This is the one and only MPE in this LUT
      cmsPipelineInsertStage(Result, cmsAT_BEGIN, CLUT);
  
      // Sample it. We cannot afford pre/post linearization this time.
      if (!cmsStageSampleCLut16bit(CLUT, BlackPreservingGrayOnlySampler, (void*) &bp, 0))
          goto Error;
  
      // Get rid of xform and tone curve
      cmsPipelineFree(bp.cmyk2cmyk);
      cmsFreeToneCurve(bp.KTone);
  
      return Result;
  
  Error:
  
      if (bp.cmyk2cmyk != NULL) cmsPipelineFree(bp.cmyk2cmyk);
      if (bp.KTone != NULL)  cmsFreeToneCurve(bp.KTone);
      if (Result != NULL) cmsPipelineFree(Result);
      return NULL;
  
  }
  
  // K Plane-preserving CMYK to CMYK ------------------------------------------------------------------------------------
  
  typedef struct {
  
      cmsPipeline*     cmyk2cmyk;     // The original transform
      cmsHTRANSFORM    hProofOutput;  // Output CMYK to Lab (last profile)
      cmsHTRANSFORM    cmyk2Lab;      // The input chain
      cmsToneCurve*    KTone;         // Black-to-black tone curve
      cmsPipeline*     LabK2cmyk;     // The output profile
      cmsFloat64Number MaxError;
  
      cmsHTRANSFORM    hRoundTrip;
      cmsFloat64Number MaxTAC;
  
  
  } PreserveKPlaneParams;
  
  
  // The CLUT will be stored at 16 bits, but calculations are performed at cmsFloat32Number precision
  static
  int BlackPreservingSampler(register const cmsUInt16Number In[], register cmsUInt16Number Out[], register void* Cargo)
  {
      int i;
      cmsFloat32Number Inf[4], Outf[4];
      cmsFloat32Number LabK[4];
      cmsFloat64Number SumCMY, SumCMYK, Error, Ratio;
      cmsCIELab ColorimetricLab, BlackPreservingLab;
      PreserveKPlaneParams* bp = (PreserveKPlaneParams*) Cargo;
  
      // Convert from 16 bits to floating point
      for (i=0; i < 4; i++)
          Inf[i] = (cmsFloat32Number) (In[i] / 65535.0);
  
      // Get the K across Tone curve
      LabK[3] = cmsEvalToneCurveFloat(bp ->KTone, Inf[3]);
  
      // If going across black only, keep black only
      if (In[0] == 0 && In[1] == 0 && In[2] == 0) {
  
          Out[0] = Out[1] = Out[2] = 0;
          Out[3] = _cmsQuickSaturateWord(LabK[3] * 65535.0);
          return TRUE;
      }
  
      // Try the original transform,
      cmsPipelineEvalFloat( Inf, Outf, bp ->cmyk2cmyk);
  
      // Store a copy of the floating point result into 16-bit
      for (i=0; i < 4; i++)
              Out[i] = _cmsQuickSaturateWord(Outf[i] * 65535.0);
  
      // Maybe K is already ok (mostly on K=0)
      if ( fabs(Outf[3] - LabK[3]) < (3.0 / 65535.0) ) {
          return TRUE;
      }
  
      // K differ, mesure and keep Lab measurement for further usage
      // this is done in relative colorimetric intent
      cmsDoTransform(bp->hProofOutput, Out, &ColorimetricLab, 1);
  
      // Is not black only and the transform doesn't keep black.
      // Obtain the Lab of output CMYK. After that we have Lab + K
      cmsDoTransform(bp ->cmyk2Lab, Outf, LabK, 1);
  
      // Obtain the corresponding CMY using reverse interpolation
      // (K is fixed in LabK[3])
      if (!cmsPipelineEvalReverseFloat(LabK, Outf, Outf, bp ->LabK2cmyk)) {
  
          // Cannot find a suitable value, so use colorimetric xform
          // which is already stored in Out[]
          return TRUE;
      }
  
      // Make sure to pass thru K (which now is fixed)
      Outf[3] = LabK[3];
  
      // Apply TAC if needed
      SumCMY   = Outf[0]  + Outf[1] + Outf[2];
      SumCMYK  = SumCMY + Outf[3];
  
      if (SumCMYK > bp ->MaxTAC) {
  
          Ratio = 1 - ((SumCMYK - bp->MaxTAC) / SumCMY);
          if (Ratio < 0)
              Ratio = 0;
      }
      else
         Ratio = 1.0;
  
      Out[0] = _cmsQuickSaturateWord(Outf[0] * Ratio * 65535.0);     // C
      Out[1] = _cmsQuickSaturateWord(Outf[1] * Ratio * 65535.0);     // M
      Out[2] = _cmsQuickSaturateWord(Outf[2] * Ratio * 65535.0);     // Y
      Out[3] = _cmsQuickSaturateWord(Outf[3] * 65535.0);
  
      // Estimate the error (this goes 16 bits to Lab DBL)
      cmsDoTransform(bp->hProofOutput, Out, &BlackPreservingLab, 1);
      Error = cmsDeltaE(&ColorimetricLab, &BlackPreservingLab);
      if (Error > bp -> MaxError)
          bp->MaxError = Error;
  
      return TRUE;
  }
  
  // This is the entry for black-plane preserving, which are non-ICC
  static
  cmsPipeline* BlackPreservingKPlaneIntents(cmsContext     ContextID,
                                            cmsUInt32Number nProfiles,
                                            cmsUInt32Number TheIntents[],
                                            cmsHPROFILE     hProfiles[],
                                            cmsBool         BPC[],
                                            cmsFloat64Number AdaptationStates[],
                                            cmsUInt32Number dwFlags)
  {
      PreserveKPlaneParams bp;
      cmsPipeline*    Result = NULL;
      cmsUInt32Number ICCIntents[256];
      cmsStage*         CLUT;
      cmsUInt32Number i, nGridPoints;
      cmsHPROFILE hLab;
  
      // Sanity check
      if (nProfiles < 1 || nProfiles > 255) return NULL;
  
      // Translate black-preserving intents to ICC ones
      for (i=0; i < nProfiles; i++)
          ICCIntents[i] = TranslateNonICCIntents(TheIntents[i]);
  
      // Check for non-cmyk profiles
      if (cmsGetColorSpace(hProfiles[0]) != cmsSigCmykData ||
          !(cmsGetColorSpace(hProfiles[nProfiles-1]) == cmsSigCmykData ||
          cmsGetDeviceClass(hProfiles[nProfiles-1]) == cmsSigOutputClass))
             return  DefaultICCintents(ContextID, nProfiles, ICCIntents, hProfiles, BPC, AdaptationStates, dwFlags);
  
      // Allocate an empty LUT for holding the result
      Result = cmsPipelineAlloc(ContextID, 4, 4);
      if (Result == NULL) return NULL;
  
  
      memset(&bp, 0, sizeof(bp));
  
      // We need the input LUT of the last profile, assuming this one is responsible of
      // black generation. This LUT will be seached in inverse order.
      bp.LabK2cmyk = _cmsReadInputLUT(hProfiles[nProfiles-1], INTENT_RELATIVE_COLORIMETRIC);
      if (bp.LabK2cmyk == NULL) goto Cleanup;
  
      // Get total area coverage (in 0..1 domain)
      bp.MaxTAC = cmsDetectTAC(hProfiles[nProfiles-1]) / 100.0;
      if (bp.MaxTAC <= 0) goto Cleanup;
  
  
      // Create a LUT holding normal ICC transform
      bp.cmyk2cmyk = DefaultICCintents(ContextID,
                                           nProfiles,
                                           ICCIntents,
                                           hProfiles,
                                           BPC,
                                           AdaptationStates,
                                           dwFlags);
      if (bp.cmyk2cmyk == NULL) goto Cleanup;
  
      // Now the tone curve
      bp.KTone = _cmsBuildKToneCurve(ContextID, 4096, nProfiles,
                                     ICCIntents,
                                     hProfiles,
                                     BPC,
                                     AdaptationStates,
                                     dwFlags);
      if (bp.KTone == NULL) goto Cleanup;
  
      // To measure the output, Last profile to Lab
      hLab = cmsCreateLab4ProfileTHR(ContextID, NULL);
      bp.hProofOutput = cmsCreateTransformTHR(ContextID, hProfiles[nProfiles-1],
                                           CHANNELS_SH(4)|BYTES_SH(2), hLab, TYPE_Lab_DBL,
                                           INTENT_RELATIVE_COLORIMETRIC,
                                           cmsFLAGS_NOCACHE|cmsFLAGS_NOOPTIMIZE);
      if ( bp.hProofOutput == NULL) goto Cleanup;
  
      // Same as anterior, but lab in the 0..1 range
      bp.cmyk2Lab = cmsCreateTransformTHR(ContextID, hProfiles[nProfiles-1],
                                           FLOAT_SH(1)|CHANNELS_SH(4)|BYTES_SH(4), hLab,
                                           FLOAT_SH(1)|CHANNELS_SH(3)|BYTES_SH(4),
                                           INTENT_RELATIVE_COLORIMETRIC,
                                           cmsFLAGS_NOCACHE|cmsFLAGS_NOOPTIMIZE);
      if (bp.cmyk2Lab == NULL) goto Cleanup;
      cmsCloseProfile(hLab);
  
      // Error estimation (for debug only)
      bp.MaxError = 0;
  
      // How many gridpoints are we going to use?
      nGridPoints = _cmsReasonableGridpointsByColorspace(cmsSigCmykData, dwFlags);
  
  
      CLUT = cmsStageAllocCLut16bit(ContextID, nGridPoints, 4, 4, NULL);
      if (CLUT == NULL) goto Cleanup;
  
      cmsPipelineInsertStage(Result, cmsAT_BEGIN, CLUT);
  
      cmsStageSampleCLut16bit(CLUT, BlackPreservingSampler, (void*) &bp, 0);
  
  Cleanup:
  
      if (bp.cmyk2cmyk) cmsPipelineFree(bp.cmyk2cmyk);
      if (bp.cmyk2Lab) cmsDeleteTransform(bp.cmyk2Lab);
      if (bp.hProofOutput) cmsDeleteTransform(bp.hProofOutput);
  
      if (bp.KTone) cmsFreeToneCurve(bp.KTone);
      if (bp.LabK2cmyk) cmsPipelineFree(bp.LabK2cmyk);
  
      return Result;
  }
  
  // Link routines ------------------------------------------------------------------------------------------------------
  
  // Chain several profiles into a single LUT. It just checks the parameters and then calls the handler
  // for the first intent in chain. The handler may be user-defined. Is up to the handler to deal with the
  // rest of intents in chain. A maximum of 255 profiles at time are supported, which is pretty reasonable.
  cmsPipeline* _cmsLinkProfiles(cmsContext     ContextID,
                                cmsUInt32Number nProfiles,
                                cmsUInt32Number TheIntents[],
                                cmsHPROFILE     hProfiles[],
                                cmsBool         BPC[],
                                cmsFloat64Number AdaptationStates[],
                                cmsUInt32Number dwFlags)
  {
      cmsUInt32Number i;
      cmsIntentsList* Intent;
  
      // Make sure a reasonable number of profiles is provided
      if (nProfiles <= 0 || nProfiles > 255) {
           cmsSignalError(ContextID, cmsERROR_RANGE, "Couldn't link '%d' profiles", nProfiles);
          return NULL;
      }
  
      for (i=0; i < nProfiles; i++) {
  
          // Check if black point is really needed or allowed. Note that
          // following Adobe's document:
          // BPC does not apply to devicelink profiles, nor to abs colorimetric,
          // and applies always on V4 perceptual and saturation.
  
          if (TheIntents[i] == INTENT_ABSOLUTE_COLORIMETRIC)
              BPC[i] = FALSE;
  
          if (TheIntents[i] == INTENT_PERCEPTUAL || TheIntents[i] == INTENT_SATURATION) {
  
              // Force BPC for V4 profiles in perceptual and saturation
              if (cmsGetProfileVersion(hProfiles[i]) >= 4.0)
                  BPC[i] = TRUE;
          }
      }
  
      // Search for a handler. The first intent in the chain defines the handler. That would
      // prevent using multiple custom intents in a multiintent chain, but the behaviour of
      // this case would present some issues if the custom intent tries to do things like
      // preserve primaries. This solution is not perfect, but works well on most cases.
  
      Intent = SearchIntent(TheIntents[0]);
      if (Intent == NULL) {
          cmsSignalError(ContextID, cmsERROR_UNKNOWN_EXTENSION, "Unsupported intent '%d'", TheIntents[0]);
          return NULL;
      }
  
      // Call the handler
      return Intent ->Link(ContextID, nProfiles, TheIntents, hProfiles, BPC, AdaptationStates, dwFlags);
  }
  
  // -------------------------------------------------------------------------------------------------
  
  // Get information about available intents. nMax is the maximum space for the supplied "Codes"
  // and "Descriptions" the function returns the total number of intents, which may be greater
  // than nMax, although the matrices are not populated beyond this level.
  cmsUInt32Number CMSEXPORT cmsGetSupportedIntents(cmsUInt32Number nMax, cmsUInt32Number* Codes, char** Descriptions)
  {
      cmsIntentsList* pt;
      cmsUInt32Number nIntents;
  
      for (nIntents=0, pt = Intents; pt != NULL; pt = pt -> Next)
      {
          if (nIntents < nMax) {
              if (Codes != NULL)
                  Codes[nIntents] = pt ->Intent;
  
              if (Descriptions != NULL)
                  Descriptions[nIntents] = pt ->Description;
          }
  
          nIntents++;
      }
  
      return nIntents;
  }
  
  // The plug-in registration. User can add new intents or override default routines
  cmsBool  _cmsRegisterRenderingIntentPlugin(cmsPluginBase* Data)
  {
      cmsPluginRenderingIntent* Plugin = (cmsPluginRenderingIntent*) Data;
      cmsIntentsList* fl;
  
      // Do we have to reset the intents?
      if (Data == NULL) {
  
         Intents = DefaultIntents;
         return TRUE;
      }
  
      fl = SearchIntent(Plugin ->Intent);
  
      if (fl == NULL) {
          fl = (cmsIntentsList*) _cmsPluginMalloc(sizeof(cmsIntentsList));
          if (fl == NULL) return FALSE;
      }
  
      fl ->Intent  = Plugin ->Intent;
      strncpy(fl ->Description, Plugin ->Description, 255);
      fl ->Description[255] = 0;
  
      fl ->Link    = Plugin ->Link;
  
      fl ->Next = Intents;
      Intents = fl;
  
      return TRUE;
  }
  
  