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

#ifndef _MSC_VER
#    include <unistd.h>
#endif

#ifdef CMS_IS_WINDOWS_
#    include <io.h>
#endif

#define MAX_INPUT_BUFFER 4096

// Global options

static cmsBool           InHexa                 = FALSE;
static cmsBool           GamutCheck             = FALSE;
static cmsBool           Width16                = FALSE;
static cmsBool           BlackPointCompensation = FALSE;
static cmsBool           lIsDeviceLink          = FALSE;
static cmsBool           lQuantize              = FALSE;
static cmsBool           lUnbounded             = TRUE;
static cmsBool           lIsFloat               = TRUE;

static cmsUInt32Number   Intent           = INTENT_PERCEPTUAL;
static cmsUInt32Number   ProofingIntent   = INTENT_PERCEPTUAL;

static int PrecalcMode  = 0;

// --------------------------------------------------------------

static char *cInProf   = NULL;
static char *cOutProf  = NULL;
static char *cProofing = NULL;

static char *IncludePart = NULL;

static cmsHANDLE hIT8in = NULL;        // CGATS input
static cmsHANDLE hIT8out = NULL;       // CGATS output

static char CGATSPatch[1024];   // Actual Patch Name
static char CGATSoutFilename[cmsMAX_PATH];

static int nMaxPatches;

static cmsHTRANSFORM hTrans, hTransXYZ, hTransLab;
static cmsBool InputNamedColor = FALSE;

static cmsColorSpaceSignature InputColorSpace, OutputColorSpace;

static cmsNAMEDCOLORLIST* InputColorant = NULL;
static cmsNAMEDCOLORLIST* OutputColorant = NULL;

static cmsFloat64Number InputRange, OutputRange;


// isatty replacement
#ifdef _MSC_VER
#define xisatty(x) _isatty( _fileno( (x) ) )
#else
#define xisatty(x) isatty( fileno( (x) ) )
#endif

//---------------------------------------------------------------------------------------------------

// Print usage to stderr
static
void Help(void)
{

    fprintf(stderr, "usage: transicc [flags] [CGATS input] [CGATS output]\n\n");

    fprintf(stderr, "flags:\n\n");
    fprintf(stderr, "-v<0..3> - Verbosity level\n");

    fprintf(stderr, "-e[op] - Encoded representation of numbers\n");
    fprintf(stderr, "\t-w - use 16 bits\n");
    fprintf(stderr, "\t-x - Hexadecimal\n\n");

    fprintf(stderr, "-s - bounded mode (clip negatives and highlights)\n");
    fprintf(stderr, "-q - Quantize (round decimals)\n\n");

    fprintf(stderr, "-i<profile> - Input profile (defaults to sRGB)\n");
    fprintf(stderr, "-o<profile> - Output profile (defaults to sRGB)\n");
    fprintf(stderr, "-l<profile> - Transform by device-link profile\n");

    PrintBuiltins();

    PrintRenderingIntents(NULL);

    fprintf(stderr, "\n");

    fprintf(stderr, "-d<0..1> - Observer adaptation state (abs.col. only)\n\n");

    fprintf(stderr, "-b - Black point compensation\n");

    fprintf(stderr, "-c<0,1,2,3> Precalculates transform (0=Off, 1=Normal, 2=Hi-res, 3=LoRes)\n\n");
    fprintf(stderr, "-n - Terse output, intended for pipe usage\n");

    fprintf(stderr, "-p<profile> - Soft proof profile\n");
    fprintf(stderr, "-m<0,1,2,3> - Soft proof intent\n");
    fprintf(stderr, "-g - Marks out-of-gamut colors on softproof\n\n");



    fprintf(stderr, "This program is intended to be a demo of the Little CMS\n"
        "color engine. Both lcms and this program are open source.\n"
        "You can obtain both in source code at https://www.littlecms.com\n"
        "For suggestions, comments, bug reports etc. send mail to\n"
        "info@littlecms.com\n\n");

}



// The toggles stuff

