//---------------------------------------------------------------------------------
//
//  Little Color Management System, fast floating point extensions
//  Copyright (c) 1998-2020 Marti Maria Saguer, all rights reserved
//
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
//---------------------------------------------------------------------------------

#include "fast_float_internal.h"

#include <stdlib.h>
#include <memory.h>


// Some pixel representations
typedef struct { cmsUInt8Number  r, g, b;    }  Scanline_rgb8bits;
typedef struct { cmsUInt8Number  r, g, b, a; }  Scanline_rgba8bits;
typedef struct { cmsUInt16Number r, g, b;    }  Scanline_rgb16bits;
typedef struct { cmsUInt16Number r, g, b, a; }  Scanline_rgba16bits;
typedef struct { cmsUInt16Number r, g, b;    }  Scanline_rgb15bits;
typedef struct { cmsUInt16Number r, g, b, a; }  Scanline_rgba15bits;
typedef struct { cmsUInt16Number r, g, b, a; }  Scanline_cmyk15bits;
typedef struct { cmsFloat32Number r, g, b;    }  Scanline_rgbFloat;
typedef struct { cmsFloat32Number r, g, b, a; }  Scanline_rgbaFloat;

// 15 bit mode. <=> 8 bits mode
#define FROM_8_TO_15(x8) (cmsUInt16Number) ((((cmsUInt64Number)x8 << 15)) / 0xFF)
#define FROM_15_TO_8(x15) (cmsUInt8Number) (((cmsUInt64Number) x15 * 0xFF + 0x4000) >> 15)


// Floating point acuracy for tests 
#define EPSILON_FLOAT_TESTS 0.005


// The callback function used by cmsSetLogErrorHandler()
static
void FatalErrorQuit(cmsContext ContextID, cmsUInt32Number ErrorCode, const char *Text)
{
       UNUSED_PARAMETER(ContextID);
       UNUSED_PARAMETER(ErrorCode);

       printf("** Fatal error: %s\n", Text);
       exit(1);
}

// Rise an error and exit
static
void Fail(const char* frm, ...)
{
       char ReasonToFailBuffer[1024];
       va_list args;

       va_start(args, frm);
       vsprintf(ReasonToFailBuffer, frm, args);
       FatalErrorQuit(0, 0, ReasonToFailBuffer);

      // unreacheable va_end(args);
}


// Creates a fake profile that only has a curve. Used in several places
static
cmsHPROFILE CreateCurves(void)
{
       cmsToneCurve* Gamma = cmsBuildGamma(0, 1.1);
       cmsToneCurve* Transfer[3];
       cmsHPROFILE h;

       Transfer[0] = Transfer[1] = Transfer[2] = Gamma;
       h = cmsCreateLinearizationDeviceLink(cmsSigRgbData, Transfer);

       cmsFreeToneCurve(Gamma);

       return h;
}


// Check for a single 15 bit Photoshop-like formatter
static
void CheckSingleFormatter15(cmsContext id, cmsUInt32Number Type, const char* Text)
{
       cmsUInt16Number Values[cmsMAXCHANNELS];
       cmsUInt8Number Buffer[1024];
       cmsFormatter f, b;
       cmsInt32Number i, j, nChannels, bytes;
       _xform_head info;

       UNUSED_PARAMETER(id);

       memset(&info, 0, sizeof(info));
       info.OutputFormat = info.InputFormat = Type;

       // Get functions to go forth and back
       f = Formatter_15Bit_Factory(Type, cmsFormatterInput, CMS_PACK_FLAGS_16BITS);
       b = Formatter_15Bit_Factory(Type, cmsFormatterOutput, CMS_PACK_FLAGS_16BITS);

       if (f.Fmt16 == NULL || b.Fmt16 == NULL) {

              Fail("no formatter for %s", Text);
              return;
       }

       nChannels = T_CHANNELS(Type);
       bytes = T_BYTES(Type);

       for (j = 0; j < 5; j++) {

              for (i = 0; i < nChannels; i++) {

                     Values[i] = (cmsUInt16Number)(i + j) << 1;
              }

              b.Fmt16((struct _cmstransform_struct*) &info, Values, Buffer, 1);
              memset(Values, 0, sizeof(Values));
              f.Fmt16((struct _cmstransform_struct*) &info, Values, Buffer, 1);

              for (i = 0; i < nChannels; i++) {

                     if (Values[i] != ((i + j) << 1)) {

                            Fail("%s failed", Text);
                            return;
                     }
              }
       }
}

#define C(a) CheckSingleFormatter15(0, a, #a)

// Check for all 15 bits formatters
static
void CheckFormatters15(void)
{
       C(TYPE_GRAY_15);
       C(TYPE_GRAY_15_REV);
       C(TYPE_GRAY_15_SE);
       C(TYPE_GRAYA_15);
       C(TYPE_GRAYA_15_SE);
       C(TYPE_GRAYA_15_PLANAR);
       C(TYPE_RGB_15);
       C(TYPE_RGB_15_PLANAR);
       C(TYPE_RGB_15_SE);
       C(TYPE_BGR_15);
       C(TYPE_BGR_15_PLANAR);
       C(TYPE_BGR_15_SE);
       C(TYPE_RGBA_15);
       C(TYPE_RGBA_15_PLANAR);
       C(TYPE_RGBA_15_SE);
       C(TYPE_ARGB_15);
       C(TYPE_ABGR_15);
       C(TYPE_ABGR_15_PLANAR);
       C(TYPE_ABGR_15_SE);
       C(TYPE_BGRA_15);
       C(TYPE_BGRA_15_SE);
       C(TYPE_YMC_15),
       C(TYPE_CMY_15);
       C(TYPE_CMY_15_PLANAR);
       C(TYPE_CMY_15_SE);
       C(TYPE_CMYK_15);
       C(TYPE_CMYK_15_REV);
       C(TYPE_CMYK_15_PLANAR);
       C(TYPE_CMYK_15_SE);
       C(TYPE_KYMC_15);
       C(TYPE_KYMC_15_SE);
       C(TYPE_KCMY_15);
       C(TYPE_KCMY_15_REV);
       C(TYPE_KCMY_15_SE);
}
#undef C


static
cmsInt32Number checkSingleComputeIncrements(cmsUInt32Number Format, cmsUInt32Number planeStride, cmsUInt32Number ExpectedChannels, cmsUInt32Number ExpectedAlpha, ...)
{
       cmsUInt32Number nChannels, nAlpha, nTotal, i, rc = 0 ;
       cmsUInt32Number ComponentStartingOrder[cmsMAXCHANNELS], ComponentPointerIncrements[cmsMAXCHANNELS];
       va_list args;

  
       va_start(args, ExpectedAlpha);

       _cmsComputeComponentIncrements(Format, planeStride, &nChannels, &nAlpha, ComponentStartingOrder, ComponentPointerIncrements);

       if (nChannels != ExpectedChannels)
              return 0;

       if (nAlpha != ExpectedAlpha)
              return 0;

       nTotal = nAlpha + nChannels;
     


       for (i = 0; i < nTotal; i++)
       {
              cmsUInt32Number so = va_arg(args, cmsUInt32Number);
              if (so != ComponentStartingOrder[i])
                     goto Error;                     
       }

       for (i = 0; i < nTotal; i++)
       {
              cmsUInt32Number so = va_arg(args, cmsUInt32Number);
              if (so != ComponentPointerIncrements[i])
                     goto Error;
       }

       // Success
       rc = 1;

Error:
       va_end(args);

       return rc;
}

#define CHECK(frm, plane, chans, alpha, ...) if (!checkSingleComputeIncrements(frm, plane, chans, alpha, __VA_ARGS__)) { printf("Format failed!\n"); return 0; }



