root/testbed/testplugin.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. DupContext
  2. CheckAllocContext
  3. CheckSimpleContext
  4. CheckAlarmColorsContext
  5. CheckAdaptationStateContext
  6. Fake1Dfloat
  7. Fake3D16
  8. my_Interpolators_Factory
  9. CheckInterp1DPlugin
  10. CheckInterp3DPlugin
  11. my_fns
  12. my_fns2
  13. Rec709Math
  14. CheckParametricCurvePlugin
  15. my_Unroll565
  16. my_Pack565
  17. my_FormatterFactory
  18. my_FormatterFactory2
  19. CheckFormattersPlugin
  20. Type_int_Read
  21. Type_int_Write
  22. Type_int_Dup
  23. Type_int_Free
  24. CheckTagTypePlugin
  25. EvaluateNegate
  26. StageAllocNegate
  27. Type_negate_Read
  28. Type_negate_Write
  29. CheckMPEPlugin
  30. FastEvaluateCurves
  31. MyOptimize
  32. CheckOptimizationPlugin
  33. MyNewIntent
  34. CheckIntentPlugin
  35. TrancendentalTransform
  36. TransformFactory
  37. CheckTransformPlugin
  38. MyMtxCreate
  39. MyMtxDestroy
  40. MyMtxLock
  41. MyMtxUnlock
  42. CheckMutexPlugin

//---------------------------------------------------------------------------------
//
//  Little Color Management System
//  Copyright (c) 1998-2014 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 "testcms2.h"

// --------------------------------------------------------------------------------------------------
// Auxiliar, duplicate a context and mark the block as non-debug because in this case the allocator 
// and deallocator have different context owners
// --------------------------------------------------------------------------------------------------

static
cmsContext DupContext(cmsContext src, void* Data)
{
    cmsContext cpy = cmsDupContext(src, Data);

    DebugMemDontCheckThis(cpy);

    return cpy;
}

// --------------------------------------------------------------------------------------------------
// Simple context functions
// --------------------------------------------------------------------------------------------------

// Allocation order
cmsInt32Number CheckAllocContext(void)
{
     cmsContext c1, c2, c3, c4;


     c1 = cmsCreateContext(NULL, NULL);                 // This creates a context by using the normal malloc
     DebugMemDontCheckThis(c1);
     cmsDeleteContext(c1); 

     c2 = cmsCreateContext(PluginMemHandler(), NULL);   // This creates a context by using the debug malloc
     DebugMemDontCheckThis(c2);
     cmsDeleteContext(c2); 

     c1 = cmsCreateContext(NULL, NULL); 
     DebugMemDontCheckThis(c1);

     c2 = cmsCreateContext(PluginMemHandler(), NULL);  
     DebugMemDontCheckThis(c2);

     cmsPluginTHR(c1, PluginMemHandler()); // Now the context have custom allocators

     c3 = DupContext(c1, NULL);     
     c4 = DupContext(c2, NULL);
     


     cmsDeleteContext(c1);  // Should be deleted by using nomal malloc
     cmsDeleteContext(c2);  // Should be deleted by using debug malloc
     cmsDeleteContext(c3);  // Should be deleted by using nomal malloc
     cmsDeleteContext(c4);  // Should be deleted by using debug malloc

     return 1;
}

// Test the very basic context capabilities
cmsInt32Number CheckSimpleContext(void)
{
    int a = 1;
    int b = 32;
    cmsInt32Number rc = 0;

    cmsContext c1, c2, c3;

    // This function creates a context with a special 
    // memory manager that check allocation
    c1 = WatchDogContext(&a);
    cmsDeleteContext(c1);

    c1 = WatchDogContext(&a);
    
    // Let's check duplication
    c2 = DupContext(c1, NULL);    
    c3 = DupContext(c2, NULL);    

    // User data should have been propagated
    rc = (*(int*) cmsGetContextUserData(c3)) == 1 ;

    // Free resources
    cmsDeleteContext(c1);
    cmsDeleteContext(c2);
    cmsDeleteContext(c3);

    if (!rc) {
        Fail("Creation of user data failed");
        return 0;
    }

    // Back to create 3 levels of inherance
    c1 = cmsCreateContext(NULL, &a);
    DebugMemDontCheckThis(c1);

    c2 = DupContext(c1, NULL);
    c3 = DupContext(c2, &b);    

    rc = (*(int*) cmsGetContextUserData(c3)) == 32 ;

    cmsDeleteContext(c1);
    cmsDeleteContext(c2);
    cmsDeleteContext(c3);

    if (!rc) {
        Fail("Modification of user data failed");
        return 0;
    }

    // All seems ok
    return rc;
}




// --------------------------------------------------------------------------------------------------
//Alarm color functions
// --------------------------------------------------------------------------------------------------

// This function tests the alarm codes across contexts
cmsInt32Number CheckAlarmColorsContext(void)
{
    cmsInt32Number rc = 0;
    const cmsUInt16Number codes[] = {0x0000, 0x1111, 0x2222, 0x3333, 0x4444, 0x5555, 0x6666, 0x7777, 0x8888, 0x9999, 0xaaaa, 0xbbbb, 0xcccc, 0xdddd, 0xeeee, 0xffff};
    cmsUInt16Number out[16];
    cmsContext c1, c2, c3;
    int i;

    c1 = WatchDogContext(NULL);

    cmsSetAlarmCodesTHR(c1, codes);
    c2 = DupContext(c1, NULL);
    c3 = DupContext(c2, NULL);
    
    cmsGetAlarmCodesTHR(c3, out);

    rc = 1;
    for (i=0; i < 16; i++) {
        if (out[i] != codes[i]) {
            Fail("Bad alarm code %x != %x", out[i], codes[i]);
            rc = 0;
            break;
        }
    }

    cmsDeleteContext(c1);
    cmsDeleteContext(c2);
    cmsDeleteContext(c3);

    return rc;
}


// --------------------------------------------------------------------------------------------------
//Adaptation state functions
// --------------------------------------------------------------------------------------------------