static
void HandleSwitches(cmsContext ContextID, int argc, char *argv[])
{
    int s;

    while ((s = xgetopt(argc, argv,
        "bBC:c:d:D:eEgGI:i:L:l:m:M:nNO:o:p:P:QqSsT:t:V:v:WwxX!:-:")) != EOF) {

    switch (s){

        case '-':
            if (strcmp(xoptarg, "help") == 0)
            {
                Help();
                exit(0);
            }
            else
            {
                FatalError("Unknown option - run without args to see valid ones.\n");
            }
            break;

        case '!':
            IncludePart = xoptarg;
            break;

        case 'b':
        case 'B':
            BlackPointCompensation = TRUE;
            break;

        case 'c':
        case 'C':
            PrecalcMode = atoi(xoptarg);
            if (PrecalcMode < 0 || PrecalcMode > 3)
                FatalError("Unknown precalc mode '%d'", PrecalcMode);
            break;

        case 'd':
        case 'D': {
            cmsFloat64Number ObserverAdaptationState = atof(xoptarg);
            if (ObserverAdaptationState < 0 ||
                ObserverAdaptationState > 1.0)
                FatalError("Adaptation states should be between 0 and 1");

            cmsSetAdaptationState(ContextID, ObserverAdaptationState);
                  }
                  break;

        case 'e':
        case 'E':
            lIsFloat = FALSE;
            break;

        case 'g':
        case 'G':
            GamutCheck = TRUE;
            break;

        case 'i':
        case 'I':
            if (lIsDeviceLink)
                FatalError("icctrans: Device-link already specified");

            cInProf = xoptarg;
            break;

        case 'l':
        case 'L':
            cInProf = xoptarg;
            lIsDeviceLink = TRUE;
            break;

            // No extra intents for proofing
        case 'm':
        case 'M':
            ProofingIntent = atoi(xoptarg);
            if (ProofingIntent > 3)
                FatalError("Unknown Proofing Intent '%d'", ProofingIntent);
            break;

            // For compatibility
        case 'n':
        case 'N':
            Verbose = 0;
            break;

            // Output profile
        case 'o':
        case 'O':
            if (lIsDeviceLink)
                FatalError("icctrans: Device-link already specified");
            cOutProf = xoptarg;
            break;

            // Proofing profile
        case 'p':
        case 'P':
            cProofing = xoptarg;
            break;

            // Quantize (get rid of decimals)
        case 'q':
        case 'Q':
            lQuantize = TRUE;
            break;

            // Inhibit unbounded mode
        case 's':
        case 'S':
               lUnbounded = FALSE;
               break;

            // The intent
        case 't':
        case 'T':
            Intent = atoi(xoptarg);
            break;

            // Verbosity level
        case 'V':
        case 'v':
            Verbose = atoi(xoptarg);
            if (Verbose < 0 || Verbose > 3) {
                FatalError("Unknown verbosity level '%d'", Verbose);
            }
            break;

            // Wide (16 bits)
        case 'W':
        case 'w':
            Width16 = TRUE;
            break;

            // Hexadecimal
        case 'x':
        case 'X':
            InHexa = TRUE;
            break;

        default:
            FatalError("Unknown option - run without args to see valid ones.\n");
            }
    }


    // If output CGATS involved, switch to float
    if ((argc - xoptind) > 2) {
        lIsFloat = TRUE;
    }
}



static
void SetRange(cmsFloat64Number range, cmsBool IsInput)
{
    if (IsInput)
        InputRange = range;
    else
        OutputRange = range;
}

// Populate a named color list with usual component names.
// I am using the first Colorant channel to store the range, but it works since
// this space is not used anyway.
static
cmsNAMEDCOLORLIST* ComponentNames(cmsContext ContextID, cmsColorSpaceSignature space, cmsBool IsInput)
{
    cmsNAMEDCOLORLIST* out;
    int i, n;
    char Buffer[cmsMAX_PATH];

    out = cmsAllocNamedColorList(0, 12, cmsMAXCHANNELS, "", "");
    if (out == NULL) return NULL;

    switch (space) {

    case cmsSigXYZData:
        SetRange(100, IsInput);
        cmsAppendNamedColor(ContextID, out, "X", NULL, NULL);
        cmsAppendNamedColor(ContextID, out, "Y", NULL, NULL);
        cmsAppendNamedColor(ContextID, out, "Z", NULL, NULL);
        break;

    case cmsSigLabData:
        SetRange(1, IsInput);
        cmsAppendNamedColor(ContextID, out, "L*", NULL, NULL);
        cmsAppendNamedColor(ContextID, out, "a*", NULL, NULL);
        cmsAppendNamedColor(ContextID, out, "b*", NULL, NULL);
        break;

    case cmsSigLuvData:
        SetRange(1, IsInput);
        cmsAppendNamedColor(ContextID, out, "L", NULL, NULL);
        cmsAppendNamedColor(ContextID, out, "u", NULL, NULL);
        cmsAppendNamedColor(ContextID, out, "v", NULL, NULL);
        break;

    case cmsSigYCbCrData:
        SetRange(255, IsInput);
        cmsAppendNamedColor(ContextID, out, "Y", NULL, NULL );
        cmsAppendNamedColor(ContextID, out, "Cb", NULL, NULL);
        cmsAppendNamedColor(ContextID, out, "Cr", NULL, NULL);
        break;


    case cmsSigYxyData:
        SetRange(1, IsInput);
        cmsAppendNamedColor(ContextID, out, "Y", NULL, NULL);
        cmsAppendNamedColor(ContextID, out, "x", NULL, NULL);
        cmsAppendNamedColor(ContextID, out, "y", NULL, NULL);
        break;

    case cmsSigRgbData:
        SetRange(255, IsInput);
        cmsAppendNamedColor(ContextID, out, "R", NULL, NULL);
        cmsAppendNamedColor(ContextID, out, "G", NULL, NULL);
        cmsAppendNamedColor(ContextID, out, "B", NULL, NULL);
        break;

    case cmsSigGrayData:
        SetRange(255, IsInput);
        cmsAppendNamedColor(ContextID, out, "G", NULL, NULL);
        break;

    case cmsSigHsvData:
        SetRange(255, IsInput);
        cmsAppendNamedColor(ContextID, out, "H", NULL, NULL);
        cmsAppendNamedColor(ContextID, out, "s", NULL, NULL);
        cmsAppendNamedColor(ContextID, out, "v", NULL, NULL);
        break;

    case cmsSigHlsData:
        SetRange(255, IsInput);
        cmsAppendNamedColor(ContextID, out, "H", NULL, NULL);
        cmsAppendNamedColor(ContextID, out, "l", NULL, NULL);
        cmsAppendNamedColor(ContextID, out, "s", NULL, NULL);
        break;

    case cmsSigCmykData:
        SetRange(1, IsInput);
        cmsAppendNamedColor(ContextID, out, "C", NULL, NULL);
        cmsAppendNamedColor(ContextID, out, "M", NULL, NULL);
        cmsAppendNamedColor(ContextID, out, "Y", NULL, NULL);
        cmsAppendNamedColor(ContextID, out, "K", NULL, NULL);
        break;

    case cmsSigCmyData:
        SetRange(1, IsInput);
        cmsAppendNamedColor(ContextID, out, "C", NULL, NULL);
        cmsAppendNamedColor(ContextID, out, "M", NULL, NULL);
        cmsAppendNamedColor(ContextID, out, "Y", NULL, NULL);
        break;

    default:

        SetRange(1, IsInput);

        n = cmsChannelsOfColorSpace(ContextID, space);

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

            sprintf(Buffer, "Channel #%d", i + 1);
            cmsAppendNamedColor(ContextID, out, Buffer, NULL, NULL);
        }
    }

    return out;

}


