784 lines
21 KiB
C
784 lines
21 KiB
C
|
|
/*
|
|
* 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 <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
#include <math.h>
|
|
#ifndef SALONEINSTLIB
|
|
#include "icc.h"
|
|
#else
|
|
#include "numsup.h"
|
|
#include "sa_conv.h"
|
|
#endif
|
|
#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_VIOLET, ICX_C_VIOLET, ICX_S_VIOLET, ICX_PS_VIOLET,
|
|
{ 0.0925, 0.0442, 0.244 },
|
|
{ 0.0925, 0.0442, 0.244 } },
|
|
{ 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_CMYKRGB, ICX_W, icSig7colorData, icSigMch7Data,
|
|
"CMYK + Red + Green + Blue" },
|
|
{ ICX_CMYKOGV, ICX_W, icSig7colorData, icSigMch7Data,
|
|
"CMYK + Orange + Green + Violet" },
|
|
{ ICX_CMYKOGB, ICX_W, icSig7colorData, icSigMch7Data,
|
|
"CMYK + Orange + Green + Blue" },
|
|
{ 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 within the set 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:
|
|
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;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|