diff mupdf-source/thirdparty/lcms2/utils/transicc/transicc.c @ 2:b50eed0cc0ef upstream

ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4. The directory name has changed: no version number in the expanded directory now.
author Franz Glasner <fzglas.hg@dom66.de>
date Mon, 15 Sep 2025 11:43:07 +0200
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mupdf-source/thirdparty/lcms2/utils/transicc/transicc.c	Mon Sep 15 11:43:07 2025 +0200
@@ -0,0 +1,1330 @@
+//---------------------------------------------------------------------------------
+//
+//  Little Color Management System
+//  Copyright (c) 1998-2023 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-2023 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;
+}