// Creates all needed color transforms
static
cmsBool OpenTransforms(cmsContext ContextID)
{
    cmsHPROFILE hInput, hOutput, hProof;
    cmsUInt32Number dwIn, dwOut, dwFlags;
    cmsNAMEDCOLORLIST* List;
    int i;

    // We don't need cache
    dwFlags = cmsFLAGS_NOCACHE;

    if (lIsDeviceLink) {

        hInput  = OpenStockProfile(0, cInProf);
        if (hInput == NULL) return FALSE;
        hOutput = NULL;
        hProof  = NULL;

        if (cmsGetDeviceClass(ContextID, hInput) == cmsSigNamedColorClass) {
            OutputColorSpace  = cmsGetColorSpace(ContextID, hInput);
            InputColorSpace = cmsGetPCS(ContextID, hInput);
        }
        else {
            InputColorSpace  = cmsGetColorSpace(ContextID, hInput);
            OutputColorSpace = cmsGetPCS(ContextID, hInput);
        }

        // Read colorant tables if present
        if (cmsIsTag(ContextID, hInput, cmsSigColorantTableTag)) {
            List = cmsReadTag(ContextID, hInput, cmsSigColorantTableTag);
            InputColorant = cmsDupNamedColorList(ContextID, List);
            InputRange = 1;
        }
        else InputColorant = ComponentNames(ContextID, InputColorSpace, TRUE);

        if (cmsIsTag(ContextID, hInput, cmsSigColorantTableOutTag)){

            List = cmsReadTag(ContextID, hInput, cmsSigColorantTableOutTag);
            OutputColorant = cmsDupNamedColorList(ContextID, List);
            OutputRange = 1;
        }
        else OutputColorant = ComponentNames(ContextID, OutputColorSpace, FALSE);

    }
    else {

        hInput  = OpenStockProfile(0, cInProf);
        if (hInput == NULL) return FALSE;

        hOutput = OpenStockProfile(0, cOutProf);
        if (hOutput == NULL) return FALSE;
        hProof  = NULL;


        if (cmsGetDeviceClass(ContextID, hInput) == cmsSigLinkClass ||
            cmsGetDeviceClass(ContextID, hOutput) == cmsSigLinkClass)
            FatalError("Use -l flag for devicelink profiles!\n");


        InputColorSpace   = cmsGetColorSpace(ContextID, hInput);
        OutputColorSpace  = cmsGetColorSpace(ContextID, hOutput);

        // Read colorant tables if present
        if (cmsIsTag(ContextID, hInput, cmsSigColorantTableTag)) {
            List = cmsReadTag(ContextID, hInput, cmsSigColorantTableTag);
            InputColorant = cmsDupNamedColorList(ContextID, List);
            if (cmsNamedColorCount(ContextID, InputColorant) <= 3)
                SetRange(255, TRUE);
            else
                SetRange(1, TRUE);  // Inks are already divided by 100 in the formatter

        }
        else InputColorant = ComponentNames(ContextID, InputColorSpace, TRUE);

        if (cmsIsTag(ContextID, hOutput, cmsSigColorantTableTag)){

            List = cmsReadTag(ContextID, hOutput, cmsSigColorantTableTag);
            OutputColorant = cmsDupNamedColorList(ContextID, List);
            if (cmsNamedColorCount(ContextID, OutputColorant) <= 3)
                SetRange(255, FALSE);
            else
                SetRange(1, FALSE);  // Inks are already divided by 100 in the formatter
        }
        else OutputColorant = ComponentNames(ContextID, OutputColorSpace, FALSE);


        if (cProofing != NULL) {

            hProof = OpenStockProfile(0, cProofing);
            if (hProof == NULL) return FALSE;
            dwFlags |= cmsFLAGS_SOFTPROOFING;
        }
    }

    // Print information on profiles
    if (Verbose > 2) {

        printf("Profile:\n");
        PrintProfileInformation(ContextID, hInput);

        if (hOutput) {

            printf("Output profile:\n");
            PrintProfileInformation(ContextID, hOutput);
        }

        if (hProof != NULL) {
            printf("Proofing profile:\n");
            PrintProfileInformation(ContextID, hProof);
        }
    }


    // Input is always in floating point
    dwIn  = cmsFormatterForColorspaceOfProfile(ContextID, hInput, 0, TRUE);

    if (lIsDeviceLink) {

        dwOut = cmsFormatterForPCSOfProfile(ContextID, hInput, lIsFloat ? 0 : 2, lIsFloat);
    }
    else {

        // 16 bits or floating point (only on output)
        dwOut = cmsFormatterForColorspaceOfProfile(ContextID, hOutput, lIsFloat ? 0 : 2, lIsFloat);
    }

    // For named color, there is a specialized formatter
    if (cmsGetDeviceClass(ContextID, hInput) == cmsSigNamedColorClass) {

        dwIn = TYPE_NAMED_COLOR_INDEX;
        InputNamedColor = TRUE;
    }

    // Precision mode
    switch (PrecalcMode) {

       case 0: dwFlags |= cmsFLAGS_NOOPTIMIZE; break;
       case 2: dwFlags |= cmsFLAGS_HIGHRESPRECALC; break;
       case 3: dwFlags |= cmsFLAGS_LOWRESPRECALC; break;
       case 1: break;

       default:
           FatalError("Unknown precalculation mode '%d'", PrecalcMode);
    }


    if (BlackPointCompensation)
        dwFlags |= cmsFLAGS_BLACKPOINTCOMPENSATION;


    if (GamutCheck) {

        cmsUInt16Number Alarm[cmsMAXCHANNELS];

        if (hProof == NULL)
            FatalError("I need proofing profile -p for gamut checking!");

        for (i=0; i < cmsMAXCHANNELS; i++)
            Alarm[i] = 0xFFFF;

        cmsSetAlarmCodes(ContextID, Alarm);
        dwFlags |= cmsFLAGS_GAMUTCHECK;
    }


    // The main transform
    hTrans = cmsCreateProofingTransform(ContextID, hInput,  dwIn, hOutput, dwOut, hProof, Intent, ProofingIntent, dwFlags);

    if (hProof) cmsCloseProfile(ContextID, hProof);

    if (hTrans == NULL) return FALSE;


    // PCS Dump if requested
    hTransXYZ = NULL; hTransLab = NULL;

    if (hOutput && Verbose > 1) {

        cmsHPROFILE hXYZ = cmsCreateXYZProfile(ContextID);
        cmsHPROFILE hLab = cmsCreateLab4Profile(ContextID, NULL);

        hTransXYZ = cmsCreateTransform(ContextID, hInput, dwIn, hXYZ,  lIsFloat ? TYPE_XYZ_DBL : TYPE_XYZ_16, Intent, cmsFLAGS_NOCACHE);
        if (hTransXYZ == NULL) return FALSE;

        hTransLab = cmsCreateTransform(ContextID, hInput, dwIn, hLab,  lIsFloat? TYPE_Lab_DBL : TYPE_Lab_16, Intent, cmsFLAGS_NOCACHE);
        if (hTransLab == NULL) return FALSE;

        cmsCloseProfile(ContextID, hXYZ);
        cmsCloseProfile(ContextID, hLab);
    }

    if (hInput) cmsCloseProfile(ContextID, hInput);
    if (hOutput) cmsCloseProfile(ContextID, hOutput);

    return TRUE;
}


