You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1289 lines
31 KiB
1289 lines
31 KiB
//
|
|
// 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 <stdarg.h>
|
|
#include <ctype.h>
|
|
|
|
#ifndef NON_WINDOWS
|
|
#include <io.h>
|
|
#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<profile> - Input profile (defaults to sRGB)\n", SW);
|
|
fprintf(stderr, "%co<profile> - Output profile (defaults to sRGB)\n", SW);
|
|
fprintf(stderr, "%cl<profile> - 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<n> - 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<profile> - 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;
|
|
}
|
|
|
|
|