//---------------------------------------------------------------------------------
//
//  Little Color Management System
//  Copyright (c) 1998-2026 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"

// --------------------------------------------------------------------------------------------------
// Auxiliary, 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(cmsContext ContextID)
{
     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);

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

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

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

     return 1;
}

// Test the very basic context capabilities
cmsInt32Number CheckSimpleContext(cmsContext ContextID)
{
    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(cmsContext ContextID)
{
    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);

    cmsSetAlarmCodes(c1, codes);
    c2 = DupContext(c1, NULL);
    c3 = DupContext(c2, NULL);

    cmsGetAlarmCodes(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(cmsContext ContextID)
{
    cmsInt32Number rc = 0;
    cmsContext c1, c2, c3;
    cmsFloat64Number old1, old2;

    old1 =  cmsSetAdaptationState(NULL, -1);

    c1 = WatchDogContext(NULL);

    cmsSetAdaptationState(c1, 0.7);

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

    rc = IsGoodVal("Adaptation state", cmsSetAdaptationState(c3, -1), 0.7, 0.001);

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

    old2 =  cmsSetAdaptationState(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(cmsContext ContextID, 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(cmsContext ContextID,
              CMSREGISTER const cmsUInt16Number Input[],
              CMSREGISTER cmsUInt16Number Output[],
              CMSREGISTER 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(cmsContext ContextID, 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-2000, cmsPluginInterpolationSig, NULL },
    my_Interpolators_Factory
};


// This is the check code for 1D interpolation plug-in
cmsInt32Number CheckInterp1DPlugin(cmsContext ContextID)
{
    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;
    }

    cmsPlugin(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(cpy, Sampled1D, 0.10f), 0.10, 0.01)) goto Error;
    if (!IsGoodVal("0.13", cmsEvalToneCurveFloat(cpy, Sampled1D, 0.13f), 0.10, 0.01)) goto Error;
    if (!IsGoodVal("0.55", cmsEvalToneCurveFloat(cpy, Sampled1D, 0.55f), 0.50, 0.01)) goto Error;
    if (!IsGoodVal("0.9999", cmsEvalToneCurveFloat(cpy, Sampled1D, 0.9999f), 0.90, 0.01)) goto Error;

    cmsFreeToneCurve(cpy, 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(NULL, Sampled1D, 0.10f), 0.10, 0.001)) goto Error;
    if (!IsGoodVal("0.13", cmsEvalToneCurveFloat(NULL, Sampled1D, 0.13f), 0.13, 0.001)) goto Error;
    if (!IsGoodVal("0.55", cmsEvalToneCurveFloat(NULL, Sampled1D, 0.55f), 0.55, 0.001)) goto Error;
    if (!IsGoodVal("0.9999", cmsEvalToneCurveFloat(NULL, Sampled1D, 0.9999f), 0.9999, 0.001)) goto Error;

    cmsFreeToneCurve(NULL, Sampled1D);
    return 1;

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

}

// Checks the 3D interpolation
cmsInt32Number CheckInterp3DPlugin(cmsContext ContextID)
{

    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;
    }

    cmsPlugin(ctx, &InterpPluginSample);

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

    // Do some interpolations with the plugin

    In[0] = 0; In[1] = 0; In[2] = 0;
    cmsPipelineEval16(ctx, 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(ctx, 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(ctx, p);
    cmsDeleteContext(ctx);

    // Now without the plug-in

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

    In[0] = 0; In[1] = 0; In[2] = 0;
    cmsPipelineEval16(NULL, 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(NULL, 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(NULL, p);
    return 1;

Error:
    cmsPipelineFree(NULL, 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(cmsContext ContextID, 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(cmsContext ContextID, 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(cmsContext ContextID, 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-2000, cmsPluginParametricCurveSig, NULL },

    1, {TYPE_709}, {5}, Rec709Math

};


static
cmsPluginParametricCurves CurvePluginSample = {
    { cmsPluginMagicNumber, 2060-2000, cmsPluginParametricCurveSig, NULL },

    2,                       // nFunctions
    { TYPE_SIN, TYPE_COS },  // Function Types
    { 1, 1 },                // ParameterCount
    my_fns                   // Evaluator
};

static
cmsPluginParametricCurves CurvePluginSample2 = {
    { cmsPluginMagicNumber, 2060-2000, 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(cmsContext ContextID)
{
    cmsContext ctx = NULL;
    cmsContext cpy = NULL;
    cmsToneCurve* sinus;
    cmsToneCurve* cosinus;
    cmsToneCurve* tangent;
    cmsToneCurve* reverse_sinus;
    cmsToneCurve* reverse_cosinus;
    cmsFloat64Number scale = 1.0;

    ctx = WatchDogContext(NULL);

    cmsPlugin(ctx, &CurvePluginSample);

    cpy = DupContext(ctx, NULL);

    cmsPlugin(cpy, &CurvePluginSample2);

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


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

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

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


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

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

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

     cmsDeleteContext(ctx);
     cmsDeleteContext(cpy);

     return 1;

Error:

     cmsFreeToneCurve(cpy, sinus);
     cmsFreeToneCurve(cpy, reverse_sinus);
     cmsFreeToneCurve(cpy, cosinus);
     cmsFreeToneCurve(cpy, reverse_cosinus);

     if (ctx != NULL) cmsDeleteContext(ctx);
     if (cpy != NULL) cmsDeleteContext(cpy);
     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(cmsContext ContextID,
                            CMSREGISTER struct _cmstransform_struct* nfo,
                            CMSREGISTER cmsUInt16Number wIn[],
                            CMSREGISTER cmsUInt8Number* accum,
                            CMSREGISTER 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(cmsContext ContextID,
                           CMSREGISTER _cmsTRANSFORM* info,
                           CMSREGISTER cmsUInt16Number wOut[],
                           CMSREGISTER cmsUInt8Number* output,
                           CMSREGISTER cmsUInt32Number Stride)
{

    CMSREGISTER 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(cmsContext ContextID, 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(cmsContext ContextID, 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-2000,
                                cmsPluginFormattersSig,
                                NULL},
                                my_FormatterFactory };



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


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

    cmsPlugin(ctx, &FormattersPluginSample);

    cpy = DupContext(ctx, NULL);

    cmsPlugin(cpy, &FormattersPluginSample2);

    xform = cmsCreateTransform(cpy, NULL, TYPE_RGB_565, NULL, TYPE_RGB_565, INTENT_PERCEPTUAL, cmsFLAGS_NULLTRANSFORM);

    cmsDoTransform(cpy, xform, stream, result, 4);

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

    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'

#define SigInt32        ((cmsTagSignature)  0x74747449)       //   'tttI'

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

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

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

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


static cmsPluginTag HiddenTagPluginSample = {

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

static cmsPluginTag HiddenTagPluginSample2 = {

    { cmsPluginMagicNumber, 2060, cmsPluginTagSig, (cmsPluginBase*) &HiddenTagPluginSample},
    SigInt32,  {  1, 1, { cmsSigUInt32ArrayType }, NULL }
};


static cmsPluginTagType TagTypePluginSample = {

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


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

    ctx = WatchDogContext(NULL);
    cmsPlugin(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(cpy2, h, SigInt, &myTag)) {
        Fail("Plug-in failed");
        goto Error;
    }

    if (!cmsWriteTag(cpy2, h, SigInt32, &myTag32)) {
        Fail("Plug-in failed");
        goto Error;
    }


    rc = cmsSaveProfileToMem(cpy2, 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(cpy2, h, data, &clen);
    if (!rc) {
        Fail("Save to mem failed");
        goto Error;
    }

    cmsCloseProfile(cpy2, h);

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

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

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

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

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


    cmsCloseProfile(ContextID, h);
    ResetFatalError(ContextID);

    h = cmsOpenProfileFromMem(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(cpy2, h, SigInt);
    if (ptr == NULL) {
        Fail("Read tag/context switching failed (2)");
        return 0;
    }

    rc = (*ptr == 1234);

    ptr = (cmsUInt32Number*)cmsReadTag(cpy2, h, SigInt32);
    if (ptr == NULL) {

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

    rc &= (*ptr == 5678);

    cmsCloseProfile(cpy2, h);

    cmsDeleteContext(cpy2);

    return rc;

Error:

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

    return 0;
}

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

static
void EvaluateNegate(cmsContext ContextID, 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(cmsContext ContextID, struct _cms_typehandler_struct* self,
 			    cmsIOHANDLER* io,
                cmsUInt32Number* nItems,
                cmsUInt32Number SizeOfTag)
{
    cmsUInt16Number   Chans;
    if (!_cmsReadUInt16Number(ContextID, io, &Chans)) return NULL;
    if (Chans != 3) return NULL;

    *nItems = 1;
    return StageAllocNegate(ContextID);
}

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

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

static
cmsPluginMultiProcessElement MPEPluginSample = {

    {cmsPluginMagicNumber, 2060-2000, cmsPluginMultiProcessElementSig, NULL},

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


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

    ctx = WatchDogContext(NULL);
    cmsPlugin(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(cpy2, pipe, cmsAT_BEGIN, StageAllocNegate(cpy2));


    In[0] = 0.3f; In[1] = 0.2f; In[2] = 0.9f;
    cmsPipelineEvalFloat(cpy2, 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(cpy2, h, cmsSigDToB3Tag, pipe)) {
        Fail("Plug-in failed");
        goto Error;
    }

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

    rc = cmsSaveProfileToMem(cpy2, 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(cpy2, h, data, &clen);
    if (!rc) {
        Fail("Save to mem failed");
        goto Error;
    }

    cmsCloseProfile(cpy2, h);

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

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

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

    cmsCloseProfile(ContextID, h);

    ResetFatalError(ContextID);

    h = cmsOpenProfileFromMem(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(cpy2, h, cmsSigDToB3Tag);
    if (pipe == NULL) {
        Fail("Read tag/context switching failed (2)");
        return 0;
    }

    // Evaluate for negation
    In[0] = 0.3f; In[1] = 0.2f; In[2] = 0.9f;
    cmsPipelineEvalFloat(cpy2, 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(cpy2, h);
    cmsDeleteContext(cpy2);
    return rc;

Error:

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

    return 0;
}


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

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

static
cmsBool MyOptimize(cmsContext ContextID, 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(ContextID, *Lut);
         mpe != NULL;
         mpe = cmsStageNext(ContextID, mpe)) {

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

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

    }

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

    return TRUE;
}

cmsPluginOptimization OptimizationPluginSample = {

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


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

    cmsPlugin(ctx, &OptimizationPluginSample);

    cpy = DupContext(ctx, NULL);

    Linear[0] = cmsBuildGamma(cpy, 1.0);
    h = cmsCreateLinearizationDeviceLink(cpy, cmsSigGrayData, Linear);
    cmsFreeToneCurve(cpy, Linear[0]);

    xform = cmsCreateTransform(cpy, h, TYPE_GRAY_8, h, TYPE_GRAY_8, INTENT_PERCEPTUAL, 0);
    cmsCloseProfile(cpy, h);

    cmsDoTransform(cpy, xform, In, Out, 4);

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

    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(ContextID, hProfiles[0]) != cmsSigGrayData ||
     cmsGetColorSpace(ContextID, hProfiles[nProfiles-1]) != cmsSigGrayData)
           return _cmsDefaultICCintents(ContextID, nProfiles,
                                   ICCIntents, hProfiles,
                                   BPC, AdaptationStates,
                                   dwFlags);

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

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

    return Result;
}

static cmsPluginRenderingIntent IntentPluginSample = {

    {cmsPluginMagicNumber, 2060-2000, cmsPluginRenderingIntentSig, NULL},

    INTENT_DECEPTIVE, MyNewIntent,  "bypass gray to gray rendering intent"
};

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

    cmsPlugin(ctx, &IntentPluginSample);

    cpy  = DupContext(ctx, NULL);

    Linear1 = cmsBuildGamma(cpy, 3.0);
    Linear2 = cmsBuildGamma(cpy, 0.1);
    h1 = cmsCreateLinearizationDeviceLink(cpy, cmsSigGrayData, &Linear1);
    h2 = cmsCreateLinearizationDeviceLink(cpy, cmsSigGrayData, &Linear2);

    cmsFreeToneCurve(cpy, Linear1);
    cmsFreeToneCurve(cpy, Linear2);

    xform = cmsCreateTransform(cpy, h1, TYPE_GRAY_8, h2, TYPE_GRAY_8, INTENT_DECEPTIVE, 0);
    cmsCloseProfile(cpy,h1); cmsCloseProfile(cpy, h2);

    cmsDoTransform(cpy, xform, In, Out, 4);

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

    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(cmsContext ContextID, 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(cmsContext ContextID, _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-2000, cmsPluginTransformSig, NULL},

     { TransformFactory }
};

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

    cmsPlugin(ctx, &FullTransformPluginSample);

    cpy  = DupContext(ctx, NULL);

    Linear = cmsBuildGamma(cpy, 1.0);
    h = cmsCreateLinearizationDeviceLink(cpy, cmsSigGrayData, &Linear);
    cmsFreeToneCurve(cpy, Linear);

    xform = cmsCreateTransform(cpy, h, TYPE_GRAY_8, h, TYPE_GRAY_8, INTENT_PERCEPTUAL, 0);
    cmsCloseProfile(cpy, h);

    cmsDoTransform(cpy, xform, In, Out, 4);

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

    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(cmsContext ContextID)
{
    cmsContext ctx = WatchDogContext(NULL);
    cmsContext cpy;
    cmsHTRANSFORM xform;
    cmsUInt8Number In[]= { 10, 20, 30, 40 };
    cmsUInt8Number Out[4];
    cmsToneCurve* Linear;
    cmsHPROFILE h;
    int i;


    cmsPlugin(ctx, &MutexPluginSample);

    cpy  = DupContext(ctx, NULL);

    Linear = cmsBuildGamma(cpy, 1.0);
    h = cmsCreateLinearizationDeviceLink(cpy, cmsSigGrayData, &Linear);
    cmsFreeToneCurve(cpy, Linear);

    xform = cmsCreateTransform(cpy, h, TYPE_GRAY_8, h, TYPE_GRAY_8, INTENT_PERCEPTUAL, 0);
    cmsCloseProfile(cpy, h);

    cmsDoTransform(cpy, xform, In, Out, 4);

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

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

    return 1;
}


cmsInt32Number CheckMethodPackDoublesFromFloat(cmsContext ContextID)
{

    cmsContext ctx = WatchDogContext(NULL);

    cmsHTRANSFORM xform;
    cmsHTRANSFORM l_pFakeProfileLAB;

    cmsFloat64Number l_D_OutputColorArrayBlack[8];
    cmsFloat64Number l_D_OutputColorArrayBlue[8];

    cmsCIELab LabInBlack;
    cmsCIELab LabInBlue;

    cmsUInt16Number Lab_UI16_Black[3];
    cmsUInt16Number Lab_UI16_Blue[3];

    cmsHPROFILE OutputCMYKProfile;
    cmsUInt32Number l_UI32_OutputFormat;


    cmsPlugin(ctx, &FullTransformPluginSample);


    l_pFakeProfileLAB = cmsCreateLab2Profile(ctx, NULL);

    if (l_pFakeProfileLAB == NULL)
        return 0;

    OutputCMYKProfile = cmsOpenProfileFromFile(ctx, "test2.icc", "r");

    if (OutputCMYKProfile == NULL)
        return 0;

    l_UI32_OutputFormat = 0;
    l_UI32_OutputFormat |= COLORSPACE_SH(PT_CMYK);
    l_UI32_OutputFormat |= PLANAR_SH(1);
    l_UI32_OutputFormat |= CHANNELS_SH(4);
    l_UI32_OutputFormat |= BYTES_SH(0);
    l_UI32_OutputFormat |= FLOAT_SH(1);


    xform = cmsCreateTransform(ctx, l_pFakeProfileLAB, TYPE_Lab_DBL, OutputCMYKProfile, l_UI32_OutputFormat, INTENT_PERCEPTUAL, 0);
    cmsCloseProfile(ctx, OutputCMYKProfile);
    cmsCloseProfile(ctx, l_pFakeProfileLAB);

    Lab_UI16_Black[0] = 0;
    Lab_UI16_Black[1] = 32768;
    Lab_UI16_Black[2] = 32768;

    Lab_UI16_Blue[0] = 0;
    Lab_UI16_Blue[1] = 8192;
    Lab_UI16_Blue[2] = 8192;

    cmsLabEncoded2Float(ctx, &LabInBlack, Lab_UI16_Black);
    cmsLabEncoded2Float(ctx, &LabInBlue, Lab_UI16_Blue);

    memset(l_D_OutputColorArrayBlack, 0, sizeof(l_D_OutputColorArrayBlack));
    memset(l_D_OutputColorArrayBlue, 0, sizeof(l_D_OutputColorArrayBlue));

    cmsDoTransform(xform, &LabInBlack, l_D_OutputColorArrayBlack, 1);
    cmsDoTransform(xform, &LabInBlue, l_D_OutputColorArrayBlue, 1);

    cmsDeleteTransform(ctx, xform);
    cmsDeleteContext(ctx);
    
    if (l_D_OutputColorArrayBlack[0] < 85 ||
        l_D_OutputColorArrayBlue[0] < 90)
        Fail("Ink amount is not right");

    return 1;
}