// Free open resources
static
void CloseTransforms(cmsContext ContextID)
{
    if (InputColorant) cmsFreeNamedColorList(ContextID, InputColorant);
    if (OutputColorant) cmsFreeNamedColorList(ContextID, OutputColorant);

    if (hTrans) cmsDeleteTransform(ContextID, hTrans);
    if (hTransLab) cmsDeleteTransform(ContextID, hTransLab);
    if (hTransXYZ) cmsDeleteTransform(ContextID, hTransXYZ);

}

// ---------------------------------------------------------------------------------------------------

// Get input from user
static
void GetLine(cmsContext ContextID, char* Buffer, const char* frm, ...)
{
    int res;
    va_list args;

    va_start(args, frm);

    do {
        if (xisatty(stdin))
            vfprintf(stderr, frm, args);

        res = scanf("%4095s", Buffer);

        if (res < 0 || toupper(Buffer[0]) == 'Q') { // Quit?

            CloseTransforms(ContextID);

            if (xisatty(stdin))
                fprintf(stderr, "Done.\n");

            exit(0);
        }
    } while (res == 0);

    va_end(args);
}


// Print a value which is given in double floating point
static
void PrintFloatResults(cmsContext ContextID, cmsFloat64Number Value[])
{
    cmsUInt32Number i, n;
    char ChannelName[cmsMAX_PATH];
    cmsFloat64Number v;

    n = cmsChannelsOfColorSpace(ContextID, OutputColorSpace);
    for (i=0; i < n; i++) {

        if (OutputColorant != NULL) {

            cmsNamedColorInfo(ContextID, OutputColorant, i, ChannelName, NULL, NULL, NULL, NULL);
        }
        else {
            OutputRange = 1;
            sprintf(ChannelName, "Channel #%u", i + 1);
        }

        v = (cmsFloat64Number) Value[i]* OutputRange;

        if (lQuantize)
            v = floor(v + 0.5);

        if (!lUnbounded) {

               if (v < 0)
                      v = 0;
               if (v > OutputRange)
                      v = OutputRange;
        }

        if (Verbose <= 0)
            printf("%.4f ", v);
        else
            printf("%s=%.4f ", ChannelName, v);
    }

    printf("\n");
}