// Similar to the previous, but for adaptation state
cmsInt32Number CheckAdaptationStateContext(void)
{
    cmsInt32Number rc = 0;
    cmsContext c1, c2, c3;
    cmsFloat64Number old1, old2;

    old1 =  cmsSetAdaptationStateTHR(NULL, -1);

    c1 = WatchDogContext(NULL);

    cmsSetAdaptationStateTHR(c1, 0.7);

    c2 = DupContext(c1, NULL);    
    c3 = DupContext(c2, NULL);
    
    rc = IsGoodVal("Adaptation state", cmsSetAdaptationStateTHR(c3, -1), 0.7, 0.001);

    cmsDeleteContext(c1);
    cmsDeleteContext(c2);
    cmsDeleteContext(c3);
   
    old2 =  cmsSetAdaptationStateTHR(NULL, -1);

    if (old1 != old2) {
        Fail("Adaptation state has changed");
        return 0;
    }

    return rc;
}

// --------------------------------------------------------------------------------------------------
// Interpolation plugin check: A fake 1D and 3D interpolation will be used to test the functionality. 
// --------------------------------------------------------------------------------------------------

// This fake interpolation takes always the closest lower node in the interpolation table for 1D 
static
void Fake1Dfloat(const cmsFloat32Number Value[], 
                    cmsFloat32Number Output[],  
                    const cmsInterpParams* p)
{
       cmsFloat32Number val2;
       int cell;
       const cmsFloat32Number* LutTable = (const cmsFloat32Number*) p ->Table;

       // Clip upper values
       if (Value[0] >= 1.0) {
           Output[0] = LutTable[p -> Domain[0]]; 
           return; 
       }

       val2 = p -> Domain[0] * Value[0];
       cell = (int) floor(val2);
       Output[0] =  LutTable[cell] ;
}

// This fake interpolation just uses scrambled negated indexes for output
static
void Fake3D16(register const cmsUInt16Number Input[],
              register cmsUInt16Number Output[],
              register const struct _cms_interp_struc* p)
{
       Output[0] =  0xFFFF - Input[2];
       Output[1] =  0xFFFF - Input[1];
       Output[2] =  0xFFFF - Input[0];
}

// The factory chooses interpolation routines on depending on certain conditions.
cmsInterpFunction my_Interpolators_Factory(cmsUInt32Number nInputChannels, 
                                           cmsUInt32Number nOutputChannels, 
                                           cmsUInt32Number dwFlags)
{
    cmsInterpFunction Interpolation;
    cmsBool  IsFloat = (dwFlags & CMS_LERP_FLAGS_FLOAT);

    // Initialize the return to zero as a non-supported mark
    memset(&Interpolation, 0, sizeof(Interpolation));

    // For 1D to 1D and floating point
    if (nInputChannels == 1 && nOutputChannels == 1 && IsFloat) {

        Interpolation.LerpFloat = Fake1Dfloat;
    }
    else
    if (nInputChannels == 3 && nOutputChannels == 3 && !IsFloat) {
    
        // For 3D to 3D and 16 bits
        Interpolation.Lerp16 = Fake3D16;
    }

    // Here is the interpolation 
    return Interpolation;
}

// Interpolation plug-in
static
cmsPluginInterpolation InterpPluginSample = {

    { cmsPluginMagicNumber, 2060, cmsPluginInterpolationSig, NULL }, 
    my_Interpolators_Factory 
};


// This is the check code for 1D interpolation plug-in
cmsInt32Number CheckInterp1DPlugin(void)
{
    cmsToneCurve* Sampled1D = NULL;
    cmsContext ctx = NULL;
    cmsContext cpy = NULL;
    const cmsFloat32Number tab[] = { 0.0f, 0.10f, 0.20f, 0.30f, 0.40f, 0.50f, 0.60f, 0.70f, 0.80f, 0.90f, 1.00f };  // A straight line

    // 1st level context
    ctx = WatchDogContext(NULL);
    if (ctx == NULL) {
        Fail("Cannot create context");
        goto Error;
    }

    cmsPluginTHR(ctx, &InterpPluginSample);

    cpy = DupContext(ctx, NULL);    
     if (cpy == NULL) {
        Fail("Cannot create context (2)");
        goto Error;
    }

    Sampled1D = cmsBuildTabulatedToneCurveFloat(cpy, 11, tab);
    if (Sampled1D == NULL) {
        Fail("Cannot create tone curve (1)");
        goto Error;
    }
    
    // Do some interpolations with the plugin
    if (!IsGoodVal("0.10", cmsEvalToneCurveFloat(Sampled1D, 0.10f), 0.10, 0.01)) goto Error;
    if (!IsGoodVal("0.13", cmsEvalToneCurveFloat(Sampled1D, 0.13f), 0.10, 0.01)) goto Error;
    if (!IsGoodVal("0.55", cmsEvalToneCurveFloat(Sampled1D, 0.55f), 0.50, 0.01)) goto Error;
    if (!IsGoodVal("0.9999", cmsEvalToneCurveFloat(Sampled1D, 0.9999f), 0.90, 0.01)) goto Error;

    cmsFreeToneCurve(Sampled1D);
    cmsDeleteContext(ctx);
    cmsDeleteContext(cpy);
   
    // Now in global context
    Sampled1D = cmsBuildTabulatedToneCurveFloat(NULL, 11, tab);
    if (Sampled1D == NULL) {
        Fail("Cannot create tone curve (2)");
        goto Error;
    }
    
    // Now without the plug-in
    if (!IsGoodVal("0.10", cmsEvalToneCurveFloat(Sampled1D, 0.10f), 0.10, 0.001)) goto Error;
    if (!IsGoodVal("0.13", cmsEvalToneCurveFloat(Sampled1D, 0.13f), 0.13, 0.001)) goto Error;
    if (!IsGoodVal("0.55", cmsEvalToneCurveFloat(Sampled1D, 0.55f), 0.55, 0.001)) goto Error;
    if (!IsGoodVal("0.9999", cmsEvalToneCurveFloat(Sampled1D, 0.9999f), 0.9999, 0.001)) goto Error;

    cmsFreeToneCurve(Sampled1D);
    return 1;

Error:
    if (ctx != NULL) cmsDeleteContext(ctx);
     if (cpy != NULL) cmsDeleteContext(ctx);
    if (Sampled1D != NULL) cmsFreeToneCurve(Sampled1D);
    return 0;

}

