// // Little cms // Copyright (C) 1998-2007 Marti Maria // // 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 "lcms.h" #include #include #ifndef NON_WINDOWS #include #endif // xgetopt() interface ----------------------------------------------------- extern int xoptind; extern char *xoptarg; extern int xopterr; extern char SW; int cdecl xgetopt(int argc, char *argv[], char *optionS); // ------------------------------------------------------------------------ // Stock profiles function extern cmsHPROFILE OpenStockProfile(const char* File); // Globals static LCMSBOOL InHexa = FALSE; static LCMSBOOL Verbose = FALSE; static LCMSBOOL GamutCheck = FALSE; static LCMSBOOL Width16 = FALSE; static LCMSBOOL BlackPointCompensation = FALSE; static LCMSBOOL PreserveBlack = FALSE; static LCMSBOOL lIsDeviceLink = FALSE; static LCMSBOOL lTerse = FALSE; static LCMSBOOL lQuantize = FALSE; static LCMSBOOL lUse255always = FALSE; static char *cInProf = NULL; static char *cOutProf = NULL; static char *cProofing = NULL; static char *IncludePart = NULL; static LCMSHANDLE hIT8in = NULL; // CGATS input static LCMSHANDLE hIT8out = NULL; // CGATS output static char CGATSPatch[1024]; // Actual Patch Name static char CGATSoutFilename[MAX_PATH]; static int Intent = INTENT_PERCEPTUAL; static int ProofingIntent = INTENT_PERCEPTUAL; static int PrecalcMode = 0; static int nMaxPatches; static cmsHPROFILE hInput, hOutput, hProof, hLab = NULL, hXYZ = NULL; static cmsHTRANSFORM hTrans, hTransXYZ, hTransLab; static icColorSpaceSignature InputColorSpace, OutputColorSpace; static cmsCIEXYZ xyz; static cmsCIELab Lab; static LPcmsNAMEDCOLORLIST InputColorant = NULL; static LPcmsNAMEDCOLORLIST OutputColorant = NULL; // isatty replacement #ifdef _MSC_VER #define xisatty(x) _isatty( _fileno( (x) ) ) #else #define xisatty(x) isatty( fileno( (x) ) ) #endif // Give up static void FatalError(const char *frm, ...) { va_list args; va_start(args, frm); vfprintf(stderr, frm, args); va_end(args); exit(1); } // Issue a message static void Warning(const char *frm, ...) { va_list args; va_start(args, frm); vfprintf(stderr, frm, args); va_end(args); } // The error handler static int MyErrorHandler(int ErrorCode, const char *ErrorText) { FatalError("icctrans: %s", ErrorText); return 0; } // Print usage to stderr static void Help(void) { fprintf(stderr, "usage: icctrans [flags] [CGATS input] [CGATS output]\n\n"); fprintf(stderr, "flags:\n\n"); fprintf(stderr, "%cv - Verbose (Print PCS as well)\n", SW); fprintf(stderr, "%cw - use 16 bits\n", SW); fprintf(stderr, "%c5 - don't use %% on inks (always 0..255, even on CMYK)\n", SW); fprintf(stderr, "%cx - Hexadecimal\n", SW); fprintf(stderr, "%cq - Quantize CGATS to 8 bits\n\n", SW); fprintf(stderr, "%ci - Input profile (defaults to sRGB)\n", SW); fprintf(stderr, "%co - Output profile (defaults to sRGB)\n", SW); fprintf(stderr, "%cl - Transform by device-link profile\n", SW); fprintf(stderr, "\nYou can use '*Lab', '*xyz' and others as built-in profiles\n\n"); fprintf(stderr, "%ct<0,1,2,3> Intent (0=Perceptual, 1=Rel.Col, 2=Saturation, 3=Abs.Col.)\n", SW); fprintf(stderr, "%cd<0..1> - Observer adaptation state (abs.col. only)\n\n", SW); fprintf(stderr, "%cb - Black point compensation\n", SW); fprintf(stderr, "%cf - Preserve black (CMYK only) 0=off, 1=black ink only, 2=full K plane\n", SW); fprintf(stderr, "%cc<0,1,2,3> Precalculates transform (0=Off, 1=Normal, 2=Hi-res, 3=LoRes)\n\n", SW); fprintf(stderr, "%cn - Terse output, intended for pipe usage\n", SW); fprintf(stderr, "%cp - Soft proof profile\n", SW); fprintf(stderr, "%cm<0,1,2,3> - Soft proof intent\n", SW); fprintf(stderr, "%cg - Marks out-of-gamut colors on softproof\n\n", SW); fprintf(stderr, "This program is intended to be a demo of the little cms\n" "engine. Both lcms and this program are freeware. You can\n" "obtain both in source code at http://www.littlecms.com\n" "For suggestions, comments, bug reports etc. send mail to\n" "info@littlecms.com\n\n"); exit(0); } // The toggles stuff static void HandleSwitches(int argc, char *argv[]) { int s; while ((s = xgetopt(argc,argv, "%C:c:VvQqWwxXhHbBnNI:i:O:o:T:t:L:l:p:P:m:M:gGF:f:d:D:!:5")) != EOF) { switch (s){ case '5': lUse255always = TRUE; 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("icctrans: Unknown precalc mode '%d'", PrecalcMode); break; case 'd': case 'D': { double ObserverAdaptationState = atof(xoptarg); if (ObserverAdaptationState != 0 && ObserverAdaptationState != 1.0) Warning("Adaptation states other that 0 or 1 are not yet implemented"); cmsSetAdaptationState(ObserverAdaptationState); } break; case 'f': case 'F': PreserveBlack = atoi(xoptarg); if (PreserveBlack < 0 || PreserveBlack > 2) FatalError("Unknown PreserveBlack '%d'", PreserveBlack); 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; case 'm': case 'M': ProofingIntent = atoi(xoptarg); if (ProofingIntent > 3) ProofingIntent = 3; if (ProofingIntent < 0) ProofingIntent = 0; break; case 'n': case 'N': lTerse = TRUE; break; case 'o': case 'O': if (lIsDeviceLink) FatalError("icctrans: Device-link already specified"); cOutProf = xoptarg; break; case 'p': case 'P': cProofing = xoptarg; break; case 'q': case 'Q': lQuantize = TRUE; break; case 't': case 'T': Intent = atoi(xoptarg); if (Intent > 3) Intent = 3; if (Intent < 0) Intent = 0; break; case 'v': case 'V': Verbose = TRUE; break; case 'W': case 'w': Width16 = TRUE; break; case 'x': case 'X': InHexa = TRUE; break; default: FatalError("icctrans: Unknown option - run without args to see valid ones.\n"); } } } // Displays the colorant table static void PrintColorantTable(cmsHPROFILE hInput, icTagSignature Sig, const char* Title) { LPcmsNAMEDCOLORLIST list; int i; if (cmsIsTag(hInput, Sig)) { printf("%s:\n", Title); list = cmsReadColorantTable(hInput, Sig); for (i=0; i < list ->nColors; i++) printf("\t%s\n", list ->List[i].Name); cmsFreeNamedColorList(list); printf("\n"); } } // Creates all needed color transforms static void OpenTransforms(void) { DWORD dwIn, dwOut, dwFlags; dwFlags = 0; if (lIsDeviceLink) { hInput = cmsOpenProfileFromFile(cInProf, "r"); hOutput = NULL; InputColorSpace = cmsGetColorSpace(hInput); OutputColorSpace = cmsGetPCS(hInput); // Read colorant tables if present if (cmsIsTag(hInput, icSigColorantTableTag)) InputColorant = cmsReadColorantTable(hInput, icSigColorantTableTag); if (cmsIsTag(hInput, icSigColorantTableOutTag)) OutputColorant = cmsReadColorantTable(hInput, icSigColorantTableOutTag); } else { hInput = OpenStockProfile(cInProf); hOutput = OpenStockProfile(cOutProf); hProof = NULL; if (cmsIsTag(hInput, icSigColorantTableTag)) InputColorant = cmsReadColorantTable(hInput, icSigColorantTableTag); if (cmsIsTag(hOutput, icSigColorantTableTag)) OutputColorant = cmsReadColorantTable(hOutput, icSigColorantTableTag); if (cProofing != NULL) { hProof = OpenStockProfile(cProofing); dwFlags |= cmsFLAGS_SOFTPROOFING; } InputColorSpace = cmsGetColorSpace(hInput); OutputColorSpace = cmsGetColorSpace(hOutput); if (cmsGetDeviceClass(hInput) == icSigLinkClass || cmsGetDeviceClass(hOutput) == icSigLinkClass) FatalError("icctrans: Use %cl flag for devicelink profiles!\n", SW); } if (Verbose) { printf("From: %s\n", cmsTakeProductName(hInput)); printf("Desc: %s\n", cmsTakeProductDesc(hInput)); printf("Info: %s\n\n", cmsTakeProductInfo(hInput)); PrintColorantTable(hInput, icSigColorantTableTag, "Input colorant table"); PrintColorantTable(hInput, icSigColorantTableOutTag, "Input colorant out table"); if (hOutput) { printf("To : %s\n", cmsTakeProductName(hOutput)); printf("Desc: %s\n", cmsTakeProductDesc(hOutput)); printf("Info: %s\n\n", cmsTakeProductInfo(hOutput)); PrintColorantTable(hOutput, icSigColorantTableTag, "Output colorant table"); PrintColorantTable(hOutput, icSigColorantTableOutTag, "Input colorant out table"); } } dwIn = BYTES_SH(2) | CHANNELS_SH(_cmsChannelsOf(InputColorSpace)); dwOut = BYTES_SH(2) | CHANNELS_SH(_cmsChannelsOf(OutputColorSpace)); if (PreserveBlack) { dwFlags |= cmsFLAGS_PRESERVEBLACK; if (PrecalcMode == 0) PrecalcMode = 1; cmsSetCMYKPreservationStrategy(PreserveBlack-1); } switch (PrecalcMode) { case 0: dwFlags |= cmsFLAGS_NOTPRECALC; break; case 2: dwFlags |= cmsFLAGS_HIGHRESPRECALC; break; case 3: dwFlags |= cmsFLAGS_LOWRESPRECALC; break; case 1: break; default: FatalError("icctrans: Unknown precalculation mode '%d'", PrecalcMode); } if (BlackPointCompensation) dwFlags |= cmsFLAGS_BLACKPOINTCOMPENSATION; if (GamutCheck) { if (hProof == NULL) FatalError("icctrans: I need proofing profile -p for gamut checking!"); cmsSetAlarmCodes(0xFF, 0xFF, 0xFF); dwFlags |= cmsFLAGS_GAMUTCHECK; } if (cmsGetDeviceClass(hInput) == icSigNamedColorClass) { dwIn = TYPE_NAMED_COLOR_INDEX; } hTrans = cmsCreateProofingTransform(hInput, dwIn, hOutput, dwOut, hProof, Intent, ProofingIntent, dwFlags); hTransXYZ = NULL; hTransLab = NULL; if (hOutput && Verbose) { hXYZ = cmsCreateXYZProfile(); hLab = cmsCreateLabProfile(NULL); hTransXYZ = cmsCreateTransform(hInput, dwIn, hXYZ, TYPE_XYZ_16, Intent, cmsFLAGS_NOTPRECALC); hTransLab = cmsCreateTransform(hInput, dwIn, hLab, TYPE_Lab_16, Intent, cmsFLAGS_NOTPRECALC); } } // Free open resources static void CloseTransforms(void) { if (InputColorant) cmsFreeNamedColorList(InputColorant); if (OutputColorant) cmsFreeNamedColorList(OutputColorant); cmsDeleteTransform(hTrans); if (hTransLab) cmsDeleteTransform(hTransLab); if (hTransXYZ) cmsDeleteTransform(hTransXYZ); cmsCloseProfile(hInput); if (hOutput) cmsCloseProfile(hOutput); if (hProof) cmsCloseProfile(hProof); if (hXYZ) cmsCloseProfile(hXYZ); if (hLab) cmsCloseProfile(hLab); } // Print a value, with a prefix, normalized to a given range static void PrintRange(const char* C, double v, double Range) { char Prefix[20]; Prefix[0] = 0; if (!lTerse) sprintf(Prefix, "%s=", C); if (InHexa) { if (Width16) printf("%s0x%x ", Prefix, (int) floor(v + .5)); else printf("%s0x%x ", Prefix, (int) floor(v / 257. + .5)); } else { double out = (v * Range) / 65535.0; if (lQuantize) out = floor(out + 0.5); printf("%s%.2f ", Prefix, out); } } static void Print255(const char* C, double v) { PrintRange(C, v, 255.0); } static void Print100(const char* C, double v) { PrintRange(C, v, lUse255always ? 255.0 : 100.0); } static void PrintCooked(const char* C, double v) { if (lQuantize) v = floor(v + 0.5); if (lTerse) printf("%.4f ", v); else printf("%s=%.4f ", C, v); } static void PrintResults(WORD Encoded[], icColorSpaceSignature ColorSpace) { int i; switch (ColorSpace) { case icSigXYZData: cmsXYZEncoded2Float(&xyz, Encoded); PrintCooked("X", xyz.X * 100.); PrintCooked("Y", xyz.Y * 100.); PrintCooked("Z", xyz.Z * 100.); break; case icSigLabData: cmsLabEncoded2Float(&Lab, Encoded); PrintCooked("L*", Lab.L); PrintCooked("a*", Lab.a); PrintCooked("b*", Lab.b); break; case icSigLuvData: Print255("L", Encoded[0]); Print255("u", Encoded[1]); Print255("v", Encoded[2]); break; case icSigYCbCrData: Print255("Y", Encoded[0]); Print255("Cb", Encoded[1]); Print255("Cr", Encoded[2]); break; case icSigYxyData: Print255("Y", Encoded[0]); Print255("x", Encoded[1]); Print255("y", Encoded[2]); break; case icSigRgbData: Print255("R", Encoded[0]); Print255("G", Encoded[1]); Print255("B", Encoded[2]); break; case icSigGrayData: Print255("G", Encoded[0]); break; case icSigHsvData: Print255("H", Encoded[0]); Print255("s", Encoded[1]); Print255("v", Encoded[2]); break; case icSigHlsData: Print255("H", Encoded[0]); Print255("l", Encoded[1]); Print255("s", Encoded[2]); break; case icSigCmykData: Print100("C", Encoded[0]); Print100("M", Encoded[1]); Print100("Y", Encoded[2]); Print100("K", Encoded[3]); break; case icSigCmyData: Print100("C", Encoded[0]); Print100("M", Encoded[1]); Print100("Y", Encoded[2]); break; default: for (i=0; i < _cmsChannelsOf(OutputColorSpace); i++) { char Buffer[256]; if (OutputColorant) sprintf(Buffer, "%s", OutputColorant->List[i].Name); else sprintf(Buffer, "Channel #%d", i + 1); Print255(Buffer, Encoded[i]); } } printf("\n"); } // Get input from user static void GetLine(char* Buffer) { scanf("%s", Buffer); if (toupper(Buffer[0]) == 'Q') { // Quit? CloseTransforms(); if (xisatty(stdin)) printf("Done.\n"); exit(0); } } // Ask for a value static double GetAnswer(const char* Prompt, double Range) { char Buffer[4096]; double val = 0.0; if (Range == 0.0) { // Range 0 means double value if (xisatty(stdin)) printf("%s? ", Prompt); GetLine(Buffer); return atof(Buffer); } else { if (InHexa) { // Hexadecimal int hexa; if (Width16) Range = 0xFFFF; else Range = 0xFF; if (xisatty(stdin)) printf("%s (0..%X)? ", Prompt, (int) Range); GetLine(Buffer); sscanf(Buffer, "%x", &hexa); val = hexa; } else { // Normal usage if (xisatty(stdin)) printf("%s (0..%d)? ", Prompt, (int) Range); GetLine(Buffer); sscanf(Buffer, "%lf", &val); } // Normalize to 0..0xffff if (val > Range) return 0xFFFF; return floor((val * 65535.0) / Range + 0.5); } } // Get a value in % static WORD Get100(const char* AskFor) { return (WORD) GetAnswer(AskFor, lUse255always ? 255.0 : 100.0); } // Get a simple value in 0..255 range static WORD GetVal(const char* AskFor) { return (WORD) GetAnswer(AskFor, 255.0); } // Get a double value static double GetDbl(const char* AskFor) { return GetAnswer(AskFor, 0.0); } // Get a named-color index static WORD GetIndex(void) { char Buffer[4096], Name[40], Prefix[40], Suffix[40]; int index, max; max = cmsNamedColorCount(hTrans)-1; if (xisatty(stdin)) printf("Color index (0..%d)? ", max); GetLine(Buffer); index = atoi(Buffer); if (index > max) FatalError("icctrans: Named color %d out of range!", index); cmsNamedColorInfo(hTrans, index, Name, Prefix, Suffix); printf("\n%s %s %s: ", Prefix, Name, Suffix); return index; } // Read values from a text file or terminal static void TakeTextValues(WORD Encoded[]) { if (xisatty(stdin)) printf("\nEnter values, 'q' to quit\n"); if (cmsGetDeviceClass(hInput) == icSigNamedColorClass) { Encoded[0] = GetIndex(); return; } switch (InputColorSpace) { case icSigXYZData: xyz.X = GetDbl("X"); xyz.Y = GetDbl("Y"); xyz.Z = GetDbl("Z"); cmsFloat2XYZEncoded(Encoded, &xyz); break; case icSigLabData: Lab.L = GetDbl("L*"); Lab.a = GetDbl("a*"); Lab.b = GetDbl("b*"); cmsFloat2LabEncoded(Encoded, &Lab); break; case icSigLuvData: Encoded[0] = GetVal("L"); Encoded[1] = GetVal("u"); Encoded[2] = GetVal("v"); break; case icSigYCbCrData: Encoded[0] = GetVal("Y"); Encoded[1] = GetVal("Cb"); Encoded[2] = GetVal("Cr"); break; case icSigYxyData: Encoded[0] = GetVal("Y"); Encoded[1] = GetVal("x"); Encoded[2] = GetVal("y"); break; case icSigRgbData: Encoded[0] = GetVal("R"); Encoded[1] = GetVal("G"); Encoded[2] = GetVal("B"); break; case icSigGrayData: Encoded[0] = GetVal("G"); break; case icSigHsvData: Encoded[0] = GetVal("H"); Encoded[1] = GetVal("s"); Encoded[2] = GetVal("v"); break; case icSigHlsData: Encoded[0] = GetVal("H"); Encoded[1] = GetVal("l"); Encoded[2] = GetVal("s"); break; case icSigCmykData: Encoded[0] = Get100("C"); Encoded[1] = Get100("M"); Encoded[2] = Get100("Y"); Encoded[3] = Get100("K"); break; case icSigCmyData: Encoded[0] = Get100("C"); Encoded[1] = Get100("M"); Encoded[2] = Get100("Y"); break; case icSigHexachromeData: Encoded[0] = Get100("C"); Encoded[1] = Get100("M"); Encoded[2] = Get100("Y"); Encoded[3] = Get100("K"); Encoded[4] = Get100("c"); Encoded[5] = Get100("m"); break; case icSig2colorData: case icSig3colorData: case icSig4colorData: case icSig5colorData: case icSig6colorData: case icSig7colorData: case icSig8colorData: case icSigMCH5Data: case icSigMCH7Data: case icSigMCH8Data: case icSigMCH9Data: case icSigMCHAData: case icSigMCHBData: case icSigMCHCData: case icSigMCHDData: case icSigMCHEData: case icSigMCHFData: { int i; for (i=0; i < _cmsChannelsOf(InputColorSpace); i++) { char Name[256]; if (InputColorant) sprintf(Name, "%s", InputColorant->List[i].Name); else sprintf(Name, "Channel #%d", i+1); Encoded[i] = GetVal(Name); } } break; default: FatalError("icctrans: Unsupported %d channel profile", _cmsChannelsOf(InputColorSpace)); } if (xisatty(stdin)) printf("\n"); } // Take a value from IT8 and scale it accordly to fill a WORD (0..FFFF) static WORD GetIT8Val(const char* Name, double Max) { double CGATSfactor = 65535.0 / Max; double res; const char* Val = cmsIT8GetData(hIT8in, CGATSPatch, Name); if (Val == NULL) FatalError("icctrans: Field '%s' not found", Name); res = atof(Val); if (res > Max) return 0xFFFF; return (WORD) floor(res * CGATSfactor + 0.5); } // Read input values from CGATS file. static void TakeCGATSValues(int nPatch, WORD Encoded[]) { // At first take the name if SAMPLE_ID is present if (cmsIT8GetPatchName(hIT8in, nPatch, CGATSPatch) == NULL) { FatalError("icctrans: 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 (cmsGetDeviceClass(hInput) == icSigNamedColorClass) { int index = cmsNamedColorIndex(hTrans, CGATSPatch); if (index < 0) FatalError("icctrans: Named color '%s' not found in the profile", CGATSPatch); Encoded[0] = (WORD) index; return; } // Color is not a spot color, proceed. switch (InputColorSpace) { // Encoding should follow CGATS specification. case icSigXYZData: xyz.X = cmsIT8GetDataDbl(hIT8in, CGATSPatch, "XYZ_X") / 100.0; xyz.Y = cmsIT8GetDataDbl(hIT8in, CGATSPatch, "XYZ_Y") / 100.0; xyz.Z = cmsIT8GetDataDbl(hIT8in, CGATSPatch, "XYZ_Z") / 100.0; cmsFloat2XYZEncoded(Encoded, &xyz); break; case icSigLabData: Lab.L = cmsIT8GetDataDbl(hIT8in, CGATSPatch, "LAB_L"); Lab.a = cmsIT8GetDataDbl(hIT8in, CGATSPatch, "LAB_A"); Lab.b = cmsIT8GetDataDbl(hIT8in, CGATSPatch, "LAB_B"); cmsFloat2LabEncoded(Encoded, &Lab); break; case icSigRgbData: Encoded[0] = GetIT8Val("RGB_R", 255.0); Encoded[1] = GetIT8Val("RGB_G", 255.0); Encoded[2] = GetIT8Val("RGB_B", 255.0); break; case icSigGrayData: Encoded[0] = GetIT8Val("GRAY", 255.0); break; case icSigCmykData: Encoded[0] = GetIT8Val("CMYK_C", lUse255always ? 255.0 : 100.0); Encoded[1] = GetIT8Val("CMYK_M", lUse255always ? 255.0 : 100.0); Encoded[2] = GetIT8Val("CMYK_Y", lUse255always ? 255.0 : 100.0); Encoded[3] = GetIT8Val("CMYK_K", lUse255always ? 255.0 : 100.0); break; case icSigCmyData: Encoded[0] = GetIT8Val("CMY_C", lUse255always ? 255.0 : 100.0); Encoded[1] = GetIT8Val("CMY_M", lUse255always ? 255.0 : 100.0); Encoded[2] = GetIT8Val("CMY_Y", lUse255always ? 255.0 : 100.0); break; default: { int i; for (i=0; i < _cmsChannelsOf(InputColorSpace); i++) { char Buffer[255]; sprintf(Buffer, "CHAN_%d", i); Encoded[i] = GetIT8Val(Buffer, 255.0); } } } } static void SetCGATSfld(const char* Col, double Val) { if (lQuantize) Val = floor(Val + 0.5); if (!cmsIT8SetDataDbl(hIT8out, CGATSPatch, Col, Val)) { FatalError("icctrans: couldn't set '%s' on output cgats '%s'", Col, CGATSoutFilename); } } static void PutCGATSValues(int nPatch, WORD Encoded[]) { cmsIT8SetData(hIT8out, CGATSPatch, "SAMPLE_ID", CGATSPatch); switch (OutputColorSpace) { // Encoding should follow CGATS specification. case icSigXYZData: cmsXYZEncoded2Float(&xyz, Encoded); SetCGATSfld("XYZ_X", xyz.X * 100.0); SetCGATSfld("XYZ_Y", xyz.Y * 100.0); SetCGATSfld("XYZ_Z", xyz.Z * 100.0); break; case icSigLabData: cmsLabEncoded2Float(&Lab, Encoded); SetCGATSfld("LAB_L", Lab.L); SetCGATSfld("LAB_A", Lab.a); SetCGATSfld("LAB_B", Lab.b); break; case icSigRgbData: SetCGATSfld("RGB_R", Encoded[0] / 257.0); SetCGATSfld("RGB_G", Encoded[1] / 257.0); SetCGATSfld("RGB_B", Encoded[2] / 257.0); break; case icSigGrayData: SetCGATSfld("GRAY", Encoded[0] / 257.0); break; case icSigCmykData: SetCGATSfld("CMYK_C", (lUse255always ? 255.0 : 100.0) * Encoded[0] / 65535.0); SetCGATSfld("CMYK_M", (lUse255always ? 255.0 : 100.0) * Encoded[1] / 65535.0); SetCGATSfld("CMYK_Y", (lUse255always ? 255.0 : 100.0) * Encoded[2] / 65535.0); SetCGATSfld("CMYK_K", (lUse255always ? 255.0 : 100.0) * Encoded[3] / 65535.0); break; case icSigCmyData: SetCGATSfld("CMY_C", (lUse255always ? 255.0 : 100.0) * Encoded[0] / 65535.0); SetCGATSfld("CMY_M", (lUse255always ? 255.0 : 100.0) * Encoded[1] / 65535.0); SetCGATSfld("CMY_Y", (lUse255always ? 255.0 : 100.0) * Encoded[2] / 65535.0); break; default: { int i; for (i=1; i <= _cmsChannelsOf(OutputColorSpace); i++) { char Buffer[255]; sprintf(Buffer, "CHAN_%d", i); SetCGATSfld(Buffer, Encoded[i-1] / 257.0); } } } } // Print XYZ/Lab values on verbose mode static void PrintPCS(WORD Input[], WORD PCSxyz[], WORD PCSLab[]) { if (Verbose && hTransXYZ && hTransLab) { if (hTransXYZ) cmsDoTransform(hTransXYZ, Input, PCSxyz, 1); if (hTransLab) cmsDoTransform(hTransLab, Input, PCSLab, 1); PrintResults(PCSxyz, icSigXYZData); PrintResults(PCSLab, icSigLabData); } } // Create data format static void SetOutputDataFormat() { cmsIT8SetPropertyStr(hIT8out, "ORIGINATOR", "icctrans"); if (IncludePart != NULL) cmsIT8SetPropertyStr(hIT8out, ".INCLUDE", IncludePart); cmsIT8SetComment(hIT8out, "Data follows"); cmsIT8SetPropertyDbl(hIT8out, "NUMBER_OF_SETS", nMaxPatches); switch (OutputColorSpace) { // Encoding should follow CGATS specification. case icSigXYZData: cmsIT8SetPropertyDbl(hIT8out, "NUMBER_OF_FIELDS", 4); cmsIT8SetDataFormat(hIT8out, 0, "SAMPLE_ID"); cmsIT8SetDataFormat(hIT8out, 1, "XYZ_X"); cmsIT8SetDataFormat(hIT8out, 2, "XYZ_Y"); cmsIT8SetDataFormat(hIT8out, 3, "XYZ_Z"); break; case icSigLabData: cmsIT8SetPropertyDbl(hIT8out, "NUMBER_OF_FIELDS", 4); cmsIT8SetDataFormat(hIT8out, 0, "SAMPLE_ID"); cmsIT8SetDataFormat(hIT8out, 1, "LAB_L"); cmsIT8SetDataFormat(hIT8out, 2, "LAB_A"); cmsIT8SetDataFormat(hIT8out, 3, "LAB_B"); break; case icSigRgbData: cmsIT8SetPropertyDbl(hIT8out, "NUMBER_OF_FIELDS", 4); cmsIT8SetDataFormat(hIT8out, 0, "SAMPLE_ID"); cmsIT8SetDataFormat(hIT8out, 1, "RGB_R"); cmsIT8SetDataFormat(hIT8out, 2, "RGB_G"); cmsIT8SetDataFormat(hIT8out, 3, "RGB_B"); break; case icSigGrayData: cmsIT8SetPropertyDbl(hIT8out, "NUMBER_OF_FIELDS", 2); cmsIT8SetDataFormat(hIT8out, 0, "SAMPLE_ID"); cmsIT8SetDataFormat(hIT8out, 1, "GRAY"); break; case icSigCmykData: cmsIT8SetPropertyDbl(hIT8out, "NUMBER_OF_FIELDS", 5); cmsIT8SetDataFormat(hIT8out, 0, "SAMPLE_ID"); cmsIT8SetDataFormat(hIT8out, 1, "CMYK_C"); cmsIT8SetDataFormat(hIT8out, 2, "CMYK_M"); cmsIT8SetDataFormat(hIT8out, 3, "CMYK_Y"); cmsIT8SetDataFormat(hIT8out, 4, "CMYK_K"); break; case icSigCmyData: cmsIT8SetPropertyDbl(hIT8out, "NUMBER_OF_FIELDS", 4); cmsIT8SetDataFormat(hIT8out, 0, "SAMPLE_ID"); cmsIT8SetDataFormat(hIT8out, 1, "CMY_C"); cmsIT8SetDataFormat(hIT8out, 2, "CMY_M"); cmsIT8SetDataFormat(hIT8out, 3, "CMY_Y"); break; default: { int i, n; char Buffer[255]; n = _cmsChannelsOf(OutputColorSpace); cmsIT8SetPropertyDbl(hIT8out, "NUMBER_OF_FIELDS", n+1); cmsIT8SetDataFormat(hIT8out, 0, "SAMPLE_ID"); for (i=1; i <= n; i++) { sprintf(Buffer, "CHAN_%d", i); cmsIT8SetDataFormat(hIT8out, i, Buffer); } } } } // Open CGATS if specified static void OpenCGATSFiles(int argc, char *argv[]) { int nParams = argc - xoptind; if (nParams >= 1) { hIT8in = cmsIT8LoadFromFile(argv[xoptind]); if (hIT8in == NULL) FatalError("icctrans: '%s' is not recognized as a CGATS file", argv[xoptind]); nMaxPatches = (int) cmsIT8GetPropertyDbl(hIT8in, "NUMBER_OF_SETS"); } if (nParams == 2) { hIT8out = cmsIT8Alloc(); SetOutputDataFormat(); strncpy(CGATSoutFilename, argv[xoptind+1], MAX_PATH-1); } if (nParams > 2) FatalError("icctrans: Too many CGATS files"); } // The main sink int main(int argc, char *argv[]) { WORD Input[MAXCHANNELS]; WORD Output[MAXCHANNELS]; WORD PCSLab[MAXCHANNELS]; WORD PCSxyz[MAXCHANNELS]; int nPatch = 0; cmsSetErrorHandler(MyErrorHandler); fprintf(stderr, "LittleCMS ColorSpace conversion calculator - v3.3\n\n"); if (argc == 1) Help(); HandleSwitches(argc, argv); // Open profiles, create transforms OpenTransforms(); // Open CGATS input if specified OpenCGATSFiles(argc, argv); for(;;) { if (hIT8in != NULL) { if (nPatch >= nMaxPatches) break; TakeCGATSValues(nPatch++, Input); } else { if (feof(stdin)) break; TakeTextValues(Input); } cmsDoTransform(hTrans, Input, Output, 1); if (hIT8out != NULL) { PutCGATSValues(nPatch, Output); } else { PrintResults(Output, OutputColorSpace); PrintPCS(Input, PCSxyz, PCSLab); } } CloseTransforms(); if (hIT8in) cmsIT8Free(hIT8in); if (hIT8out) { cmsIT8SaveToFile(hIT8out, CGATSoutFilename); cmsIT8Free(hIT8out); } return 0; }