// Get a named-color index
static
cmsUInt16Number GetIndex(cmsContext ContextID)
{
    char Buffer[4096], Name[cmsMAX_PATH], Prefix[40], Suffix[40];
    int index, max;
    const cmsNAMEDCOLORLIST* NamedColorList;

    NamedColorList = cmsGetNamedColorList(hTrans);
    if (NamedColorList == NULL) return 0;

    max = cmsNamedColorCount(ContextID, NamedColorList)-1;

    GetLine(ContextID, Buffer, "Color index (0..%d)? ", max);
    index = atoi(Buffer);

    if (index > max)
        FatalError("Named color %d out of range!", index);

    cmsNamedColorInfo(ContextID, NamedColorList, index, Name, Prefix, Suffix, NULL, NULL);

    printf("\n%s %s %s\n", Prefix, Name, Suffix);

    return (cmsUInt16Number) index;
}

// Read values from a text file or terminal
static
void TakeFloatValues(cmsContext ContextID, cmsFloat64Number Float[])
{
    cmsUInt32Number i, n;
    char ChannelName[cmsMAX_PATH];
    char Buffer[4096];

    if (xisatty(stdin))
        fprintf(stderr, "\nEnter values, 'q' to quit\n");

    if (InputNamedColor) {

        // This is named color index, which is always cmsUInt16Number
        cmsUInt16Number index = GetIndex(ContextID);
        memcpy(Float, &index, sizeof(cmsUInt16Number));
        return;
    }

    n = cmsChannelsOfColorSpace(ContextID, InputColorSpace);
    for (i=0; i < n; i++) {

        if (InputColorant) {
            cmsNamedColorInfo(ContextID, InputColorant, i, ChannelName, NULL, NULL, NULL, NULL);
        }
        else {
            InputRange = 1;
            sprintf(ChannelName, "Channel #%u", i+1);
        }

        GetLine(ContextID, Buffer, "%s? ", ChannelName);

        Float[i] = (cmsFloat64Number) atof(Buffer) / InputRange;
    }

    if (xisatty(stdin))
        fprintf(stderr, "\n");
}

static
void PrintPCSFloat(cmsContext ContextID, cmsFloat64Number Input[])
{
    if (Verbose > 1 && hTransXYZ && hTransLab) {

        cmsCIEXYZ XYZ = { 0, 0, 0 };
        cmsCIELab Lab = { 0, 0, 0 };

        if (hTransXYZ) cmsDoTransform(ContextID, hTransXYZ, Input, &XYZ, 1);
        if (hTransLab) cmsDoTransform(ContextID, hTransLab, Input, &Lab, 1);

        printf("[PCS] Lab=(%.4f,%.4f,%.4f) XYZ=(%.4f,%.4f,%.4f)\n", Lab.L, Lab.a, Lab.b,
            XYZ.X * 100.0, XYZ.Y * 100.0, XYZ.Z * 100.0);

    }
}




// -----------------------------------------------------------------------------------------------

static
void PrintEncodedResults(cmsContext ContextID, cmsUInt16Number Encoded[])
{
    cmsUInt32Number i, n;
    char ChannelName[cmsMAX_PATH];
    cmsUInt32Number v;

    n = cmsChannelsOfColorSpace(ContextID, OutputColorSpace);
    for (i=0; i < n; i++) {

        if (OutputColorant != NULL) {

            cmsNamedColorInfo(ContextID, OutputColorant, i, ChannelName, NULL, NULL, NULL, NULL);
        }
        else {
            sprintf(ChannelName, "Channel #%u", i + 1);
        }

        if (Verbose > 0)
            printf("%s=", ChannelName);

        v = Encoded[i];

        if (InHexa) {

            if (Width16)
                printf("0x%04X ", (int) floor(v + .5));
            else
                printf("0x%02X ", (int) floor(v / 257. + .5));

        } else {

            if (Width16)
                printf("%d ", (int) floor(v + .5));
            else
                printf("%d ", (int) floor(v / 257. + .5));
        }

    }

    printf("\n");
}

// Print XYZ/Lab values on verbose mode

static
void PrintPCSEncoded(cmsContext ContextID, cmsFloat64Number Input[])
{
    if (Verbose > 1 && hTransXYZ && hTransLab) {

        cmsUInt16Number XYZ[3], Lab[3];

        if (hTransXYZ) cmsDoTransform(ContextID, hTransXYZ, Input, XYZ, 1);
        if (hTransLab) cmsDoTransform(ContextID, hTransLab, Input, Lab, 1);

        printf("[PCS] Lab=(0x%04X,0x%04X,0x%04X) XYZ=(0x%04X,0x%04X,0x%04X)\n", Lab[0], Lab[1], Lab[2],
            XYZ[0], XYZ[1], XYZ[2]);

    }
}


// --------------------------------------------------------------------------------------



// Take a value from IT8 and scale it accordly to fill a cmsUInt16Number (0..FFFF)

static
cmsFloat64Number GetIT8Val(cmsContext ContextID, const char* Name, cmsFloat64Number Max)
{
    const char* Val = cmsIT8GetData(ContextID, hIT8in, CGATSPatch, Name);

    if (Val == NULL)
        FatalError("Field '%s' not found", Name);

    return atof(Val) / Max;

}


// Read input values from CGATS file.