// Checks the 3D interpolation
cmsInt32Number CheckInterp3DPlugin(void)
{

    cmsPipeline* p;
    cmsStage* clut;
    cmsContext ctx;
    cmsUInt16Number In[3], Out[3];
    cmsUInt16Number identity[] = { 

       0,       0,       0,      
       0,       0,       0xffff, 
       0,       0xffff,  0,      
       0,       0xffff,  0xffff, 
       0xffff,  0,       0,      
       0xffff,  0,       0xffff, 
       0xffff,  0xffff,  0,      
       0xffff,  0xffff,  0xffff 
    };


    ctx = WatchDogContext(NULL);
    if (ctx == NULL) {
        Fail("Cannot create context");
       return 0;
    }


    cmsPluginTHR(ctx, &InterpPluginSample);


    p =  cmsPipelineAlloc(ctx, 3, 3);
    clut = cmsStageAllocCLut16bit(ctx, 2, 3, 3, identity);
    cmsPipelineInsertStage(p, cmsAT_BEGIN, clut);

    // Do some interpolations with the plugin

    In[0] = 0; In[1] = 0; In[2] = 0;
    cmsPipelineEval16(In, Out, p);

    if (!IsGoodWord("0", Out[0], 0xFFFF - 0)) goto Error;
    if (!IsGoodWord("1", Out[1], 0xFFFF - 0)) goto Error;
    if (!IsGoodWord("2", Out[2], 0xFFFF - 0)) goto Error;

    In[0] = 0x1234; In[1] = 0x5678; In[2] = 0x9ABC;
    cmsPipelineEval16(In, Out, p);

    if (!IsGoodWord("0", 0xFFFF - 0x9ABC, Out[0])) goto Error;
    if (!IsGoodWord("1", 0xFFFF - 0x5678, Out[1])) goto Error;
    if (!IsGoodWord("2", 0xFFFF - 0x1234, Out[2])) goto Error;

    cmsPipelineFree(p);
    cmsDeleteContext(ctx);

    // Now without the plug-in

    p =  cmsPipelineAlloc(NULL, 3, 3);
    clut = cmsStageAllocCLut16bit(NULL, 2, 3, 3, identity);
    cmsPipelineInsertStage(p, cmsAT_BEGIN, clut);

    In[0] = 0; In[1] = 0; In[2] = 0;
    cmsPipelineEval16(In, Out, p);

    if (!IsGoodWord("0", 0, Out[0])) goto Error;
    if (!IsGoodWord("1", 0, Out[1])) goto Error;
    if (!IsGoodWord("2", 0, Out[2])) goto Error;

    In[0] = 0x1234; In[1] = 0x5678; In[2] = 0x9ABC;
    cmsPipelineEval16(In, Out, p);

    if (!IsGoodWord("0", 0x1234, Out[0])) goto Error;
    if (!IsGoodWord("1", 0x5678, Out[1])) goto Error;
    if (!IsGoodWord("2", 0x9ABC, Out[2])) goto Error;

    cmsPipelineFree(p);
    return 1;

Error:
    cmsPipelineFree(p);
    return 0;

}

// --------------------------------------------------------------------------------------------------
// Parametric curve plugin check: sin(x)/cos(x) function will be used to test the functionality. 
// --------------------------------------------------------------------------------------------------

#define TYPE_SIN  1000
#define TYPE_COS  1010
#define TYPE_TAN  1020
#define TYPE_709  709

static cmsFloat64Number my_fns(cmsInt32Number Type, 
                        const cmsFloat64Number Params[], 
                        cmsFloat64Number R)
{
    cmsFloat64Number Val;
    switch (Type) {

    case TYPE_SIN:     
        Val = Params[0]* sin(R * M_PI);
        break;

    case -TYPE_SIN:
        Val = asin(R) / (M_PI * Params[0]);
        break;

    case TYPE_COS:     
        Val = Params[0]* cos(R * M_PI);
        break;

    case -TYPE_COS:
        Val = acos(R) / (M_PI * Params[0]);
        break;

    default: return -1.0;

     }

   return Val;
}

static 
cmsFloat64Number my_fns2(cmsInt32Number Type, 
                        const cmsFloat64Number Params[], 
                        cmsFloat64Number R)
{
    cmsFloat64Number Val;
    switch (Type) {

    case TYPE_TAN:     
        Val = Params[0]* tan(R * M_PI);
        break;

    case -TYPE_TAN:
        Val = atan(R) / (M_PI * Params[0]);
        break;

     default: return -1.0;
     }

   return Val;
}


static double Rec709Math(int Type, const double Params[], double R)
{ 
    double Fun = 0;

    switch (Type)
    {
    case 709:

        if (R <= (Params[3]*Params[4])) Fun = R / Params[3];
        else Fun = pow(((R - Params[2])/Params[1]), Params[0]);
        break;

    case -709:

        if (R <= Params[4]) Fun = R * Params[3];
        else Fun = Params[1] * pow(R, (1/Params[0])) + Params[2];
        break;
    }
    return Fun;
}


// Add nonstandard TRC curves -> Rec709

cmsPluginParametricCurves Rec709Plugin = {

    { cmsPluginMagicNumber, 2060, cmsPluginParametricCurveSig, NULL },

    1, {TYPE_709}, {5}, Rec709Math

};


static
cmsPluginParametricCurves CurvePluginSample = {
    { cmsPluginMagicNumber, 2060, cmsPluginParametricCurveSig, NULL }, 
    
    2,                       // nFunctions
    { TYPE_SIN, TYPE_COS },  // Function Types
    { 1, 1 },                // ParameterCount
    my_fns                   // Evaluator
};

static
cmsPluginParametricCurves CurvePluginSample2 = {
    { cmsPluginMagicNumber, 2060, cmsPluginParametricCurveSig, NULL }, 
    
    1,                       // nFunctions
    { TYPE_TAN},             // Function Types
    { 1 },                   // ParameterCount
    my_fns2                  // Evaluator
};

