Switch to libcms2-2.6
[openjpeg.git] / thirdparty / liblcms2 / src / cmscnvrt.c
index 8dadc87597901a96631a1a9e891c051ec43f2f65..1a93e83f90655c552e92ab0dd6488915828cd064 100644 (file)
@@ -1,24 +1,24 @@
 //---------------------------------------------------------------------------------
 //
 //  Little Color Management System
-//  Copyright (c) 1998-2010 Marti Maria Saguer
+//  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 
+// 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 
+// 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 
+// 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 
+// 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, 
+cmsPipeline* _cmsLinkProfiles(cmsContext     ContextID,
                               cmsUInt32Number nProfiles,
-                              cmsUInt32Number Intents[], 
-                              cmsHPROFILE     hProfiles[], 
+                              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. 
+// 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, 
+static
+cmsPipeline* DefaultICCintents(cmsContext     ContextID,
                                cmsUInt32Number nProfiles,
-                               cmsUInt32Number Intents[], 
-                               cmsHPROFILE     hProfiles[], 
+                               cmsUInt32Number Intents[],
+                               cmsHPROFILE     hProfiles[],
                                cmsBool         BPC[],
                                cmsFloat64Number AdaptationStates[],
                                cmsUInt32Number dwFlags);
@@ -56,10 +56,10 @@ cmsPipeline* DefaultICCintents(cmsContext     ContextID,
 // 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, 
+cmsPipeline*  BlackPreservingKOnlyIntents(cmsContext     ContextID,
                                           cmsUInt32Number nProfiles,
-                                          cmsUInt32Number Intents[], 
-                                          cmsHPROFILE     hProfiles[], 
+                                          cmsUInt32Number Intents[],
+                                          cmsHPROFILE     hProfiles[],
                                           cmsBool         BPC[],
                                           cmsFloat64Number AdaptationStates[],
                                           cmsUInt32Number dwFlags);
@@ -69,10 +69,10 @@ cmsPipeline*  BlackPreservingKOnlyIntents(cmsContext     ContextID,
 // 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, 
+cmsPipeline*  BlackPreservingKPlaneIntents(cmsContext     ContextID,
                                            cmsUInt32Number nProfiles,
-                                           cmsUInt32Number Intents[], 
-                                           cmsHPROFILE     hProfiles[], 
+                                           cmsUInt32Number Intents[],
+                                           cmsHPROFILE     hProfiles[],
                                            cmsBool         BPC[],
                                            cmsFloat64Number AdaptationStates[],
                                            cmsUInt32Number dwFlags);
@@ -92,7 +92,7 @@ typedef struct _cms_intents_list {
 
 
 // Built-in intents
-static cmsIntentsList DefaultIntents[] = { 
+static cmsIntentsList DefaultIntents[] = {
 
     { INTENT_PERCEPTUAL,                            "Perceptual",                                   DefaultICCintents,            &DefaultIntents[1] },
     { INTENT_RELATIVE_COLORIMETRIC,                 "Relative colorimetric",                        DefaultICCintents,            &DefaultIntents[2] },
@@ -103,35 +103,88 @@ static cmsIntentsList DefaultIntents[] = {
     { 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 } 
+    { INTENT_PRESERVE_K_PLANE_SATURATION,           "Saturation preserving black plane",            BlackPreservingKPlaneIntents, NULL }
 };
 
 
 // A pointer to the begining of the list
-static cmsIntentsList *Intents = DefaultIntents;
+_cmsIntentsPluginChunkType _cmsIntentsPluginChunk = { NULL };
+
+// Duplicates the zone of memory used by the plug-in in the new context
+static
+void DupPluginIntentsList(struct _cmsContext_struct* ctx, 
+                                               const struct _cmsContext_struct* src)
+{
+   _cmsIntentsPluginChunkType newHead = { NULL };
+   cmsIntentsList*  entry;
+   cmsIntentsList*  Anterior = NULL;
+   _cmsIntentsPluginChunkType* head = (_cmsIntentsPluginChunkType*) src->chunks[IntentPlugin];
+
+    // Walk the list copying all nodes
+   for (entry = head->Intents;
+        entry != NULL;
+        entry = entry ->Next) {
+
+            cmsIntentsList *newEntry = ( cmsIntentsList *) _cmsSubAllocDup(ctx ->MemPool, entry, sizeof(cmsIntentsList));
+   
+            if (newEntry == NULL) 
+                return;
+
+            // We want to keep the linked list order, so this is a little bit tricky
+            newEntry -> Next = NULL;
+            if (Anterior)
+                Anterior -> Next = newEntry;
+     
+            Anterior = newEntry;
+
+            if (newHead.Intents == NULL)
+                newHead.Intents = newEntry;
+    }
+
+  ctx ->chunks[IntentPlugin] = _cmsSubAllocDup(ctx->MemPool, &newHead, sizeof(_cmsIntentsPluginChunkType));
+}
+
+void  _cmsAllocIntentsPluginChunk(struct _cmsContext_struct* ctx, 
+                                         const struct _cmsContext_struct* src)
+{
+    if (src != NULL) {
+
+        // Copy all linked list
+        DupPluginIntentsList(ctx, src);
+    }
+    else {
+        static _cmsIntentsPluginChunkType IntentsPluginChunkType = { NULL };
+        ctx ->chunks[IntentPlugin] = _cmsSubAllocDup(ctx ->MemPool, &IntentsPluginChunkType, sizeof(_cmsIntentsPluginChunkType));
+    }
+}
+
 
 // Search the list for a suitable intent. Returns NULL if not found
-static 
-cmsIntentsList* SearchIntent(cmsUInt32Number Intent)
+static
+cmsIntentsList* SearchIntent(cmsContext ContextID, cmsUInt32Number Intent)
 {
+    _cmsIntentsPluginChunkType* ctx = ( _cmsIntentsPluginChunkType*) _cmsContextGetClientChunk(ContextID, IntentPlugin);
     cmsIntentsList* pt;
 
-    for (pt = Intents; pt != NULL; pt = pt -> Next)
+    for (pt = ctx -> Intents; pt != NULL; pt = pt -> Next)
+        if (pt ->Intent == Intent) return pt;
+
+    for (pt = DefaultIntents; 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 
+// 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, 
+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
@@ -164,17 +217,21 @@ void ComputeBlackPointCompensation(const cmsCIEXYZ* BlackPointIn,
 static
 cmsFloat64Number CHAD2Temp(const cmsMAT3* Chad)
 {
-    // Convert D50 across CHAD to get the absolute white point
-     cmsVEC3 d, s;
-     cmsCIEXYZ Dest;
-     cmsCIExyY DestChromaticity;
-     cmsFloat64Number TempK;
+    // 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, Chad, &s);
+    _cmsMAT3eval(&d, &m2, &s);
 
     Dest.X = d.n[VX];
     Dest.Y = d.n[VY];
@@ -190,33 +247,32 @@ cmsFloat64Number CHAD2Temp(const cmsMAT3* Chad)
 
 // Compute a CHAD based on a given temperature
 static
-void Temp2CHAD(cmsMAT3* Chad, cmsFloat64Number Temp)
+    void Temp2CHAD(cmsMAT3* Chad, cmsFloat64Number Temp)
 {
     cmsCIEXYZ White;
     cmsCIExyY ChromaticityOfWhite;
-    
-    cmsWhitePointFromTemp(&ChromaticityOfWhite, Temp);  
-    cmsxyY2XYZ(&White, &ChromaticityOfWhite);
-    _cmsAdaptationMatrix(Chad, NULL, cmsD50_XYZ(), &White);
 
+    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 cmsCIEXYZ* WhitePointIn,
                                const cmsMAT3* ChromaticAdaptationMatrixIn,
                                const cmsCIEXYZ* WhitePointOut,
                                const cmsMAT3* ChromaticAdaptationMatrixOut,
                                cmsMAT3* m)
 {
-    cmsMAT3 Scale, m1, m2, m3;
+    cmsMAT3 Scale, m1, m2, m3, m4;
 
     // Adaptation state
     if (AdaptationState == 1.0) {
 
-        // Observer is fully adapted. Keep chromatic adaptation. 
+        // 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);
@@ -230,23 +286,32 @@ cmsBool  ComputeAbsoluteIntent(cmsFloat64Number AdaptationState,
         _cmsVEC3init(&Scale.v[1], 0,  WhitePointIn->Y / WhitePointOut->Y, 0);
         _cmsVEC3init(&Scale.v[2], 0, 0,  WhitePointIn->Z / WhitePointOut->Z);
 
-        m1 = *ChromaticAdaptationMatrixIn;
-        if (!_cmsMAT3inverse(&m1, &m2)) return FALSE; 
-        _cmsMAT3per(&m3, &m2, &Scale);
 
-        // m3 holds CHAD from input white to D50 times abs. col. scaling
         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);
+            _cmsMAT3per(m, &m2, ChromaticAdaptationMatrixOut);
+
+            m3 = *ChromaticAdaptationMatrixIn;
+            if (!_cmsMAT3inverse(&m3, &m4)) return FALSE;
+            _cmsMAT3per(m, &m2, &m4);
 
         } else {
 
             cmsMAT3 MixedCHAD;
             cmsFloat64Number TempSrc, TempDest, Temp;
 
-            TempSrc  = CHAD2Temp(ChromaticAdaptationMatrixIn);  // K for source white
-            TempDest = CHAD2Temp(ChromaticAdaptationMatrixOut); // K for dest white
+            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
 
@@ -256,9 +321,9 @@ cmsBool  ComputeAbsoluteIntent(cmsFloat64Number AdaptationState,
                 return TRUE;
             }
 
-            Temp = AdaptationState * TempSrc + (1.0 - AdaptationState) * TempDest;
+            Temp = (1.0 - AdaptationState) * TempDest + AdaptationState * TempSrc;
 
-            // Get a CHAD from D50 to whatever output temperature. This replaces output CHAD
+            // Get a CHAD from whatever output temperature to D50. This replaces output CHAD
             Temp2CHAD(&MixedCHAD, Temp);
 
             _cmsMAT3per(m, &m3, &MixedCHAD);
@@ -276,12 +341,12 @@ 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]);
 
@@ -295,10 +360,10 @@ cmsBool IsEmptyLayer(cmsMAT3* m, cmsVEC3* off)
 
 // Compute the conversion layer
 static
-cmsBool ComputeConversion(int i, cmsHPROFILE hProfiles[], 
-                                 cmsUInt32Number Intent, 
-                                 cmsBool BPC, 
-                                 cmsFloat64Number AdaptationState, 
+cmsBool ComputeConversion(int i, cmsHPROFILE hProfiles[],
+                                 cmsUInt32Number Intent,
+                                 cmsBool BPC,
+                                 cmsFloat64Number AdaptationState,
                                  cmsMAT3* m, cmsVEC3* off)
 {
 
@@ -312,7 +377,7 @@ cmsBool ComputeConversion(int i, cmsHPROFILE hProfiles[],
     if (Intent == INTENT_ABSOLUTE_COLORIMETRIC) {
 
         cmsCIEXYZ WhitePointIn, WhitePointOut;
-        cmsMAT3 ChromaticAdaptationMatrixIn, ChromaticAdaptationMatrixOut;  
+        cmsMAT3 ChromaticAdaptationMatrixIn, ChromaticAdaptationMatrixOut;
 
         _cmsReadMediaWhitePoint(&WhitePointIn,  hProfiles[i-1]);
         _cmsReadCHAD(&ChromaticAdaptationMatrixIn, hProfiles[i-1]);
@@ -320,8 +385,8 @@ cmsBool ComputeConversion(int i, cmsHPROFILE hProfiles[],
         _cmsReadMediaWhitePoint(&WhitePointOut,  hProfiles[i]);
         _cmsReadCHAD(&ChromaticAdaptationMatrixOut, hProfiles[i]);
 
-        if (!ComputeAbsoluteIntent(AdaptationState, 
-                                  &WhitePointIn,  &ChromaticAdaptationMatrixIn, 
+        if (!ComputeAbsoluteIntent(AdaptationState,
+                                  &WhitePointIn,  &ChromaticAdaptationMatrixIn,
                                   &WhitePointOut, &ChromaticAdaptationMatrixOut, m)) return FALSE;
 
     }
@@ -333,24 +398,24 @@ cmsBool ComputeConversion(int i, cmsHPROFILE hProfiles[],
             cmsCIEXYZ BlackPointIn, BlackPointOut;
 
             cmsDetectBlackPoint(&BlackPointIn,  hProfiles[i-1], Intent, 0);
-            cmsDetectBlackPoint(&BlackPointOut, hProfiles[i], 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) 
+                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. 
+    // 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) 
+    // y' = (Mx'c + Off) /c = Mx' + (Off / c)
 
     for (k=0; k < 3; k++) {
         off ->n[k] /= MAX_ENCODEABLE_XYZ;
@@ -360,7 +425,7 @@ cmsBool ComputeConversion(int i, cmsHPROFILE hProfiles[],
 }
 
 
-// Add a conversion stage if needed. If a matrix/offset m is given, it applies to XYZ space 
+// 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)
 {
@@ -370,57 +435,61 @@ cmsBool AddConversion(cmsPipeline* Result, cmsColorSpaceSignature InPCS, cmsColo
     // Handle PCS mismatches. A specialized stage is added to the LUT in such case
     switch (InPCS) {
 
-        case cmsSigXYZData: // Input profile operates in XYZ
+    case cmsSigXYZData: // Input profile operates in XYZ
 
-            switch (OutPCS) {
+        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 cmsSigXYZData:  // XYZ -> XYZ
+            if (!IsEmptyLayer(m, off) &&
+                !cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl)))
+                return FALSE;
+            break;
 
+        case cmsSigLabData:  // XYZ -> Lab
+            if (!IsEmptyLayer(m, off) &&
+                !cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl)))
+                return FALSE;
+            if (!cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocXYZ2Lab(Result ->ContextID)))
+                return FALSE;
+            break;
 
-        case cmsSigLabData: // Input profile operates in Lab
+        default:
+            return FALSE;   // Colorspace mismatch
+        }
+        break;
 
-            switch (OutPCS) {
+    case cmsSigLabData: // Input profile operates in Lab
 
-            case cmsSigXYZData:  // Lab -> XYZ
+        switch (OutPCS) {
 
-                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 cmsSigXYZData:  // Lab -> XYZ
 
-            case cmsSigLabData:  // Lab -> Lab
+            if (!cmsPipelineInsertStage(Result, cmsAT_END, _cmsStageAllocLab2XYZ(Result ->ContextID)))
+                return FALSE;
+            if (!IsEmptyLayer(m, off) &&
+                !cmsPipelineInsertStage(Result, cmsAT_END, cmsStageAllocMatrix(Result ->ContextID, 3, 3, m_as_dbl, off_as_dbl)))
+                return FALSE;
+            break;
 
-                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;
+        case cmsSigLabData:  // Lab -> Lab
 
-            default:
-                return FALSE;  // Mismatch
+            if (!IsEmptyLayer(m, off)) {
+                if (!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)))
+                    return FALSE;
             }
             break;
 
-
-            // On colorspaces other than PCS, check for same space
         default:
-            if (InPCS != OutPCS) return FALSE;
-            break;
+            return FALSE;  // Mismatch
+        }
+        break;
+
+        // On colorspaces other than PCS, check for same space
+    default:
+        if (InPCS != OutPCS) return FALSE;
+        break;
     }
 
     return TRUE;
@@ -434,6 +503,10 @@ cmsBool ColorSpaceIsCompatible(cmsColorSpaceSignature a, cmsColorSpaceSignature
     // 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;
@@ -444,30 +517,31 @@ cmsBool ColorSpaceIsCompatible(cmsColorSpaceSignature a, cmsColorSpaceSignature
 
 // Default handler for ICC-style intents
 static
-cmsPipeline* DefaultICCintents(cmsContext       ContextID, 
+cmsPipeline* DefaultICCintents(cmsContext       ContextID,
                                cmsUInt32Number  nProfiles,
-                               cmsUInt32Number  TheIntents[], 
-                               cmsHPROFILE      hProfiles[], 
+                               cmsUInt32Number  TheIntents[],
+                               cmsHPROFILE      hProfiles[],
                                cmsBool          BPC[],
                                cmsFloat64Number AdaptationStates[],
                                cmsUInt32Number  dwFlags)
 {
-    cmsPipeline* Lut, *Result;
+    cmsPipeline* Lut = NULL;
+    cmsPipeline* Result;
     cmsHPROFILE hProfile;
     cmsMAT3 m;
     cmsVEC3 off;
-    cmsColorSpaceSignature ColorSpaceIn, ColorSpaceOut, CurrentColorSpace; 
+    cmsColorSpaceSignature ColorSpaceIn, ColorSpaceOut, CurrentColorSpace;
     cmsProfileClassSignature ClassSig;
     cmsUInt32Number  i, Intent;
 
-       // For safety
-       if (nProfiles == 0) return NULL;
+    // 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]);    
+    CurrentColorSpace = cmsGetColorSpace(hProfiles[0]);
 
     for (i=0; i < nProfiles; i++) {
 
@@ -476,16 +550,16 @@ cmsPipeline* DefaultICCintents(cmsContext       ContextID,
         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
+        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];
 
@@ -506,9 +580,9 @@ cmsPipeline* DefaultICCintents(cmsContext       ContextID,
             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) {
+        // 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);
@@ -522,7 +596,7 @@ cmsPipeline* DefaultICCintents(cmsContext       ContextID,
                 _cmsMAT3identity(&m);
                 _cmsVEC3init(&off, 0, 0, 0);
              }
-        
+
 
             if (!AddConversion(Result, CurrentColorSpace, ColorSpaceIn, &m, &off)) goto Error;
 
@@ -531,13 +605,13 @@ cmsPipeline* DefaultICCintents(cmsContext       ContextID,
 
             if (lIsInput) {
                 // Input direction means non-pcs connection, so proceed like devicelinks
-                Lut = _cmsReadInputLUT(hProfile, Intent);       
+                Lut = _cmsReadInputLUT(hProfile, Intent);
                 if (Lut == NULL) goto Error;
             }
             else {
 
                 // Output direction means PCS connection. Intent may apply here
-                Lut = _cmsReadOutputLUT(hProfile, Intent); 
+                Lut = _cmsReadOutputLUT(hProfile, Intent);
                 if (Lut == NULL) goto Error;
 
 
@@ -548,17 +622,21 @@ cmsPipeline* DefaultICCintents(cmsContext       ContextID,
         }
 
         // Concatenate to the output LUT
-        cmsPipelineCat(Result, Lut);
-        cmsPipelineFree(Lut);                            
+        if (!cmsPipelineCat(Result, Lut))
+            goto Error;
+
+        cmsPipelineFree(Lut);
+        Lut = NULL;
 
         // Update current space
-        CurrentColorSpace = ColorSpaceOut;              
+        CurrentColorSpace = ColorSpaceOut;
     }
 
     return Result;
 
 Error:
 
+    if (Lut != NULL) cmsPipelineFree(Lut);
     if (Result != NULL) cmsPipelineFree(Result);
     return NULL;
 
@@ -567,10 +645,10 @@ Error:
 
 
 // Wrapper for DLL calling convention
-cmsPipeline*  CMSEXPORT _cmsDefaultICCintents(cmsContext     ContextID, 
+cmsPipeline*  CMSEXPORT _cmsDefaultICCintents(cmsContext     ContextID,
                                               cmsUInt32Number nProfiles,
-                                              cmsUInt32Number TheIntents[], 
-                                              cmsHPROFILE     hProfiles[], 
+                                              cmsUInt32Number TheIntents[],
+                                              cmsHPROFILE     hProfiles[],
                                               cmsBool         BPC[],
                                               cmsFloat64Number AdaptationStates[],
                                               cmsUInt32Number dwFlags)
@@ -587,7 +665,7 @@ int TranslateNonICCIntents(int Intent)
     switch (Intent) {
         case INTENT_PRESERVE_K_ONLY_PERCEPTUAL:
         case INTENT_PRESERVE_K_PLANE_PERCEPTUAL:
-            return INTENT_PERCEPTUAL; 
+            return INTENT_PERCEPTUAL;
 
         case INTENT_PRESERVE_K_ONLY_RELATIVE_COLORIMETRIC:
         case INTENT_PRESERVE_K_PLANE_RELATIVE_COLORIMETRIC:
@@ -632,10 +710,10 @@ int BlackPreservingGrayOnlySampler(register const cmsUInt16Number In[], register
 
 // This is the entry for black-preserving K-only intents, which are non-ICC
 static
-cmsPipeline*  BlackPreservingKOnlyIntents(cmsContext     ContextID, 
+cmsPipeline*  BlackPreservingKOnlyIntents(cmsContext     ContextID,
                                           cmsUInt32Number nProfiles,
-                                          cmsUInt32Number TheIntents[], 
-                                          cmsHPROFILE     hProfiles[], 
+                                          cmsUInt32Number TheIntents[],
+                                          cmsHPROFILE     hProfiles[],
                                           cmsBool         BPC[],
                                           cmsFloat64Number AdaptationStates[],
                                           cmsUInt32Number dwFlags)
@@ -651,12 +729,12 @@ cmsPipeline*  BlackPreservingKOnlyIntents(cmsContext     ContextID,
     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]); 
+    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) 
+        cmsGetColorSpace(hProfiles[nProfiles-1]) != cmsSigCmykData)
            return DefaultICCintents(ContextID, nProfiles, ICCIntents, hProfiles, BPC, AdaptationStates, dwFlags);
 
     memset(&bp, 0, sizeof(bp));
@@ -666,43 +744,44 @@ cmsPipeline*  BlackPreservingKOnlyIntents(cmsContext     ContextID,
     if (Result == NULL) return NULL;
 
     // Create a LUT holding normal ICC transform
-    bp.cmyk2cmyk = DefaultICCintents(ContextID, 
+    bp.cmyk2cmyk = DefaultICCintents(ContextID,
         nProfiles,
-        ICCIntents, 
-        hProfiles, 
+        ICCIntents,
+        hProfiles,
         BPC,
         AdaptationStates,
         dwFlags);
 
     if (bp.cmyk2cmyk == NULL) goto Error;
-    
+
     // Now, compute the tone curve
-    bp.KTone = _cmsBuildKToneCurve(ContextID, 
-        4096, 
+    bp.KTone = _cmsBuildKToneCurve(ContextID,
+        4096,
         nProfiles,
-        ICCIntents, 
-        hProfiles, 
+        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);
+    if (!cmsPipelineInsertStage(Result, cmsAT_BEGIN, CLUT))
+        goto Error;
 
     // Sample it. We cannot afford pre/post linearization this time.
-    if (!cmsStageSampleCLut16bit(CLUT, BlackPreservingGrayOnlySampler, (void*) &bp, 0)) 
+    if (!cmsStageSampleCLut16bit(CLUT, BlackPreservingGrayOnlySampler, (void*) &bp, 0))
         goto Error;
-    
+
     // Get rid of xform and tone curve
     cmsPipelineFree(bp.cmyk2cmyk);
     cmsFreeToneCurve(bp.KTone);
@@ -729,9 +808,9 @@ typedef struct {
     cmsPipeline*     LabK2cmyk;     // The output profile
     cmsFloat64Number MaxError;
 
-    cmsHTRANSFORM    hRoundTrip;               
+    cmsHTRANSFORM    hRoundTrip;
     cmsFloat64Number MaxTAC;
-    
+
 
 } PreserveKPlaneParams;
 
@@ -742,18 +821,18 @@ int BlackPreservingSampler(register const cmsUInt16Number In[], register cmsUInt
 {
     int i;
     cmsFloat32Number Inf[4], Outf[4];
-    cmsFloat32Number LabK[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++) 
+    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) {
 
@@ -761,28 +840,28 @@ int BlackPreservingSampler(register const cmsUInt16Number In[], register cmsUInt
         Out[3] = _cmsQuickSaturateWord(LabK[3] * 65535.0);
         return TRUE;
     }
-    
-    // Try the original transform, 
-    cmsPipelineEvalFloat( Inf, Outf, bp ->cmyk2cmyk);  
-    
+
+    // 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++) 
+    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 
+    // Obtain the corresponding CMY using reverse interpolation
     // (K is fixed in LabK[3])
     if (!cmsPipelineEvalReverseFloat(LabK, Outf, Outf, bp ->LabK2cmyk)) {
 
@@ -793,10 +872,10 @@ int BlackPreservingSampler(register const cmsUInt16Number In[], register cmsUInt
 
     // Make sure to pass thru K (which now is fixed)
     Outf[3] = LabK[3];
-       
-    // Apply TAC if needed    
+
+    // Apply TAC if needed
     SumCMY   = Outf[0]  + Outf[1] + Outf[2];
-    SumCMYK  = SumCMY + Outf[3];      
+    SumCMYK  = SumCMY + Outf[3];
 
     if (SumCMYK > bp ->MaxTAC) {
 
@@ -813,9 +892,9 @@ int BlackPreservingSampler(register const cmsUInt16Number In[], register cmsUInt
     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) 
+    cmsDoTransform(bp->hProofOutput, Out, &BlackPreservingLab, 1);
+    Error = cmsDeltaE(&ColorimetricLab, &BlackPreservingLab);
+    if (Error > bp -> MaxError)
         bp->MaxError = Error;
 
     return TRUE;
@@ -823,10 +902,10 @@ int BlackPreservingSampler(register const cmsUInt16Number In[], register cmsUInt
 
 // This is the entry for black-plane preserving, which are non-ICC
 static
-cmsPipeline* BlackPreservingKPlaneIntents(cmsContext     ContextID, 
+cmsPipeline* BlackPreservingKPlaneIntents(cmsContext     ContextID,
                                           cmsUInt32Number nProfiles,
-                                          cmsUInt32Number TheIntents[], 
-                                          cmsHPROFILE     hProfiles[], 
+                                          cmsUInt32Number TheIntents[],
+                                          cmsHPROFILE     hProfiles[],
                                           cmsBool         BPC[],
                                           cmsFloat64Number AdaptationStates[],
                                           cmsUInt32Number dwFlags)
@@ -835,26 +914,27 @@ cmsPipeline* BlackPreservingKPlaneIntents(cmsContext     ContextID,
     cmsPipeline*    Result = NULL;
     cmsUInt32Number ICCIntents[256];
     cmsStage*         CLUT;
-    cmsUInt32Number i, nGridPoints;    
+    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]); 
+    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) 
+        !(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
@@ -864,38 +944,43 @@ cmsPipeline* BlackPreservingKPlaneIntents(cmsContext     ContextID,
 
     // 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, 
+                                         ICCIntents,
+                                         hProfiles,
                                          BPC,
                                          AdaptationStates,
                                          dwFlags);
+    if (bp.cmyk2cmyk == NULL) goto Cleanup;
 
     // Now the tone curve
     bp.KTone = _cmsBuildKToneCurve(ContextID, 4096, nProfiles,
-                                   ICCIntents, 
-                                   hProfiles, 
-                                   BPC, 
+                                   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, 
+    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, 
+    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)
@@ -904,21 +989,22 @@ cmsPipeline* BlackPreservingKPlaneIntents(cmsContext     ContextID,
     // 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);
+    if (!cmsPipelineInsertStage(Result, cmsAT_BEGIN, CLUT))
+        goto Cleanup;
 
     cmsStageSampleCLut16bit(CLUT, BlackPreservingSampler, (void*) &bp, 0);
 
 Cleanup:
 
     if (bp.cmyk2cmyk) cmsPipelineFree(bp.cmyk2cmyk);
-    if (bp.cmyk2Lab) cmsDeleteTransform(bp.cmyk2Lab);       
+    if (bp.cmyk2Lab) cmsDeleteTransform(bp.cmyk2Lab);
     if (bp.hProofOutput) cmsDeleteTransform(bp.hProofOutput);
-    
-    if (bp.KTone) cmsFreeToneCurve(bp.KTone);   
+
+    if (bp.KTone) cmsFreeToneCurve(bp.KTone);
     if (bp.LabK2cmyk) cmsPipelineFree(bp.LabK2cmyk);
 
     return Result;
@@ -927,12 +1013,12 @@ Cleanup:
 // 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 
+// 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, 
+cmsPipeline* _cmsLinkProfiles(cmsContext     ContextID,
                               cmsUInt32Number nProfiles,
-                              cmsUInt32Number TheIntents[], 
-                              cmsHPROFILE     hProfiles[], 
+                              cmsUInt32Number TheIntents[],
+                              cmsHPROFILE     hProfiles[],
                               cmsBool         BPC[],
                               cmsFloat64Number AdaptationStates[],
                               cmsUInt32Number dwFlags)
@@ -948,9 +1034,9 @@ cmsPipeline* _cmsLinkProfiles(cmsContext     ContextID,
 
     for (i=0; i < nProfiles; i++) {
 
-        // Check if black point is really needed or allowed. Note that 
+        // 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, 
+        // 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)
@@ -961,7 +1047,7 @@ cmsPipeline* _cmsLinkProfiles(cmsContext     ContextID,
             // 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
@@ -969,11 +1055,11 @@ cmsPipeline* _cmsLinkProfiles(cmsContext     ContextID,
     // 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;
-       }
+    Intent = SearchIntent(ContextID, 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);
@@ -981,58 +1067,75 @@ cmsPipeline* _cmsLinkProfiles(cmsContext     ContextID,
 
 // -------------------------------------------------------------------------------------------------
 
-// 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 
+// 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)
+cmsUInt32Number CMSEXPORT cmsGetSupportedIntentsTHR(cmsContext ContextID, cmsUInt32Number nMax, cmsUInt32Number* Codes, char** Descriptions)
 {
+    _cmsIntentsPluginChunkType* ctx = ( _cmsIntentsPluginChunkType*) _cmsContextGetClientChunk(ContextID, IntentPlugin);
     cmsIntentsList* pt;
     cmsUInt32Number nIntents;
 
-    for (nIntents=0, pt = Intents; pt != NULL; pt = pt -> Next)
+
+    for (nIntents=0, pt = ctx->Intents; pt != NULL; pt = pt -> Next)
     {
         if (nIntents < nMax) {
-            if (Codes != NULL) 
+            if (Codes != NULL)
                 Codes[nIntents] = pt ->Intent;
 
-            if (Descriptions != NULL) 
+            if (Descriptions != NULL)
                 Descriptions[nIntents] = pt ->Description;
         }
 
         nIntents++;
     }
 
+    for (nIntents=0, pt = DefaultIntents; 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;
 }
 
+cmsUInt32Number CMSEXPORT cmsGetSupportedIntents(cmsUInt32Number nMax, cmsUInt32Number* Codes, char** Descriptions)
+{
+    return cmsGetSupportedIntentsTHR(NULL, nMax, Codes, Descriptions);
+}
+
 // The plug-in registration. User can add new intents or override default routines
-cmsBool  _cmsRegisterRenderingIntentPlugin(cmsPluginBase* Data)
+cmsBool  _cmsRegisterRenderingIntentPlugin(cmsContext id, cmsPluginBase* Data)
 {
+    _cmsIntentsPluginChunkType* ctx = ( _cmsIntentsPluginChunkType*) _cmsContextGetClientChunk(id, IntentPlugin);
     cmsPluginRenderingIntent* Plugin = (cmsPluginRenderingIntent*) Data;
     cmsIntentsList* fl;
 
-    // Do we have to reset the intents?
+    // Do we have to reset the custom intents?
     if (Data == NULL) {
-    
-       Intents = DefaultIntents;
-       return TRUE;
+
+        ctx->Intents = NULL;
+        return TRUE;
     }
 
-    fl = SearchIntent(Plugin ->Intent);
+    fl = (cmsIntentsList*) _cmsPluginMalloc(id, sizeof(cmsIntentsList));
+    if (fl == NULL) return FALSE;
 
-    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;
+    strncpy(fl ->Description, Plugin ->Description, sizeof(fl ->Description)-1);
+    fl ->Description[sizeof(fl ->Description)-1] = 0;
 
     fl ->Link    = Plugin ->Link;
 
-    fl ->Next = Intents;
-    Intents = fl;
+    fl ->Next = ctx ->Intents;
+    ctx ->Intents = fl;
 
     return TRUE;
 }