static
void TakeCGATSValues(cmsContext ContextID, int nPatch, cmsFloat64Number Float[])
{

    // At first take the name if SAMPLE_ID is present
    if (cmsIT8GetPatchName(ContextID, hIT8in, nPatch, CGATSPatch) == NULL) {
        FatalError("Sorry, I need 'SAMPLE_ID' on input CGATS to operate.");
    }


    // Special handling for named color profiles.
    // Lookup the name in the names database (the transform)

    if (InputNamedColor) {

        const cmsNAMEDCOLORLIST* NamedColorList;
        int index;

        NamedColorList = cmsGetNamedColorList(hTrans);
        if (NamedColorList == NULL)
            FatalError("Malformed named color profile");

        index = cmsNamedColorIndex(ContextID, NamedColorList, CGATSPatch);
        if (index < 0)
            FatalError("Named color '%s' not found in the profile", CGATSPatch);

        Float[0] = index;
        return;
    }

    // Color is not a spot color, proceed.

    switch (InputColorSpace) {

        // Encoding should follow CGATS specification.

    case cmsSigXYZData:
        Float[0] = cmsIT8GetDataDbl(ContextID, hIT8in, CGATSPatch, "XYZ_X") / 100.0;
        Float[1] = cmsIT8GetDataDbl(ContextID, hIT8in, CGATSPatch, "XYZ_Y") / 100.0;
        Float[2] = cmsIT8GetDataDbl(ContextID, hIT8in, CGATSPatch, "XYZ_Z") / 100.0;
        break;

    case cmsSigLabData:
        Float[0] = cmsIT8GetDataDbl(ContextID, hIT8in, CGATSPatch, "LAB_L");
        Float[1] = cmsIT8GetDataDbl(ContextID, hIT8in, CGATSPatch, "LAB_A");
        Float[2] = cmsIT8GetDataDbl(ContextID, hIT8in, CGATSPatch, "LAB_B");
        break;


    case cmsSigRgbData:
        Float[0] = GetIT8Val(ContextID, "RGB_R", 255.0);
        Float[1] = GetIT8Val(ContextID, "RGB_G", 255.0);
        Float[2] = GetIT8Val(ContextID, "RGB_B", 255.0);
        break;

    case cmsSigGrayData:
        Float[0] = GetIT8Val(ContextID, "GRAY", 255.0);
        break;

    case cmsSigCmykData:
        Float[0] = GetIT8Val(ContextID, "CMYK_C", 1.0);
        Float[1] = GetIT8Val(ContextID, "CMYK_M", 1.0);
        Float[2] = GetIT8Val(ContextID, "CMYK_Y", 1.0);
        Float[3] = GetIT8Val(ContextID, "CMYK_K", 1.0);
        break;

    case cmsSigCmyData:
        Float[0] = GetIT8Val(ContextID, "CMY_C", 1.0);
        Float[1] = GetIT8Val(ContextID, "CMY_M", 1.0);
        Float[2] = GetIT8Val(ContextID, "CMY_Y", 1.0);
        break;

    case cmsSig1colorData:
    case cmsSig2colorData:
    case cmsSig3colorData:
    case cmsSig4colorData:
    case cmsSig5colorData:
    case cmsSig6colorData:
    case cmsSig7colorData:
    case cmsSig8colorData:
    case cmsSig9colorData:
    case cmsSig10colorData:
    case cmsSig11colorData:
    case cmsSig12colorData:
    case cmsSig13colorData:
    case cmsSig14colorData:
    case cmsSig15colorData:
        {
            cmsUInt32Number i, n;

            n = cmsChannelsOfColorSpace(ContextID, InputColorSpace);
            for (i=0; i < n; i++) {

                char Buffer[255];

                sprintf(Buffer, "%uCLR_%u", n, i+1);
                Float[i] = GetIT8Val(ContextID, Buffer, 100.0);
            }

        }
        break;

    default:
        {
            cmsUInt32Number i, n;

            n = cmsChannelsOfColorSpace(ContextID, InputColorSpace);
            for (i=0; i < n; i++) {

                char Buffer[255];

                sprintf(Buffer, "CHAN_%u", i+1);
                Float[i] = GetIT8Val(ContextID, Buffer, 1.0);
            }

        }
    }

}

static
void SetCGATSfld(cmsContext ContextID, const char* Col, cmsFloat64Number Val)
{
    if (lQuantize)
        Val = floor(Val + 0.5);

    if (!cmsIT8SetDataDbl(ContextID, hIT8out, CGATSPatch, Col, Val)) {
        FatalError("couldn't set '%s' on output cgats '%s'", Col, CGATSoutFilename);
    }
}