// Validate the compute increments function
cmsInt32Number CheckComputeIncrements(void)
{      
       CHECK(TYPE_GRAY_8,    0, 1, 0, /**/ 0,    /**/ 1);
       CHECK(TYPE_GRAYA_8,   0, 1, 1, /**/ 0, 1, /**/ 2, 2);
       CHECK(TYPE_AGRAY_8,   0, 1, 1, /**/ 1, 0, /**/ 2, 2);
       CHECK(TYPE_GRAY_16,   0, 1, 0, /**/ 0,    /**/ 2);
       CHECK(TYPE_GRAYA_16,  0, 1, 1, /**/ 0, 2, /**/ 4, 4);
       CHECK(TYPE_AGRAY_16,  0, 1, 1, /**/ 2, 0, /**/ 4, 4);

       CHECK(TYPE_GRAY_FLT,  0, 1, 0, /**/ 0,    /**/ 4);
       CHECK(TYPE_GRAYA_FLT, 0, 1, 1, /**/ 0, 4, /**/ 8, 8);
       CHECK(TYPE_AGRAY_FLT, 0, 1, 1, /**/ 4, 0, /**/ 8, 8);

       CHECK(TYPE_GRAY_DBL,  0, 1, 0, /**/ 0,      /**/ 8);
       CHECK(TYPE_AGRAY_DBL, 0, 1, 1, /**/ 8, 0,   /**/ 16, 16);

       CHECK(TYPE_RGB_8,    0, 3, 0, /**/ 0, 1, 2,     /**/ 3, 3, 3);
       CHECK(TYPE_RGBA_8,   0, 3, 1, /**/ 0, 1, 2, 3,  /**/ 4, 4, 4, 4);
       CHECK(TYPE_ARGB_8,   0, 3, 1, /**/ 1, 2, 3, 0,  /**/ 4, 4, 4, 4);

       CHECK(TYPE_RGB_16,  0, 3, 0, /**/ 0, 2, 4,     /**/ 6, 6, 6);
       CHECK(TYPE_RGBA_16, 0, 3, 1, /**/ 0, 2, 4, 6,  /**/ 8, 8, 8, 8);
       CHECK(TYPE_ARGB_16, 0, 3, 1, /**/ 2, 4, 6, 0,  /**/ 8, 8, 8, 8);

       CHECK(TYPE_RGB_FLT,  0, 3, 0, /**/ 0, 4, 8,     /**/ 12, 12, 12);
       CHECK(TYPE_RGBA_FLT, 0, 3, 1, /**/ 0, 4, 8, 12,  /**/ 16, 16, 16, 16);
       CHECK(TYPE_ARGB_FLT, 0, 3, 1, /**/ 4, 8, 12, 0,  /**/ 16, 16, 16, 16);

       CHECK(TYPE_BGR_8,  0, 3, 0, /**/ 2, 1, 0,     /**/ 3, 3, 3);
       CHECK(TYPE_BGRA_8, 0, 3, 1, /**/ 2, 1, 0, 3,  /**/ 4, 4, 4, 4);
       CHECK(TYPE_ABGR_8, 0, 3, 1, /**/ 3, 2, 1, 0,  /**/ 4, 4, 4, 4);

       CHECK(TYPE_BGR_16,  0, 3, 0, /**/ 4, 2, 0,     /**/ 6, 6, 6);
       CHECK(TYPE_BGRA_16, 0, 3, 1, /**/ 4, 2, 0, 6,  /**/ 8, 8, 8, 8);
       CHECK(TYPE_ABGR_16, 0, 3, 1, /**/ 6, 4, 2, 0,  /**/ 8, 8, 8, 8);

       CHECK(TYPE_BGR_FLT, 0, 3, 0,  /**/ 8, 4, 0,     /**/  12, 12, 12);
       CHECK(TYPE_BGRA_FLT, 0, 3, 1, /**/ 8, 4, 0, 12,  /**/ 16, 16, 16, 16);
       CHECK(TYPE_ABGR_FLT, 0, 3, 1, /**/ 12, 8, 4, 0,  /**/ 16, 16, 16, 16);


       CHECK(TYPE_CMYK_8,  0, 4, 0, /**/ 0, 1, 2, 3,     /**/ 4, 4, 4, 4);
       CHECK(TYPE_CMYKA_8, 0, 4, 1, /**/ 0, 1, 2, 3, 4,  /**/ 5, 5, 5, 5, 5);
       CHECK(TYPE_ACMYK_8, 0, 4, 1, /**/ 1, 2, 3, 4, 0,  /**/ 5, 5, 5, 5, 5);

       CHECK(TYPE_KYMC_8,  0, 4, 0, /**/ 3, 2, 1, 0,     /**/ 4, 4, 4, 4);
       CHECK(TYPE_KYMCA_8, 0, 4, 1, /**/ 3, 2, 1, 0, 4,  /**/ 5, 5, 5, 5, 5);
       CHECK(TYPE_AKYMC_8, 0, 4, 1, /**/ 4, 3, 2, 1, 0,  /**/ 5, 5, 5, 5, 5);

       CHECK(TYPE_KCMY_8,  0, 4, 0, /**/ 1, 2, 3, 0,      /**/ 4, 4, 4, 4);
       
       CHECK(TYPE_CMYK_16, 0, 4, 0, /**/ 0, 2, 4, 6,      /**/ 8, 8, 8, 8);
       CHECK(TYPE_CMYKA_16, 0, 4, 1, /**/ 0, 2, 4, 6, 8,  /**/ 10, 10, 10, 10, 10);
       CHECK(TYPE_ACMYK_16, 0, 4, 1, /**/ 2, 4, 6, 8, 0,  /**/ 10, 10, 10, 10, 10);

       CHECK(TYPE_KYMC_16, 0, 4, 0,  /**/ 6, 4, 2, 0,     /**/ 8, 8, 8, 8);
       CHECK(TYPE_KYMCA_16, 0, 4, 1, /**/ 6, 4, 2, 0, 8,  /**/ 10, 10, 10, 10, 10);
       CHECK(TYPE_AKYMC_16, 0, 4, 1, /**/ 8, 6, 4, 2, 0,  /**/ 10, 10, 10, 10, 10);

       CHECK(TYPE_KCMY_16, 0, 4, 0, /**/ 2, 4, 6, 0,      /**/ 8, 8, 8, 8);

       // Planar

       CHECK(TYPE_GRAYA_8_PLANAR, 100, 1, 1, /**/ 0, 100,  /**/ 1, 1);
       CHECK(TYPE_AGRAY_8_PLANAR, 100, 1, 1, /**/ 100, 0,  /**/ 1, 1);

       CHECK(TYPE_GRAYA_16_PLANAR, 100, 1, 1, /**/ 0, 100,   /**/ 2, 2);
       CHECK(TYPE_AGRAY_16_PLANAR, 100, 1, 1, /**/ 100, 0,   /**/ 2, 2);

       CHECK(TYPE_GRAYA_FLT_PLANAR, 100, 1, 1, /**/ 0, 100,   /**/ 4, 4);
       CHECK(TYPE_AGRAY_FLT_PLANAR, 100, 1, 1, /**/ 100, 0,   /**/ 4, 4);

       CHECK(TYPE_GRAYA_DBL_PLANAR, 100, 1, 1, /**/ 0, 100,   /**/ 8, 8);
       CHECK(TYPE_AGRAY_DBL_PLANAR, 100, 1, 1, /**/ 100, 0,   /**/ 8, 8);

       CHECK(TYPE_RGB_8_PLANAR,  100, 3, 0, /**/ 0, 100, 200,      /**/ 1, 1, 1);
       CHECK(TYPE_RGBA_8_PLANAR, 100, 3, 1, /**/ 0, 100, 200, 300, /**/ 1, 1, 1, 1);
       CHECK(TYPE_ARGB_8_PLANAR, 100, 3, 1, /**/ 100, 200, 300, 0,  /**/ 1, 1, 1, 1);

       CHECK(TYPE_BGR_8_PLANAR,  100, 3, 0, /**/ 200, 100, 0,       /**/ 1, 1, 1);
       CHECK(TYPE_BGRA_8_PLANAR, 100, 3, 1, /**/ 200, 100, 0, 300,  /**/ 1, 1, 1, 1);
       CHECK(TYPE_ABGR_8_PLANAR, 100, 3, 1, /**/ 300, 200, 100, 0,  /**/ 1, 1, 1, 1);

       CHECK(TYPE_RGB_16_PLANAR, 100, 3, 0, /**/ 0, 100, 200,      /**/ 2, 2, 2);
       CHECK(TYPE_RGBA_16_PLANAR, 100, 3, 1, /**/ 0, 100, 200, 300, /**/ 2, 2, 2, 2);
       CHECK(TYPE_ARGB_16_PLANAR, 100, 3, 1, /**/ 100, 200, 300, 0,  /**/ 2, 2, 2, 2);

       CHECK(TYPE_BGR_16_PLANAR, 100, 3, 0, /**/ 200, 100, 0,       /**/ 2, 2, 2);
       CHECK(TYPE_BGRA_16_PLANAR, 100, 3, 1, /**/ 200, 100, 0, 300,  /**/ 2, 2, 2, 2);
       CHECK(TYPE_ABGR_16_PLANAR, 100, 3, 1, /**/ 300, 200, 100, 0,  /**/ 2, 2, 2, 2);

       return 1;
}



// Check 15 bit mode accuracy
static
cmsBool Valid15(cmsUInt16Number a, cmsUInt8Number b)
{
       return abs(FROM_15_TO_8(a) - b) <= 2;
}

// Check the test macros itselves
static
void Check15bitMacros(void)
{
       int i;

       printf("Checking 15 bit <=> 8 bit macros...");

       for (i = 0; i < 256; i++)
       {
              cmsUInt16Number n = FROM_8_TO_15(i);
              cmsUInt8Number m = FROM_15_TO_8(n);

              if (m != i)
                     Fail("Failed on %d (->%d->%d)", i, n, m);
       }
       printf("ok\n");
}

// Do an in-depth test by checking all RGB cube of 8 bits, going from profilein to profileout. 
// Results should be same except for 2 contone levels allowed for roundoff. Note 15 bits is more 
// precise than 8 bits and this is a source of discrepancies. Cache is disabled
static 
void TryAllValues15(cmsHPROFILE hlcmsProfileIn, cmsHPROFILE hlcmsProfileOut, cmsInt32Number Intent)
{
       Scanline_rgb8bits* buffer8in;
       Scanline_rgb15bits* buffer15in;
       Scanline_rgb8bits* buffer8out;
       Scanline_rgb15bits* buffer15out;
       int r, g, b, j;
       cmsUInt32Number npixels = 256 * 256 * 256;  // All RGB cube in 8 bits

       cmsHTRANSFORM xform15 = cmsCreateTransformTHR(0, hlcmsProfileIn, TYPE_RGB_15, hlcmsProfileOut, TYPE_RGB_15, Intent, cmsFLAGS_NOCACHE);
       cmsHTRANSFORM xform8 = cmsCreateTransformTHR(0, hlcmsProfileIn, TYPE_RGB_8, hlcmsProfileOut, TYPE_RGB_8, Intent, cmsFLAGS_NOCACHE);       // Transforms already created
       
       cmsCloseProfile(hlcmsProfileIn);
       cmsCloseProfile(hlcmsProfileOut);

       if (xform15 == NULL || xform8 == NULL) {

              Fail("NULL transforms on check for 15 bit conversions");
       }

       // Since this is just a test, I will not check memory allocation...
       buffer8in = (Scanline_rgb8bits*)malloc(npixels * sizeof(Scanline_rgb8bits));
       buffer15in = (Scanline_rgb15bits*)malloc(npixels * sizeof(Scanline_rgb15bits));
       buffer8out = (Scanline_rgb8bits*)malloc(npixels * sizeof(Scanline_rgb8bits));
       buffer15out = (Scanline_rgb15bits*)malloc(npixels * sizeof(Scanline_rgb15bits));

       // Fill input values for 8 and 15 bits
       j = 0;
       for (r = 0; r < 256; r++)
              for (g = 0; g < 256; g++)
                     for (b = 0; b < 256; b++) {

              buffer8in[j].r = (cmsUInt8Number)r;
              buffer8in[j].g = (cmsUInt8Number)g;
              buffer8in[j].b = (cmsUInt8Number)b;

              buffer15in[j].r = FROM_8_TO_15(r);
              buffer15in[j].g = FROM_8_TO_15(g);
              buffer15in[j].b = FROM_8_TO_15(b);

              j++;
       }
       
       cmsDoTransform(xform15, buffer15in, buffer15out, npixels);
       cmsDoTransform(xform8,  buffer8in, buffer8out,  npixels);

       j = 0;
       for (r = 0; r < 256; r++)
              for (g = 0; g < 256; g++)
                     for (b = 0; b < 256; b++) {

              // Check the results
              if (!Valid15(buffer15out[j].r, buffer8out[j].r) ||
                  !Valid15(buffer15out[j].g, buffer8out[j].g) ||
                  !Valid15(buffer15out[j].b, buffer8out[j].b))
                  Fail("Conversion failed at (%d %d %d) != (%d %d %d)", buffer8out[j].r, buffer8out[j].g, buffer8out[j].b,
                  FROM_15_TO_8(buffer15out[j].r), FROM_15_TO_8(buffer15out[j].g), FROM_15_TO_8(buffer15out[j].b));

              j++;
       }

       free(buffer8in); free(buffer15in);
       free(buffer8out); free(buffer15out);
       cmsDeleteTransform(xform15);
       cmsDeleteTransform(xform8);   
}