// --------------------------------------------------------------------------------------------------
// In this test, the DupContext function will be checked as well                      
// --------------------------------------------------------------------------------------------------
cmsInt32Number CheckParametricCurvePlugin(void)
{
    cmsContext ctx = NULL;
    cmsContext cpy = NULL;
    cmsContext cpy2 = NULL;
    cmsToneCurve* sinus;
    cmsToneCurve* cosinus;
    cmsToneCurve* tangent;
    cmsToneCurve* reverse_sinus;
    cmsToneCurve* reverse_cosinus;
    cmsFloat64Number scale = 1.0;


    ctx = WatchDogContext(NULL);

    cmsPluginTHR(ctx, &CurvePluginSample);

    cpy = DupContext(ctx, NULL);
    
    cmsPluginTHR(cpy, &CurvePluginSample2);

    cpy2 =  DupContext(cpy, NULL);
    
    cmsPluginTHR(cpy2, &Rec709Plugin);
    

    sinus = cmsBuildParametricToneCurve(cpy, TYPE_SIN, &scale);
    cosinus = cmsBuildParametricToneCurve(cpy, TYPE_COS, &scale);
    tangent = cmsBuildParametricToneCurve(cpy, TYPE_TAN, &scale);
    reverse_sinus = cmsReverseToneCurve(sinus);
    reverse_cosinus = cmsReverseToneCurve(cosinus);


     if (!IsGoodVal("0.10", cmsEvalToneCurveFloat(sinus, 0.10f), sin(0.10 * M_PI) , 0.001)) goto Error;
     if (!IsGoodVal("0.60", cmsEvalToneCurveFloat(sinus, 0.60f), sin(0.60* M_PI), 0.001)) goto Error;
     if (!IsGoodVal("0.90", cmsEvalToneCurveFloat(sinus, 0.90f), sin(0.90* M_PI), 0.001)) goto Error;

     if (!IsGoodVal("0.10", cmsEvalToneCurveFloat(cosinus, 0.10f), cos(0.10* M_PI), 0.001)) goto Error;
     if (!IsGoodVal("0.60", cmsEvalToneCurveFloat(cosinus, 0.60f), cos(0.60* M_PI), 0.001)) goto Error;
     if (!IsGoodVal("0.90", cmsEvalToneCurveFloat(cosinus, 0.90f), cos(0.90* M_PI), 0.001)) goto Error;

     if (!IsGoodVal("0.10", cmsEvalToneCurveFloat(tangent, 0.10f), tan(0.10* M_PI), 0.001)) goto Error;
     if (!IsGoodVal("0.60", cmsEvalToneCurveFloat(tangent, 0.60f), tan(0.60* M_PI), 0.001)) goto Error;
     if (!IsGoodVal("0.90", cmsEvalToneCurveFloat(tangent, 0.90f), tan(0.90* M_PI), 0.001)) goto Error;

     
     if (!IsGoodVal("0.10", cmsEvalToneCurveFloat(reverse_sinus, 0.10f), asin(0.10)/M_PI, 0.001)) goto Error;
     if (!IsGoodVal("0.60", cmsEvalToneCurveFloat(reverse_sinus, 0.60f), asin(0.60)/M_PI, 0.001)) goto Error;
     if (!IsGoodVal("0.90", cmsEvalToneCurveFloat(reverse_sinus, 0.90f), asin(0.90)/M_PI, 0.001)) goto Error;

     if (!IsGoodVal("0.10", cmsEvalToneCurveFloat(reverse_cosinus, 0.10f), acos(0.10)/M_PI, 0.001)) goto Error;
     if (!IsGoodVal("0.60", cmsEvalToneCurveFloat(reverse_cosinus, 0.60f), acos(0.60)/M_PI, 0.001)) goto Error;
     if (!IsGoodVal("0.90", cmsEvalToneCurveFloat(reverse_cosinus, 0.90f), acos(0.90)/M_PI, 0.001)) goto Error;

     cmsFreeToneCurve(sinus);
     cmsFreeToneCurve(cosinus);
     cmsFreeToneCurve(tangent);
     cmsFreeToneCurve(reverse_sinus);
     cmsFreeToneCurve(reverse_cosinus);

     cmsDeleteContext(ctx);
     cmsDeleteContext(cpy);
     cmsDeleteContext(cpy2);

     return 1;

Error:
     
     cmsFreeToneCurve(sinus);
     cmsFreeToneCurve(reverse_sinus);
     cmsFreeToneCurve(cosinus);
     cmsFreeToneCurve(reverse_cosinus);

     if (ctx != NULL) cmsDeleteContext(ctx);
     if (cpy != NULL) cmsDeleteContext(cpy);
     if (cpy2 != NULL) cmsDeleteContext(cpy2);
     return 0;
}

// --------------------------------------------------------------------------------------------------
// formatters plugin check: 5-6-5 RGB format
// --------------------------------------------------------------------------------------------------

// We define this special type as 0 bytes not float, and set the upper bit 

#define TYPE_RGB_565  (COLORSPACE_SH(PT_RGB)|CHANNELS_SH(3)|BYTES_SH(0) | (1 << 23))

cmsUInt8Number* my_Unroll565(register struct _cmstransform_struct* nfo, 
                            register cmsUInt16Number wIn[], 
                            register cmsUInt8Number* accum,
                            register cmsUInt32Number Stride)
{
    cmsUInt16Number pixel = *(cmsUInt16Number*) accum;  // Take whole pixel

    double r = floor(((double) (pixel & 31) * 65535.0) / 31.0 + 0.5);
    double g = floor((((pixel >> 5) & 63) * 65535.0) / 63.0 + 0.5);
    double b = floor((((pixel >> 11) & 31) * 65535.0) / 31.0 + 0.5);
    
    wIn[2] = (cmsUInt16Number) r;
    wIn[1] = (cmsUInt16Number) g;
    wIn[0] = (cmsUInt16Number) b;
    
    return accum + 2;
}

cmsUInt8Number* my_Pack565(register _cmsTRANSFORM* info, 
                           register cmsUInt16Number wOut[],
                           register cmsUInt8Number* output,
                           register cmsUInt32Number Stride)
{

    register cmsUInt16Number pixel;
    int r, g, b;

    r = (int) floor(( wOut[2] * 31) / 65535.0 + 0.5);
    g = (int) floor(( wOut[1] * 63) / 65535.0 + 0.5);
    b = (int) floor(( wOut[0] * 31) / 65535.0 + 0.5);


    pixel = (r & 31)  | (( g & 63) << 5) | ((b & 31) << 11);

    
    *(cmsUInt16Number*) output = pixel;
    return output + 2;
}