static
void PutCGATSValues(cmsContext ContextID, cmsFloat64Number Float[])
{
    cmsIT8SetData(ContextID, hIT8out, CGATSPatch, "SAMPLE_ID", CGATSPatch);
    switch (OutputColorSpace) {


    // Encoding should follow CGATS specification.

    case cmsSigXYZData:

        SetCGATSfld(ContextID, "XYZ_X", Float[0] * 100.0);
        SetCGATSfld(ContextID, "XYZ_Y", Float[1] * 100.0);
        SetCGATSfld(ContextID, "XYZ_Z", Float[2] * 100.0);
        break;

    case cmsSigLabData:

        SetCGATSfld(ContextID, "LAB_L", Float[0]);
        SetCGATSfld(ContextID, "LAB_A", Float[1]);
        SetCGATSfld(ContextID, "LAB_B", Float[2]);
        break;


    case cmsSigRgbData:
        SetCGATSfld(ContextID, "RGB_R", Float[0] * 255.0);
        SetCGATSfld(ContextID, "RGB_G", Float[1] * 255.0);
        SetCGATSfld(ContextID, "RGB_B", Float[2] * 255.0);
        break;

    case cmsSigGrayData:
        SetCGATSfld(ContextID, "GRAY", Float[0] * 255.0);
        break;

    case cmsSigCmykData:
        SetCGATSfld(ContextID, "CMYK_C", Float[0]);
        SetCGATSfld(ContextID, "CMYK_M", Float[1]);
        SetCGATSfld(ContextID, "CMYK_Y", Float[2]);
        SetCGATSfld(ContextID, "CMYK_K", Float[3]);
        break;

    case cmsSigCmyData:
        SetCGATSfld(ContextID, "CMY_C", Float[0]);
        SetCGATSfld(ContextID, "CMY_M", Float[1]);
        SetCGATSfld(ContextID, "CMY_Y", Float[2]);
        break;

    case cmsSig1colorData:
    case cmsSig2colorData:
    case cmsSig3colorData:
    case cmsSig4colorData:
    case cmsSig5colorData:
    case cmsSig6colorData:
    case cmsSig7colorData:
    case cmsSig8colorData:
    case cmsSig9colorData:
    case cmsSig10colorData:
    case cmsSig11colorData:
    case cmsSig12colorData:
    case cmsSig13colorData:
    case cmsSig14colorData:
    case cmsSig15colorData:
        {

            cmsInt32Number i, n;

            n = cmsChannelsOfColorSpace(ContextID, InputColorSpace);
            for (i=0; i < n; i++) {

                char Buffer[255];

                sprintf(Buffer, "%uCLR_%u", n, i+1);

                SetCGATSfld(ContextID, Buffer, Float[i] * 100.0);
            }
        }
        break;

    default:
        {

            cmsInt32Number i, n;

            n = cmsChannelsOfColorSpace(ContextID, InputColorSpace);
            for (i=0; i < n; i++) {

                char Buffer[255];

                sprintf(Buffer, "CHAN_%u", i+1);

                SetCGATSfld(ContextID, Buffer, Float[i]);
            }
        }
    }
}



// Create data format
static
void SetOutputDataFormat(cmsContext ContextID)
{
    cmsIT8DefineDblFormat(ContextID, hIT8out, "%.4g");
    cmsIT8SetPropertyStr(ContextID, hIT8out, "ORIGINATOR", "icctrans");

    if (IncludePart != NULL)
        cmsIT8SetPropertyStr(ContextID, hIT8out, ".INCLUDE", IncludePart);

    cmsIT8SetComment(ContextID, hIT8out, "Data follows");
    cmsIT8SetPropertyDbl(ContextID, hIT8out, "NUMBER_OF_SETS", nMaxPatches);


    switch (OutputColorSpace) {


        // Encoding should follow CGATS specification.

    case cmsSigXYZData:
        cmsIT8SetPropertyDbl(ContextID, hIT8out, "NUMBER_OF_FIELDS", 4);
        cmsIT8SetDataFormat(ContextID, hIT8out, 0, "SAMPLE_ID");
        cmsIT8SetDataFormat(ContextID, hIT8out, 1, "XYZ_X");
        cmsIT8SetDataFormat(ContextID, hIT8out, 2, "XYZ_Y");
        cmsIT8SetDataFormat(ContextID, hIT8out, 3, "XYZ_Z");
        break;

    case cmsSigLabData:
        cmsIT8SetPropertyDbl(ContextID, hIT8out, "NUMBER_OF_FIELDS", 4);
        cmsIT8SetDataFormat(ContextID, hIT8out, 0, "SAMPLE_ID");
        cmsIT8SetDataFormat(ContextID, hIT8out, 1, "LAB_L");
        cmsIT8SetDataFormat(ContextID, hIT8out, 2, "LAB_A");
        cmsIT8SetDataFormat(ContextID, hIT8out, 3, "LAB_B");
        break;


    case cmsSigRgbData:
        cmsIT8SetPropertyDbl(ContextID, hIT8out, "NUMBER_OF_FIELDS", 4);
        cmsIT8SetDataFormat(ContextID, hIT8out, 0, "SAMPLE_ID");
        cmsIT8SetDataFormat(ContextID, hIT8out, 1, "RGB_R");
        cmsIT8SetDataFormat(ContextID, hIT8out, 2, "RGB_G");
        cmsIT8SetDataFormat(ContextID, hIT8out, 3, "RGB_B");
        break;

    case cmsSigGrayData:
        cmsIT8SetPropertyDbl(ContextID, hIT8out, "NUMBER_OF_FIELDS", 2);
        cmsIT8SetDataFormat(ContextID, hIT8out, 0, "SAMPLE_ID");
        cmsIT8SetDataFormat(ContextID, hIT8out, 1, "GRAY");
        break;

    case cmsSigCmykData:
        cmsIT8SetPropertyDbl(ContextID, hIT8out, "NUMBER_OF_FIELDS", 5);
        cmsIT8SetDataFormat(ContextID, hIT8out, 0, "SAMPLE_ID");
        cmsIT8SetDataFormat(ContextID, hIT8out, 1, "CMYK_C");
        cmsIT8SetDataFormat(ContextID, hIT8out, 2, "CMYK_M");
        cmsIT8SetDataFormat(ContextID, hIT8out, 3, "CMYK_Y");
        cmsIT8SetDataFormat(ContextID, hIT8out, 4, "CMYK_K");
        break;

    case cmsSigCmyData:
        cmsIT8SetPropertyDbl(ContextID, hIT8out, "NUMBER_OF_FIELDS", 4);
        cmsIT8SetDataFormat(ContextID, hIT8out, 0, "SAMPLE_ID");
        cmsIT8SetDataFormat(ContextID, hIT8out, 1, "CMY_C");
        cmsIT8SetDataFormat(ContextID, hIT8out, 2, "CMY_M");
        cmsIT8SetDataFormat(ContextID, hIT8out, 3, "CMY_Y");
        break;

    case cmsSig1colorData:
    case cmsSig2colorData:
    case cmsSig3colorData:
    case cmsSig4colorData:
    case cmsSig5colorData:
    case cmsSig6colorData:
    case cmsSig7colorData:
    case cmsSig8colorData:
    case cmsSig9colorData:
    case cmsSig10colorData:
    case cmsSig11colorData:
    case cmsSig12colorData:
    case cmsSig13colorData:
    case cmsSig14colorData:
    case cmsSig15colorData:
        {
            int i, n;
            char Buffer[255];

            n = cmsChannelsOfColorSpace(ContextID, OutputColorSpace);
            cmsIT8SetPropertyDbl(ContextID, hIT8out, "NUMBER_OF_FIELDS", n+1);
            cmsIT8SetDataFormat(ContextID, hIT8out, 0, "SAMPLE_ID");

            for (i=1; i <= n; i++) {
                sprintf(Buffer, "%dCLR_%d", n, i);
                cmsIT8SetDataFormat(ContextID, hIT8out, i, Buffer);
            }
        }
        break;

    default: {

        int i, n;
        char Buffer[255];

        n = cmsChannelsOfColorSpace(ContextID, OutputColorSpace);
        cmsIT8SetPropertyDbl(ContextID, hIT8out, "NUMBER_OF_FIELDS", n+1);
        cmsIT8SetDataFormat(ContextID, hIT8out, 0, "SAMPLE_ID");

        for (i=1; i <= n; i++) {
            sprintf(Buffer, "CHAN_%d", i);
            cmsIT8SetDataFormat(ContextID, hIT8out, i, Buffer);
        }
    }
    }
}