// Convert some known values
static
void Check15bitsConversions(void)
{
       Check15bitMacros();

       printf("Checking accuracy of 15 bits on CLUT...");
       TryAllValues15(cmsOpenProfileFromFile("test5.icc", "r"), cmsOpenProfileFromFile("test3.icc", "r"), INTENT_PERCEPTUAL);
       printf("Ok\n");

       printf("Checking accuracy of 15 bits on same profile ...");
       TryAllValues15(cmsOpenProfileFromFile("test0.icc", "r"), cmsOpenProfileFromFile("test0.icc", "r"), INTENT_PERCEPTUAL);
       printf("Ok\n");

       printf("Checking accuracy of 15 bits on Matrix...");
       TryAllValues15(cmsOpenProfileFromFile("test5.icc", "r"), cmsOpenProfileFromFile("test0.icc", "r"), INTENT_PERCEPTUAL);
       printf("Ok\n");

       printf("All 15 bits tests passed OK\n\n");
}

// Next test checks results of optimized 16 bits versus raw 16 bits. 
static
void TryAllValues16bits(cmsHPROFILE hlcmsProfileIn, cmsHPROFILE hlcmsProfileOut, cmsInt32Number Intent)
{
    cmsContext Raw = cmsCreateContext(NULL, NULL);
    cmsContext Plugin = cmsCreateContext(cmsFastFloatExtensions(), NULL);

    Scanline_rgba16bits* bufferIn;
    Scanline_rgba16bits* bufferRawOut;
    Scanline_rgba16bits* bufferPluginOut;
    int r, g, b;

    int j;
    cmsUInt32Number npixels = 256 * 256 * 256;

    cmsHTRANSFORM xformRaw = cmsCreateTransformTHR(Raw, hlcmsProfileIn, TYPE_RGBA_16, hlcmsProfileOut, TYPE_RGBA_16, Intent, cmsFLAGS_NOCACHE| cmsFLAGS_COPY_ALPHA);
    cmsHTRANSFORM xformPlugin = cmsCreateTransformTHR(Plugin, hlcmsProfileIn, TYPE_RGBA_16, hlcmsProfileOut, TYPE_RGBA_16, Intent, cmsFLAGS_NOCACHE| cmsFLAGS_COPY_ALPHA);

    cmsCloseProfile(hlcmsProfileIn);
    cmsCloseProfile(hlcmsProfileOut);

    if (xformRaw == NULL || xformPlugin == NULL) {

        Fail("NULL transforms on check float conversions");
    }

    // Again, no checking on mem alloc because this is just a test
    bufferIn = (Scanline_rgba16bits*)malloc(npixels * sizeof(Scanline_rgba16bits));
    bufferRawOut = (Scanline_rgba16bits*)malloc(npixels * sizeof(Scanline_rgba16bits));
    bufferPluginOut = (Scanline_rgba16bits*)malloc(npixels * sizeof(Scanline_rgba16bits));

    // Same input to both transforms
    j = 0;
    for (r = 0; r < 256; r++)
        for (g = 0; g < 256; g++)
            for (b = 0; b < 256; b++) {

                bufferIn[j].r = FROM_8_TO_16(0xf8);
                bufferIn[j].g = FROM_8_TO_16(0xf8);
                bufferIn[j].b = FROM_8_TO_16(0xf8);
                bufferIn[j].a = 0xffff;

                j++;
            }

    // Different transforms, different output buffers
    cmsDoTransform(xformRaw, bufferIn, bufferRawOut, npixels);
    cmsDoTransform(xformPlugin, bufferIn, bufferPluginOut, npixels);

#if 1
    // Lets compare results
    j = 0;
    for (r = 0; r < 256; r++)
        for (g = 0; g < 256; g++)
            for (b = 0; b < 256; b++) {

                if (bufferRawOut[j].r != bufferPluginOut[j].r ||
                    bufferRawOut[j].g != bufferPluginOut[j].g ||
                    bufferRawOut[j].b != bufferPluginOut[j].b ||
                    bufferRawOut[j].a != bufferPluginOut[j].a)
                    Fail(
                    "Conversion failed at [%x %x %x %x] (%x %x %x %x) != (%x %x %x %x)", 
                        bufferIn[j].r, bufferIn[j].g, bufferIn[j].b, bufferIn[j].a,
                        bufferRawOut[j].r, bufferRawOut[j].g, bufferRawOut[j].b, bufferRawOut[j].a,
                        bufferPluginOut[j].r, bufferPluginOut[j].g, bufferPluginOut[j].b, bufferPluginOut[j].a);

                j++;
            }
#endif

    free(bufferIn); free(bufferRawOut);
    free(bufferPluginOut);

    cmsDeleteTransform(xformRaw);
    cmsDeleteTransform(xformPlugin);

    cmsDeleteContext(Plugin);
    cmsDeleteContext(Raw);
}

static
void CheckAccuracy16Bits(void)
{
    // CLUT should be as 16 bits or better
    printf("Checking accuracy of 16 bits CLUT...");
    TryAllValues16bits(cmsOpenProfileFromFile("test5.icc", "r"), cmsOpenProfileFromFile("test3.icc", "r"), INTENT_PERCEPTUAL);
    printf("All 16 bits tests passed OK\n\n");
}

// --------------------------------------------------------------------------------------------------
// A C C U R A C Y   C H E C K S
// --------------------------------------------------------------------------------------------------


// Check result accuracy
static
cmsBool ValidFloat(cmsFloat32Number a, cmsFloat32Number b)
{
       return fabsf(a-b) < EPSILON_FLOAT_TESTS;
}

// Do an in-depth test by checking all RGB cube of 8 bits, going from profilein to profileout. 
// Values with and without optimization are checked (different contexts, one with the plugin and another without)
// Results should be same except for EPSILON_FLOAT_TESTS allowed for accuracy/speed tradeoff. Cache is disabled
static
void TryAllValuesFloat(cmsHPROFILE hlcmsProfileIn, cmsHPROFILE hlcmsProfileOut, cmsInt32Number Intent)
{
       cmsContext Raw     = cmsCreateContext(NULL, NULL);
       cmsContext Plugin  = cmsCreateContext(cmsFastFloatExtensions(), NULL);

       Scanline_rgbFloat* bufferIn;
       Scanline_rgbFloat* bufferRawOut;
       Scanline_rgbFloat* bufferPluginOut;
       int r, g, b;
       
       int j;
       cmsUInt32Number npixels = 256 * 256 * 256;

       cmsHTRANSFORM xformRaw = cmsCreateTransformTHR(Raw, hlcmsProfileIn, TYPE_RGB_FLT, hlcmsProfileOut, TYPE_RGB_FLT, Intent, cmsFLAGS_NOCACHE);
       cmsHTRANSFORM xformPlugin = cmsCreateTransformTHR(Plugin, hlcmsProfileIn, TYPE_RGB_FLT, hlcmsProfileOut, TYPE_RGB_FLT, Intent, cmsFLAGS_NOCACHE);

       cmsCloseProfile(hlcmsProfileIn);
       cmsCloseProfile(hlcmsProfileOut);

       if (xformRaw == NULL || xformPlugin == NULL) {

              Fail("NULL transforms on check float conversions");
       }

       // Again, no checking on mem alloc because this is just a test
       bufferIn = (Scanline_rgbFloat*)malloc(npixels * sizeof(Scanline_rgbFloat));
       bufferRawOut = (Scanline_rgbFloat*)malloc(npixels * sizeof(Scanline_rgbFloat));
       bufferPluginOut = (Scanline_rgbFloat*)malloc(npixels * sizeof(Scanline_rgbFloat));

       // Same input to both transforms
       j = 0;
       for (r = 0; r < 256; r++)
              for (g = 0; g < 256; g++)
                     for (b = 0; b < 256; b++) {

              bufferIn[j].r = (cmsFloat32Number)r / 255.0f;
              bufferIn[j].g = (cmsFloat32Number)g / 255.0f;
              bufferIn[j].b = (cmsFloat32Number)b / 255.0f;
              
              j++;
              }
       
       // Different transforms, different output buffers
       cmsDoTransform(xformRaw,    bufferIn, bufferRawOut, npixels);
       cmsDoTransform(xformPlugin, bufferIn, bufferPluginOut, npixels);

       // Lets compare results
       j = 0;
       for (r = 0; r < 256; r++)
              for (g = 0; g < 256; g++)
                     for (b = 0; b < 256; b++) {

              if (!ValidFloat(bufferRawOut[j].r, bufferPluginOut[j].r) ||
                     !ValidFloat(bufferRawOut[j].g, bufferPluginOut[j].g) ||
                     !ValidFloat(bufferRawOut[j].b, bufferPluginOut[j].b))
                     Fail("Conversion failed at (%f %f %f) != (%f %f %f)", bufferRawOut[j].r, bufferRawOut[j].g, bufferRawOut[j].b,
                     bufferPluginOut[j].r, bufferPluginOut[j].g, bufferPluginOut[j].b);

              j++;
              }

       free(bufferIn); free(bufferRawOut);
       free(bufferPluginOut);

       cmsDeleteTransform(xformRaw);
       cmsDeleteTransform(xformPlugin);

       cmsDeleteContext(Plugin);
       cmsDeleteContext(Raw);
}

