/* * International Color Consortium color transform expanded support * Known colorants support. * * Author: Graeme W. Gill * Date: 24/2/2002 * Version: 1.00 * * Copyright 2002 Graeme W. Gill * All rights reserved. * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :- * see the License.txt file for licencing details. * */ /* TTBD: Would like to have some short string way of defining whether the ink combination is additive or subtractive, instead of just guessing. */ #include #include #include #include #include "icc.h" #include "xcolorants.h" #include "sort.h" /* Colorant table for N color device characterisation. */ /* This is ordered to match the ICC colorspace convention. */ /* NOTE:- need to keep these in same order as ink mask */ /* enumeration (lsb to msb), or strange things result! */ static struct { inkmask m; /* Mask value */ char *c; /* 1/2 Character name */ char *s; /* Everyday name */ char *ps; /* Postscript colorant name */ double aXYZ[3]; /* Rough XYZ values (0..1) for that additive colorant */ double sXYZ[3]; /* Rough XYZ values (0..1) for that subtractive colorant */ } icx_ink_table[] = { { ICX_CYAN, ICX_C_CYAN, ICX_S_CYAN, ICX_PS_CYAN, { 0.12, 0.18, 0.48 }, { 0.12, 0.18, 0.48 } }, { ICX_MAGENTA, ICX_C_MAGENTA, ICX_S_MAGENTA, ICX_PS_MAGENTA, { 0.38, 0.19, 0.20 }, { 0.38, 0.19, 0.20 } }, { ICX_YELLOW, ICX_C_YELLOW, ICX_S_YELLOW, ICX_PS_YELLOW, { 0.76, 0.81, 0.11 }, { 0.76, 0.81, 0.11 } }, { ICX_BLACK, ICX_C_BLACK, ICX_S_BLACK, ICX_PS_BLACK, { 0.01, 0.01, 0.01 }, { 0.04, 0.04, 0.04 } }, { ICX_ORANGE, ICX_C_ORANGE, ICX_S_ORANGE, ICX_PS_ORANGE, { 0.59, 0.41, 0.03 }, { 0.59, 0.41, 0.05 } }, { ICX_RED, ICX_C_RED, ICX_S_RED, ICX_PS_RED, { 0.412414, 0.212642, 0.019325 }, { 0.40, 0.21, 0.05 } }, { ICX_GREEN, ICX_C_GREEN, ICX_S_GREEN, ICX_PS_GREEN, { 0.357618, 0.715136, 0.119207 }, { 0.11, 0.27, 0.21 } }, { ICX_BLUE, ICX_C_BLUE, ICX_S_BLUE, ICX_PS_BLUE, { 0.180511, 0.072193, 0.950770 }, { 0.11, 0.27, 0.47 } }, { ICX_WHITE, ICX_C_WHITE, ICX_S_WHITE, ICX_PS_WHITE, { 0.950543, 1.0, 1.089303 }, /* D65 ? */ { 0.9642, 1.00, 0.8249 } }, /* D50 */ { ICX_LIGHT_CYAN, ICX_C_LIGHT_CYAN, ICX_S_LIGHT_CYAN, ICX_PS_LIGHT_CYAN, { 0.76, 0.89, 1.08 }, { 0.76, 0.89, 1.08 } }, { ICX_LIGHT_MAGENTA, ICX_C_LIGHT_MAGENTA, ICX_S_LIGHT_MAGENTA, ICX_PS_LIGHT_MAGENTA, { 0.83, 0.74, 1.02 }, { 0.83, 0.74, 1.02 } }, { ICX_LIGHT_YELLOW, ICX_C_LIGHT_YELLOW, ICX_S_LIGHT_YELLOW, ICX_PS_LIGHT_YELLOW, { 0.88, 0.97, 0.72 }, { 0.88, 0.97, 0.72 } }, { ICX_LIGHT_BLACK, ICX_C_LIGHT_BLACK, ICX_S_LIGHT_BLACK, ICX_PS_LIGHT_BLACK, { 0.56, 0.60, 0.65 }, { 0.56, 0.60, 0.65 } }, { ICX_MEDIUM_CYAN, ICX_C_MEDIUM_CYAN, ICX_S_MEDIUM_CYAN, ICX_PS_MEDIUM_CYAN, { 0.61, 0.81, 1.07 }, { 0.61, 0.81, 1.07 } }, { ICX_MEDIUM_MAGENTA, ICX_C_MEDIUM_MAGENTA, ICX_S_MEDIUM_MAGENTA, ICX_PS_MEDIUM_MAGENTA, { 0.74, 0.53, 0.97 }, { 0.74, 0.53, 0.97 } }, { ICX_MEDIUM_YELLOW, ICX_C_MEDIUM_YELLOW, ICX_S_MEDIUM_YELLOW, ICX_PS_MEDIUM_YELLOW, { 0.82, 0.93, 0.40 }, { 0.82, 0.93, 0.40 } }, { ICX_MEDIUM_BLACK, ICX_C_MEDIUM_BLACK, ICX_S_MEDIUM_BLACK, ICX_PS_MEDIUM_BLACK, { 0.27, 0.29, 0.31 }, { 0.27, 0.29, 0.31 } }, { ICX_LIGHT_LIGHT_BLACK, ICX_C_LIGHT_LIGHT_BLACK, ICX_S_LIGHT_LIGHT_BLACK, ICX_PS_LIGHT_LIGHT_BLACK, { 0.76, 0.72, 0.65 }, /* Very rough - should substiture real numbers */ { 0.76, 0.72, 0.65 } }, { 0, "", "", "", { 0.0, 0.0, 0.0 }, { 0.0, 0.0, 0.0 } } }; /* Colorant table for N color device characterisation */ static struct { inkmask m; /* Mask combination */ inkmask rm; /* Light ink reduced mask combination */ icColorSpaceSignature psig; /* Appropriate primary ICC signature */ icColorSpaceSignature ssig; /* Appropriate secondary ICC signature */ char *desc; /* Description */ } icx_colcomb_table[] = { { ICX_K, ICX_K, icSigGrayData, icSigGrayData, "Print grey" }, { ICX_W, ICX_W, icSigGrayData, icSigGrayData, "Video grey" }, { ICX_IRGB, ICX_IRGB, icSigRgbData, icSigRgbData, "Print RGB" }, { ICX_RGB, ICX_RGB, icSigRgbData, icSigRgbData, "Video RGB" }, { ICX_CMYK, ICX_CMYK, icSigCmykData, icSigCmykData, "CMYK" }, { ICX_CMY, ICX_CMY, icSigCmyData, icSigCmyData, "CMY" }, { ICX_CMYKcm, ICX_CMYK, icSig6colorData, icSigMch6Data, "CMYK + Light CM" }, { ICX_CMYKcmk, ICX_CMYK, icSig7colorData, icSig7colorData, "CMYK + Light CMK" }, { ICX_CMYKRB, ICX_W, icSig6colorData, icSigMch6Data, "CMYK + Red + Blue" }, { ICX_CMYKOG, ICX_W, icSig6colorData, icSigMch6Data, "CMYK + Orange + Green" }, { ICX_CMYKcmk1k, ICX_CMYK, icSig8colorData, icSigMch8Data, "CMYK + Light CMK + Light Light K" }, { ICX_CMYKOGcm, ICX_CMYKOG, icSig8colorData, icSigMch8Data, "CMYK + Orange + Green + Light CM" }, { ICX_CMYKcm2c2m, ICX_CMYK, icSig8colorData, icSigMch8Data, "CMYK + Light CM + Medium CM" }, { 0, 0, 0, 0, "" } }; /* Given an ink combination mask, return the number of recognised inks in it */ int icx_noofinks(inkmask mask) { int i, count = 0; for (i = 0; icx_ink_table[i].m != 0; i++) { if (mask & icx_ink_table[i].m) { count++; } } return count; } /* Given an ink combination mask, return the 1-2 character based string */ /* If winv is nz, include ICX_INVERTED indicator if set */ /* Return NULL on error. free() after use */ char *icx_inkmask2char(inkmask mask, int winv) { int i; char *rv; if ((rv = malloc(2 * ICX_MXINKS + 1)) == NULL) return NULL; *rv = '\000'; if (winv && (mask & ICX_INVERTED)) strcat(rv, "i"); for (i = 0; icx_ink_table[i].m != 0; i++) { if (mask & icx_ink_table[i].m) { strcat(rv, icx_ink_table[i].c); } } return rv; } /* Given the 1-2 character string, return the ink combination mask */ /* Note that ICX_ADDITIVE will be guessed */ /* Return 0 if unrecognised character in string */ inkmask icx_char2inkmask(char *chstring) { inkmask mask = 0; int k, i; char *cp; cp = chstring; for (k = 0; ; k++) { if (*cp == '\000') { break; } /* "Inverted" prefix ? */ if (k == 0 && *cp == 'i') { mask |= ICX_INVERTED; cp++; continue; } /* Colorant ? */ for (i = 0; icx_ink_table[i].m != 0; i++) { if (strncmp(cp, icx_ink_table[i].c, strlen(icx_ink_table[i].c)) == 0) { mask |= icx_ink_table[i].m; cp += strlen(icx_ink_table[i].c); break; } } if (icx_ink_table[i].m == 0) { /* oops - unrecognised character */ return 0; } } /* Check it out againts known combinations to guess additive */ for (i = 0; icx_colcomb_table[i].m != 0; i++) { if ((icx_colcomb_table[i].m & ~ICX_ADDITIVE) == mask) { mask = icx_colcomb_table[i].m; break; } } return mask; } /* Given an ink combination mask that may contain light inks, */ /* return the corresponding ink mask without light inks. */ /* Return 0 if ink combination not recognised. */ inkmask icx_ink2primary_ink(inkmask mask) { int i; for (i = 0; icx_colcomb_table[i].m != 0; i++) { if (mask == icx_colcomb_table[i].m) { return icx_colcomb_table[i].rm; } } return 0; } /* Given an ink combination mask and a single ink mask, */ /* return the index number for that ink. */ /* Return -1 if mask1 not in mask */ int icx_ink2index(inkmask mask, inkmask mask1) { int i, count = 0; if ((mask1 & mask) == 0) return -1; for (i = 0; icx_ink_table[i].m != 0; i++) { if (mask1 == icx_ink_table[i].m) { return count; } if (mask & icx_ink_table[i].m) { count++; } } return -1; } /* Given an ink combination mask and a index number, */ /* return the single ink mask. */ /* Return 0 if there are no inks at that index */ inkmask icx_index2ink(inkmask mask, int ixno) { int i, count = 0; for (i = 0; icx_ink_table[i].m != 0; i++) { if (mask & icx_ink_table[i].m) { if (ixno == count) return icx_ink_table[i].m; count++; } } return 0; } /* Given a single ink mask, */ /* return its string representation */ char *icx_ink2string(inkmask mask) { int i; for (i = 0; icx_ink_table[i].m != 0; i++) { if (mask == icx_ink_table[i].m) return icx_ink_table[i].s; } return NULL; } /* Given a single ink mask, */ /* return its 1-2 character representation */ char *icx_ink2char(inkmask mask) { int i; for (i = 0; icx_ink_table[i].m != 0; i++) { if (mask == icx_ink_table[i].m) return icx_ink_table[i].c; } return NULL; } /* Given a single ink mask, */ /* return its Postscript string representation */ char *icx_ink2psstring(inkmask mask) { int i; for (i = 0; icx_ink_table[i].m != 0; i++) { if (mask == icx_ink_table[i].m) return icx_ink_table[i].ps; } return NULL; } /* Return an enumerated single colorant description */ /* Return 0 if no such enumeration, single colorant mask if there is */ inkmask icx_enum_colorant( int no, /* Enumeration mask index */ char **desc /* Return enumeration description */ ) { int i; for (i = 0; icx_ink_table[i].m != 0; i++) { if (i == no) { if (desc != NULL) *desc = icx_ink_table[i].s; return icx_ink_table[i].m; } } return 0; } /* Return an enumerated colorant combination */ /* Return 0 if no such enumeration, colorant combination mask if there is */ inkmask icx_enum_colorant_comb( int no, /* Enumeration mask index */ char **desc /* Return enumeration description */ ) { int i; for (i = 0; icx_colcomb_table[i].m != 0; i++) { if (i == no) { if (desc != NULL) *desc = icx_colcomb_table[i].desc; return icx_colcomb_table[i].m; } } return 0; } /* Given an colorant combination mask, */ /* check if it matches the given ICC colorspace signature. */ /* return NZ if it does. */ /* (We don't check colorant colors for multi-colorant devices though) */ int icx_colorant_comb_match_icc( inkmask mask, /* Colorant combination mask */ icColorSpaceSignature sig /* ICC signature */ ) { int i; for (i = 0; icx_colcomb_table[i].m != 0; i++) { if (mask == icx_colcomb_table[i].m) { if (sig == icx_colcomb_table[i].psig || sig == icx_colcomb_table[i].ssig) { return 1; } else { return 0; } } } return 0; } /* Given an ICC colorspace signature, return the appropriate */ /* colorant combination mask. Return 0 if ambiguous signature. */ inkmask icx_icc_to_colorant_comb(icColorSpaceSignature sig, icProfileClassSignature deviceClass) { switch (sig) { case icSigGrayData: if (deviceClass == icSigOutputClass) return ICX_K; return ICX_W; case icSigCmyData: return ICX_CMY; case icSigRgbData: if (deviceClass == icSigOutputClass) return ICX_IRGB; return ICX_RGB; case icSigCmykData: return ICX_CMYK; default: break; } return 0; } /* Given an ICC colorspace signature, and a matching list */ /* of the D50 L*a*b* colors of the colorants, return the best matching */ /* colorant combination mask. Return 0 if not an applicable colorspace. */ /* (Note we're not dealing with colorant order here.) */ inkmask icx_icc_cv_to_colorant_comb( icColorSpaceSignature sig, /* Input ICC colorspace signature */ icProfileClassSignature deviceClass, /* Device class */ double cvals[][3] /* Input L*a*b* colorant values */ ) { int i, j; int imask; int ninks, ncol; double slab[ICX_MXINKS][3]; double alab[ICX_MXINKS][3]; typedef struct { int x; double v; } mchstr; mchstr mch[MAX_CHAN][ICX_MXINKS]; /* match index */ int used[ICX_MXINKS]; int co[ICX_MXINKS]; /* Combination counter */ double cmv; /* Combination match value */ int bco[ICX_MXINKS]; /* Best Combinat */ double bcmv; /* Best Match value */ int order[MAX_CHAN]; /* Place holder, not currently used - return ink order */ switch (sig) { case icSigXYZData: case icSigLabData: case icSigLuvData: case icSigYCbCrData: case icSigYxyData: case icSigHsvData: case icSigHlsData: case icSigNamedData: return 0; case icSigGrayData: if (deviceClass == icSigOutputClass) return ICX_K; return ICX_W; case icSigCmyData: return ICX_CMY; case icSigRgbData: if (deviceClass == icSigOutputClass) return ICX_IRGB; return ICX_RGB; case icSigCmykData: return ICX_CMYK; default: break; } #ifdef NEVER /* Rely on device class (above) */ if (sig == icSigGrayData) { /* This only works reliably if we got the Lab data from a non-ICC */ /* conformant monochrome profile (one that doesn't force device 0 */ /* input of the grayTRTC to black), or uses a LUT based monochrome */ /* profile. */ /* It also won't work if someone uses a monochrome profile for a */ /* device with a non grey colorant. */ /* Really ICC should have icSigWhiteData, icSigBlackData, icSig1colorData ? */ if (cvals[0][0] > 50.0) { return ICX_W; } else { return ICX_K; } } #endif /* NEVER */ /* Compute Lab values of stock inks, and count them */ for (ninks = 0; ninks < ICX_MXINKS; ninks++) { if (icx_ink_table[ninks].m == 0) break; icmXYZ2Lab(&icmD50, slab[ninks], icx_ink_table[ninks].sXYZ); icmXYZ2Lab(&icmD50, alab[ninks], icx_ink_table[ninks].aXYZ); } ncol = icmCSSig2nchan(sig); /* Number of colorants */ /* Compute ideal matching of device colorants to stock inks */ for (i = 0; i < ncol; i++) { for (j = 0; j < ninks; j++) { double tt; mch[i][j].x = j; mch[i][j].v = icmCIE94sq(cvals[i], slab[j]); tt = icmCIE94sq(cvals[i], alab[j]); if (tt < mch[i][j].v) mch[i][j].v = tt; } /* Sort the matches for this colorant */ #define HEAP_COMPARE(A,B) (A.v < B.v) HEAPSORT(mchstr, mch[i], ninks) #undef HEAP_COMPARE //printf("\n~1 Colorant %d has best matches\n",i); //for (j = 0; j < ninks; j++) //printf("~1 ix %d color = '%s' dE %f\n",j, icx_ink_table[mch[i][j].x].s, mch[i][j].v); } /* Do exaustive combination search, using early */ /* out to keep combination count down.*/ /* Reset the combination counter */ for (j = 0; j < ninks; j++) /* Reset ink used flag */ used[j] = 0; cmv = 0.0; for (i = ncol-1; i >= 0; i--) { for (j = 0; j < ninks; j++) { /* Set to lowest usable number */ if (used[mch[i][j].x] == 0) { /* Can use this one */ used[mch[i][j].x] = 1; /* Now it's matched */ cmv += mch[i][j].v; co[i] = j; break; /* we assume ncol < ninks */ } } } //printf("\n~1 Initial combination has cmv = %f\n",cmv); //for (i = 0; i < ncol; i++) //printf("~1 Chan %d color = '%s'\n",i, icx_ink_table[mch[i][co[i]].x].s); /* Set this as the best initial match */ for (i = 0; i < ncol; i++) bco[i] = co[i]; bcmv = cmv; /* Now go through all other combinations */ for (;;) { /* Increment counter */ for (i = 0;;) { /* Work up the digits, incrementing each one */ for (; i < ncol; i++) { j = co[i]; used[mch[i][j].x] = 0; cmv -= mch[i][j].v; while (++j < ninks && (used[mch[i][j].x] != 0 || (cmv + mch[i][j].v) >= bcmv)) { }; if (j < ninks) { /* No carry */ used[mch[i][j].x] = 1; cmv += mch[i][j].v; co[i] = j; break; } /* Carry to next colorant */ } if (i >= ncol) break; /* Run out of digits to increment */ /* Now work down again, resetting digit */ for (--i; i >= 0; i--) { for (j = 0; j < ninks; j++) { /* Set to lowest usable number */ if (used[mch[i][j].x] == 0 && (cmv + mch[i][j].v) < bcmv) { /* Can use this one */ used[mch[i][j].x] = 1; /* Now it's matched */ cmv += mch[i][j].v; co[i] = j; break; } } if (j >= ninks) { /* No combination is feasible */ i++; /* Go back to incrementing next highest digit */ break; } } if (i < 0) /* We reset all the lower digits */ break; /* We're at a good combination, so done. */ } if (i >= ncol) break; /* We're done all combinations */ //printf("\n~1 New combination has cmv = %f\n",cmv); //for (i = 0; i < ncol; i++) //printf("~1 Chan %d color = '%s'\n",i, icx_ink_table[mch[i][co[i]].x].s); /* See if this is better (it always should be !) */ if (cmv < bcmv) { for (i = 0; i < ncol; i++) bco[i] = co[i]; bcmv = cmv; } } /* Compile the result */ for (imask = 0, i = 0; i < ncol; i++) { imask |= icx_ink_table[mch[i][bco[i]].x].m; order[i] = bco[i]; /* icx ink_table index corresponding to channel */ } /* Slight hack to recognise some additive combinations */ if (imask == ICX_WHITE) imask = ICX_W; else if (imask == (ICX_RED | ICX_GREEN | ICX_BLUE)) imask = ICX_RGB; return imask; } /* Given a colorant combination mask */ /* return the primary matching ICC colorspace signature. */ /* return 0 if there is no match */ icColorSpaceSignature icx_colorant_comb_to_icc( inkmask mask /* Colorant combination mask */ ) { int i; for (i = 0; icx_colcomb_table[i].m != 0; i++) { if (mask == icx_colcomb_table[i].m) return icx_colcomb_table[i].psig; } return 0; } /* - - - - - - - - - - - - - - - - - */ /* Approximate device colorant model */ /* Given device values, return an estimate of the XYZ value for that color. */ static void icxColorantLu_to_XYZ( icxColorantLu *s, double XYZ[3], /* Output */ double d[ICX_MXINKS] /* Input */ ) { int e, j; if (s->mask & ICX_ADDITIVE ) { /* We assume a simple additive model with gamma */ XYZ[0] = XYZ[1] = XYZ[2] = 0.0; for (e = 0; e < s->di; e++) { double v = d[e]; if (v < 0.0) v = 0.0; else if (v > 1.0) v = 1.0; if (v <= 0.03928) v /= 12.92; else v = pow((0.055 + v)/1.055, 2.4); /* Gamma */ for (j = 0; j < 3; j++) XYZ[j] += v * icx_ink_table[s->iix[e]].aXYZ[j]; } /* Normalise Y to 1.0, & add black glare */ for (j = 0; j < 3; j++) { XYZ[j] *= s->Ynorm; XYZ[j] = XYZ[j] * (1.0 - icx_ink_table[s->bkix].aXYZ[j]) + icx_ink_table[s->bkix].aXYZ[j]; } } else { /* We assume a simple screened subtractive filter model, with dot gain */ /* start with white */ XYZ[0] = icx_ink_table[s->whix].sXYZ[0]; XYZ[1] = icx_ink_table[s->whix].sXYZ[1]; XYZ[2] = icx_ink_table[s->whix].sXYZ[2]; /* And filter it out for each component */ for (e = 0; e < s->di; e++) { double v = d[e]; if (v < 0.0) v = 0.0; else if (v > 1.0) v = 1.0; v = 1.0 - pow(1.0 - v, 2.2); /* Compute dot gain */ for (j = 0; j < 3; j++) { double fv; /* Normalise filtering effect of this colorant */ fv = icx_ink_table[s->iix[e]].aXYZ[j]/icx_ink_table[s->whix].sXYZ[j]; /* Compute screened filtering effect */ fv = (1.0 - v) + v * fv; /* Apply filter to our current value */ XYZ[j] *= fv; } } } } /* Given device values, return an estimate of the */ /* relative Lab value for that color. */ static void icxColorantLu_to_rLab( icxColorantLu *s, double Lab[3], /* Output */ double d[ICX_MXINKS] /* Input */ ) { icxColorantLu_to_XYZ(s, Lab, d); /* Compute XYZ */ icmXYZ2Lab(&s->wp, Lab, Lab); /* Convert from XYZ to Lab */ } /* We're done with aproximate device model */ static void icxColorantLu_del(icxColorantLu *s) { if (s != NULL) { free(s); } } /* Given an ink definition, return an aproximate */ /* device to CIE color converted object. */ icxColorantLu *new_icxColorantLu(inkmask mask) { int i, e; icxColorantLu *s; if ((s = (icxColorantLu *)malloc(sizeof(icxColorantLu))) == NULL) { fprintf(stderr,"icxColorantLu: malloc failed allocating object\n"); exit(-1); } /* Initialise methods */ s->del = icxColorantLu_del; s->dev_to_XYZ = icxColorantLu_to_XYZ; s->dev_to_rLab = icxColorantLu_to_rLab; /* Init */ s->mask = mask; for (e = i = 0; icx_ink_table[i].m != 0; i++) { if (ICX_WHITE == icx_ink_table[i].m) s->whix = i; if (ICX_BLACK == icx_ink_table[i].m) s->bkix = i; if (mask & icx_ink_table[i].m) s->iix[e++] = i; } s->di = e; s->Ynorm = 0.0; if (mask & ICX_ADDITIVE ) { for (e = 0; e < s->di; e++) s->Ynorm += icx_ink_table[s->iix[e]].aXYZ[1]; s->Ynorm = 1.0/s->Ynorm; icmAry2XYZ(s->wp, icx_ink_table[s->whix].aXYZ); } else { icmAry2XYZ(s->wp, icx_ink_table[s->whix].sXYZ); } return s; }