cmsFormatter my_FormatterFactory(cmsUInt32Number Type, 
                                  cmsFormatterDirection Dir, 
                                  cmsUInt32Number dwFlags)
{
    cmsFormatter Result = { NULL };

    if ((Type == TYPE_RGB_565) && 
        !(dwFlags & CMS_PACK_FLAGS_FLOAT) &&
        (Dir == cmsFormatterInput)) {
            Result.Fmt16 = my_Unroll565;       
    }
    return Result;
}


cmsFormatter my_FormatterFactory2(cmsUInt32Number Type, 
                                  cmsFormatterDirection Dir, 
                                  cmsUInt32Number dwFlags)
{
    cmsFormatter Result = { NULL };

    if ((Type == TYPE_RGB_565) && 
        !(dwFlags & CMS_PACK_FLAGS_FLOAT) &&
        (Dir == cmsFormatterOutput)) {
            Result.Fmt16 = my_Pack565;       
    }
    return Result;
}

static
cmsPluginFormatters FormattersPluginSample = { {cmsPluginMagicNumber, 
                                2060,  
                                cmsPluginFormattersSig, 
                                NULL}, 
                                my_FormatterFactory };



static
cmsPluginFormatters FormattersPluginSample2 = { {cmsPluginMagicNumber, 
                                2060,  
                                cmsPluginFormattersSig, 
                                NULL}, 
                                my_FormatterFactory2 };


cmsInt32Number CheckFormattersPlugin(void)
{
    cmsContext ctx = WatchDogContext(NULL);
    cmsContext cpy;
    cmsContext cpy2;
    cmsHTRANSFORM xform;
    cmsUInt16Number stream[]= { 0xffffU, 0x1234U, 0x0000U, 0x33ddU };
    cmsUInt16Number result[4];
    int i;

    
    cmsPluginTHR(ctx, &FormattersPluginSample);

    cpy = DupContext(ctx, NULL);
    
    cmsPluginTHR(cpy, &FormattersPluginSample2);

    cpy2 = DupContext(cpy, NULL);
    
    xform = cmsCreateTransformTHR(cpy2, NULL, TYPE_RGB_565, NULL, TYPE_RGB_565, INTENT_PERCEPTUAL, cmsFLAGS_NULLTRANSFORM);

    cmsDoTransform(xform, stream, result, 4);

    cmsDeleteTransform(xform);
    cmsDeleteContext(ctx);
    cmsDeleteContext(cpy);
    cmsDeleteContext(cpy2);

    for (i=0; i < 4; i++)
        if (stream[i] != result[i]) return 0;

    return 1;
}

// --------------------------------------------------------------------------------------------------
// TagTypePlugin plugin check
// --------------------------------------------------------------------------------------------------

#define SigIntType      ((cmsTagTypeSignature)  0x74747448)   //   'tttH'
#define SigInt          ((cmsTagSignature)  0x74747448)       //   'tttH'

static
void *Type_int_Read(struct _cms_typehandler_struct* self,
                            cmsIOHANDLER* io, 
               cmsUInt32Number* nItems, 
               cmsUInt32Number SizeOfTag)
{
    cmsUInt32Number* Ptr = (cmsUInt32Number*) _cmsMalloc(self ->ContextID, sizeof(cmsUInt32Number));
    if (Ptr == NULL) return NULL;
    if (!_cmsReadUInt32Number(io, Ptr)) return NULL;
    *nItems = 1;
    return Ptr;
}

static
cmsBool Type_int_Write(struct _cms_typehandler_struct* self,
                        cmsIOHANDLER* io, 
                        void* Ptr, cmsUInt32Number nItems)
{
    return _cmsWriteUInt32Number(io, *(cmsUInt32Number*) Ptr);
}

static
void* Type_int_Dup(struct _cms_typehandler_struct* self, 
                   const void *Ptr, cmsUInt32Number n)
{
    return _cmsDupMem(self ->ContextID, Ptr, n * sizeof(cmsUInt32Number));
}

void Type_int_Free(struct _cms_typehandler_struct* self, 
                   void* Ptr)
{
    _cmsFree(self ->ContextID, Ptr);
}


static cmsPluginTag HiddenTagPluginSample = {

    { cmsPluginMagicNumber, 2060, cmsPluginTagSig, NULL},
    SigInt,  {  1, 1, { SigIntType }, NULL }  
};

static cmsPluginTagType TagTypePluginSample = {

     { cmsPluginMagicNumber, 2060, cmsPluginTagTypeSig,  (cmsPluginBase*) &HiddenTagPluginSample},
     { SigIntType, Type_int_Read, Type_int_Write, Type_int_Dup, Type_int_Free, NULL }        
};


cmsInt32Number CheckTagTypePlugin(void)
{
    cmsContext ctx = NULL;
    cmsContext cpy = NULL;
    cmsContext cpy2 = NULL;
    cmsHPROFILE h = NULL;
    cmsUInt32Number myTag = 1234;
    cmsUInt32Number rc = 0;
    char* data = NULL;
    cmsUInt32Number *ptr = NULL;
    cmsUInt32Number clen = 0;


    ctx = WatchDogContext(NULL);
    cmsPluginTHR(ctx, &TagTypePluginSample);

    cpy = DupContext(ctx, NULL);    
    cpy2 = DupContext(cpy, NULL);
    
    cmsDeleteContext(ctx);
    cmsDeleteContext(cpy);
    
    h = cmsCreateProfilePlaceholder(cpy2);
    if (h == NULL) {
        Fail("Create placeholder failed");
        goto Error;
    }


    if (!cmsWriteTag(h, SigInt, &myTag)) {
        Fail("Plug-in failed");
        goto Error;
    }

    rc = cmsSaveProfileToMem(h, NULL, &clen);
    if (!rc) {
        Fail("Fetch mem size failed");
        goto Error;        
    }


    data = (char*) malloc(clen);
    if (data == NULL) {
        Fail("malloc failed ?!?");
        goto Error;
    }


    rc = cmsSaveProfileToMem(h, data, &clen);
    if (!rc) {
        Fail("Save to mem failed");
        goto Error;
    }

    cmsCloseProfile(h);

    cmsSetLogErrorHandler(NULL);
    h = cmsOpenProfileFromMem(data, clen);    
    if (h == NULL) {
        Fail("Open profile failed");
        goto Error;
    }

    ptr = (cmsUInt32Number*) cmsReadTag(h, SigInt);
    if (ptr != NULL) {

        Fail("read tag/context switching failed");
        goto Error;
    }

    cmsCloseProfile(h);
    ResetFatalError();

    h = cmsOpenProfileFromMemTHR(cpy2, data, clen);    
    if (h == NULL) {
        Fail("Open profile from mem failed");
        goto Error;
    }

    // Get rid of data
    free(data); data = NULL;

    ptr = (cmsUInt32Number*) cmsReadTag(h, SigInt);
    if (ptr == NULL) {        
        Fail("Read tag/conext switching failed (2)");
        return 0;
    }
   
    rc = (*ptr == 1234);

    cmsCloseProfile(h);

    cmsDeleteContext(cpy2);

    return rc;

Error:

    if (h != NULL) cmsCloseProfile(h);
    if (ctx != NULL) cmsDeleteContext(ctx);
    if (cpy != NULL) cmsDeleteContext(cpy);
    if (cpy2 != NULL) cmsDeleteContext(cpy2);
    if (data) free(data);

    return 0;
}