static
void TryAllValuesFloatAlpha(cmsHPROFILE hlcmsProfileIn, cmsHPROFILE hlcmsProfileOut, cmsInt32Number Intent)
{
       cmsContext Raw     = cmsCreateContext(NULL, NULL);
       cmsContext Plugin  = cmsCreateContext(cmsFastFloatExtensions(), NULL);

       Scanline_rgbaFloat* bufferIn;
       Scanline_rgbaFloat* bufferRawOut;
       Scanline_rgbaFloat* bufferPluginOut;
       int r, g, b;
       
       int j;
       cmsUInt32Number npixels = 256 * 256 * 256;

       cmsHTRANSFORM xformRaw = cmsCreateTransformTHR(Raw, hlcmsProfileIn, TYPE_RGBA_FLT, hlcmsProfileOut, TYPE_RGBA_FLT, Intent, cmsFLAGS_NOCACHE);
       cmsHTRANSFORM xformPlugin = cmsCreateTransformTHR(Plugin, hlcmsProfileIn, TYPE_RGBA_FLT, hlcmsProfileOut, TYPE_RGBA_FLT, Intent, cmsFLAGS_NOCACHE);

       cmsCloseProfile(hlcmsProfileIn);
       cmsCloseProfile(hlcmsProfileOut);

       if (xformRaw == NULL || xformPlugin == NULL) {

              Fail("NULL transforms on check float conversions");
       }

       // Again, no checking on mem alloc because this is just a test
       bufferIn = (Scanline_rgbaFloat*)malloc(npixels * sizeof(Scanline_rgbaFloat));
       bufferRawOut = (Scanline_rgbaFloat*)malloc(npixels * sizeof(Scanline_rgbaFloat));
       bufferPluginOut = (Scanline_rgbaFloat*)malloc(npixels * sizeof(Scanline_rgbaFloat));

       // Same input to both transforms
       j = 0;
       for (r = 0; r < 256; r++)
              for (g = 0; g < 256; g++)
                     for (b = 0; b < 256; b++) {

              bufferIn[j].r = (cmsFloat32Number)r / 255.0f;
              bufferIn[j].g = (cmsFloat32Number)g / 255.0f;
              bufferIn[j].b = (cmsFloat32Number)b / 255.0f;
              bufferIn[j].a = (cmsFloat32Number) 1.0f;
              
              j++;
              }
       
       // Different transforms, different output buffers
       cmsDoTransform(xformRaw,    bufferIn, bufferRawOut, npixels);
       cmsDoTransform(xformPlugin, bufferIn, bufferPluginOut, npixels);

#if 1
       // Lets compare results
       j = 0;
       for (r = 0; r < 256; r++)
              for (g = 0; g < 256; g++)
                     for (b = 0; b < 256; b++) {

              if (!ValidFloat(bufferRawOut[j].r, bufferPluginOut[j].r) ||
                     !ValidFloat(bufferRawOut[j].g, bufferPluginOut[j].g) ||
                     !ValidFloat(bufferRawOut[j].b, bufferPluginOut[j].b) ||
                     !ValidFloat(bufferRawOut[j].a, bufferPluginOut[j].a))
                     Fail("Conversion failed at (%f %f %f %f) != (%f %f %f %f)", bufferRawOut[j].r, bufferRawOut[j].g, bufferRawOut[j].b, bufferRawOut[j].a,
                     bufferPluginOut[j].r, bufferPluginOut[j].g, bufferPluginOut[j].b, bufferPluginOut[j].a);

              j++;
              }
#endif

       free(bufferIn); free(bufferRawOut);
       free(bufferPluginOut);

       cmsDeleteTransform(xformRaw);
       cmsDeleteTransform(xformPlugin);

       cmsDeleteContext(Plugin);
       cmsDeleteContext(Raw);
}



// Next test checks results of optimized floating point versus 16 bits. That is, converting the float to 16 bits, operating 
// in 16 bits and back to float. Results again should be in range of epsilon
static
cmsBool Valid16Float(cmsUInt16Number a, cmsFloat32Number b)
{
       return fabs(((cmsFloat32Number)a / (cmsFloat32Number) 0xFFFF) - b) < EPSILON_FLOAT_TESTS;
}

// Do an in-depth test by checking all RGB cube of 8 bits, going from profilein to profileout. 16 bits temporary is used as reference
static
void TryAllValuesFloatVs16(cmsHPROFILE hlcmsProfileIn, cmsHPROFILE hlcmsProfileOut, cmsInt32Number Intent)
{          
       Scanline_rgbFloat* bufferIn;
       Scanline_rgb16bits* bufferIn16;

       Scanline_rgbFloat* bufferFloatOut;
       Scanline_rgb16bits* buffer16Out;
       int r, g, b;

       int j;
       cmsUInt32Number npixels = 256 * 256 * 256;

       cmsHTRANSFORM xformRaw = cmsCreateTransform(hlcmsProfileIn, TYPE_RGB_16, hlcmsProfileOut, TYPE_RGB_16, Intent, cmsFLAGS_NOCACHE);
       cmsHTRANSFORM xformPlugin = cmsCreateTransform(hlcmsProfileIn, TYPE_RGB_FLT, hlcmsProfileOut, TYPE_RGB_FLT, Intent, cmsFLAGS_NOCACHE);

       cmsCloseProfile(hlcmsProfileIn);
       cmsCloseProfile(hlcmsProfileOut);

       if (xformRaw == NULL || xformPlugin == NULL) {

              Fail("NULL transforms on check float vs 16 conversions");
       }

       // Again, no checking on mem alloc because this is just a test
       bufferIn = (Scanline_rgbFloat*)malloc(npixels * sizeof(Scanline_rgbFloat));
       bufferIn16 = (Scanline_rgb16bits*)malloc(npixels * sizeof(Scanline_rgb16bits));
       bufferFloatOut = (Scanline_rgbFloat*)malloc(npixels * sizeof(Scanline_rgbFloat));
       buffer16Out = (Scanline_rgb16bits*)malloc(npixels * sizeof(Scanline_rgb16bits));


       // Fill two equivalent input buffers
       j = 0;
       for (r = 0; r < 256; r++)
              for (g = 0; g < 256; g++)
                     for (b = 0; b < 256; b++) {

              bufferIn[j].r = (cmsFloat32Number)r / 255.0f;
              bufferIn[j].g = (cmsFloat32Number)g / 255.0f;
              bufferIn[j].b = (cmsFloat32Number)b / 255.0f;

              bufferIn16[j].r = FROM_8_TO_16(r);
              bufferIn16[j].g = FROM_8_TO_16(g);
              bufferIn16[j].b = FROM_8_TO_16(b);

              j++;
       }

       // Convert 
       cmsDoTransform(xformRaw, bufferIn16, buffer16Out, npixels);
       cmsDoTransform(xformPlugin, bufferIn, bufferFloatOut, npixels);


       j = 0;
       for (r = 0; r < 256; r++)
              for (g = 0; g < 256; g++)
                     for (b = 0; b < 256; b++) {

              // Check for same values
              if (!Valid16Float(buffer16Out[j].r, bufferFloatOut[j].r) ||
                     !Valid16Float(buffer16Out[j].g, bufferFloatOut[j].g) ||
                     !Valid16Float(buffer16Out[j].b, bufferFloatOut[j].b))
                     Fail("Conversion failed at (%f %f %f) != (%f %f %f)", buffer16Out[j].r / 65535.0, buffer16Out[j].g / 65535.0, buffer16Out[j].b / 65535.0,
                     bufferFloatOut[j].r, bufferFloatOut[j].g, bufferFloatOut[j].b);

              j++;
       }

       free(bufferIn16); free(buffer16Out);
       free(bufferIn); free(bufferFloatOut);
       cmsDeleteTransform(xformRaw);
       cmsDeleteTransform(xformPlugin);
}


// Check change format feature
static
void CheckChangeFormat(void)
{
    cmsHPROFILE hsRGB, hLab;
    cmsHTRANSFORM xform;
    cmsUInt8Number rgb8[3]  = { 10, 120, 40 };
    cmsUInt16Number rgb16[3] = { 10* 257, 120*257, 40*257 };
    cmsUInt16Number lab16_1[3], lab16_2[3];

    printf("Checking change format feature...");

    hsRGB = cmsCreate_sRGBProfile();
    hLab = cmsCreateLab4Profile(NULL);


    xform = cmsCreateTransform(hsRGB, TYPE_RGB_16, hLab, TYPE_Lab_16, INTENT_PERCEPTUAL, 0);

    cmsCloseProfile(hsRGB);
    cmsCloseProfile(hLab);

    cmsDoTransform(xform, rgb16, lab16_1, 1);

    cmsChangeBuffersFormat(xform, TYPE_RGB_8, TYPE_Lab_16);

    cmsDoTransform(xform, rgb8, lab16_2, 1);
    cmsDeleteTransform(xform);

    if (memcmp(lab16_1, lab16_2, sizeof(lab16_1)) != 0)
        Fail("Change format failed!");

    printf("Ok\n");

}

static
cmsBool ValidInt(cmsUInt16Number a, cmsUInt16Number b)
{
    return abs(a - b) <= 32;
}