// Open CGATS if specified

static
void OpenCGATSFiles(cmsContext ContextID, int argc, char *argv[])
{
    int nParams = argc - xoptind;

    if (nParams >= 1)  {

        hIT8in = cmsIT8LoadFromFile(0, argv[xoptind]);

        if (hIT8in == NULL)
            FatalError("'%s' is not recognized as a CGATS file", argv[xoptind]);

        nMaxPatches = (int) cmsIT8GetPropertyDbl(ContextID, hIT8in, "NUMBER_OF_SETS");
    }

    if (nParams == 2) {

        hIT8out = cmsIT8Alloc(NULL);
        SetOutputDataFormat(ContextID);
        strncpy(CGATSoutFilename, argv[xoptind+1], cmsMAX_PATH-1);
    }

    if (nParams > 2) FatalError("Too many CGATS files");
}



// The main sink
int main(int argc, char *argv[])
{
    cmsUInt16Number Output[cmsMAXCHANNELS];
    cmsFloat64Number OutputFloat[cmsMAXCHANNELS];
    cmsFloat64Number InputFloat[cmsMAXCHANNELS];
    cmsContext ContextID =  NULL;

    int nPatch = 0;

    fprintf(stderr, "LittleCMS ColorSpace conversion calculator - 5.1 [LittleCMS %2.2f]\n", cmsGetEncodedCMMversion() / 1000.0);
    fprintf(stderr, "Copyright (c) 1998-2022 Marti Maria Saguer. See COPYING file for details.\n");
    fflush(stderr);

    InitUtils(ContextID, "transicc");

    Verbose = 1;

    if (argc == 1) {

        Help();
        return 0;
    }

    HandleSwitches(ContextID, argc, argv);

    // Open profiles, create transforms
    if (!OpenTransforms(ContextID)) return 1;

    // Open CGATS input if specified
    OpenCGATSFiles(ContextID, argc, argv);

    // Main loop: read all values and convert them
    for(;;) {

        if (hIT8in != NULL) {

            if (nPatch >= nMaxPatches) break;
            TakeCGATSValues(ContextID, nPatch++, InputFloat);

        } else {

            if (feof(stdin)) break;
            TakeFloatValues(ContextID, InputFloat);

        }

        if (lIsFloat)
            cmsDoTransform(ContextID, hTrans, InputFloat, OutputFloat, 1);
        else
            cmsDoTransform(ContextID, hTrans, InputFloat, Output, 1);


        if (hIT8out != NULL) {

            PutCGATSValues(ContextID, OutputFloat);
        }
        else {

            if (lIsFloat) {
                PrintFloatResults(ContextID, OutputFloat); PrintPCSFloat(ContextID, InputFloat);
            }
            else {
                PrintEncodedResults(ContextID, Output);   PrintPCSEncoded(ContextID, InputFloat);
            }

        }
    }


    // Cleanup
    CloseTransforms(ContextID);

    if (hIT8in)
        cmsIT8Free(ContextID, hIT8in);

    if (hIT8out) {
        cmsIT8SaveToFile(ContextID, hIT8out, CGATSoutFilename);
        cmsIT8Free(ContextID, hIT8out);
    }

    // All is ok
    return 0;
}