// --------------------------------------------------------------------------------------------------
// MPE plugin check:
// --------------------------------------------------------------------------------------------------
#define SigNegateType ((cmsStageSignature)0x6E202020)

static
void EvaluateNegate(const cmsFloat32Number In[], 
                     cmsFloat32Number Out[], 
                     const cmsStage *mpe)
{
    Out[0] = 1.0f - In[0];
    Out[1] = 1.0f - In[1];
    Out[2] = 1.0f - In[2];
}

static
cmsStage* StageAllocNegate(cmsContext ContextID)
{
    return _cmsStageAllocPlaceholder(ContextID,
                 SigNegateType, 3, 3, EvaluateNegate, 
                 NULL, NULL, NULL);
}

static
void *Type_negate_Read(struct _cms_typehandler_struct* self,
                            cmsIOHANDLER* io, 
                cmsUInt32Number* nItems, 
                cmsUInt32Number SizeOfTag)
{
    cmsUInt16Number   Chans;
    if (!_cmsReadUInt16Number(io, &Chans)) return NULL;
    if (Chans != 3) return NULL;

    *nItems = 1;
    return StageAllocNegate(self -> ContextID);
}

static
cmsBool Type_negate_Write(struct _cms_typehandler_struct* self,
                        cmsIOHANDLER* io, 
                        void* Ptr, cmsUInt32Number nItems)
{

    if (!_cmsWriteUInt16Number(io, 3)) return FALSE;    
    return TRUE;
}

static
cmsPluginMultiProcessElement MPEPluginSample = {

    {cmsPluginMagicNumber, 2060, cmsPluginMultiProcessElementSig, NULL}, 

    { (cmsTagTypeSignature) SigNegateType, Type_negate_Read, Type_negate_Write, NULL, NULL, NULL }
};


cmsInt32Number CheckMPEPlugin(void)
{
    cmsContext ctx = NULL;
    cmsContext cpy = NULL;
    cmsContext cpy2 = NULL;
    cmsHPROFILE h = NULL;
    cmsUInt32Number myTag = 1234;
    cmsUInt32Number rc = 0;
    char* data = NULL;
    cmsUInt32Number clen = 0;
    cmsFloat32Number In[3], Out[3];
    cmsPipeline* pipe;

    ctx = WatchDogContext(NULL);
    cmsPluginTHR(ctx, &MPEPluginSample);

    cpy =  DupContext(ctx, NULL);    
    cpy2 = DupContext(cpy, NULL);
    
    cmsDeleteContext(ctx);
    cmsDeleteContext(cpy);
    
    h = cmsCreateProfilePlaceholder(cpy2);
    if (h == NULL) {
        Fail("Create placeholder failed");
        goto Error;
    }
    
    pipe = cmsPipelineAlloc(cpy2, 3, 3);
    cmsPipelineInsertStage(pipe, cmsAT_BEGIN, StageAllocNegate(cpy2));


    In[0] = 0.3f; In[1] = 0.2f; In[2] = 0.9f;
    cmsPipelineEvalFloat(In, Out, pipe);

    rc = (IsGoodVal("0", Out[0], 1.0-In[0], 0.001) && 
           IsGoodVal("1", Out[1], 1.0-In[1], 0.001) && 
           IsGoodVal("2", Out[2], 1.0-In[2], 0.001));

    if (!rc) {
        Fail("Pipeline failed");
        goto Error;    
    }

    if (!cmsWriteTag(h, cmsSigDToB3Tag, pipe)) {
        Fail("Plug-in failed");
        goto Error;
    }

    // This cleans the stage as well
    cmsPipelineFree(pipe);

    rc = cmsSaveProfileToMem(h, NULL, &clen);
    if (!rc) {
        Fail("Fetch mem size failed");
        goto Error;        
    }


    data = (char*) malloc(clen);
    if (data == NULL) {
        Fail("malloc failed ?!?");
        goto Error;
    }


    rc = cmsSaveProfileToMem(h, data, &clen);
    if (!rc) {
        Fail("Save to mem failed");
        goto Error;
    }

    cmsCloseProfile(h);


    cmsSetLogErrorHandler(NULL);
    h = cmsOpenProfileFromMem(data, clen);    
    if (h == NULL) {
        Fail("Open profile failed");
        goto Error;
    } 

    pipe = (cmsPipeline*) cmsReadTag(h, cmsSigDToB3Tag);
    if (pipe != NULL) {

        // Unsupported stage, should fail
        Fail("read tag/context switching failed");
        goto Error;
    }

    cmsCloseProfile(h);

    ResetFatalError();

    h = cmsOpenProfileFromMemTHR(cpy2, data, clen);    
    if (h == NULL) {
        Fail("Open profile from mem failed");
        goto Error;
    }

    // Get rid of data
    free(data); data = NULL;

    pipe = (cmsPipeline*) cmsReadTag(h, cmsSigDToB3Tag);
    if (pipe == NULL) {        
        Fail("Read tag/conext switching failed (2)");
        return 0;
    }
   
    // Evaluate for negation
    In[0] = 0.3f; In[1] = 0.2f; In[2] = 0.9f;
    cmsPipelineEvalFloat(In, Out, pipe);

     rc = (IsGoodVal("0", Out[0], 1.0-In[0], 0.001) && 
           IsGoodVal("1", Out[1], 1.0-In[1], 0.001) && 
           IsGoodVal("2", Out[2], 1.0-In[2], 0.001));
        
    cmsCloseProfile(h);

    cmsDeleteContext(cpy2);

    return rc;

Error:

    if (h != NULL) cmsCloseProfile(h);
    if (ctx != NULL) cmsDeleteContext(ctx);
    if (cpy != NULL) cmsDeleteContext(cpy);
    if (cpy2 != NULL) cmsDeleteContext(cpy2);
    if (data) free(data);

    return 0;
}