static
void CheckLab2Roundtrip(void)
{
    cmsHPROFILE hsRGB, hLab;
    cmsHTRANSFORM xform, xform2;
    cmsInt8Number* lab;
    cmsInt32Number Mb, j;
    cmsInt32Number r, g, b;
    Scanline_rgb8bits* In;
    Scanline_rgb8bits* Out;

    printf("Checking lab2 roundtrip...");

    hsRGB = cmsCreate_sRGBProfile();
    hLab = cmsCreateLab2Profile(NULL);


    xform = cmsCreateTransform(hsRGB, TYPE_RGB_8, hLab, TYPE_Lab_8, INTENT_RELATIVE_COLORIMETRIC, cmsFLAGS_NOOPTIMIZE|cmsFLAGS_BLACKPOINTCOMPENSATION);
    xform2 = cmsCreateTransform(hLab, TYPE_Lab_8, hsRGB, TYPE_RGB_8, INTENT_RELATIVE_COLORIMETRIC, cmsFLAGS_NOOPTIMIZE | cmsFLAGS_BLACKPOINTCOMPENSATION);

    cmsCloseProfile(hsRGB);
    cmsCloseProfile(hLab);


    Mb = 256 * 256 * 256 * sizeof(Scanline_rgb8bits);
    In = (Scanline_rgb8bits*)malloc(Mb);
    Out = (Scanline_rgb8bits*)malloc(Mb);
    lab = (cmsInt8Number*)malloc(256 * 256 * 256 * 3 * sizeof(cmsInt8Number));

    j = 0;
    for (r = 0; r < 256; r++)
        for (g = 0; g < 256; g++)
            for (b = 0; b < 256; b++)
            {

                In[j].r = (cmsUInt8Number)r;
                In[j].g = (cmsUInt8Number)g;
                In[j].b = (cmsUInt8Number)b;
                j++;
            }


    cmsDoTransform(xform, In, lab, 256 * 256 * 256);
    cmsDoTransform(xform2, lab, Out, 256 * 256 * 256);

    cmsDeleteTransform(xform);
    cmsDeleteTransform(xform2);


    j = 0;
    for (r = 0; r < 256; r++)
        for (g = 0; g < 256; g++)
            for (b = 0; b < 256; b++) {

                // Check for same values
                if (!ValidInt(In[j].r, Out[j].r) ||
                    !ValidInt(In[j].g, Out[j].g) ||
                    !ValidInt(In[j].b, Out[j].b))
                    Fail("Conversion failed at (%d %d %d) != (%d %d %d)", In[j].r, In[j].g, In[j].b,
                        Out[j].r, Out[j].g, Out[j].b);

                j++;
            }


    free(In);
    free(Out);
    free(lab);
    printf("Ok\n");

}

// Convert some known values
static
void CheckConversionFloat(void)
{
       printf("Crash test...");
       TryAllValuesFloatAlpha(cmsOpenProfileFromFile("test5.icc", "r"), cmsOpenProfileFromFile("test0.icc", "r"), INTENT_PERCEPTUAL);
       printf("Ok\n");

       printf("Crash (II) test...");
       TryAllValuesFloatAlpha(cmsOpenProfileFromFile("test0.icc", "r"), cmsOpenProfileFromFile("test0.icc", "r"), INTENT_PERCEPTUAL);
       printf("Ok\n");

       // Matrix-shaper should be accurate 
       printf("Checking accuracy on Matrix-shaper...");
       TryAllValuesFloat(cmsOpenProfileFromFile("test5.icc", "r"), cmsOpenProfileFromFile("test0.icc", "r"), INTENT_PERCEPTUAL);
       printf("Ok\n");

       // CLUT should be as 16 bits or better
       printf("Checking accuracy of CLUT...");
       TryAllValuesFloatVs16(cmsOpenProfileFromFile("test5.icc", "r"), cmsOpenProfileFromFile("test3.icc", "r"), INTENT_PERCEPTUAL);
       printf("Ok\n");

       // Same profile should give same values (we test both methods)
       printf("Checking accuracy on same profile ...");
       TryAllValuesFloatVs16(cmsOpenProfileFromFile("test0.icc", "r"), cmsOpenProfileFromFile("test0.icc", "r"), INTENT_PERCEPTUAL);
       TryAllValuesFloat(cmsOpenProfileFromFile("test0.icc", "r"), cmsOpenProfileFromFile("test0.icc", "r"), INTENT_PERCEPTUAL);
       printf("Ok\n");
}



// --------------------------------------------------------------------------------------------------
// P E R F O R M A N C E   C H E C K S
// --------------------------------------------------------------------------------------------------


static 
cmsFloat64Number MPixSec(cmsFloat64Number diff)
{
       cmsFloat64Number seconds = (cmsFloat64Number)diff / (cmsFloat64Number)CLOCKS_PER_SEC;
       return (256.0 * 256.0 * 256.0) / (1024.0*1024.0*seconds);
}

typedef cmsFloat64Number(*perf_fn)(cmsContext ct, cmsHPROFILE hlcmsProfileIn, cmsHPROFILE hlcmsProfileOut);


static
void PerformanceHeader(void)
{
       printf("                                  MPixel/sec.   MByte/sec.\n");
}

static
cmsFloat64Number Performance(const char* Title, perf_fn fn, cmsContext ct, const char* inICC, const char* outICC, size_t sz, cmsFloat64Number prev)
{
       cmsHPROFILE hlcmsProfileIn;
       cmsHPROFILE hlcmsProfileOut;

       if (inICC == NULL)
              hlcmsProfileIn = CreateCurves();
       else
              hlcmsProfileIn = cmsOpenProfileFromFile(inICC, "r");

       if (outICC == NULL)
              hlcmsProfileOut = CreateCurves();
       else
              hlcmsProfileOut = cmsOpenProfileFromFile(outICC, "r");


       cmsFloat64Number n = fn(ct, hlcmsProfileIn, hlcmsProfileOut);

       printf("%-30s: ", Title); fflush(stdout);
       printf("%-12.2f %-12.2f", n, n * sz);

       if (prev > 0.0) {

              cmsFloat64Number imp = n / prev;
              if (imp > 1)
                   printf(" (x %-2.1f)",  imp);
       }

       printf("\n"); fflush(stdout);
       return n;
}


static
void ComparativeCt(cmsContext ct1, cmsContext ct2, const char* Title, perf_fn fn1, perf_fn fn2, const char* inICC, const char* outICC)
{
       cmsHPROFILE hlcmsProfileIn;
       cmsHPROFILE hlcmsProfileOut;

       if (inICC == NULL)
              hlcmsProfileIn = CreateCurves();
       else
              hlcmsProfileIn = cmsOpenProfileFromFile(inICC, "r");

       if (outICC == NULL)
              hlcmsProfileOut = CreateCurves();
       else
              hlcmsProfileOut = cmsOpenProfileFromFile(outICC, "r");


       cmsFloat64Number n1 = fn1(ct1, hlcmsProfileIn, hlcmsProfileOut);

       if (inICC == NULL)
              hlcmsProfileIn = CreateCurves();
       else
              hlcmsProfileIn = cmsOpenProfileFromFile(inICC, "r");

       if (outICC == NULL)
              hlcmsProfileOut = CreateCurves();
       else
              hlcmsProfileOut = cmsOpenProfileFromFile(outICC, "r");

       cmsFloat64Number n2 = fn2(ct2, hlcmsProfileIn, hlcmsProfileOut);


       printf("%-30s: ", Title); fflush(stdout);
       printf("%-12.2f %-12.2f\n", n1, n2);
}

static
void Comparative(const char* Title, perf_fn fn1, perf_fn fn2, const char* inICC, const char* outICC)
{
       ComparativeCt(0, 0, Title, fn1, fn2, inICC, outICC);
}

// The worst case is used, no cache and all rgb combinations
static
cmsFloat64Number SpeedTest8bitsRGB(cmsContext ct, cmsHPROFILE hlcmsProfileIn, cmsHPROFILE hlcmsProfileOut)
{
       cmsInt32Number r, g, b, j;
       clock_t atime;
       cmsFloat64Number diff;
       cmsHTRANSFORM hlcmsxform;
       Scanline_rgb8bits *In;
       cmsUInt32Number Mb;

       if (hlcmsProfileIn == NULL || hlcmsProfileOut == NULL)
              Fail("Unable to open profiles");

       hlcmsxform = cmsCreateTransformTHR(ct, hlcmsProfileIn, TYPE_RGB_8, hlcmsProfileOut, TYPE_RGB_8, INTENT_PERCEPTUAL, cmsFLAGS_NOCACHE);
       cmsCloseProfile(hlcmsProfileIn);
       cmsCloseProfile(hlcmsProfileOut);

       Mb = 256 * 256 * 256 * sizeof(Scanline_rgb8bits);
       In = (Scanline_rgb8bits*)malloc(Mb);

       j = 0;
       for (r = 0; r < 256; r++)
              for (g = 0; g < 256; g++)
                     for (b = 0; b < 256; b++) {

                            In[j].r = (cmsUInt8Number)r;
                            In[j].g = (cmsUInt8Number)g;
                            In[j].b = (cmsUInt8Number)b;

                            j++;
                     }

       atime = clock();

       cmsDoTransform(hlcmsxform, In, In, 256 * 256 * 256);

       diff = clock() - atime;
       free(In);

       cmsDeleteTransform(hlcmsxform);

       return MPixSec(diff);
}

static
cmsFloat64Number SpeedTest8bitsRGBA(cmsContext ct, cmsHPROFILE hlcmsProfileIn, cmsHPROFILE hlcmsProfileOut)
{
       cmsInt32Number r, g, b, j;
       clock_t atime;
       cmsFloat64Number diff;
       cmsHTRANSFORM hlcmsxform;
       Scanline_rgba8bits *In;
       cmsUInt32Number Mb;

       if (hlcmsProfileIn == NULL || hlcmsProfileOut == NULL)
              Fail("Unable to open profiles");

       hlcmsxform = cmsCreateTransformTHR(ct, hlcmsProfileIn, TYPE_RGBA_8, hlcmsProfileOut, TYPE_RGBA_8, INTENT_PERCEPTUAL, cmsFLAGS_NOCACHE);
       cmsCloseProfile(hlcmsProfileIn);
       cmsCloseProfile(hlcmsProfileOut);

       Mb = 256 * 256 * 256 * sizeof(Scanline_rgba8bits);
       In = (Scanline_rgba8bits*)malloc(Mb);

       j = 0;
       for (r = 0; r < 256; r++)
              for (g = 0; g < 256; g++)
                     for (b = 0; b < 256; b++) {

                            In[j].r = (cmsUInt8Number)r;
                            In[j].g = (cmsUInt8Number)g;
                            In[j].b = (cmsUInt8Number)b;
                            In[j].a = 0;

                            j++;
                     }

       atime = clock();

       cmsDoTransform(hlcmsxform, In, In, 256 * 256 * 256);

       diff = clock() - atime;
       free(In);

       cmsDeleteTransform(hlcmsxform);
       return MPixSec(diff);

}