// --------------------------------------------------------------------------------------------------
// Optimization plugin check:
// --------------------------------------------------------------------------------------------------

static
void FastEvaluateCurves(register const cmsUInt16Number In[],
                                     register cmsUInt16Number Out[],
                                     register const void* Data)
{
    Out[0] = In[0];
}

static
cmsBool MyOptimize(cmsPipeline** Lut, 
                   cmsUInt32Number  Intent, 
                   cmsUInt32Number* InputFormat, 
                   cmsUInt32Number* OutputFormat, 
                   cmsUInt32Number* dwFlags)
{
    cmsStage* mpe;
     _cmsStageToneCurvesData* Data;

    //  Only curves in this LUT? All are identities?
    for (mpe = cmsPipelineGetPtrToFirstStage(*Lut);
         mpe != NULL;
         mpe = cmsStageNext(mpe)) {

            if (cmsStageType(mpe) != cmsSigCurveSetElemType) return FALSE;

            // Check for identity
            Data = (_cmsStageToneCurvesData*) cmsStageData(mpe);
            if (Data ->nCurves != 1) return FALSE;
            if (cmsEstimateGamma(Data->TheCurves[0], 0.1) > 1.0) return FALSE;
          
    }

    *dwFlags |= cmsFLAGS_NOCACHE;
    _cmsPipelineSetOptimizationParameters(*Lut, FastEvaluateCurves, NULL, NULL, NULL);

    return TRUE;
}

cmsPluginOptimization OptimizationPluginSample = {

    {cmsPluginMagicNumber, 2060, cmsPluginOptimizationSig, NULL}, 
    MyOptimize
};


cmsInt32Number CheckOptimizationPlugin(void)
{
    cmsContext ctx = WatchDogContext(NULL);
    cmsContext cpy;
    cmsContext cpy2;
    cmsHTRANSFORM xform;
    cmsUInt8Number In[]= { 10, 20, 30, 40 };
    cmsUInt8Number Out[4];
    cmsToneCurve* Linear[1];
    cmsHPROFILE h;
    int i;
    
    cmsPluginTHR(ctx, &OptimizationPluginSample);

    cpy = DupContext(ctx, NULL);
    cpy2 = DupContext(cpy, NULL);
    
    Linear[0] = cmsBuildGamma(cpy2, 1.0);
    h = cmsCreateLinearizationDeviceLinkTHR(cpy2, cmsSigGrayData, Linear);
    cmsFreeToneCurve(Linear[0]);

    xform = cmsCreateTransformTHR(cpy2, h, TYPE_GRAY_8, h, TYPE_GRAY_8, INTENT_PERCEPTUAL, 0);
    cmsCloseProfile(h);

    cmsDoTransform(xform, In, Out, 4);

    cmsDeleteTransform(xform);
    cmsDeleteContext(ctx);
    cmsDeleteContext(cpy);
    cmsDeleteContext(cpy2);

    for (i=0; i < 4; i++)
        if (In[i] != Out[i]) return 0;

    return 1;
}


// --------------------------------------------------------------------------------------------------
// Check the intent plug-in
// --------------------------------------------------------------------------------------------------

/*
   This example creates a new rendering intent, at intent number 300, that is identical to perceptual 
   intent for all color spaces but gray to gray transforms, in this case it bypasses the data. 
   Note that it has to clear all occurrences of intent 300 in the intents array to avoid 
   infinite recursion.
*/

#define INTENT_DECEPTIVE   300

static
cmsPipeline*  MyNewIntent(cmsContext      ContextID, 
                          cmsUInt32Number nProfiles,
                          cmsUInt32Number TheIntents[], 
                          cmsHPROFILE     hProfiles[], 
                          cmsBool         BPC[],
                          cmsFloat64Number AdaptationStates[],
                          cmsUInt32Number dwFlags)
{
    cmsPipeline*    Result;
    cmsUInt32Number ICCIntents[256];
    cmsUInt32Number i;

 for (i=0; i < nProfiles; i++) 
        ICCIntents[i] = (TheIntents[i] == INTENT_DECEPTIVE) ? INTENT_PERCEPTUAL : 
                                                 TheIntents[i];

 if (cmsGetColorSpace(hProfiles[0]) != cmsSigGrayData ||
     cmsGetColorSpace(hProfiles[nProfiles-1]) != cmsSigGrayData) 
           return _cmsDefaultICCintents(ContextID, nProfiles, 
                                   ICCIntents, hProfiles, 
                                   BPC, AdaptationStates, 
                                   dwFlags);

    Result = cmsPipelineAlloc(ContextID, 1, 1);
    if (Result == NULL) return NULL;

    cmsPipelineInsertStage(Result, cmsAT_BEGIN,
                            cmsStageAllocIdentity(ContextID, 1));

    return Result;
}

static cmsPluginRenderingIntent IntentPluginSample = {

    {cmsPluginMagicNumber, 2060, cmsPluginRenderingIntentSig, NULL},
                     
    INTENT_DECEPTIVE, MyNewIntent,  "bypass gray to gray rendering intent" 
};