// The worst case is used, no cache and all rgb combinations
static
cmsFloat64Number SpeedTest15bitsRGB(cmsContext ct, cmsHPROFILE hlcmsProfileIn, cmsHPROFILE hlcmsProfileOut)
{
       cmsInt32Number r, g, b, j;
       clock_t atime;
       cmsFloat64Number diff;
       cmsHTRANSFORM hlcmsxform;
       Scanline_rgb15bits *In;
       cmsUInt32Number Mb;

       if (hlcmsProfileIn == NULL || hlcmsProfileOut == NULL)
              Fail("Unable to open profiles");

       hlcmsxform = cmsCreateTransformTHR(ct, hlcmsProfileIn, TYPE_RGB_15, hlcmsProfileOut, TYPE_RGB_15, INTENT_PERCEPTUAL, cmsFLAGS_NOCACHE);
       cmsCloseProfile(hlcmsProfileIn);
       cmsCloseProfile(hlcmsProfileOut);

       Mb = 256 * 256 * 256 * sizeof(Scanline_rgb15bits);
       In = (Scanline_rgb15bits*)malloc(Mb);

       j = 0;
       for (r = 0; r < 256; r++)
              for (g = 0; g < 256; g++)
                     for (b = 0; b < 256; b++) {

              In[j].r = (cmsUInt16Number)r;
              In[j].g = (cmsUInt16Number)g;
              In[j].b = (cmsUInt16Number)b;

              j++;
      }
      
       atime = clock();

       cmsDoTransform(hlcmsxform, In, In, 256 * 256 * 256);

       diff = clock() - atime;
       free(In);
       
       cmsDeleteTransform(hlcmsxform);

       return MPixSec(diff);
}

static
cmsFloat64Number SpeedTest15bitsRGBA(cmsContext ct, cmsHPROFILE hlcmsProfileIn, cmsHPROFILE hlcmsProfileOut)
{
       cmsInt32Number r, g, b, j;
       clock_t atime;
       cmsFloat64Number diff;
       cmsHTRANSFORM hlcmsxform;
       Scanline_rgba15bits *In;
       cmsUInt32Number Mb;

       if (hlcmsProfileIn == NULL || hlcmsProfileOut == NULL)
              Fail("Unable to open profiles");

       hlcmsxform = cmsCreateTransformTHR(ct, hlcmsProfileIn, TYPE_RGBA_15, hlcmsProfileOut, TYPE_RGBA_15, INTENT_PERCEPTUAL, cmsFLAGS_NOCACHE);
       cmsCloseProfile(hlcmsProfileIn);
       cmsCloseProfile(hlcmsProfileOut);

       Mb = 256 * 256 * 256 * sizeof(Scanline_rgba15bits);
       In = (Scanline_rgba15bits*)malloc(Mb);

       j = 0;
       for (r = 0; r < 256; r++)
              for (g = 0; g < 256; g++)
                     for (b = 0; b < 256; b++) {

              In[j].r = (cmsUInt16Number)r;
              In[j].g = (cmsUInt16Number)g;
              In[j].b = (cmsUInt16Number)b;
              In[j].a = 0;

              j++;
       }      

       atime = clock();

       cmsDoTransform(hlcmsxform, In, In, 256 * 256 * 256);

       diff = clock() - atime;
       free(In);

       cmsDeleteTransform(hlcmsxform);
       return MPixSec(diff);

}

static
cmsFloat64Number SpeedTest15bitsCMYK(cmsContext ct, cmsHPROFILE hlcmsProfileIn, cmsHPROFILE hlcmsProfileOut)
{

       cmsInt32Number r, g, b, j;
       clock_t atime;
       cmsFloat64Number diff;
       cmsHTRANSFORM hlcmsxform;
       Scanline_cmyk15bits *In;
       cmsUInt32Number Mb;

       if (hlcmsProfileIn == NULL || hlcmsProfileOut == NULL)
              Fail("Unable to open profiles");

       hlcmsxform = cmsCreateTransformTHR(ct, hlcmsProfileIn, TYPE_CMYK_15, hlcmsProfileOut, TYPE_CMYK_15, INTENT_PERCEPTUAL, cmsFLAGS_NOCACHE);
       cmsCloseProfile(hlcmsProfileIn);
       cmsCloseProfile(hlcmsProfileOut);

       Mb = 256 * 256 * 256 * sizeof(Scanline_cmyk15bits);
       In = (Scanline_cmyk15bits*)malloc(Mb);

       j = 0;
       for (r = 0; r < 256; r++)
              for (g = 0; g < 256; g++)
                     for (b = 0; b < 256; b++) {

              In[j].r = (cmsUInt16Number)r;
              In[j].g = (cmsUInt16Number)g;
              In[j].b = (cmsUInt16Number)b;
              In[j].a = (cmsUInt16Number)0;

              j++;
       }

       atime = clock();

       cmsDoTransform(hlcmsxform, In, In, 256 * 256 * 256);

       diff = clock() - atime;
       free(In);

       cmsDeleteTransform(hlcmsxform);
       return MPixSec(diff);
}

// The worst case is used, no cache and all rgb combinations
static
cmsFloat64Number SpeedTest16bitsRGB(cmsContext ct, cmsHPROFILE hlcmsProfileIn, cmsHPROFILE hlcmsProfileOut)
{
    cmsInt32Number r, g, b, j;
    clock_t atime;
    cmsFloat64Number diff;
    cmsHTRANSFORM hlcmsxform;
    Scanline_rgb16bits *In;
    cmsUInt32Number Mb;

    if (hlcmsProfileIn == NULL || hlcmsProfileOut == NULL)
        Fail("Unable to open profiles");

    hlcmsxform = cmsCreateTransformTHR(ct, hlcmsProfileIn, TYPE_RGB_16, hlcmsProfileOut, TYPE_RGB_16, INTENT_PERCEPTUAL, cmsFLAGS_NOCACHE);
    cmsCloseProfile(hlcmsProfileIn);
    cmsCloseProfile(hlcmsProfileOut);

    Mb = 256 * 256 * 256 * sizeof(Scanline_rgb16bits);
    In = (Scanline_rgb16bits*)malloc(Mb);

    j = 0;
    for (r = 0; r < 256; r++)
        for (g = 0; g < 256; g++)
            for (b = 0; b < 256; b++) {

                In[j].r = (cmsUInt16Number)FROM_8_TO_16(r);
                In[j].g = (cmsUInt16Number)FROM_8_TO_16(g);
                In[j].b = (cmsUInt16Number)FROM_8_TO_16(b);

                j++;
            }

    atime = clock();

    cmsDoTransform(hlcmsxform, In, In, 256 * 256 * 256);

    diff = clock() - atime;
    free(In);

    cmsDeleteTransform(hlcmsxform);

    return MPixSec(diff);
}



static
void SpeedTest8(void)
{
    cmsContext noPlugin = cmsCreateContext(0, 0);

    cmsFloat64Number t[10];

    printf("\n\n");
    printf("P E R F O R M A N C E   T E S T S   8 B I T S  (D E F A U L T)\n");
    printf("==============================================================\n\n");
    fflush(stdout);

    PerformanceHeader();
    t[0] = Performance("8 bits on CLUT profiles  ", SpeedTest8bitsRGB, noPlugin, "test5.icc", "test3.icc", sizeof(Scanline_rgb8bits), 0);
    t[1] = Performance("8 bits on Matrix-Shaper  ", SpeedTest8bitsRGB, noPlugin, "test5.icc", "test0.icc", sizeof(Scanline_rgb8bits), 0);
    t[2] = Performance("8 bits on same MatrixSh  ", SpeedTest8bitsRGB, noPlugin, "test0.icc", "test0.icc", sizeof(Scanline_rgb8bits), 0);
    t[3] = Performance("8 bits on curves         ", SpeedTest8bitsRGB, noPlugin, NULL, NULL, sizeof(Scanline_rgb8bits), 0);

    // Note that context 0 has the plug-in installed

    printf("\n\n");
    printf("P E R F O R M A N C E   T E S T S  8 B I T S  (P L U G I N)\n");
    printf("===========================================================\n\n");
    fflush(stdout);

    PerformanceHeader();
    Performance("8 bits on CLUT profiles  ", SpeedTest8bitsRGB, 0, "test5.icc", "test3.icc", sizeof(Scanline_rgb8bits), t[0]);
    Performance("8 bits on Matrix-Shaper  ", SpeedTest8bitsRGB, 0, "test5.icc", "test0.icc", sizeof(Scanline_rgb8bits), t[1]);
    Performance("8 bits on same MatrixSh  ", SpeedTest8bitsRGB, 0, "test0.icc", "test0.icc", sizeof(Scanline_rgb8bits), t[2]);
    Performance("8 bits on curves         ", SpeedTest8bitsRGB, 0, NULL, NULL, sizeof(Scanline_rgb8bits), t[3]);

    cmsDeleteContext(noPlugin);
}

#if 0
static
void SpeedTest8(void)
{
       printf("\n\nP E R F O R M A N C E   T E S T S   8  B I T S\n");
       printf(    "==============================================\n\n");

       PerformanceHeader();
       Performance("8 bits on CLUT profiles         ", SpeedTest8bitsRGB, 0, "test5.icc", "test3.icc", sizeof(Scanline_rgb8bits), 0);
       Performance("8 bits on Matrix-Shaper profiles", SpeedTest8bitsRGB, 0, "test5.icc", "test0.icc", sizeof(Scanline_rgb8bits), 0);
       Performance("8 bits on same Matrix-Shaper    ", SpeedTest8bitsRGB, 0, "test0.icc", "test0.icc", sizeof(Scanline_rgb8bits), 0);
       Performance("8 bits on curves                ", SpeedTest8bitsRGB, 0, NULL, NULL, sizeof(Scanline_rgb8bits), 0);
       
}
#endif


static
void SpeedTest15(void)
{
       printf("\n\nP E R F O R M A N C E   T E S T S   1 5  B I T S\n");
       printf(    "================================================\n\n");
       
       PerformanceHeader();
       Performance("15 bits on CLUT profiles         ", SpeedTest15bitsRGB, 0, "test5.icc", "test3.icc", sizeof(Scanline_rgb15bits), 0);
       Performance("15 bits on Matrix-Shaper profiles", SpeedTest15bitsRGB, 0, "test5.icc", "test0.icc", sizeof(Scanline_rgb15bits), 0);
       Performance("15 bits on same Matrix-Shaper    ", SpeedTest15bitsRGB, 0, "test0.icc", "test0.icc", sizeof(Scanline_rgb15bits), 0);
       Performance("15 bits on curves                ", SpeedTest15bitsRGB, 0, NULL, NULL, sizeof(Scanline_rgb15bits), 0);
       Performance("15 bits on CMYK CLUT profiles    ", SpeedTest15bitsCMYK, 0, "test1.icc", "test2.icc", sizeof(Scanline_rgba15bits), 0);
}

static
void SpeedTest16(void)
{
    printf("\n\nP E R F O R M A N C E   T E S T S   1 6  B I T S\n");
    printf("================================================\n\n");

    PerformanceHeader();
    Performance("16 bits on CLUT profiles         ", SpeedTest16bitsRGB, 0, "test5.icc", "test3.icc", sizeof(Scanline_rgb15bits), 0);
    
}

// The worst case is used, no cache and all rgb combinations
static
cmsFloat64Number SpeedTestFloatRGB(cmsContext ct, cmsHPROFILE hlcmsProfileIn, cmsHPROFILE hlcmsProfileOut)
{
       cmsInt32Number r, g, b, j;
       clock_t atime;
       cmsFloat64Number diff;
       cmsHTRANSFORM hlcmsxform;
       Scanline_rgbFloat *In;
       cmsUInt32Number Mb;

       if (hlcmsProfileIn == NULL || hlcmsProfileOut == NULL)
              Fail("Unable to open profiles");

       hlcmsxform = cmsCreateTransformTHR(ct, hlcmsProfileIn, TYPE_RGB_FLT, hlcmsProfileOut, TYPE_RGB_FLT, INTENT_PERCEPTUAL, cmsFLAGS_NOCACHE);
       cmsCloseProfile(hlcmsProfileIn);
       cmsCloseProfile(hlcmsProfileOut);

       Mb = 256 * 256 * 256 * sizeof(Scanline_rgbFloat);
       In = (Scanline_rgbFloat*)malloc(Mb);

       j = 0;
       for (r = 0; r < 256; r++)
              for (g = 0; g < 256; g++)
                     for (b = 0; b < 256; b++) {

              In[j].r = (cmsFloat32Number)r / 255.0f;
              In[j].g = (cmsFloat32Number)g / 255.0f;
              In[j].b = (cmsFloat32Number)b / 255.0f;

              j++;
                     }
      
       atime = clock();

       cmsDoTransform(hlcmsxform, In, In, 256 * 256 * 256);

       diff = clock() - atime;
       free(In);
      
       cmsDeleteTransform(hlcmsxform);
       return MPixSec(diff);
}




static
void SpeedTestFloat(void)
{
       cmsContext noPlugin = cmsCreateContext(0, 0);
       
       cmsFloat64Number t[10];

       printf("\n\n");
       printf("P E R F O R M A N C E   T E S T S   F L O A T  (D E F A U L T)\n");
       printf("==============================================================\n\n");
       fflush(stdout);

       PerformanceHeader();
       t[0] = Performance("Floating point on CLUT profiles  ", SpeedTestFloatRGB, noPlugin, "test5.icc", "test3.icc", sizeof(Scanline_rgbFloat), 0);
       t[1] = Performance("Floating point on Matrix-Shaper  ", SpeedTestFloatRGB, noPlugin, "test5.icc", "test0.icc", sizeof(Scanline_rgbFloat), 0);
       t[2] = Performance("Floating point on same MatrixSh  ", SpeedTestFloatRGB, noPlugin, "test0.icc", "test0.icc", sizeof(Scanline_rgbFloat), 0);
       t[3] = Performance("Floating point on curves         ", SpeedTestFloatRGB, noPlugin, NULL, NULL, sizeof(Scanline_rgbFloat), 0);

       // Note that context 0 has the plug-in installed

       printf("\n\n");
       printf("P E R F O R M A N C E   T E S T S  F L O A T  (P L U G I N)\n");
       printf("===========================================================\n\n");
       fflush(stdout);

       PerformanceHeader();                   
       Performance("Floating point on CLUT profiles  ", SpeedTestFloatRGB, 0, "test5.icc", "test3.icc", sizeof(Scanline_rgbFloat), t[0]);
       Performance("Floating point on Matrix-Shaper  ", SpeedTestFloatRGB, 0, "test5.icc", "test0.icc", sizeof(Scanline_rgbFloat), t[1]);
       Performance("Floating point on same MatrixSh  ", SpeedTestFloatRGB, 0, "test0.icc", "test0.icc", sizeof(Scanline_rgbFloat), t[2]);
       Performance("Floating point on curves         ", SpeedTestFloatRGB, 0, NULL, NULL, sizeof(Scanline_rgbFloat), t[3]);

       cmsDeleteContext(noPlugin);
}






static
cmsFloat64Number SpeedTestFloatByUsing16BitsRGB(cmsContext ct, cmsHPROFILE hlcmsProfileIn, cmsHPROFILE hlcmsProfileOut)
{
       cmsInt32Number r, g, b, j;
       clock_t atime;
       cmsFloat64Number diff;
       cmsHTRANSFORM xform16;
       Scanline_rgbFloat *In;
       Scanline_rgb16bits *tmp16;       
       cmsUInt32Number MbFloat, Mb16;

       UNUSED_PARAMETER(ct);

       if (hlcmsProfileIn == NULL || hlcmsProfileOut == NULL)
              Fail("Unable to open profiles");

       xform16    = cmsCreateTransformTHR(0, hlcmsProfileIn, TYPE_RGB_16, hlcmsProfileOut, TYPE_RGB_16, INTENT_PERCEPTUAL, cmsFLAGS_NOCACHE);

       cmsCloseProfile(hlcmsProfileIn);
       cmsCloseProfile(hlcmsProfileOut);

       MbFloat = 256 * 256 * 256 * sizeof(Scanline_rgbFloat);
       Mb16    = 256 * 256 * 256 * sizeof(Scanline_rgb16bits);

       In    = (Scanline_rgbFloat*)malloc(MbFloat);       
       tmp16 = (Scanline_rgb16bits*)malloc(Mb16);
       

       j = 0;
       for (r = 0; r < 256; r++)
              for (g = 0; g < 256; g++)
                     for (b = 0; b < 256; b++) {

              In[j].r = (cmsFloat32Number)r / 255.0f;
              In[j].g = (cmsFloat32Number)g / 255.0f;
              In[j].b = (cmsFloat32Number)b / 255.0f;

              j++;
       }
      

       atime = clock();

       
       for (j = 0; j < 256 * 256 * 256; j++) {

              tmp16[j].r = (cmsUInt16Number)floor(In[j].r * 65535.0 + 0.5);
              tmp16[j].g = (cmsUInt16Number)floor(In[j].g * 65535.0 + 0.5);
              tmp16[j].b = (cmsUInt16Number)floor(In[j].b * 65535.0 + 0.5);

              j++;
       }

       cmsDoTransform(xform16, tmp16, tmp16, 256 * 256 * 256);

       for (j = 0; j < 256 * 256 * 256; j++) {

              In[j].r = (cmsFloat32Number) (tmp16[j].r / 65535.0 );
              In[j].g = (cmsFloat32Number) (tmp16[j].g / 65535.0);
              In[j].b = (cmsFloat32Number) (tmp16[j].b / 65535.0);

              j++;
       }

       diff = clock() - atime;
       free(In);
      
       cmsDeleteTransform(xform16);
       return MPixSec(diff);
}





static
void ComparativeFloatVs16bits(void)
{
       printf("\n\n");
       printf("C O M P A R A T I V E  converting to 16 bit vs. using float plug-in.\n");
       printf("                              values given in MegaPixels per second.\n");
       printf("====================================================================\n");       
       printf("                                  16 bits tmp.  Float plugin\n");
       fflush(stdout);

       Comparative("Floating point on CLUT profiles  ", SpeedTestFloatByUsing16BitsRGB, SpeedTestFloatRGB,  "test5.icc", "test3.icc");
       Comparative("Floating point on Matrix-Shaper  ", SpeedTestFloatByUsing16BitsRGB, SpeedTestFloatRGB,  "test5.icc", "test0.icc");
       Comparative("Floating point on same MatrixSh  ", SpeedTestFloatByUsing16BitsRGB, SpeedTestFloatRGB,  "test0.icc", "test0.icc");
       Comparative("Floating point on curves         ", SpeedTestFloatByUsing16BitsRGB, SpeedTestFloatRGB,  NULL, NULL);
}







typedef struct
{
       Scanline_rgba8bits pixels[256][256];
       cmsUInt8Number     padding[4];

} padded_line;

typedef struct
{
       padded_line line[256];
} big_bitmap;