cmsInt32Number CheckIntentPlugin(void)
{
    cmsContext ctx = WatchDogContext(NULL);
    cmsContext cpy;
    cmsContext cpy2;
    cmsHTRANSFORM xform;
    cmsHPROFILE h1, h2;
    cmsToneCurve* Linear1;
    cmsToneCurve* Linear2;
    cmsUInt8Number In[]= { 10, 20, 30, 40 };
    cmsUInt8Number Out[4];
    int i;
    
    cmsPluginTHR(ctx, &IntentPluginSample);

    cpy  = DupContext(ctx, NULL);    
    cpy2 = DupContext(cpy, NULL);
    
    Linear1 = cmsBuildGamma(cpy2, 3.0);
    Linear2 = cmsBuildGamma(cpy2, 0.1);
    h1 = cmsCreateLinearizationDeviceLinkTHR(cpy2, cmsSigGrayData, &Linear1);
    h2 = cmsCreateLinearizationDeviceLinkTHR(cpy2, cmsSigGrayData, &Linear2);

    cmsFreeToneCurve(Linear1);
    cmsFreeToneCurve(Linear2);

    xform = cmsCreateTransformTHR(cpy2, h1, TYPE_GRAY_8, h2, TYPE_GRAY_8, INTENT_DECEPTIVE, 0);
    cmsCloseProfile(h1); cmsCloseProfile(h2);

    cmsDoTransform(xform, In, Out, 4);

    cmsDeleteTransform(xform);
    cmsDeleteContext(ctx);
    cmsDeleteContext(cpy);
    cmsDeleteContext(cpy2);

    for (i=0; i < 4; i++)
        if (Out[i] != In[i]) return 0;

    return 1;    
}


// --------------------------------------------------------------------------------------------------
// Check the full transform plug-in
// --------------------------------------------------------------------------------------------------

// This is a sample intent that only works for gray8 as output, and always returns '42'
static
void TrancendentalTransform(struct _cmstransform_struct * CMM,
                              const void* InputBuffer,
                              void* OutputBuffer,
                              cmsUInt32Number Size,
                              cmsUInt32Number Stride)
{
    cmsUInt32Number i;

    for (i=0; i < Size; i++)
    {
        ((cmsUInt8Number*) OutputBuffer)[i] = 0x42;
    }

}


cmsBool  TransformFactory(_cmsTransformFn* xformPtr,
                          void** UserData,
                           _cmsFreeUserDataFn* FreePrivateDataFn,
                           cmsPipeline** Lut,
                           cmsUInt32Number* InputFormat,
                           cmsUInt32Number* OutputFormat,
                           cmsUInt32Number* dwFlags)

{
    if (*OutputFormat == TYPE_GRAY_8)
    {
        // *Lut holds the pipeline to be applied
        *xformPtr = TrancendentalTransform;
        return TRUE;
    }

    return FALSE;
}


// The Plug-in entry point
static cmsPluginTransform FullTransformPluginSample = {
                           
     { cmsPluginMagicNumber, 2060, cmsPluginTransformSig, NULL}, 

     TransformFactory                          
};

cmsInt32Number CheckTransformPlugin(void)
{
    cmsContext ctx = WatchDogContext(NULL);
    cmsContext cpy;
    cmsContext cpy2;
    cmsHTRANSFORM xform;
    cmsUInt8Number In[]= { 10, 20, 30, 40 };
    cmsUInt8Number Out[4];
    cmsToneCurve* Linear;
    cmsHPROFILE h;
    int i;

    
    cmsPluginTHR(ctx, &FullTransformPluginSample);

    cpy  = DupContext(ctx, NULL);
    cpy2 = DupContext(cpy, NULL);
    
    Linear = cmsBuildGamma(cpy2, 1.0);
    h = cmsCreateLinearizationDeviceLinkTHR(cpy2, cmsSigGrayData, &Linear);
    cmsFreeToneCurve(Linear);

    xform = cmsCreateTransformTHR(cpy2, h, TYPE_GRAY_8, h, TYPE_GRAY_8, INTENT_PERCEPTUAL, 0);
    cmsCloseProfile(h);

    cmsDoTransform(xform, In, Out, 4);


    cmsDeleteTransform(xform);
    cmsDeleteContext(ctx);
    cmsDeleteContext(cpy);
    cmsDeleteContext(cpy2);

    for (i=0; i < 4; i++)
        if (Out[i] != 0x42) return 0;

    return 1;
}


// --------------------------------------------------------------------------------------------------
// Check the mutex plug-in
// --------------------------------------------------------------------------------------------------

typedef struct {
    int nlocks;
} MyMtx;


static
void* MyMtxCreate(cmsContext id)
{
   MyMtx* mtx = (MyMtx*) _cmsMalloc(id, sizeof(MyMtx));
   mtx ->nlocks = 0;
   return mtx;
}

static
void MyMtxDestroy(cmsContext id, void* mtx)
{
    MyMtx* mtx_ = (MyMtx*) mtx;

    if (mtx_->nlocks != 0)
        Die("Locks != 0 when setting free a mutex");

    _cmsFree(id, mtx);

}

static
cmsBool MyMtxLock(cmsContext id, void* mtx)
{
    MyMtx* mtx_ = (MyMtx*) mtx;
    mtx_->nlocks++;

    return TRUE;
}

static
void MyMtxUnlock(cmsContext id, void* mtx)
{
    MyMtx* mtx_ = (MyMtx*) mtx;
    mtx_->nlocks--;

}


static cmsPluginMutex MutexPluginSample = {
                           
     { cmsPluginMagicNumber, 2060, cmsPluginMutexSig, NULL}, 

     MyMtxCreate,  MyMtxDestroy,  MyMtxLock,  MyMtxUnlock                       
};


cmsInt32Number CheckMutexPlugin(void)
{
    cmsContext ctx = WatchDogContext(NULL);
    cmsContext cpy;
    cmsContext cpy2;
    cmsHTRANSFORM xform;
    cmsUInt8Number In[]= { 10, 20, 30, 40 };
    cmsUInt8Number Out[4];
    cmsToneCurve* Linear;
    cmsHPROFILE h;
    int i;

    
    cmsPluginTHR(ctx, &MutexPluginSample);

    cpy  = DupContext(ctx, NULL);
    cpy2 = DupContext(cpy, NULL);
    
    Linear = cmsBuildGamma(cpy2, 1.0);
    h = cmsCreateLinearizationDeviceLinkTHR(cpy2, cmsSigGrayData, &Linear);
    cmsFreeToneCurve(Linear);

    xform = cmsCreateTransformTHR(cpy2, h, TYPE_GRAY_8, h, TYPE_GRAY_8, INTENT_PERCEPTUAL, 0);
    cmsCloseProfile(h);

    cmsDoTransform(xform, In, Out, 4);


    cmsDeleteTransform(xform);
    cmsDeleteContext(ctx);
    cmsDeleteContext(cpy);
    cmsDeleteContext(cpy2);

    for (i=0; i < 4; i++)
        if (Out[i] != In[i]) return 0;

    return 1;
}

/* [<][>][^][v][top][bottom][index][help] */