static
cmsFloat64Number SpeedTest8bitDoTransform(cmsContext ct, cmsHPROFILE hlcmsProfileIn, cmsHPROFILE hlcmsProfileOut)
{
       cmsInt32Number r, g, b, j;
       clock_t atime;
       cmsFloat64Number diff;
       cmsHTRANSFORM hlcmsxform;
       big_bitmap* In;
       big_bitmap* Out;
       cmsUInt32Number Mb;

       if (hlcmsProfileIn == NULL || hlcmsProfileOut == NULL)
              Fail("Unable to open profiles");

       hlcmsxform = cmsCreateTransformTHR(ct, hlcmsProfileIn, TYPE_RGBA_8, hlcmsProfileOut, TYPE_RGBA_8, INTENT_PERCEPTUAL, cmsFLAGS_NOCACHE);
       cmsCloseProfile(hlcmsProfileIn);
       cmsCloseProfile(hlcmsProfileOut);


       // Our test bitmap is 256 x 256 padded lines
       Mb = sizeof(big_bitmap);

       In = (big_bitmap*)malloc(Mb);
       Out = (big_bitmap*)malloc(Mb);

       for (r = 0; r < 256; r++)
              for (g = 0; g < 256; g++)
                     for (b = 0; b < 256; b++) {

                            In->line[r].pixels[g][b].r = (cmsUInt8Number)r;
                            In->line[r].pixels[g][b].g = (cmsUInt8Number)g;
                            In->line[r].pixels[g][b].b = (cmsUInt8Number)b;
                            In->line[r].pixels[g][b].a = 0;
                     }

       atime = clock();

       for (j = 0; j < 256; j++) {

              cmsDoTransform(hlcmsxform, In->line[j].pixels, Out->line[j].pixels, 256 * 256);
       }

       diff = clock() - atime;
       free(In); free(Out);

       cmsDeleteTransform(hlcmsxform);
       return MPixSec(diff);

}


static
cmsFloat64Number SpeedTest8bitLineStride(cmsContext ct, cmsHPROFILE hlcmsProfileIn, cmsHPROFILE hlcmsProfileOut)
{
       cmsInt32Number r, g, b;
       clock_t atime;
       cmsFloat64Number diff;
       cmsHTRANSFORM hlcmsxform;
       big_bitmap* In;
       big_bitmap* Out;
       cmsUInt32Number Mb;

       if (hlcmsProfileIn == NULL || hlcmsProfileOut == NULL)
              Fail("Unable to open profiles");

       hlcmsxform = cmsCreateTransformTHR(ct, hlcmsProfileIn, TYPE_RGBA_8, hlcmsProfileOut, TYPE_RGBA_8, INTENT_PERCEPTUAL, cmsFLAGS_NOCACHE);
       cmsCloseProfile(hlcmsProfileIn);
       cmsCloseProfile(hlcmsProfileOut);


       // Our test bitmap is 256 x 256 padded lines
       Mb = sizeof(big_bitmap);

       In = (big_bitmap*)malloc(Mb);
       Out = (big_bitmap*)malloc(Mb);

       for (r = 0; r < 256; r++)
              for (g = 0; g < 256; g++)
                     for (b = 0; b < 256; b++) {

                            In->line[r].pixels[g][b].r = (cmsUInt8Number)r;
                            In->line[r].pixels[g][b].g = (cmsUInt8Number)g;
                            In->line[r].pixels[g][b].b = (cmsUInt8Number)b;
                            In->line[r].pixels[g][b].a = 0;
                     }

       atime = clock();
       
       cmsDoTransformLineStride(hlcmsxform, In, Out, 256*256, 256, sizeof(padded_line), sizeof(padded_line), 0, 0);
       
       diff = clock() - atime;
       free(In); free(Out);

       cmsDeleteTransform(hlcmsxform);
       return MPixSec(diff);

}

static
void ComparativeLineStride8bits(void)
{
       cmsContext NoPlugin, Plugin;

       printf("\n\n");
       printf("C O M P A R A T I V E cmsDoTransform() vs. cmsDoTransformLineStride()\n");
       printf("                              values given in MegaPixels per second.\n");
       printf("====================================================================\n");

       fflush(stdout);

       NoPlugin = cmsCreateContext(NULL, NULL);
       Plugin = cmsCreateContext(cmsFastFloatExtensions(), NULL);

       ComparativeCt(NoPlugin, Plugin, "CLUT profiles  ", SpeedTest8bitDoTransform, SpeedTest8bitLineStride, "test5.icc", "test3.icc");
       ComparativeCt(NoPlugin, Plugin, "CLUT 16 bits   ", SpeedTest16bitsRGB,       SpeedTest16bitsRGB, "test5.icc", "test3.icc");
       ComparativeCt(NoPlugin, Plugin, "Matrix-Shaper  ", SpeedTest8bitDoTransform, SpeedTest8bitLineStride, "test5.icc", "test0.icc");
       ComparativeCt(NoPlugin, Plugin, "same MatrixSh  ", SpeedTest8bitDoTransform, SpeedTest8bitLineStride, "test0.icc", "test0.icc");
       ComparativeCt(NoPlugin, Plugin, "curves         ", SpeedTest8bitDoTransform, SpeedTest8bitLineStride, NULL, NULL);

       cmsDeleteContext(Plugin);
       cmsDeleteContext(NoPlugin);
}



static
void TestGrayTransformPerformance()
{
       cmsInt32Number j;
       clock_t atime;
       cmsFloat64Number diff;
       cmsHTRANSFORM hlcmsxform;
       float *In;

       cmsInt32Number pixels;
       cmsUInt32Number Mb;
       cmsToneCurve* gamma18;
       cmsToneCurve* gamma22;

       cmsHPROFILE hlcmsProfileIn;
       cmsHPROFILE hlcmsProfileOut;


       gamma18 = cmsBuildGamma(0, 1.8);
       gamma22 = cmsBuildGamma(0, 2.2);

       hlcmsProfileIn = cmsCreateGrayProfile(NULL, gamma18);
       hlcmsProfileOut = cmsCreateGrayProfile(NULL, gamma22);


       cmsFreeToneCurve(gamma18);
       cmsFreeToneCurve(gamma22);

       hlcmsxform = cmsCreateTransform(hlcmsProfileIn, TYPE_GRAY_FLT | EXTRA_SH(1), hlcmsProfileOut, TYPE_GRAY_FLT|EXTRA_SH(1), INTENT_PERCEPTUAL, 0);
       cmsCloseProfile(hlcmsProfileIn);
       cmsCloseProfile(hlcmsProfileOut);

       pixels = 256 * 256 * 256;
       Mb = pixels* 2*sizeof(float);
       In = malloc(Mb);

       for (j = 0; j < pixels*2; j++)
              In[j] = (j % 256) / 255.0f;

       atime = clock();

       cmsDoTransform(hlcmsxform, In, In, pixels);

       diff = clock() - atime;
       free(In);

       cmsDeleteTransform(hlcmsxform);
       printf("Gray conversion using two gray profiles\t %-12.2f MPixels/Sec.\n", MPixSec(diff));
}

static
void TestGrayTransformPerformance1()
{
       cmsInt32Number j;
       clock_t atime;
       cmsFloat64Number diff;
       cmsHTRANSFORM hlcmsxform;
       float *In;

       cmsInt32Number pixels;
       cmsUInt32Number Mb;
       cmsToneCurve* gamma18;
       cmsToneCurve* gamma22;

       cmsHPROFILE hlcmsProfileIn;
       cmsHPROFILE hlcmsProfileOut;


       gamma18 = cmsBuildGamma(0, 1.8);
       gamma22 = cmsBuildGamma(0, 1./2.2);

       hlcmsProfileIn = cmsCreateLinearizationDeviceLink(cmsSigGrayData, &gamma18);
       hlcmsProfileOut = cmsCreateLinearizationDeviceLink(cmsSigGrayData, &gamma22);


       cmsFreeToneCurve(gamma18);
       cmsFreeToneCurve(gamma22);

       hlcmsxform = cmsCreateTransform(hlcmsProfileIn, TYPE_GRAY_FLT, hlcmsProfileOut, TYPE_GRAY_FLT, INTENT_PERCEPTUAL, 0);
       cmsCloseProfile(hlcmsProfileIn);
       cmsCloseProfile(hlcmsProfileOut);

       pixels = 256 * 256 * 256;
       Mb = pixels* sizeof(float);
       In = malloc(Mb);

       for (j = 0; j < pixels; j++)
              In[j] = (j % 256) / 255.0f;

       atime = clock();

       cmsDoTransform(hlcmsxform, In, In, pixels);

       diff = clock() - atime;
       free(In);

       cmsDeleteTransform(hlcmsxform);
       printf("Gray conversion using two devicelinks\t %-12.2f MPixels/Sec.\n", MPixSec(diff));
}


// The harness test
int main()
{
       printf("FastFloating point extensions testbed - 1.2\n");
       printf("Copyright (c) 1998-2020 Marti Maria Saguer, all rights reserved\n");
       
       printf("\nInstalling error logger ... ");
       cmsSetLogErrorHandler(FatalErrorQuit);
       printf("done.\n");

       printf("Installing plug-in ... ");
       cmsPlugin(cmsFastFloatExtensions());
       printf("done.\n\n");
              

       CheckComputeIncrements();

       // 15 bit functionality
       CheckFormatters15();
       Check15bitsConversions();    
 
       // 16 bits functionality
       CheckAccuracy16Bits();

       // Change format
       CheckChangeFormat();

       // Floating point functionality
       CheckConversionFloat();  
       printf("All floating point tests passed OK\n");
                 
       SpeedTest8();
       SpeedTest16();
       SpeedTest15();
       SpeedTestFloat();

     
       ComparativeFloatVs16bits();
       ComparativeLineStride8bits();

       // Test gray performance
       printf("\n\n");
       printf("F L O A T   G R A Y   conversions performance.\n");
       printf("====================================================================\n");
       TestGrayTransformPerformance();
       TestGrayTransformPerformance1();
       
       printf("\nAll tests passed OK\n");


       return 0;
}



