/* * International Color Consortium color transform expanded support * * Author: Graeme W. Gill * Date: 2/7/00 * Version: 1.00 * * Copyright 2000, 2001 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. * * Based on the old iccXfm class. */ /* * This module expands the basic icclib functionality, * providing more functionality in exercising. * The implementation for the three different types * of profile representation, are in their own source files. */ /* * TTBD: * Some of the error handling is crude. Shouldn't use * error(), should return status. * */ #include #include #include #ifdef __sun #include #endif #if defined(__IBMC__) && defined(_M_IX86) #include #endif #include "numlib.h" #include "counters.h" #include "plot.h" #include "../h/sort.h" #include "xicc.h" /* definitions for this library */ #define USE_CAM /* Use CIECAM02 for clipping and gamut mapping, else use Lab */ #undef DEBUG /* Plot 1d Luts */ #ifdef DEBUG #include "plot.h" #endif #define MAX_INVSOLN 4 static void xicc_del(xicc *p); icxLuBase * xicc_get_luobj(xicc *p, int flags, icmLookupFunc func, icRenderingIntent intent, icColorSpaceSignature pcsor, icmLookupOrder order, icxViewCond *vc, icxInk *ink); static icxLuBase *xicc_set_luobj(xicc *p, icmLookupFunc func, icRenderingIntent intent, icmLookupOrder order, int flags, int no, int nobw, cow *points, icxMatrixModel *skm, double dispLuminance, double wpscale, double smooth, double avgdev, icxViewCond *vc, icxInk *ink, xcal *cal, int quality); static void icxLutSpaces(icxLuBase *p, icColorSpaceSignature *ins, int *inn, icColorSpaceSignature *outs, int *outn, icColorSpaceSignature *pcs); static void icxLuSpaces(icxLuBase *p, icColorSpaceSignature *ins, int *inn, icColorSpaceSignature *outs, int *outn, icmLuAlgType *alg, icRenderingIntent *intt, icmLookupFunc *fnc, icColorSpaceSignature *pcs); static void icxLu_get_native_ranges (icxLuBase *p, double *inmin, double *inmax, double *outmin, double *outmax); static void icxLu_get_ranges (icxLuBase *p, double *inmin, double *inmax, double *outmin, double *outmax); static void icxLuEfv_wh_bk_points(icxLuBase *p, double *wht, double *blk, double *kblk); int xicc_get_viewcond(xicc *p, icxViewCond *vc); /* The different profile types are in their own source filesm */ /* and are included to keep their functions private. (static) */ #include "xmono.c" #include "xmatrix.c" #include "xlut.c" /* New xfit3 in & out optimising based profiles */ //#include "xlut1.c" /* Old xfit1 device curve based profiles */ #ifdef NT /* You'd think there might be some standards.... */ # ifndef __BORLANDC__ # define stricmp _stricmp # endif #else # define stricmp strcasecmp #endif /* Utilities */ /* Return a string description of the given enumeration value */ const char *icx2str(icmEnumType etype, int enumval) { if (etype == icmColorSpaceSignature) { if (((icColorSpaceSignature)enumval) == icxSigJabData) return "Jab"; else if (((icColorSpaceSignature)enumval) == icxSigJChData) return "JCh"; else if (((icColorSpaceSignature)enumval) == icxSigLChData) return "LCh"; } else if (etype == icmRenderingIntent) { if (((icRenderingIntent)enumval) == icxAppearance) return "icxAppearance"; else if (((icRenderingIntent)enumval) == icxAbsAppearance) return "icxAbsAppearance"; else if (((icRenderingIntent)enumval) == icxPerceptualAppearance) return "icxPerceptualAppearance"; else if (((icRenderingIntent)enumval) == icxAbsPerceptualAppearance) return "icxAbsPerceptualAppearance"; else if (((icRenderingIntent)enumval) == icxSaturationAppearance) return "icxSaturationAppearance"; else if (((icRenderingIntent)enumval) == icxAbsSaturationAppearance) return "icxAbsSaturationAppearance"; } return icm2str(etype, enumval); } /* Common xicc stuff */ /* Return information about the native lut in/out colorspaces. */ /* Any pointer may be NULL if value is not to be returned */ static void icxLutSpaces( icxLuBase *p, /* This */ icColorSpaceSignature *ins, /* Return input color space */ int *inn, /* Return number of input components */ icColorSpaceSignature *outs, /* Return output color space */ int *outn, /* Return number of output components */ icColorSpaceSignature *pcs /* Return PCS color space */ ) { p->plu->lutspaces(p->plu, ins, inn, outs, outn, pcs); } /* Return information about the overall lookup in/out colorspaces, */ /* including allowance for any PCS override. */ /* Any pointer may be NULL if value is not to be returned */ static void icxLuSpaces( icxLuBase *p, /* This */ icColorSpaceSignature *ins, /* Return input color space */ int *inn, /* Return number of input components */ icColorSpaceSignature *outs, /* Return output color space */ int *outn, /* Return number of output components */ icmLuAlgType *alg, /* Return type of lookup algorithm used */ icRenderingIntent *intt, /* Return the intent implemented */ icmLookupFunc *fnc, /* Return the profile function being implemented */ icColorSpaceSignature *pcs /* Return the effective PCS */ ) { icmLookupFunc function; icColorSpaceSignature npcs; /* Native PCS */ p->plu->spaces(p->plu, NULL, inn, NULL, outn, alg, NULL, &function, &npcs, NULL); if (intt != NULL) *intt = p->intent; if (fnc != NULL) *fnc = function; if (ins != NULL) *ins = p->ins; if (outs != NULL) *outs = p->outs; if (pcs != NULL) *pcs = p->pcs; } /* Return the native (internaly visible) colorspace value ranges */ static void icxLu_get_native_ranges ( icxLuBase *p, double *inmin, double *inmax, /* Return maximum range of inspace values */ double *outmin, double *outmax /* Return maximum range of outspace values */ ) { int i; if (inmin != NULL) { for (i = 0; i < p->inputChan; i++) inmin[i] = p->ninmin[i]; } if (inmax != NULL) { for (i = 0; i < p->inputChan; i++) inmax[i] = p->ninmax[i]; } if (outmin != NULL) { for (i = 0; i < p->outputChan; i++) outmin[i] = p->noutmin[i]; } if (outmax != NULL) { for (i = 0; i < p->outputChan; i++) outmax[i] = p->noutmax[i]; } } /* Return the effective (externaly visible) colorspace value ranges */ static void icxLu_get_ranges ( icxLuBase *p, double *inmin, double *inmax, /* Return maximum range of inspace values */ double *outmin, double *outmax /* Return maximum range of outspace values */ ) { int i; if (inmin != NULL) { for (i = 0; i < p->inputChan; i++) inmin[i] = p->inmin[i]; } if (inmax != NULL) { for (i = 0; i < p->inputChan; i++) inmax[i] = p->inmax[i]; } if (outmin != NULL) { for (i = 0; i < p->outputChan; i++) outmin[i] = p->outmin[i]; } if (outmax != NULL) { for (i = 0; i < p->outputChan; i++) outmax[i] = p->outmax[i]; } } /* - - - - - - - - - - - - - - - - - - - - - - - - - - - */ /* Routine to figure out a suitable black point for CMYK */ /* Structure to hold optimisation information */ typedef struct { icmLuBase *p; int kch; /* K channel, -1 if none */ double tlimit, klimit; /* Ink limit values */ int inn; /* Number of input channels */ icColorSpaceSignature outs; /* Output space */ double p1[3]; /* white pivot point in abs Lab */ double p2[3]; /* Point on vector towards black in abs Lab */ double toll; /* Tollerance of black direction */ } bpfind; /* Optimise device values to minimise L, while remaining */ /* within the ink limit, and staying in line between p1 (white) and p2 (black dir) */ static double bpfindfunc(void *adata, double pv[]) { bpfind *b = (bpfind *)adata; double rv = 0.0; double Lab[3]; double lr, ta, tb, terr; /* L ratio, target a, target b, target error */ double ovr = 0.0; int e; /* Compute amount outside total limit */ if (b->tlimit >= 0.0) { double sum; for (sum = 0.0, e = 0; e < b->inn; e++) sum += pv[e]; if (sum > b->tlimit) { ovr = sum - b->tlimit; #ifdef DEBUG printf("~1 total ink ovr = %f\n",ovr); #endif } } /* Compute amount outside black limit */ if (b->klimit >= 0.0 && b->kch >= 0) { double kval = pv[b->kch] - b->klimit; if (kval > ovr) { ovr = kval; #ifdef DEBUG printf("~1 black ink ovr = %f\n",ovr); #endif } } /* Compute amount outside device value limits 0.0 - 1.0 */ { double dval; for (dval = -1.0, e = 0; e < b->inn; e++) { if (pv[e] < 0.0) { if (-pv[e] > dval) dval = -pv[e]; } else if (pv[e] > 1.0) { if ((pv[e] - 1.0) > dval) dval = pv[e] - 1.0; } } if (dval > ovr) ovr = dval; } /* Compute the Lab value: */ b->p->lookup(b->p, Lab, pv); if (b->outs == icSigXYZData) icmXYZ2Lab(&icmD50, Lab, Lab); #ifdef DEBUG printf("~1 p1 = %f %f %f, p2 = %f %f %f\n",b->p1[0],b->p1[1],b->p1[2],b->p2[0],b->p2[1],b->p2[2]); printf("~1 device value %f %f %f %f, Lab = %f %f %f\n",pv[0],pv[1],pv[2],pv[3],Lab[0],Lab[1],Lab[2]); #endif /* Primary aim is to minimise L value */ rv = Lab[0]; /* See how out of line from p1 to p2 we are */ lr = (Lab[0] - b->p1[0])/(b->p2[0] - b->p1[0]); /* Distance towards p2 from p1 */ ta = lr * (b->p2[1] - b->p1[1]) + b->p1[1]; /* Target a value */ tb = lr * (b->p2[2] - b->p1[2]) + b->p1[2]; /* Target b value */ terr = (ta - Lab[1]) * (ta - Lab[1]) + (tb - Lab[2]) * (tb - Lab[2]); if (terr < b->toll) /* Tollerance error doesn't count until it's over tollerance */ terr = 0.0; #ifdef DEBUG printf("~1 target error %f\n",terr); #endif rv += XICC_BLACK_FIND_ABERR_WEIGHT * terr; /* Make ab match more important than min. L */ #ifdef DEBUG printf("~1 out of range error %f\n",ovr); #endif rv += 200 * ovr; #ifdef DEBUG printf("~1 black find tc ret %f\n",rv); #endif return rv; } /* Try and compute a real black point in XYZ given an iccLu, */ /* and also return the K only black or the normal black if the device doesn't have K */ /* black[] will be unchanged if black cannot be computed. */ /* Note that the black point will be in the space of the Lu */ /* converted to XYZ, so will have the Lu's intent etc. */ /* (Note that this is duplicated in xlut.c set_icxLuLut() !!!) */ static void icxLu_comp_bk_point( icxLuBase *x, int gblk, /* If nz, compute black if possible. */ double *white, /* XYZ Input, used for computing black */ double *black, /* XYZ Input & Output. Set if gblk NZ and can be computed */ double *kblack /* XYZ Output. Looked up if possible or set to black[] otherwise */ ) { icmLuBase *p = x->plu; icmLuBase *op = p; /* Original icmLu, in case we replace p */ icc *icco = p->icp; icmHeader *h = icco->header; icColorSpaceSignature ins, outs; int inn, outn; icmLuAlgType alg; icRenderingIntent intt; icmLookupFunc fnc; icmLookupOrder ord; int kch = -1; double dblack[MAX_CHAN]; /* device black value */ int e; #ifdef DEBUG printf("~1 icxLu_comp_bk_point() called, gblk %d, white = %s, black = %s\n",gblk,icmPdv(3, white),icmPdv(3,black)); #endif /* Default return incoming black as K only black */ kblack[0] = black[0]; kblack[1] = black[1]; kblack[2] = black[2]; /* Get the effective characteristics of the Lu */ p->spaces(p, &ins, &inn, &outs, &outn, &alg, &intt, &fnc, NULL, &ord); if (fnc == icmBwd) { /* Hmm. We've got PCS to device, and we want device to PCS. */ /* Strictly speaking this is a dubious approach, since for a cLut profile */ /* the B2A table could make the effective white and black points */ /* anything it likes, and they don't have to match what the corresponding */ /* A2B table does. In our usage it's probably OK, since we tend */ /* to use colorimetric B2A */ #ifdef DEBUG printf("~1 getting icmFwd\n"); #endif if ((p = icco->get_luobj(icco, icmFwd, intt, ins, ord)) == NULL) error("icxLu_comp_bk_point: assert: getting Fwd Lookup failed!"); p->spaces(p, &ins, &inn, &outs, &outn, &alg, &intt, &fnc, NULL, &ord); } if (outs != icSigXYZData && outs != icSigLabData) { error("icxLu_comp_bk_point: assert: icc Lu output is not XYZ or Lab!, outs = 0x%x, "); } #ifdef DEBUG printf("~1 icxLu_comp_bk_point called for inn = %d, ins = %s\n", inn, icx2str(icmColorSpaceSignature,ins)); #endif switch (ins) { case icSigXYZData: case icSigLabData: case icSigLuvData: case icSigYxyData: #ifdef DEBUG printf("~1 Assuming CIE colorspace black is 0.0\n"); #endif if (gblk) { for (e = 0; e < inn; e++) black[0] = 0.0; } kblack[0] = black[0]; kblack[1] = black[1]; kblack[2] = black[2]; return; case icSigRgbData: #ifdef DEBUG printf("~1 RGB:\n"); #endif for (e = 0; e < inn; e++) dblack[e] = 0.0; break; case icSigGrayData: { /* Could be additive or subtractive */ double dval[1]; double minv[3], maxv[3]; #ifdef DEBUG printf("~1 Gray:\n"); #endif /* Check out 0 and 100% colorant */ dval[0] = 0.0; p->lookup(p, minv, dval); if (outs == icSigXYZData) icmXYZ2Lab(&icmD50, minv, minv); dval[0] = 1.0; p->lookup(p, maxv, dval); if (outs == icSigXYZData) icmXYZ2Lab(&icmD50, maxv, maxv); if (minv[0] < maxv[0]) dblack[0] = 0.0; else dblack[0] = 1.0; } break; case icSigCmyData: for (e = 0; e < inn; e++) dblack[e] = 1.0; break; case icSigCmykData: #ifdef DEBUG printf("~1 CMYK:\n"); #endif kch = 3; dblack[0] = 0.0; dblack[1] = 0.0; dblack[2] = 0.0; dblack[3] = 1.0; if (alg == icmLutType) { icxLuLut *pp = (icxLuLut *)x; if (pp->ink.tlimit >= 0.0) dblack[kch] = pp->ink.tlimit; }; break; /* Use a heursistic. */ /* This duplicates code in icxGetLimits() :-( */ /* Colorant guessing should go in icclib ? */ case icSig2colorData: case icSig3colorData: case icSig4colorData: case icSig5colorData: case icSig6colorData: case icSig7colorData: case icSig8colorData: case icSig9colorData: case icSig10colorData: case icSig11colorData: case icSig12colorData: case icSig13colorData: case icSig14colorData: case icSig15colorData: case icSigMch5Data: case icSigMch6Data: case icSigMch7Data: case icSigMch8Data: { double dval[MAX_CHAN]; double ncval[3]; double cvals[MAX_CHAN][3]; int nlighter, ndarker; /* Decide if the colorspace is additive or subtractive */ #ifdef DEBUG printf("~1 N channel:\n"); #endif /* First the no colorant value */ for (e = 0; e < inn; e++) dval[e] = 0.0; p->lookup(p, ncval, dval); if (outs == icSigXYZData) icmXYZ2Lab(&icmD50, ncval, ncval); /* Then all the colorants */ nlighter = ndarker = 0; for (e = 0; e < inn; e++) { dval[e] = 1.0; p->lookup(p, cvals[e], dval); if (outs == icSigXYZData) icmXYZ2Lab(&icmD50, cvals[e], cvals[e]); dval[e] = 0.0; if (fabs(cvals[e][0] - ncval[0]) > 5.0) { if (cvals[e][0] > ncval[0]) nlighter++; else ndarker++; } } if (ndarker == 0 && nlighter > 0) { /* Assume additive */ for (e = 0; e < inn; e++) dblack[e] = 0.0; #ifdef DEBUG printf("~1 N channel is additive:\n"); #endif } else if (ndarker > 0 && nlighter == 0) { /* Assume subtractive. */ double pbk[3] = { 0.0,0.0,0.0 }; /* Perfect black */ double smd = 1e10; /* Smallest distance */ #ifdef DEBUG printf("~1 N channel is subtractive:\n"); #endif /* See if we can guess the black channel */ for (e = 0; e < inn; e++) { double tt; tt = icmNorm33sq(pbk, cvals[e]); if (tt < smd) { smd = tt; kch = e; } } /* See if the black seems sane */ if (cvals[kch][0] > 40.0 || fabs(cvals[kch][1]) > 10.0 || fabs(cvals[kch][2]) > 10.0) { if (p != op) p->del(p); #ifdef DEBUG printf("~1 black doesn't look sanem so assume nothing\n"); #endif return; /* Assume nothing */ } /* Chosen kch as black */ for (e = 0; e < inn; e++) dblack[e] = 0.0; dblack[kch] = 1.0; if (alg == icmLutType) { icxLuLut *pp = (icxLuLut *)x; if (pp->ink.tlimit >= 0.0) dblack[kch] = pp->ink.tlimit; }; #ifdef DEBUG printf("~1 N channel K = chan %d\n",kch); #endif } else { if (p != op) p->del(p); #ifdef DEBUG printf("~1 can't figure if additive or subtractive, so assume nothing\n"); #endif return; /* Assume nothing */ } } break; default: #ifdef DEBUG printf("~1 unhandled colorspace, so assume nothing\n"); #endif if (p != op) p->del(p); return; /* Don't do anything */ } /* Lookup the K only value */ if (kch >= 0) { p->lookup(p, kblack, dblack); /* We always return XYZ */ if (outs == icSigLabData) icmLab2XYZ(&icmD50, kblack, kblack); } if (gblk == 0) { /* That's all we have to do */ #ifdef DEBUG printf("~1 gblk == 0, so only return kblack\n"); #endif if (p != op) p->del(p); return; } /* Lookup the device black or K only value as a default */ p->lookup(p, black, dblack); /* May be XYZ or Lab */ #ifdef DEBUG printf("~1 Got default lu black %f %f %f, kch = %d\n", black[0],black[1],black[2],kch); #endif /* !!! Hmm. For CMY and RGB we are simply using the device */ /* combination values as the black point. In reality we might */ /* want to have the option of using a neutral black point, */ /* just like CMYK ?? */ if (kch >= 0) { /* The space is subtractive with a K channel. */ /* If XICC_NEUTRAL_CMYK_BLACK then locate the darkest */ /* CMYK within limits with the same chromaticity as the white point, */ /* otherwise locate the device value within the ink limits that is */ /* in the direction of the K channel */ bpfind bfs; /* Callback context */ double sr[MXDO]; /* search radius */ double tt[MXDO]; /* Temporary */ double rs0[MXDO], rs1[MXDO]; /* Random start candidates */ int trial; double brv; /* Setup callback function context */ bfs.p = p; bfs.inn = inn; bfs.outs = outs; bfs.kch = kch; bfs.tlimit = -1.0; bfs.klimit = -1.0; bfs.toll = XICC_BLACK_POINT_TOLL; if (alg == icmLutType) { icxLuLut *pp = (icxLuLut *)x; pp->kch = kch; bfs.tlimit = pp->ink.tlimit; bfs.klimit = pp->ink.klimit; #ifdef DEBUG printf("~1 tlimit = %f, klimit = %f\n",bfs.tlimit,bfs.klimit); #endif }; #ifdef XICC_NEUTRAL_CMYK_BLACK #ifdef DEBUG printf("~1 Searching for neutral black\n"); #endif /* white has been given to us in XYZ */ icmXYZ2Lab(&icmD50, bfs.p1, white); /* pivot Lab */ icmCpy3(bfs.p2, white); /* temp white XYZ */ icmScale3(bfs.p2, bfs.p2, 0.02); /* Scale white XYZ towards 0,0,0 */ icmXYZ2Lab(&icmD50, bfs.p2, bfs.p2); /* Convert black direction to Lab */ #else /* Use K directin black */ #ifdef DEBUG printf("~1 Searching for K direction black\n"); #endif icmXYZ2Lab(&icmD50, bfs.p1, white); /* Pivot */ /* Now figure abs Lab value of K only, as the direction */ /* to use for the rich black. */ for (e = 0; e < inn; e++) dblack[e] = 0.0; if (bfs.klimit < 0.0) dblack[kch] = 1.0; else dblack[kch] = bfs.klimit; /* K value */ p->lookup(p, black, dblack); if (outs == icSigXYZData) { icmXYZ2Lab(&icmD50, bfs.p2, black); /* K direction */ } else { icmAry2Ary(bfs.p2, black); } #endif #ifdef DEBUG printf("~1 Lab pivot %f %f %f, Lab K direction %f %f %f\n",bfs.p1[0],bfs.p1[1],bfs.p1[2],bfs.p2[0],bfs.p2[1],bfs.p2[2]); #endif /* Start with the K only as the current best value */ brv = bpfindfunc((void *)&bfs, dblack); #ifdef DEBUG printf("~1 initial brv for K only = %f\n",brv); #endif /* Set the random start 0 location as 000K */ /* and the random start 1 location as CMY0 */ { double tt; for (e = 0; e < inn; e++) dblack[e] = rs0[e] = 0.0; if (bfs.klimit < 0.0) dblack[kch] = rs0[kch] = 1.0; else dblack[kch] = rs0[kch] = bfs.klimit; /* K value */ if (bfs.tlimit < 0.0) tt = 1.0; else tt = bfs.tlimit/(inn - 1.0); for (e = 0; e < inn; e++) rs1[e] = tt; rs1[kch] = 0.0; /* K value */ } /* Find the device black point using optimization */ /* Do several trials to avoid local minima. */ rand32(0x12345678); /* Make trial values deterministic */ for (trial = 0; trial < 200; trial++) { double rv; /* Temporary */ /* Start first trial at 000K */ if (trial == 0) { for (e = 0; e < inn; e++) { tt[e] = rs0[e]; sr[e] = 0.1; } } else { /* Base is random between 000K and CMY0: */ if (trial < 100) { rv = d_rand(0.0, 1.0); for (e = 0; e < inn; e++) { tt[e] = rv * rs0[e] + (1.0 - rv) * rs1[e]; sr[e] = 0.1; } /* Base on current best */ } else { for (e = 0; e < inn; e++) { tt[e] = dblack[e]; sr[e] = 0.1; } } /* Then add random start offset */ for (rv = 0.0, e = 0; e < inn; e++) { tt[e] += d_rand(-0.5, 0.5); if (tt[e] < 0.0) tt[e] = 0.0; else if (tt[e] > 1.0) tt[e] = 1.0; } } /* Clip black */ if (bfs.klimit >= 0.0 && tt[kch] > bfs.klimit) tt[kch] = bfs.klimit; /* Compute amount outside total limit */ if (bfs.tlimit >= 0.0) { for (rv = 0.0, e = 0; e < inn; e++) rv += tt[e]; if (rv > bfs.tlimit) { rv /= (double)inn; for (e = 0; e < inn; e++) tt[e] -= rv; } } if (powell(&rv, inn, tt, sr, 0.000001, 1000, bpfindfunc, (void *)&bfs, NULL, NULL) == 0) { #ifdef DEBUG printf("~1 trial %d, rv %f bp %f %f %f %f\n",trial,rv,tt[0],tt[1],tt[2],tt[3]); #endif if (rv < brv) { #ifdef DEBUG printf("~1 new best\n"); #endif brv = rv; for (e = 0; e < inn; e++) dblack[e] = tt[e]; } } } if (brv > 1000.0) error("icxLu_comp_bk_point: Black point powell failed"); for (e = 0; e < inn; e++) { /* Make sure device values are in range */ if (dblack[e] < 0.0) dblack[e] = 0.0; else if (dblack[e] > 1.0) dblack[e] = 1.0; } /* Now have device black in dblack[] */ #ifdef DEBUG printf("~1 got device black %f %f %f %f\n",dblack[0], dblack[1], dblack[2], dblack[3]); #endif p->lookup(p, black, dblack); /* Convert to PCS */ } if (p != op) p->del(p); /* We always return XYZ */ if (outs == icSigLabData) icmLab2XYZ(&icmD50, black, black); #ifdef DEBUG printf("~1 returning %f %f %f\n", black[0], black[1], black[2]); #endif return; } /* - - - - - - - - - - - - - - - - - - - - - - - */ /* Return the media white and black points */ /* in the xlu effective PCS colorspace. Pointers may be NULL. */ /* (ie. these will be relative values for relative intent etc.) */ static void icxLuEfv_wh_bk_points( icxLuBase *p, double *wht, double *blk, double *kblk /* K only black */ ) { double white[3], black[3], kblack[3]; /* Get the Lu PCS converted to XYZ icc black and white points in XYZ */ if (p->plu->lu_wh_bk_points(p->plu, white, black)) { /* Black point is assumed. We should determine one instead. */ /* Lookup K only black too */ icxLu_comp_bk_point(p, 1, white, black, kblack); } else { /* Lookup a possible K only black */ icxLu_comp_bk_point(p, 0, white, black, kblack); } //printf("~1 white %f %f %f, black %f %f %f, kblack %f %f %f\n",white[0],white[1],white[2],black[0],black[1],black[2],kblack[0],kblack[1],kblack[2]); /* Convert to possibl xicc override PCS */ switch (p->pcs) { case icSigXYZData: break; /* Don't have to do anyting */ case icSigLabData: icmXYZ2Lab(&icmD50, white, white); /* Convert from XYZ to Lab */ icmXYZ2Lab(&icmD50, black, black); icmXYZ2Lab(&icmD50, kblack, kblack); break; case icxSigJabData: p->cam->XYZ_to_cam(p->cam, white, white); /* Convert from XYZ to Jab */ p->cam->XYZ_to_cam(p->cam, black, black); p->cam->XYZ_to_cam(p->cam, kblack, kblack); break; default: break; } //printf("~1 icxLuEfv_wh_bk_points: pcsor %s White %f %f %f, Black %f %f %f\n", icx2str(icmColorSpaceSignature,p->pcs), white[0], white[1], white[2], black[0], black[1], black[2]); if (wht != NULL) { wht[0] = white[0]; wht[1] = white[1]; wht[2] = white[2]; } if (blk != NULL) { blk[0] = black[0]; blk[1] = black[1]; blk[2] = black[2]; } if (kblk != NULL) { kblk[0] = kblack[0]; kblk[1] = kblack[1]; kblk[2] = kblack[2]; } } /* Create an instance of an xicc object */ xicc *new_xicc( icc *picc /* icc we are expanding */ ) { xicc *p; if ((p = (xicc *) calloc(1,sizeof(xicc))) == NULL) return NULL; p->pp = picc; p->del = xicc_del; p->get_luobj = xicc_get_luobj; p->set_luobj = xicc_set_luobj; p->get_viewcond = xicc_get_viewcond; /* Create an xcal if there is the right tag in the profile */ p->cal = xiccReadCalTag(p->pp); p->nodel_cal = 0; /* We created it, we will delete it */ return p; } /* Do away with the xicc (but not the icc!) */ static void xicc_del( xicc *p ) { if (p->cal != NULL && p->nodel_cal == 0) p->cal->del(p->cal); free (p); } /* Return an expanded lookup object, initialised */ /* from the icc. */ /* Return NULL on error, check errc+err for reason. */ /* Set the pcsor & intent to consistent and values if */ /* Jab and/or icxAppearance has been requested. */ /* Create the underlying icm lookup object that is used */ /* to create and implement the icx one. The icm will be used */ /* to translate from native to effective PCS, unless the */ /* effective PCS is Jab, in which case the icm will be set to */ /* have an effective PCS of XYZ. Since native<->effecive PCS conversion */ /* is done at the to/from_abs() stage, none of this affects the individual */ /* conversion steps, which will all talk the native PCS (unless merged). */ icxLuBase *xicc_get_luobj( xicc *p, /* this */ int flags, /* clip, merge flags */ icmLookupFunc func, /* Functionality */ icRenderingIntent intent, /* Intent */ icColorSpaceSignature pcsor,/* PCS override (0 = def) */ icmLookupOrder order, /* Search Order */ icxViewCond *vc, /* Viewing Condition (may be NULL if pcsor is not CIECAM) */ icxInk *ink /* inking details (NULL for default) */ ) { icmLuBase *plu; icxLuBase *xplu; icmLuAlgType alg; icRenderingIntent n_intent = intent; /* Native Intent to request */ icColorSpaceSignature n_pcs = icmSigDefaultData; /* Native PCS to request */ //printf("~1 xicc_get_luobj got intent %s and pcsor %s\n",icx2str(icmRenderingIntent,intent),icx2str(icmColorSpaceSignature,pcsor)); /* Ensure that appropriate PCS is slected for an appearance intent */ if (intent == icxAppearance || intent == icxAbsAppearance || intent == icxPerceptualAppearance || intent == icxAbsPerceptualAppearance || intent == icxSaturationAppearance || intent == icxAbsSaturationAppearance) { pcsor = icxSigJabData; /* Translate non-Jab intents to the equivalent appearance "intent" if pcsor == Jab. */ /* This is how we get these when the UI's don't list all the apperances intents, */ /* we select the analogous non-apperance intent with pcsor = Jab. */ /* Note that Abs/non-abs selects between Apperance and AbsAppearance. */ } else if (pcsor == icxSigJabData) { if (intent == icRelativeColorimetric) intent = icxAppearance; else if (intent == icAbsoluteColorimetric) intent = icxAbsAppearance; else if (intent == icPerceptual) intent = icxPerceptualAppearance; else if (intent == icmAbsolutePerceptual) intent = icxAbsPerceptualAppearance; else if (intent == icSaturation) intent = icxSaturationAppearance; else if (intent == icmAbsoluteSaturation) intent = icxAbsSaturationAppearance; else intent = icxAppearance; } /* Translate intent asked for into intent needed in icclib */ if (intent == icxAppearance || intent == icxAbsAppearance) n_intent = icAbsoluteColorimetric; else if (intent == icxPerceptualAppearance || intent == icxAbsPerceptualAppearance) n_intent = icmAbsolutePerceptual; else if (intent == icxSaturationAppearance || intent == icxAbsSaturationAppearance) n_intent = icmAbsoluteSaturation; if (pcsor != icmSigDefaultData) n_pcs = pcsor; /* There is an icclib override */ if (pcsor == icxSigJabData) /* xicc override */ n_pcs = icSigXYZData; /* Translate to XYZ */ //printf("~1 xicc_get_luobj processed intent %s and pcsor %s\n",icx2str(icmRenderingIntent,intent),icx2str(icmColorSpaceSignature,pcsor)); //printf("~1 xicc_get_luobj icclib intent %s and pcsor %s\n",icx2str(icmRenderingIntent,n_intent),icx2str(icmColorSpaceSignature,n_pcs)); /* Get icclib lookup object */ if ((plu = p->pp->get_luobj(p->pp, func, n_intent, n_pcs, order)) == NULL) { p->errc = p->pp->errc; /* Copy error */ strcpy(p->err, p->pp->err); return NULL; } /* Figure out what the algorithm is */ plu->spaces(plu, NULL, NULL, NULL, NULL, &alg, NULL, NULL, &n_pcs, NULL); /* make sure its "Abs CAM" */ if (vc!= NULL && (intent == icxAbsAppearance || intent == icxAbsPerceptualAppearance || intent == icxAbsSaturationAppearance)) { /* make sure its "Abs CAM" */ /* Set white point and flare color to D50 */ /* (Hmm. This doesn't match what happens within collink with absolute intent!!) */ vc->Wxyz[0] = icmD50.X/icmD50.Y; vc->Wxyz[1] = icmD50.Y/icmD50.Y; // Normalise white reference to Y = 1 ? vc->Wxyz[2] = icmD50.Z/icmD50.Y; vc->Fxyz[0] = icmD50.X; vc->Fxyz[1] = icmD50.Y; vc->Fxyz[2] = icmD50.Z; } /* Call xiccLu wrapper creation */ switch (alg) { case icmMonoFwdType: xplu = new_icxLuMono(p, flags, plu, func, intent, pcsor, vc, 0); break; case icmMonoBwdType: xplu = new_icxLuMono(p, flags, plu, func, intent, pcsor, vc, 1); break; case icmMatrixFwdType: xplu = new_icxLuMatrix(p, flags, plu, func, intent, pcsor, vc, 0); break; case icmMatrixBwdType: xplu = new_icxLuMatrix(p, flags, plu, func, intent, pcsor, vc, 1); break; case icmLutType: xplu = new_icxLuLut(p, flags, plu, func, intent, pcsor, vc, ink); break; default: xplu = NULL; break; } return xplu; } /* Return an expanded lookup object, initialised */ /* from the icc, and then overwritten by a conversion */ /* created from the supplied scattered data points. */ /* The Lut is assumed to be a device -> native PCS profile. */ /* If the SET_WHITE and/or SET_BLACK flags are set, */ /* discover the white/black point, set it in the icc, */ /* and make the Lut relative to them. */ /* Return NULL on error, check errc+err for reason */ static icxLuBase *xicc_set_luobj( xicc *p, /* this */ icmLookupFunc func, /* Functionality */ icRenderingIntent intent, /* Intent */ icmLookupOrder order, /* Search Order */ int flags, /* white/black point, verbose flags etc. */ int no, /* Number of points */ int nobw, /* Number of points to look for white & black patches in */ cow *points, /* Array of input points in target PCS space */ icxMatrixModel *skm, /* Optional skeleton model (used for input profiles) */ double dispLuminance, /* > 0.0 if display luminance value and is known */ double wpscale, /* > 0.0 if input white point is to be scaled */ double smooth, /* RSPL smoothing factor, -ve if raw */ double avgdev, /* reading Average Deviation as a proportion of the input range */ icxViewCond *vc, /* Viewing Condition (NULL if not using CAM) */ icxInk *ink, /* inking details (NULL for default) */ xcal *cal, /* Optional cal, will override any existing (not deleted with xicc)*/ int quality /* Quality metric, 0..3 */ ) { icmLuBase *plu; icxLuBase *xplu = NULL; icmLuAlgType alg; if (cal != NULL) { if (p->cal != NULL && p->nodel_cal == 0) p->cal->del(p->cal); p->cal = cal; p->nodel_cal = 1; /* We were given it, so don't delete it */ } if (func != icmFwd) { p->errc = 1; sprintf(p->err,"Can only create Device->PCS profiles from scattered data."); xplu = NULL; return xplu; } /* Get icclib lookup object */ if ((plu = p->pp->get_luobj(p->pp, func, intent, 0, order)) == NULL) { p->errc = p->pp->errc; /* Copy error */ strcpy(p->err, p->pp->err); return NULL; } /* Figure out what the algorithm is */ plu->spaces(plu, NULL, NULL, NULL, NULL, &alg, NULL, NULL, NULL, NULL); /* Call xiccLu wrapper creation */ switch (alg) { case icmMonoFwdType: p->errc = 1; sprintf(p->err,"Setting Monochrome Fwd profile from scattered data not supported."); plu->del(plu); xplu = NULL; /* Not supported yet */ break; case icmMatrixFwdType: if (smooth < 0.0) smooth = -smooth; xplu = set_icxLuMatrix(p, plu, flags, no, nobw, points, skm, dispLuminance, wpscale, quality, smooth); break; case icmLutType: /* ~~~ Should add check that it is a fwd profile ~~~ */ xplu = set_icxLuLut(p, plu, func, intent, flags, no, nobw, points, skm, dispLuminance, wpscale, smooth, avgdev, vc, ink, quality); break; default: break; } return xplu; } /* ------------------------------------------------------ */ /* Viewing Condition Parameter stuff */ #ifdef NEVER /* Not currently used */ /* Guess viewing parameters from the technology signature */ static void guess_from_techsig( icTechnologySignature tsig, double *Ybp ) { double Yb = -1.0; switch (tsig) { /* These are all inputing either a representation of */ /* a natural scene captured on another medium, or are assuming */ /* that the medium is the original. A _good_ system would */ /* let the user indicate which is the case. */ case icSigReflectiveScanner: case icSigFilmScanner: Yb = 0.2; break; /* Direct scene to value devices. */ case icSigDigitalCamera: case icSigVideoCamera: Yb = 0.2; break; /* Emmisive displays. */ /* We could try tweaking the white point on the assumption */ /* that the viewer will be adapted to a combination of both */ /* the CRT white point, and the ambient light. */ case icSigVideoMonitor: case icSigCRTDisplay: case icSigPMDisplay: case icSigAMDisplay: Yb = 0.2; break; /* Photo CD has its own viewing definitions */ /* (It represents original scene colors) */ case icSigPhotoCD: Yb = 0.2; break; /* Projection devices, either direct, or */ /* via another intermediate medium. */ case icSigProjectionTelevision: Yb = 0.1; /* Assume darkened room, little background */ break; case icSigFilmWriter: Yb = 0.0; /* Assume a dark room - no background */ break; /* Printed media devices. */ case icSigInkJetPrinter: case icSigThermalWaxPrinter: case icSigElectrophotographicPrinter: case icSigElectrostaticPrinter: case icSigDyeSublimationPrinter: case icSigPhotographicPaperPrinter: case icSigPhotoImageSetter: case icSigGravure: case icSigOffsetLithography: case icSigSilkscreen: case icSigFlexography: Yb = 0.2; break; default: Yb = 0.2; } if (Ybp != NULL) *Ybp = Yb; } #endif /* NEVER */ /* See if we can read or guess the viewing conditions */ /* for an ICC profile. */ /* Return value 0 if it is well defined */ /* Return value 1 if it is a guess */ /* Return value 2 if it is not possible/appropriate */ int xicc_get_viewcond( xicc *p, /* Expanded profile we're working with */ icxViewCond *vc /* Viewing parameters to return */ ) { icc *pp = p->pp; /* Base ICC */ /* Numbers we're trying to find */ ViewingCondition Ev = vc_none; double Wxyz[3] = {-1.0, -1.0, -1.0}; /* Adapting white color */ double La = -1.0; /* Adapting luminance */ double Ixyz[3] = {-1.0, -1.0, -1.0}; /* Illuminant color */ double Li = -1.0; /* Illuminant luminance */ double Lb = -1.0; /* Backgrount luminance */ double Yb = -1.0; /* Background relative luminance to Lv */ double Lve = -1.0; /* Emissive device image luminance */ double Lvr = -1.0; /* Reflective device image luminance */ double Lv = -1.0; /* device image luminance */ double Yf = -1.0; /* Flare relative luminance to Lv */ double Fxyz[3] = {-1.0, -1.0, -1.0}; /* Flare color */ icTechnologySignature tsig = icMaxEnumTechnology; /* Technology Signature */ icProfileClassSignature devc = icMaxEnumClass; int trans = -1; /* Set to 0 if not transparency, 1 if it is */ /* Collect all the information we can find */ /* Emmisive devices image white luminance */ { icmXYZArray *luminanceTag; if ((luminanceTag = (icmXYZArray *)pp->read_tag(pp, icSigLuminanceTag)) != NULL && luminanceTag->ttype == icSigXYZType && luminanceTag->size >= 1) { Lve = luminanceTag->data[0].Y; /* Copy structure */ } } /* Flare: */ { icmMeasurement *ro; if ((ro = (icmMeasurement *)pp->read_tag(pp, icSigMeasurementTag)) != NULL && ro->ttype == icSigMeasurementType) { Yf = ro->flare; /* ro->illuminant ie D50, D65, D93, A etc. */ } } /* Media White Point */ { icmXYZArray *whitePointTag; if ((whitePointTag = (icmXYZArray *)pp->read_tag(pp, icSigMediaWhitePointTag)) != NULL && whitePointTag->ttype == icSigXYZType && whitePointTag->size >= 1) { Wxyz[0] = whitePointTag->data[0].X; Wxyz[1] = whitePointTag->data[0].Y; Wxyz[2] = whitePointTag->data[0].Z; } } /* ViewingConditions: */ { icmViewingConditions *ro; if ((ro = (icmViewingConditions *)pp->read_tag(pp, icSigViewingConditionsTag)) != NULL && ro->ttype == icSigViewingConditionsType) { /* ro->illuminant.X */ /* ro->illuminant.Z */ Li = ro->illuminant.Y; /* Reflect illuminant off the media white */ Lvr = Li * Wxyz[1]; /* Illuminant color */ Ixyz[0] = ro->illuminant.X/ro->illuminant.Y; Ixyz[1] = 1.0; Ixyz[2] = ro->illuminant.Z/ro->illuminant.Y; /* Assume ICC surround is CICAM97 background */ /* ro->surround.X */ /* ro->surround.Z */ La = ro->surround.Y; /* ro->stdIlluminant ie D50, D65, D93, A etc. */ } } /* Stuff we might need */ /* Technology: */ { icmSignature *ro; /* Try and read the tag from the file */ if ((ro = (icmSignature *)pp->read_tag(pp, icSigTechnologyTag)) != NULL && ro->ttype != icSigSignatureType) { tsig = ro->sig; } } devc = pp->header->deviceClass; /* Type of profile */ if (devc == icSigLinkClass || devc == icSigAbstractClass || devc == icSigColorSpaceClass || devc == icSigNamedColorClass) return 2; /* icSigInputClass icSigDisplayClass icSigOutputClass */ if ((pp->header->flags & icTransparency) != 0) trans = 1; else trans = 0; /* figure Lv if we have the information */ if (Lve >= 0.0) Lv = Lve; /* Emmisive image white luminance */ else Lv = Lvr; /* Reflectance image white luminance */ /* Fudge the technology signature */ if (tsig == icMaxEnumTechnology) { if (devc == icSigDisplayClass) tsig = icSigCRTDisplay; } #ifndef NEVER printf("Enumeration = %d\n", Ev); printf("Viewing Conditions:\n"); printf("White adaptation color %f %f %f\n",Wxyz[0], Wxyz[1], Wxyz[2]); printf("Adapting Luminance La = %f\n",La); printf("Illuminant color %f %f %f\n",Ixyz[0], Ixyz[1], Ixyz[2]); printf("Illuminant Luminance Li = %f\n",Li); printf("Background Luminance Lb = %f\n",Lb); printf("Relative Background Yb = %f\n",Yb); printf("Emissive Image White Lve = %f\n",Lve); printf("Reflective Image White Lvr = %f\n",Lvr); printf("Device Image White Lv = %f\n",Lv); printf("Relative Flare Yf = %f\n",Yf); printf("Flare color %f %f %f\n",Fxyz[0], Fxyz[1], Fxyz[2]); printf("Technology = %s\n",tag2str(tsig)); printf("deviceClass = %s\n",tag2str(devc)); printf("Transparency = %d\n",trans); #endif /* See if the viewing conditions are completely defined as ICC can do it */ if (Wxyz[0] >= 0.0 && Wxyz[1] >= 0.0 && Wxyz[2] >= 0.0 && La >= 0.0 && Yb >= 0.0 && Lv >= 0.0 && Yf >= 0.0 && Fxyz[0] >= 0.0 && Fxyz[1] >= 0.0 && Fxyz[2] >= 0.0) { vc->Ev = vc_none; vc->Wxyz[0] = Wxyz[0]; vc->Wxyz[1] = Wxyz[1]; vc->Wxyz[2] = Wxyz[2]; vc->La = La; vc->Yb = Yb; vc->Lv = Lv; vc->Yf = Yf; vc->Fxyz[0] = Fxyz[0]; vc->Fxyz[1] = Fxyz[1]; vc->Fxyz[2] = Fxyz[2]; return 0; } /* Hmm. We didn't get all the info an ICC can contain. */ /* We will try to guess some reasonable defaults */ /* Have we at least got an adaptation white point ? */ if (Wxyz[0] < 0.0 || Wxyz[1] < 0.0 || Wxyz[2] < 0.0) return 2; /* No */ /* Have we got the technology ? */ if (tsig == icMaxEnumTechnology) return 2; /* Hopeless */ /* Guess from the technology */ switch (tsig) { /* This is inputing either a representation of */ /* a natural scene captured on another a print medium, or */ /* are is assuming that the medium is the original. */ /* We will assume that the print is the original. */ case icSigReflectiveScanner: { if (La < 0.0) /* No adapting luminance */ La = 34.0; /* Use a practical print evaluation number */ if (Yb < 0.0) /* No background relative luminance */ Yb = 0.2; /* Assume grey world */ if (Lv < 0.0) /* No device image luminance */ Ev = vc_average; /* Assume average viewing conditions */ if (Yf < 0.0) /* No flare figure */ Yf = 0.01; /* Assume 1% flare */ if (Fxyz[0] < 0.0 || Fxyz[1] < 0.0 || Fxyz[2] < 0.0) /* No flare color */ Fxyz[0] = Wxyz[0], Fxyz[1] = Wxyz[1], Fxyz[2] = Wxyz[2]; break; } /* This is inputing either a representation of */ /* a natural scene captured on another a photo medium, or */ /* are is assuming that the medium is the original. */ /* We will assume a compromise media original, natural scene */ case icSigFilmScanner: { if (La < 0.0) /* No adapting luminance */ La = 50.0; /* Use bright indoors, dull outdoors */ if (Yb < 0.0) /* No background relative luminance */ Yb = 0.2; /* Assume grey world */ if (Lv < 0.0) /* No device image luminance */ Ev = vc_average; /* Assume average viewing conditions */ if (Yf < 0.0) /* No flare figure */ Yf = 0.005; /* Assume 0.5% flare */ if (Fxyz[0] < 0.0 || Fxyz[1] < 0.0 || Fxyz[2] < 0.0) /* No flare color */ Fxyz[0] = Wxyz[0], Fxyz[1] = Wxyz[1], Fxyz[2] = Wxyz[2]; break; } /* Direct scene to value devices. */ case icSigDigitalCamera: case icSigVideoCamera: { if (La < 0.0) /* No adapting luminance */ La = 110.0; /* Use very bright indoors, usual outdoors */ if (Yb < 0.0) /* No background relative luminance */ Yb = 0.2; /* Assume grey world */ if (Lv < 0.0) /* No device image luminance */ Ev = vc_average; /* Assume average viewing conditions */ if (Yf < 0.0) /* No flare figure */ Yf = 0.0; /* Assume 0% flare */ if (Fxyz[0] < 0.0 || Fxyz[1] < 0.0 || Fxyz[2] < 0.0) /* No flare color */ Fxyz[0] = Wxyz[0], Fxyz[1] = Wxyz[1], Fxyz[2] = Wxyz[2]; break; } /* Emmisive displays. */ /* Assume a video monitor is in a darker environment than a CRT */ case icSigVideoMonitor: { if (La < 0.0) /* No adapting luminance */ La = 4.0; /* Darkened work environment */ if (Yb < 0.0) /* No background relative luminance */ Yb = 0.2; /* Assume grey world */ if (Lv < 0.0) /* No device image luminance */ Ev = vc_dim; /* Assume dim viewing conditions */ if (Yf < 0.0) /* No flare figure */ Yf = 0.01; /* Assume 1% flare */ if (Fxyz[0] < 0.0 || Fxyz[1] < 0.0 || Fxyz[2] < 0.0) /* No flare color */ Fxyz[0] = Wxyz[0], Fxyz[1] = Wxyz[1], Fxyz[2] = Wxyz[2]; break; } /* Assume a typical work environment */ case icSigCRTDisplay: case icSigPMDisplay: case icSigAMDisplay: { if (La < 0.0) /* No adapting luminance */ La = 33.0; /* Typical work environment */ if (Yb < 0.0) /* No background relative luminance */ Yb = 0.2; /* Assume grey world */ if (Lv < 0.0) /* No device image luminance */ Ev = vc_average; /* Assume average viewing conditions */ if (Yf < 0.0) /* No flare figure */ Yf = 0.02; /* Assume 2% flare */ if (Fxyz[0] < 0.0 || Fxyz[1] < 0.0 || Fxyz[2] < 0.0) /* No flare color */ Fxyz[0] = Wxyz[0], Fxyz[1] = Wxyz[1], Fxyz[2] = Wxyz[2]; break; } /* Photo CD has its own viewing definitions */ /* (It represents original scene colors) */ case icSigPhotoCD: { if (La < 0.0) /* No adapting luminance */ La = 320.0; /* Bright outdoors */ if (Yb < 0.0) /* No background relative luminance */ Yb = 0.2; /* Assume grey world */ if (Lv < 0.0) /* No device image luminance */ Ev = vc_average; /* Assume average viewing conditions */ if (Yf < 0.0) /* No flare figure */ Yf = 0.00; /* Assume 0% flare */ if (Fxyz[0] < 0.0 || Fxyz[1] < 0.0 || Fxyz[2] < 0.0) /* No flare color */ Fxyz[0] = Wxyz[0], Fxyz[1] = Wxyz[1], Fxyz[2] = Wxyz[2]; break; } /* Projection devices, either direct, or */ /* via another intermediate medium. */ /* Assume darkened room, little background */ case icSigProjectionTelevision: { if (La < 0.0) /* No adapting luminance */ La = 7.0; /* Dark environment */ if (Yb < 0.0) /* No background relative luminance */ Yb = 0.1; /* Assume little background */ if (Lv < 0.0) /* No device image luminance */ Ev = vc_dim; /* Dim environment */ if (Yf < 0.0) /* No flare figure */ Yf = 0.01; /* Assume 1% flare */ if (Fxyz[0] < 0.0 || Fxyz[1] < 0.0 || Fxyz[2] < 0.0) /* No flare color */ Fxyz[0] = Wxyz[0], Fxyz[1] = Wxyz[1], Fxyz[2] = Wxyz[2]; break; } /* Assume very darkened room, no background */ case icSigFilmWriter: { if (La < 0.0) /* No adapting luminance */ La = 7.0; /* Dark environment */ if (Yb < 0.0) /* No background relative luminance */ Yb = 0.0; /* Assume no background */ if (Lv < 0.0) /* No device image luminance */ Ev = vc_dark; /* Dark environment */ if (Yf < 0.0) /* No flare figure */ Yf = 0.01; /* Assume 1% flare */ if (Fxyz[0] < 0.0 || Fxyz[1] < 0.0 || Fxyz[2] < 0.0) /* No flare color */ Fxyz[0] = Wxyz[0], Fxyz[1] = Wxyz[1], Fxyz[2] = Wxyz[2]; break; } /* Printed media devices. */ /* Assume a normal print viewing environment */ case icSigInkJetPrinter: case icSigThermalWaxPrinter: case icSigElectrophotographicPrinter: case icSigElectrostaticPrinter: case icSigDyeSublimationPrinter: case icSigPhotographicPaperPrinter: case icSigPhotoImageSetter: case icSigGravure: case icSigOffsetLithography: case icSigSilkscreen: case icSigFlexography: { if (La < 0.0) /* No adapting luminance */ La = 40.0; /* Use a practical print evaluation number */ if (Yb < 0.0) /* No background relative luminance */ Yb = 0.2; /* Assume grey world */ if (Lv < 0.0) /* No device image luminance */ Ev = vc_average; /* Assume average viewing conditions */ if (Yf < 0.0) /* No flare figure */ Yf = 0.01; /* Assume 1% flare */ if (Fxyz[0] < 0.0 || Fxyz[1] < 0.0 || Fxyz[2] < 0.0) /* No flare color */ Fxyz[0] = Wxyz[0], Fxyz[1] = Wxyz[1], Fxyz[2] = Wxyz[2]; break; } default: { return 2; } } return 1; } /* Write our viewing conditions to the underlying ICC profile, */ /* using a private tag. */ void xicc_set_viewcond( xicc *p, /* Expanded profile we're working with */ icxViewCond *vc /* Viewing parameters to return */ ) { //icc *pp = p->pp; /* Base ICC */ // ~~1 Not implemented yet } /* Return an enumerated viewing condition */ /* Return enumeration if OK, -999 if there is no such enumeration. */ /* xicc may be NULL if just the description is wanted, */ /* or an explicit white point is provided. */ int xicc_enum_viewcond( xicc *p, /* Expanded profile to get white point (May be NULL if desc NZ) */ icxViewCond *vc, /* Viewing parameters to return, May be NULL if desc is nz */ int no, /* Enumeration to return, -1 for default, -2 for none */ char *as, /* String alias to number, NULL if none */ int desc, /* NZ - Just return a description of this enumeration in vc */ double *wp /* Provide white point if xicc is NULL */ ) { if (desc == 0) { /* We're setting the viewing condition */ icc *pp; /* Base ICC */ icmXYZArray *whitePointTag; if (vc == NULL) return -999; if (p == NULL) { if (wp == NULL) return -999; vc->Wxyz[0] = wp[0]; vc->Wxyz[1] = wp[1]; vc->Wxyz[2] = wp[2]; } else { pp = p->pp; if ((whitePointTag = (icmXYZArray *)pp->read_tag(pp, icSigMediaWhitePointTag)) != NULL && whitePointTag->ttype == icSigXYZType && whitePointTag->size >= 1) { vc->Wxyz[0] = whitePointTag->data[0].X; vc->Wxyz[1] = whitePointTag->data[0].Y; vc->Wxyz[2] = whitePointTag->data[0].Z; } else { if (wp == NULL) { sprintf(p->err,"Enum VC: Failed to read Media White point"); p->errc = 2; return -999; } vc->Wxyz[0] = wp[0]; vc->Wxyz[1] = wp[1]; vc->Wxyz[2] = wp[2]; } } /* Set a default flare color */ vc->Fxyz[0] = vc->Wxyz[0]; vc->Fxyz[1] = vc->Wxyz[1]; vc->Fxyz[2] = vc->Wxyz[2]; } /* Typical adapting field luminances and white luminance in reflective setup: E = illuminance in Lux Lv = White luminance assuming 100% reflectance La = Adapting field luminance in cd/m^2, assuming 20% reflectance from surround E La Lv Condition 11 0.7 4 Twilight 32 2 10 Subdued indoor lighting 64 4 20 Less than typical office light; sometimes recommended for display-only workplaces (sRGB) 350 22 111 Typical Office (sRGB annex D) 500 32 160 Practical print evaluationa (ISO-3664 P2) 1000 64 318 Good Print evaluation (CIE 116-1995) 1000 64 318 Television Studio lighting 1000 64 318 Overcast Outdoors 2000 127 637 Critical print evaluation (ISO-3664 P1) 10000 637 3183 Typical outdoors, full daylight 50000 3185 15915 Bright summers day */ if (no == -1 || (as != NULL && stricmp(as,"d") == 0)) { no = -1; if (vc != NULL) { vc->desc = " d - Default Viewing Condition"; vc->Ev = vc_average; /* Average viewing conditions */ vc->La = 50.0; /* Practical to Good lighting */ vc->Lv = 250.0; /* Average viewing conditions ratio */ vc->Yb = 0.2; /* Grey world */ vc->Yf = 0.01; /* 1% flare */ } } else if (no == 0 || (as != NULL && stricmp(as,"pp") == 0)) { no = 0; if (vc != NULL) { vc->desc = " pp - Practical Reflection Print (ISO-3664 P2)"; vc->Ev = vc_average; /* Average viewing conditions */ vc->La = 32.0; /* Use a practical print evaluation number */ vc->Yb = 0.2; /* Grey world */ vc->Yf = 0.01; /* 1% flare */ } } else if (no == 1 || (as != NULL && stricmp(as,"pe") == 0)) { no = 1; if (vc != NULL) { vc->desc = " pe - Print evaluation environment (CIE 116-1995)"; vc->Ev = vc_average; /* Average viewing conditions */ vc->La = 64.0; /* Good */ vc->Yb = 0.2; /* Grey world */ vc->Yf = 0.01; /* 1% flare */ } } else if (no == 2 || (as != NULL && stricmp(as,"pc") == 0)) { no = 2; if (vc != NULL) { vc->desc = " pc - Critical print evaluation environment (ISO-3664 P1)"; vc->Ev = vc_average; /* Average viewing conditions */ vc->La = 127.0; /* Critical */ vc->Yb = 0.2; /* Grey world */ vc->Yf = 0.01; /* 1% flare */ } } else if (no == 3 || (as != NULL && stricmp(as,"mt") == 0)) { no = 3; if (vc != NULL) { vc->desc = " mt - Monitor in typical work environment"; vc->Ev = vc_average; /* Average viewing conditions */ vc->La = 22.0; /* Typical work environment */ vc->Yb = 0.2; /* Grey world */ vc->Yf = 0.02; /* 2% flare */ } } else if (no == 4 || (as != NULL && stricmp(as,"mb") == 0)) { no = 4; if (vc != NULL) { vc->desc = " mb - Bright monitor in bright work environment"; vc->Ev = vc_average; /* Average viewing conditions */ vc->La = 42.0; /* Bright work environment */ vc->Yb = 0.2; /* Grey world */ vc->Yf = 0.02; /* 2% flare */ } } else if (no == 5 || (as != NULL && stricmp(as,"md") == 0)) { no = 5; if (vc != NULL) { vc->desc = " md - Monitor in darkened work environment"; vc->Ev = vc_dim; /* Dim viewing conditions */ vc->La = 4.0; /* Darkened work environment */ vc->Yb = 0.2; /* Grey world */ vc->Yf = 0.01; /* 1% flare */ } } else if (no == 6 || (as != NULL && stricmp(as,"jm") == 0)) { no = 6; if (vc != NULL) { vc->desc = " jm - Projector in dim environment"; vc->Ev = vc_dim; /* Dim viewing conditions */ vc->La = 10.0; /* Adaptation is from display */ vc->Yb = 0.2; /* Grey world */ vc->Yf = 0.01; /* 1% flare */ } } else if (no == 7 || (as != NULL && stricmp(as,"jd") == 0)) { no = 7; if (vc != NULL) { vc->desc = " jd - Projector in dark environment"; vc->Ev = vc_dark; /* Dark viewing conditions */ vc->La = 10.0; /* Adaptation is from display */ vc->Yb = 0.2; /* Grey world */ vc->Yf = 0.01; /* 1% flare ? */ } } else if (no == 8 || (as != NULL && stricmp(as,"pcd") == 0)) { no = 8; if (vc != NULL) { vc->desc = "pcd - Photo CD - original scene outdoors"; vc->Ev = vc_average; /* Average viewing conditions */ vc->La = 320.0; /* Typical outdoors, 1600 cd/m^2 */ vc->Yb = 0.2; /* Grey world */ vc->Yf = 0.00; /* 0% flare */ } } else if (no == 9 || (as != NULL && stricmp(as,"ob") == 0)) { no = 9; if (vc != NULL) { vc->desc = " ob - Original scene - Bright Outdoors"; vc->Ev = vc_average; /* Average viewing conditions */ vc->La = 2000.0; /* Bright Outdoors */ vc->Yb = 0.2; /* Grey world */ vc->Yf = 0.00; /* 0% flare */ } } else if (no == 10 || (as != NULL && stricmp(as,"cx") == 0)) { no = 10; if (vc != NULL) { vc->desc = " cx - Cut Sheet Transparencies on a viewing box"; vc->Ev = vc_cut_sheet; /* Cut sheet viewing conditions */ vc->La = 53.0; /* Dim, adapted to slide ? */ vc->Yb = 0.2; /* Grey world */ vc->Yf = 0.01; /* 1% flare ? */ } } else { if (p != NULL) { sprintf(p->err,"Enum VC: Unrecognised enumeration %d",no); p->errc = 1; } return -999; } return no; } /* Debug: dump a Viewing Condition to standard out */ void xicc_dump_viewcond( icxViewCond *vc ) { printf("Viewing Condition:\n"); if (vc->Ev == vc_dark) printf(" Surround to Image: Dark\n"); else if (vc->Ev == vc_dim) printf(" Surround to Image: Dim\n"); else if (vc->Ev == vc_average) printf(" Surround to Image: Average\n"); else if (vc->Ev == vc_cut_sheet) printf(" Transparency on Light box\n"); printf(" Adapted white = %f %f %f\n",vc->Wxyz[0], vc->Wxyz[1], vc->Wxyz[2]); printf(" Adapted luminance = %f cd/m^2\n",vc->La); printf(" Background to image ratio = %f\n",vc->Yb); if (vc->Ev == vc_none) printf(" Image luminance = %f cd/m^2\n",vc->Lv); printf(" Flare to image ratio = %f\n",vc->Yf); printf(" Flare color = %f %f %f\n",vc->Fxyz[0], vc->Fxyz[1], vc->Fxyz[2]); } /* Debug: dump an Inking setup to standard out */ void xicc_dump_inking(icxInk *ik) { printf("Inking settings:\n"); if (ik->tlimit < 0.0) printf("No total limit\n"); else printf("Total limit = %f%%\n",ik->tlimit * 100.0); if (ik->klimit < 0.0) printf("No black limit\n"); else printf("Black limit = %f%%\n",ik->klimit * 100.0); if (ik->KonlyLmin) printf("K only black as locus Lmin\n"); else printf("Normal black as locus Lmin\n"); if (ik->k_rule == icxKvalue) { printf("Inking rule is a fixed K target\n"); } if (ik->k_rule == icxKlocus) { printf("Inking rule is a fixed locus target\n"); } if (ik->k_rule == icxKluma5 || ik->k_rule == icxKluma5k) { if (ik->k_rule == icxKluma5) printf("Inking rule is a 5 parameter locus function of L\n"); else printf("Inking rule is a 5 parameter K function of L\n"); printf("Ksmth = %f\n",ik->c.Ksmth); printf("Kskew = %f\n",ik->c.Kskew); printf("Kstle = %f\n",ik->c.Kstle); printf("Kstpo = %f\n",ik->c.Kstpo); printf("Kenpo = %f\n",ik->c.Kenpo); printf("Kenle = %f\n",ik->c.Kenle); printf("Kshap = %f\n",ik->c.Kshap); } if (ik->k_rule == icxKl5l || ik->k_rule == icxKl5lk) { if (ik->k_rule == icxKl5l) printf("Inking rule is a 2x5 parameter locus function of L and K aux\n"); else printf("Inking rule is a 2x5 parameter K function of L and K aux\n"); printf("Min Ksmth = %f\n",ik->c.Ksmth); printf("Min Kskew = %f\n",ik->c.Kskew); printf("Min Kstle = %f\n",ik->c.Kstle); printf("Min Kstpo = %f\n",ik->c.Kstpo); printf("Min Kenpo = %f\n",ik->c.Kenpo); printf("Min Kenle = %f\n",ik->c.Kenle); printf("Min Kshap = %f\n",ik->c.Kshap); printf("Max Ksmth = %f\n",ik->x.Ksmth); printf("Max Kskew = %f\n",ik->x.Kskew); printf("Max Kstle = %f\n",ik->x.Kstle); printf("Max Kstpo = %f\n",ik->x.Kstpo); printf("Max Kenpo = %f\n",ik->x.Kenpo); printf("Max Kenle = %f\n",ik->x.Kenle); printf("Max Kshap = %f\n",ik->x.Kshap); } } /* ------------------------------------------------------ */ /* Gamut Mapping Intent stuff */ /* Return an enumerated gamut mapping intent */ /* Return enumeration if OK, icxIllegalGMIntent if there is no such enumeration. */ int xicc_enum_gmapintent( icxGMappingIntent *gmi, /* Gamut Mapping parameters to return */ int no, /* Enumeration selected, icxNoGMIntent for none */ char *as /* Alias string selector, NULL for none */ ) { #ifdef USE_CAM int colccas = 0x2; /* Use cas clipping for colorimetric style intents */ int perccas = 0x1; /* Use cas for perceptual style intents */ #else int colccas = 0x0; /* Use Lab for colorimetric style intents */ int perccas = 0x0; /* Use Lab for perceptual style intents */ fprintf(stderr,"!!!!!! Warning, USE_CAM is off in xicc.c !!!!!!\n"); #endif /* Assert default if no guidance given */ if (no == icxNoGMIntent && as == NULL) no = icxDefaultGMIntent; if (no == 0 || no == icxAbsoluteGMIntent || (as != NULL && stricmp(as,"a") == 0)) { /* Map Absolute appearance space Jab to Jab and clip out of gamut */ no = 0; gmi->as = "a"; gmi->desc = " a - Absolute Colorimetric (in Jab) [ICC Absolute Colorimetric]"; gmi->icci = icAbsoluteColorimetric; gmi->usecas = colccas; /* Use absolute appearance space */ gmi->usemap = 0; /* Don't use gamut mapping */ gmi->greymf = 0.0; gmi->glumwcpf = 0.0; gmi->glumwexf = 0.0; gmi->glumbcpf = 0.0; gmi->glumbexf = 0.0; gmi->glumknf = 0.0; gmi->gamcpf = 0.0; gmi->gamexf = 0.0; gmi->gamcknf = 0.0; gmi->gamxknf = 0.0; gmi->gampwf = 0.0; gmi->gamswf = 0.0; gmi->satenh = 0.0; /* No saturation enhancement */ } else if (no == 1 || (as != NULL && stricmp(as,"aw") == 0)) { /* I'm not sure how often this intent is useful. It's less likely than */ /* I though that a printer white point won't fit within the gamut */ /* of a display profile, since the display white always has Y = 1.0, */ /* and no paper has better than about 95% reflectance. */ /* Perhaps it may be more useful for targeting printer profiles ? */ /* Map Absolute Jab to Jab and scale source to avoid clipping the white point */ no = 1; gmi->as = "aw"; gmi->desc = "aw - Absolute Colorimetric (in Jab) with scaling to fit white point"; gmi->icci = icAbsoluteColorimetric; gmi->usecas = 0x100 | colccas; /* Absolute Appearance space with scaling */ /* to avoid clipping the source white point */ gmi->usemap = 0; /* Don't use gamut mapping */ gmi->greymf = 0.0; gmi->glumwcpf = 0.0; gmi->glumwexf = 0.0; gmi->glumbcpf = 0.0; gmi->glumbexf = 0.0; gmi->glumknf = 0.0; gmi->gamcpf = 0.0; gmi->gamexf = 0.0; gmi->gamcknf = 0.0; gmi->gamxknf = 0.0; gmi->gampwf = 0.0; gmi->gamswf = 0.0; gmi->satenh = 0.0; /* No saturation enhancement */ } else if (no == 2 || (as != NULL && stricmp(as,"aa") == 0)) { /* Map appearance space Jab to Jab and clip out of gamut */ no = 2; gmi->as = "aa"; gmi->desc = "aa - Absolute Appearance"; gmi->icci = icRelativeColorimetric; gmi->usecas = perccas; /* Appearance space */ gmi->usemap = 0; /* Don't use gamut mapping */ gmi->greymf = 0.0; gmi->glumwcpf = 0.0; gmi->glumwexf = 0.0; gmi->glumbcpf = 0.0; gmi->glumbexf = 0.0; gmi->glumknf = 0.0; gmi->gamcpf = 0.0; gmi->gamexf = 0.0; gmi->gamcknf = 0.0; gmi->gamxknf = 0.0; gmi->gampwf = 0.0; gmi->gamswf = 0.0; gmi->satenh = 0.0; /* No saturation enhancement */ } else if (no == 3 || no == icxRelativeGMIntent || (as != NULL && stricmp(as,"r") == 0)) { /* Align neutral axes and linearly map white point, then */ /* map appearance space Jab to Jab and clip out of gamut */ no = 3; gmi->as = "r"; gmi->desc = " r - White Point Matched Appearance [ICC Relative Colorimetric]"; gmi->icci = icRelativeColorimetric; gmi->usecas = perccas; /* Appearance space */ gmi->usemap = 1; /* Use gamut mapping */ gmi->greymf = 1.0; /* Fully align grey axis */ gmi->glumwcpf = 1.0; /* Fully compress grey axis at white end */ gmi->glumwexf = 1.0; /* Fully expand grey axis at white end */ gmi->glumbcpf = 0.0; /* No compression at black end */ gmi->glumbexf = 0.0; /* No expansion at black end */ gmi->glumknf = 0.0; gmi->gamcpf = 0.0; gmi->gamexf = 0.0; gmi->gamcknf = 0.0; gmi->gamxknf = 0.0; gmi->gampwf = 0.0; gmi->gamswf = 0.0; gmi->satenh = 0.0; /* No saturation enhancement */ } else if (no == 4 || (as != NULL && stricmp(as,"la") == 0)) { /* Align neutral axes and linearly map white and black points, then */ /* map appearance space Jab to Jab and clip out of gamut */ no = 4; gmi->as = "la"; gmi->desc = "la - Luminance axis matched Appearance"; gmi->icci = icRelativeColorimetric; gmi->usecas = perccas; /* Appearance space */ gmi->usemap = 1; /* Use gamut mapping */ gmi->greymf = 1.0; /* Fully align grey axis */ gmi->glumwcpf = 1.0; /* Fully compress grey axis at white end */ gmi->glumwexf = 1.0; /* Fully expand grey axis at white end */ gmi->glumbcpf = 1.0; /* Fully compress grey axis at black end */ gmi->glumbexf = 1.0; /* Fully expand grey axis at black end */ gmi->glumknf = 0.0; /* No knee on grey mapping */ gmi->gamcpf = 0.0; /* No gamut compression */ gmi->gamexf = 0.0; /* No gamut expansion */ gmi->gamcknf = 0.0; /* No knee in gamut compress */ gmi->gamxknf = 0.0; /* No knee in gamut expand */ gmi->gampwf = 0.0; /* No Perceptual surface weighting factor */ gmi->gamswf = 0.0; /* No Saturation surface weighting factor */ gmi->satenh = 0.0; /* No saturation enhancement */ } else if (no == 5 || no == icxDefaultGMIntent || no == icxPerceptualGMIntent || (as != NULL && stricmp(as,"p") == 0)) { /* Align neutral axes and perceptually map white and black points, */ /* perceptually compress out of gamut and map appearance space Jab to Jab. */ no = 5; gmi->as = "p"; gmi->desc = " p - Perceptual (Preferred) (Default) [ICC Perceptual]"; gmi->icci = icPerceptual; gmi->usecas = perccas; /* Appearance space */ gmi->usemap = 1; /* Use gamut mapping */ gmi->greymf = 1.0; /* Fully align grey axis */ gmi->glumwcpf = 1.0; /* Fully compress grey axis at white end */ gmi->glumwexf = 1.0; /* Fully expand grey axis at white end */ gmi->glumbcpf = 1.0; /* Fully compress grey axis at black end */ gmi->glumbexf = 1.0; /* Fully expand grey axis at black end */ gmi->glumknf = 1.0; /* Sigma knee in grey compress/expand */ gmi->gamcpf = 1.0; /* Full gamut compression */ gmi->gamexf = 0.0; /* No gamut expansion */ gmi->gamcknf = 0.8; /* High Sigma knee in gamut compress */ gmi->gamxknf = 0.0; /* No knee in gamut expand */ gmi->gampwf = 1.0; /* Full Perceptual surface weighting factor */ gmi->gamswf = 0.0; /* No Saturation surface weighting factor */ gmi->satenh = 0.0; /* No saturation enhancement */ } else if (no == 6 || (as != NULL && stricmp(as,"pa") == 0)) { /* Don't align neutral axes, but perceptually compress out of gamut */ /* and map appearance space Jab to Jab. */ no = 5; gmi->as = "pa"; gmi->desc = "pa - Perceptual Apperance "; gmi->icci = icPerceptual; gmi->usecas = perccas; /* Appearance space */ gmi->usemap = 1; /* Use gamut mapping */ gmi->greymf = 0.0; /* Don't align grey axis */ gmi->glumwcpf = 1.0; /* Fully compress grey axis at white end */ gmi->glumwexf = 1.0; /* Fully expand grey axis at white end */ gmi->glumbcpf = 1.0; /* Fully compress grey axis at black end */ gmi->glumbexf = 1.0; /* Fully expand grey axis at black end */ gmi->glumknf = 1.0; /* Sigma knee in grey compress/expand */ gmi->gamcpf = 1.0; /* Full gamut compression */ gmi->gamexf = 0.0; /* No gamut expansion */ gmi->gamcknf = 0.8; /* High Sigma knee in gamut compress */ gmi->gamxknf = 0.0; /* No knee in gamut expand */ gmi->gampwf = 1.0; /* Full Perceptual surface weighting factor */ gmi->gamswf = 0.0; /* No Saturation surface weighting factor */ gmi->satenh = 0.0; /* No saturation enhancement */ } else if (no == 7 || (as != NULL && stricmp(as,"ms") == 0)) { /* Align neutral axes and perceptually map white and black points, */ /* perceptually compress and expand to match gamuts and map Jab to Jab. */ no = 6; gmi->as = "ms"; gmi->desc = "ms - Saturation"; gmi->icci = icSaturation; gmi->usecas = perccas; /* Appearance space */ gmi->usemap = 1; /* Use gamut mapping */ gmi->greymf = 1.0; /* Fully align grey axis */ gmi->glumwcpf = 1.0; /* Fully compress grey axis at white end */ gmi->glumwexf = 1.0; /* Fully expand grey axis at white end */ gmi->glumbcpf = 1.0; /* Fully compress grey axis at black end */ gmi->glumbexf = 1.0; /* Fully expand grey axis at black end */ gmi->glumknf = 1.0; /* Sigma knee in grey compress/expand */ gmi->gamcpf = 1.0; /* Full gamut compression */ gmi->gamexf = 1.0; /* Full gamut expansion */ gmi->gamcknf = 1.0; /* High Sigma knee in gamut compress/expand */ gmi->gamxknf = 0.4; /* Moderate Sigma knee in gamut compress/expand */ gmi->gampwf = 0.2; /* Slight perceptual surface weighting factor */ gmi->gamswf = 0.8; /* Most saturation surface weighting factor */ gmi->satenh = 0.0; /* No saturation enhancement */ } else if (no == 8 || no == icxSaturationGMIntent || (as != NULL && stricmp(as,"s") == 0)) { /* Same as "ms" but enhance saturation */ no = 7; gmi->as = "s"; gmi->desc = " s - Enhanced Saturation [ICC Saturation]"; gmi->icci = icSaturation; gmi->usecas = perccas; /* Appearance space */ gmi->usemap = 1; /* Use gamut mapping */ gmi->greymf = 1.0; /* Fully align grey axis */ gmi->glumwcpf = 1.0; /* Fully compress grey axis at white end */ gmi->glumwexf = 1.0; /* Fully expand grey axis at white end */ gmi->glumbcpf = 1.0; /* Fully compress grey axis at black end */ gmi->glumbexf = 1.0; /* Fully expand grey axis at black end */ gmi->glumknf = 1.0; /* Sigma knee in grey compress/expand */ gmi->gamcpf = 1.0; /* Full gamut compression */ gmi->gamexf = 1.0; /* Full gamut expansion */ gmi->gamcknf = 1.0; /* High sigma knee in gamut compress */ gmi->gamxknf = 0.5; /* Moderate sigma knee in gamut expand */ gmi->gampwf = 0.0; /* No Perceptual surface weighting factor */ gmi->gamswf = 1.0; /* Full Saturation surface weighting factor */ gmi->satenh = 0.9; /* Medium saturation enhancement */ } else if (no == 9 || (as != NULL && stricmp(as,"al") == 0)) { /* Map absolute L*a*b* to L*a*b* and clip out of gamut */ no = 8; gmi->as = "al"; gmi->desc = "al - Absolute Colorimetric (Lab)"; gmi->icci = icAbsoluteColorimetric; gmi->usecas = 0x0; /* Don't use appearance space, use L*a*b* */ gmi->usemap = 0; /* Don't use gamut mapping */ gmi->greymf = 0.0; gmi->glumwcpf = 0.0; gmi->glumwexf = 0.0; gmi->glumbcpf = 0.0; gmi->glumbexf = 0.0; gmi->glumknf = 0.0; gmi->gamcpf = 0.0; gmi->gamexf = 0.0; gmi->gamcknf = 0.0; gmi->gamxknf = 0.0; gmi->gampwf = 0.0; gmi->gamswf = 0.0; gmi->satenh = 0.0; /* No saturation enhancement */ } else if (no == 10 || (as != NULL && stricmp(as,"rl") == 0)) { /* Align neutral axes and linearly map white point, then */ /* map L*a*b* to L*a*b* and clip out of gamut */ no = 3; gmi->as = "rl"; gmi->desc = "rl - White Point Matched Appearance (Lab)"; gmi->icci = icRelativeColorimetric; gmi->usecas = 0x0; /* Don't use appearance space, use L*a*b* */ gmi->usemap = 1; /* Use gamut mapping */ gmi->greymf = 1.0; /* And linearly map white point */ gmi->glumwcpf = 1.0; gmi->glumwexf = 1.0; gmi->glumbcpf = 0.0; gmi->glumbexf = 0.0; gmi->glumknf = 0.0; gmi->gamcpf = 0.0; gmi->gamexf = 0.0; gmi->gamcknf = 0.0; gmi->gamxknf = 0.0; gmi->gampwf = 0.0; gmi->gamswf = 0.0; gmi->satenh = 0.0; /* No saturation enhancement */ } else { /* icxIllegalGMIntent */ return icxIllegalGMIntent; } return no; } /* Debug: dump a Gamut Mapping specification */ void xicc_dump_gmi( icxGMappingIntent *gmi /* Gamut Mapping parameters to return */ ) { printf(" Gamut Mapping Specification:\n"); if (gmi->desc != NULL) printf(" Description = '%s'\n",gmi->desc); printf(" Closest ICC intent = '%s'\n",icm2str(icmRenderingIntent,gmi->icci)); if ((gmi->usecas & 0xff) == 0) printf(" Not using Color Apperance Space\n"); else if ((gmi->usecas & 0xff) == 1) printf(" Using Color Apperance Space\n"); else if ((gmi->usecas & 0xff) == 2) printf(" Using Absolute Color Apperance Space\n"); if ((gmi->usecas & 0x100) != 0) printf(" Scaling source to avoid white point clipping\n"); if (gmi->usemap == 0) printf(" Not using Mapping\n"); else { printf(" Using Mapping with parameters:\n"); printf(" Grey axis alignment factor %f\n", gmi->greymf); printf(" Grey axis white compression factor %f\n", gmi->glumwcpf); printf(" Grey axis white expansion factor %f\n", gmi->glumwexf); printf(" Grey axis black compression factor %f\n", gmi->glumbcpf); printf(" Grey axis black expansion factor %f\n", gmi->glumbexf); printf(" Grey axis knee factor %f\n", gmi->glumknf); printf(" Gamut compression factor %f\n", gmi->gamcpf); printf(" Gamut expansion factor %f\n", gmi->gamexf); printf(" Gamut compression knee factor %f\n", gmi->gamcknf); printf(" Gamut expansion knee factor %f\n", gmi->gamxknf); printf(" Gamut Perceptual mapping weighting factor %f\n", gmi->gampwf); printf(" Gamut Saturation mapping weighting factor %f\n", gmi->gamswf); printf(" Saturation enhancement factor %f\n", gmi->satenh); } } /* ------------------------------------------------------ */ /* Turn xicc xcal into limit calibration callback */ /* Given an icc profile, try and create an xcal */ /* Return NULL on error or no cal */ xcal *xiccReadCalTag(icc *p) { xcal *cal = NULL; icTagSignature sig = icmMakeTag('t','a','r','g'); icmText *ro; int oi, tab; //printf("~1 about to look for CAL in profile\n"); if ((ro = (icmText *)p->read_tag(p, sig)) != NULL) { cgatsFile *cgf; cgats *icg; if (ro->ttype != icSigTextType) return NULL; //printf("~1 found 'targ' tag\n"); if ((icg = new_cgats()) == NULL) { return NULL; } if ((cgf = new_cgatsFileMem(ro->data, ro->size)) != NULL) { icg->add_other(icg, "CTI3"); oi = icg->add_other(icg, "CAL"); //printf("~1 created cgats object from 'targ' tag\n"); if (icg->read(icg, cgf) == 0) { for (tab = 0; tab < icg->ntables; tab++) { if (icg->t[tab].tt == tt_other && icg->t[tab].oi == oi) { break; } } if (tab < icg->ntables) { //printf("~1 found CAL table\n"); if ((cal = new_xcal()) == NULL) { icg->del(icg); cgf->del(cgf); return NULL; } if (cal->read_cgats(cal, icg, tab, "'targ' tag") != 0) { #ifdef DEBUG printf("read_cgats on cal tag failed\n"); #endif cal->del(cal); cal = NULL; } //else printf("~1 read CAL and creaded xcal object OK\n"); } } cgf->del(cgf); } icg->del(icg); } return cal; } /* A callback that uses an xcal, that can be used with icc get_tac */ void xiccCalCallback(void *cntx, double *out, double *in) { xcal *cal = (xcal *)cntx; cal->interp(cal, out, in); } /* ---------------------------------------------- */ /* Utility function - given an open icc profile, */ /* guess which channel is the black. */ /* Return -1 if there is no black channel or it can't be guessed */ int icxGuessBlackChan(icc *p) { int kch = -1; switch (p->header->colorSpace) { case icSigCmykData: kch = 3; break; /* Use a heuristic to detect the black channel. */ /* This duplicates code in icxLu_comp_bk_point() :-( */ /* Colorant guessing should go in icclib ? */ case icSig2colorData: case icSig3colorData: case icSig4colorData: case icSig5colorData: case icSig6colorData: case icSig7colorData: case icSig8colorData: case icSig9colorData: case icSig10colorData: case icSig11colorData: case icSig12colorData: case icSig13colorData: case icSig14colorData: case icSig15colorData: case icSigMch5Data: case icSigMch6Data: case icSigMch7Data: case icSigMch8Data: { icmLuBase *lu; double dval[MAX_CHAN]; double ncval[3]; double cvals[MAX_CHAN][3]; int inn, e, nlighter, ndarker; /* Grab a lookup object */ if ((lu = p->get_luobj(p, icmFwd, icRelativeColorimetric, icSigLabData, icmLuOrdNorm)) == NULL) error("icxGetLimits: assert: getting Fwd Lookup failed!"); lu->spaces(lu, NULL, &inn, NULL, NULL, NULL, NULL, NULL, NULL, NULL); /* Decide if the colorspace is aditive or subtractive */ /* First the no colorant value */ for (e = 0; e < inn; e++) dval[e] = 0.0; lu->lookup(lu, ncval, dval); /* Then all the colorants */ nlighter = ndarker = 0; for (e = 0; e < inn; e++) { dval[e] = 1.0; lu->lookup(lu, cvals[e], dval); dval[e] = 0.0; if (fabs(cvals[e][0] - ncval[0]) > 5.0) { if (cvals[e][0] > ncval[0]) nlighter++; else ndarker++; } } if (ndarker > 0 && nlighter == 0) { /* Assume subtractive. */ double pbk[3] = { 0.0,0.0,0.0 }; /* Perfect black */ double smd = 1e10; /* Smallest distance */ /* Guess the black channel */ for (e = 0; e < inn; e++) { double tt; tt = icmNorm33sq(pbk, cvals[e]); if (tt < smd) { smd = tt; kch = e; } } /* See if the black seems sane */ if (cvals[kch][0] > 40.0 || fabs(cvals[kch][1]) > 10.0 || fabs(cvals[kch][2]) > 10.0) { kch = -1; } } lu->del(lu); } break; default: break; } return kch; } /* Utility function - given an open icc profile, */ /* estmate the total ink limit and black ink limit. */ /* Note that this is rather rough, because ICC profiles */ /* don't have a tag for this information, and ICC profiles */ /* don't have any straightforward way of identifying particular */ /* color channels for > 4 color. */ /* If there are no limits, or they are not discoverable or */ /* applicable, return values of -1.0 */ void icxGetLimits( xicc *xp, double *tlimit, double *klimit ) { icc *p = xp->pp; int nch; double max[MAX_CHAN]; /* Max of each channel */ double total; total = p->get_tac(p, max, xp->cal != NULL ? xiccCalCallback : NULL, (void *)xp->cal); if (total < 0.0) { /* Not valid */ if (tlimit != NULL) *tlimit = -1.0; if (klimit != NULL) *klimit = -1.0; return; } nch = icmCSSig2nchan(p->header->colorSpace); /* No effective limit */ if (tlimit != NULL) { if (total >= (double)nch) { *tlimit = -1.0; } else { *tlimit = total; } } if (klimit != NULL) { int kch; kch = icxGuessBlackChan(p); if (kch < 0 || max[kch] >= 1.0) { *klimit = -1.0; } else { *klimit = max[kch]; } } } /* Replace a non-set limit (ie. < 0.0) with the heuristic from */ /* the given profile. */ void icxDefaultLimits( xicc *xp, double *tlout, double tlin, double *klout, double klin ) { if (tlin < 0.0 || klin < 0.0) { double tl, kl; icxGetLimits(xp, &tl, &kl); if (tlin < 0.0) tlin = tl; if (klin < 0.0) klin = kl; } if (tlout != NULL) *tlout = tlin; if (klout != NULL) *klout = klin; } /* Structure to hold optimisation information */ typedef struct { xcal *cal; double ilimit; double uilimit; } ulimctx; /* Callback to find equivalent underlying total limit */ /* and try and maximize it while remaining within gamut */ static double ulimitfunc(void *cntx, double pv[]) { ulimctx *cx = (ulimctx *)cntx; xcal *cal = cx->cal; int devchan = cal->devchan; int i; double dv, odv; double og = 0.0, rv = 0.0; double usum = 0.0, sum = 0.0; /* Comute calibrated sum of channels except last */ for (i = 0; i < (devchan-1); i++) { double dv = pv[i]; /* Underlying (pre-calibration) device value */ usum += dv; /* Underlying sum */ if (dv < 0.0) { og += -dv; dv = 0.0; } else if (dv > 1.0) { og += dv - 1.0; dv = 1.0; } else dv = cal->interp_ch(cal, i, dv); /* Calibrated device value */ sum += dv; /* Calibrated device sum */ } /* Compute the omitted channel value */ dv = cx->ilimit - sum; /* Omitted calibrated device value */ if (dv < 0.0) { og += -dv; dv = 0.0; } else if (dv > 1.0) { og += dv - 1.0; dv = 1.0; } else dv = cal->inv_interp_ch(cal, i, dv); /* Omitted underlying device value */ usum += dv; /* Underlying sum */ cx->uilimit = usum; rv = 10000.0 * og - usum; /* Penalize out of gamut, maximize underlying sum */ //printf("~1 returning %f from %f %f %f %f\n",rv,pv[0],pv[1],pv[2],dv); return rv; } /* Given a calibrated total ink limit and an xcal, return the */ /* equivalent underlying (pre-calibration) total ink limit. */ /* This is the maximum equivalent, that makes sure that */ /* the calibrated limit is met or exceeded. */ double icxMaxUnderlyingLimit(xcal *cal, double ilimit) { ulimctx cx; int i; double dv[MAX_CHAN]; double sr[MAX_CHAN]; double rv; /* Residual value */ if (cal->devchan <= 1) { return cal->inv_interp_ch(cal, 0, ilimit); } cx.cal = cal; cx.ilimit = ilimit; for (i = 0; i < (cal->devchan-1); i++) { sr[i] = 0.05; dv[i] = 0.1; } if (powell(&rv, cal->devchan-1, dv, sr, 0.000001, 1000, ulimitfunc, (void *)&cx, NULL, NULL) != 0) { warning("icxUnderlyingLimit() failed for chan %d, ilimit %f\n",cal->devchan,ilimit); return ilimit; } ulimitfunc((void *)&cx, dv); return cx.uilimit; } /* ------------------------------------------------------ */ /* Conversion and deltaE formular that include partial */ /* derivatives, for use within fit parameter optimisations. */ /* CIE XYZ to perceptual Lab with partial derivatives. */ void icxdXYZ2Lab(icmXYZNumber *w, double *out, double dout[3][3], double *in) { double wp[3], tin[3], dtin[3]; int i; wp[0] = w->X, wp[1] = w->Y, wp[2] = w->Z; for (i = 0; i < 3; i++) { tin[i] = in[i]/wp[i]; dtin[i] = 1.0/wp[i]; if (tin[i] > 0.008856451586) { dtin[i] *= pow(tin[i], -2.0/3.0) / 3.0; tin[i] = pow(tin[i],1.0/3.0); } else { dtin[i] *= 7.787036979; tin[i] = 7.787036979 * tin[i] + 16.0/116.0; } } out[0] = 116.0 * tin[1] - 16.0; dout[0][0] = 0.0; dout[0][1] = 116.0 * dtin[1]; dout[0][2] = 0.0; out[1] = 500.0 * (tin[0] - tin[1]); dout[1][0] = 500.0 * dtin[0]; dout[1][1] = 500.0 * -dtin[1]; dout[1][2] = 0.0; out[2] = 200.0 * (tin[1] - tin[2]); dout[2][0] = 0.0; dout[2][1] = 200.0 * dtin[1]; dout[2][2] = 200.0 * -dtin[2]; } /* Return the normal Delta E squared, given two Lab values, */ /* including partial derivatives. */ double icxdLabDEsq(double dout[2][3], double *Lab0, double *Lab1) { double rv = 0.0, tt; tt = Lab0[0] - Lab1[0]; dout[0][0] = 2.0 * tt; dout[1][0] = -2.0 * tt; rv += tt * tt; tt = Lab0[1] - Lab1[1]; dout[0][1] = 2.0 * tt; dout[1][1] = -2.0 * tt; rv += tt * tt; tt = Lab0[2] - Lab1[2]; dout[0][2] = 2.0 * tt; dout[1][2] = -2.0 * tt; rv += tt * tt; return rv; } /* Return the CIE94 Delta E color difference measure, squared */ /* including partial derivatives. */ double icxdCIE94sq(double dout[2][3], double Lab0[3], double Lab1[3]) { double desq, _desq[2][3]; double dlsq; double dcsq, _dcsq[2][2]; /* == [x][1,2] */ double c12, _c12[2][2]; /* == [x][1,2] */ double dhsq, _dhsq[2][2]; /* == [x][1,2] */ double rv; { double dl, da, db; dl = Lab0[0] - Lab1[0]; dlsq = dl * dl; /* dl squared */ da = Lab0[1] - Lab1[1]; db = Lab0[2] - Lab1[2]; /* Compute normal Lab delta E squared */ desq = dlsq + da * da + db * db; _desq[0][0] = 2.0 * dl; _desq[1][0] = -2.0 * dl; _desq[0][1] = 2.0 * da; _desq[1][1] = -2.0 * da; _desq[0][2] = 2.0 * db; _desq[1][2] = -2.0 * db; } { double c1, c2, dc, tt; /* Compute chromanance for the two colors */ c1 = sqrt(Lab0[1] * Lab0[1] + Lab0[2] * Lab0[2]); c2 = sqrt(Lab1[1] * Lab1[1] + Lab1[2] * Lab1[2]); c12 = sqrt(c1 * c2); /* Symetric chromanance */ tt = 0.5 * (pow(c2, 0.5) + 1e-12)/(pow(c1, 1.5) + 1e-12); _c12[0][0] = Lab0[1] * tt; _c12[0][1] = Lab0[2] * tt; tt = 0.5 * (pow(c1, 0.5) + 1e-12)/(pow(c2, 1.5) + 1e-12); _c12[1][0] = Lab1[1] * tt; _c12[1][1] = Lab1[2] * tt; /* delta chromanance squared */ dc = c2 - c1; dcsq = dc * dc; if (c1 < 1e-12 || c2 < 1e-12) { c1 += 1e-12; c2 += 1e-12; } _dcsq[0][0] = -2.0 * Lab0[1] * (c2 - c1)/c1; _dcsq[0][1] = -2.0 * Lab0[2] * (c2 - c1)/c1; _dcsq[1][0] = 2.0 * Lab1[1] * (c2 - c1)/c2; _dcsq[1][1] = 2.0 * Lab1[2] * (c2 - c1)/c2; } /* Compute delta hue squared */ dhsq = desq - dlsq - dcsq; if (dhsq >= 0.0) { _dhsq[0][0] = _desq[0][1] - _dcsq[0][0]; _dhsq[0][1] = _desq[0][2] - _dcsq[0][1]; _dhsq[1][0] = _desq[1][1] - _dcsq[1][0]; _dhsq[1][1] = _desq[1][2] - _dcsq[1][1]; } else { dhsq = 0.0; _dhsq[0][0] = 0.0; _dhsq[0][1] = 0.0; _dhsq[1][0] = 0.0; _dhsq[1][1] = 0.0; } { double sc, scsq, scf; double sh, shsq, shf; /* Weighting factors for delta chromanance & delta hue */ sc = 1.0 + 0.048 * c12; scsq = sc * sc; sh = 1.0 + 0.014 * c12; shsq = sh * sh; rv = dlsq + dcsq/scsq + dhsq/shsq; scf = 0.048 * -2.0 * dcsq/(scsq * sc); shf = 0.014 * -2.0 * dhsq/(shsq * sh); dout[0][0] = _desq[0][0]; dout[0][1] = _dcsq[0][0]/scsq + _c12[0][0] * scf + _dhsq[0][0]/shsq + _c12[0][0] * shf; dout[0][2] = _dcsq[0][1]/scsq + _c12[0][1] * scf + _dhsq[0][1]/shsq + _c12[0][1] * shf; dout[1][0] = _desq[1][0]; dout[1][1] = _dcsq[1][0]/scsq + _c12[1][0] * scf + _dhsq[1][0]/shsq + _c12[1][0] * shf; dout[1][2] = _dcsq[1][1]/scsq + _c12[1][1] * scf + _dhsq[1][1]/shsq + _c12[1][1] * shf; return rv; } } // ~~99 not sure if these are correct: /* Return the normal Delta E given two Lab values, */ /* including partial derivatives. */ double icxdLabDE(double dout[2][3], double *Lab0, double *Lab1) { double rv = 0.0, tt; tt = Lab0[0] - Lab1[0]; dout[0][0] = 1.0 * tt; dout[1][0] = -1.0 * tt; rv += tt * tt; tt = Lab0[1] - Lab1[1]; dout[0][1] = 1.0 * tt; dout[1][1] = -1.0 * tt; rv += tt * tt; tt = Lab0[2] - Lab1[2]; dout[0][2] = 1.0 * tt; dout[1][2] = -1.0 * tt; rv += tt * tt; return sqrt(rv); } /* Return the CIE94 Delta E color difference measure */ /* including partial derivatives. */ double icxdCIE94(double dout[2][3], double Lab0[3], double Lab1[3]) { double desq, _desq[2][3]; double dlsq; double dcsq, _dcsq[2][2]; /* == [x][1,2] */ double c12, _c12[2][2]; /* == [x][1,2] */ double dhsq, _dhsq[2][2]; /* == [x][1,2] */ double rv; { double dl, da, db; dl = Lab0[0] - Lab1[0]; dlsq = dl * dl; /* dl squared */ da = Lab0[1] - Lab1[1]; db = Lab0[2] - Lab1[2]; /* Compute normal Lab delta E squared */ desq = dlsq + da * da + db * db; _desq[0][0] = 1.0 * dl; _desq[1][0] = -1.0 * dl; _desq[0][1] = 1.0 * da; _desq[1][1] = -1.0 * da; _desq[0][2] = 1.0 * db; _desq[1][2] = -1.0 * db; } { double c1, c2, dc, tt; /* Compute chromanance for the two colors */ c1 = sqrt(Lab0[1] * Lab0[1] + Lab0[2] * Lab0[2]); c2 = sqrt(Lab1[1] * Lab1[1] + Lab1[2] * Lab1[2]); c12 = sqrt(c1 * c2); /* Symetric chromanance */ tt = 0.5 * (pow(c2, 0.5) + 1e-12)/(pow(c1, 1.5) + 1e-12); _c12[0][0] = Lab0[1] * tt; _c12[0][1] = Lab0[2] * tt; tt = 0.5 * (pow(c1, 0.5) + 1e-12)/(pow(c2, 1.5) + 1e-12); _c12[1][0] = Lab1[1] * tt; _c12[1][1] = Lab1[2] * tt; /* delta chromanance squared */ dc = c2 - c1; dcsq = dc * dc; if (c1 < 1e-12 || c2 < 1e-12) { c1 += 1e-12; c2 += 1e-12; } _dcsq[0][0] = -1.0 * Lab0[1] * (c2 - c1)/c1; _dcsq[0][1] = -1.0 * Lab0[2] * (c2 - c1)/c1; _dcsq[1][0] = 1.0 * Lab1[1] * (c2 - c1)/c2; _dcsq[1][1] = 1.0 * Lab1[2] * (c2 - c1)/c2; } /* Compute delta hue squared */ dhsq = desq - dlsq - dcsq; if (dhsq >= 0.0) { _dhsq[0][0] = _desq[0][1] - _dcsq[0][0]; _dhsq[0][1] = _desq[0][2] - _dcsq[0][1]; _dhsq[1][0] = _desq[1][1] - _dcsq[1][0]; _dhsq[1][1] = _desq[1][2] - _dcsq[1][1]; } else { dhsq = 0.0; _dhsq[0][0] = 0.0; _dhsq[0][1] = 0.0; _dhsq[1][0] = 0.0; _dhsq[1][1] = 0.0; } { double sc, scsq, scf; double sh, shsq, shf; /* Weighting factors for delta chromanance & delta hue */ sc = 1.0 + 0.048 * c12; scsq = sc * sc; sh = 1.0 + 0.014 * c12; shsq = sh * sh; rv = dlsq + dcsq/scsq + dhsq/shsq; scf = 0.048 * -1.0 * dcsq/(scsq * sc); shf = 0.014 * -1.0 * dhsq/(shsq * sh); dout[0][0] = _desq[0][0]; dout[0][1] = _dcsq[0][0]/scsq + _c12[0][0] * scf + _dhsq[0][0]/shsq + _c12[0][0] * shf; dout[0][2] = _dcsq[0][1]/scsq + _c12[0][1] * scf + _dhsq[0][1]/shsq + _c12[0][1] * shf; dout[1][0] = _desq[1][0]; dout[1][1] = _dcsq[1][0]/scsq + _c12[1][0] * scf + _dhsq[1][0]/shsq + _c12[1][0] * shf; dout[1][2] = _dcsq[1][1]/scsq + _c12[1][1] * scf + _dhsq[1][1]/shsq + _c12[1][1] * shf; return sqrt(rv); } } /* ------------------------------------------------------ */ /* A power-like function, based on Graphics Gems adjustment curve. */ /* Avoids "toe" problem of pure power. */ /* Adjusted so that "power" 2 and 0.5 agree with real power at 0.5 */ double icx_powlike(double vv, double pp) { double tt, g; if (pp >= 1.0) { g = 2.0 * (pp - 1.0); vv = vv/(g - g * vv + 1.0); } else { g = 2.0 - 2.0/pp; vv = (vv - g * vv)/(1.0 - g * vv); } return vv; } /* Compute the necessary aproximate power, to transform */ /* the given value from src to dst. They are assumed to be */ /* in the range 0.0 .. 1.0 */ double icx_powlike_needed(double src, double dst) { double pp, g; if (dst <= src) { g = -((src - dst)/(dst * src - dst)); pp = (0.5 * g) + 1.0; } else { g = -((src - dst)/((dst - 1.0) * src)); pp = 1.0/(1.0 - 0.5 * g); } return pp; } /* ------------------------------------------------------ */ /* Parameterized transfer/dot gain function. */ /* Used for device modelling. Including partial */ /* derivative for input and parameters. */ /* NOTE that clamping the input values seems to cause */ /* conjgrad() problems. */ /* Transfer function */ double icxTransFunc( double *v, /* Pointer to first parameter */ int luord, /* Number of parameters */ double vv /* Source of value */ ) { double g; int ord; /* Process all the shaper orders from low to high. */ /* [These shapers were inspired by a Graphics Gem idea */ /* (Gems IV, VI.3, "Fast Alternatives to Perlin's Bias and */ /* Gain Functions, pp 401). */ /* They have the nice properties that they are smooth, and */ /* are monotonic. The control parameter has been */ /* altered to have a range from -oo to +oo rather than 0.0 to 1.0 */ /* so that the search space is less non-linear. */ for (ord = 0; ord < luord; ord++) { int nsec; /* Number of sections */ double sec; /* Section */ g = v[ord]; /* Parameter */ nsec = ord + 1; /* Increase sections for each order */ vv *= (double)nsec; sec = floor(vv); if (((int)sec) & 1) g = -g; /* Alternate action in each section */ vv -= sec; if (g >= 0.0) { vv = vv/(g - g * vv + 1.0); } else { vv = (vv - g * vv)/(1.0 - g * vv); } vv += sec; vv /= (double)nsec; } return vv; } /* Inverse transfer function */ double icxInvTransFunc( double *v, /* Pointer to first parameter */ int luord, /* Number of parameters */ double vv /* Source of value */ ) { double g; int ord; /* Process the shaper orders in reverse from high to low. */ /* [These shapers were inspired by a Graphics Gem idea */ /* (Gems IV, VI.3, "Fast Alternatives to Perlin's Bias and */ /* Gain Functions, pp 401). */ /* They have the nice properties that they are smooth, and */ /* are monotonic. The control parameter has been */ /* altered to have a range from -oo to +oo rather than 0.0 to 1.0 */ /* so that the search space is less non-linear. */ for (ord = luord-1; ord >= 0; ord--) { int nsec; /* Number of sections */ double sec; /* Section */ g = -v[ord]; /* Inverse parameter */ nsec = ord + 1; /* Increase sections for each order */ vv *= (double)nsec; sec = floor(vv); if (((int)sec) & 1) g = -g; /* Alternate action in each section */ vv -= sec; if (g >= 0.0) { vv = vv/(g - g * vv + 1.0); } else { vv = (vv - g * vv)/(1.0 - g * vv); } vv += sec; vv /= (double)nsec; } return vv; } /* Transfer function with offset and scale */ double icxSTransFunc( double *v, /* Pointer to first parameter */ int luord, /* Number of parameters */ double vv, /* Source of value */ double min, /* Scale values */ double max ) { max -= min; vv = (vv - min)/max; vv = icxTransFunc(v, luord, vv); vv = (vv * max) + min; return vv; } /* Inverse Transfer function with offset and scale */ double icxInvSTransFunc( double *v, /* Pointer to first parameter */ int luord, /* Number of parameters */ double vv, /* Source of value */ double min, /* Scale values */ double max ) { max -= min; vv = (vv - min)/max; vv = icxInvTransFunc(v, luord, vv); vv = (vv * max) + min; return vv; } /* Transfer function with partial derivative */ /* with respect to the parameters. */ double icxdpTransFunc( double *v, /* Pointer to first parameter */ double *dv, /* Return derivative wrt each parameter */ int luord, /* Number of parameters */ double vv /* Source of value */ ) { double g; int i, ord; /* Process all the shaper orders from high to low. */ for (ord = 0; ord < luord; ord++) { double dsv; /* del for del in g */ double ddv; /* del for del in vv */ int nsec; /* Number of sections */ double sec; /* Section */ g = v[ord]; /* Parameter */ nsec = ord + 1; /* Increase sections for each order */ vv *= (double)nsec; sec = floor(vv); if (((int)sec) & 1) { g = -g; /* Alternate action in each section */ } vv -= sec; if (g >= 0.0) { double tt = g - g * vv + 1.0; dsv = (vv * vv - vv)/(tt * tt); ddv = (g + 1.0)/(tt * tt); vv = vv/tt; } else { double tt = 1.0 - g * vv; dsv = (vv * vv - vv)/(tt * tt); ddv = (1.0 - g)/(tt * tt); vv = (vv - g * vv)/tt; } vv += sec; vv /= (double)nsec; dsv /= (double)nsec; if (((int)sec) & 1) dsv = -dsv; dv[ord] = dsv; for (i = ord - 1; i >= 0; i--) dv[i] *= ddv; } return vv; } /* Transfer function with offset and scale, and */ /* partial derivative with respect to the parameters. */ double icxdpSTransFunc( double *v, /* Pointer to first parameter */ double *dv, /* Return derivative wrt each parameter */ int luord, /* Number of parameters */ double vv, /* Source of value */ double min, /* Scale values */ double max ) { int i; max -= min; vv = (vv - min)/max; vv = icxdpTransFunc(v, dv, luord, vv); vv = (vv * max) + min; for (i = 0; i < luord; i++) dv[i] *= max; return vv; } /* Transfer function with partial derivative */ /* with respect to the input value. */ double icxdiTransFunc( double *v, /* Pointer to first parameter */ double *pdin, /* Return derivative wrt source value */ int luord, /* Number of parameters */ double vv /* Source of value */ ) { double g, din; int ord; #ifdef NEVER if (vv < 0.0 || vv > 1.0) { if (vv < 0.0) vv = 0.0; else vv = 1.0; *pdin = 0.0; return vv; } #endif din = 1.0; /* Process all the shaper orders from high to low. */ for (ord = 0; ord < luord; ord++) { double ddv; /* del for del in vv */ int nsec; /* Number of sections */ double sec; /* Section */ g = v[ord]; /* Parameter */ nsec = ord + 1; /* Increase sections for each order */ vv *= (double)nsec; sec = floor(vv); if (((int)sec) & 1) { g = -g; /* Alternate action in each section */ } vv -= sec; if (g >= 0.0) { double tt = g - g * vv + 1.0; ddv = (g + 1.0)/(tt * tt); vv = vv/tt; } else { double tt = 1.0 - g * vv; ddv = (1.0 - g)/(tt * tt); vv = (vv - g * vv)/tt; } vv += sec; vv /= (double)nsec; din *= ddv; } *pdin = din; return vv; } /* Transfer function with offset and scale, and */ /* partial derivative with respect to the input value. */ double icxdiSTransFunc( double *v, /* Pointer to first parameter */ double *pdv, /* Return derivative wrt source value */ int luord, /* Number of parameters */ double vv, /* Source of value */ double min, /* Scale values */ double max ) { max -= min; vv = (vv - min)/max; vv = icxdiTransFunc(v, pdv, luord, vv); vv = (vv * max) + min; return vv; } /* Transfer function with partial derivative */ /* with respect to the parameters and the input value. */ double icxdpdiTransFunc( double *v, /* Pointer to first parameter */ double *dv, /* Return derivative wrt each parameter */ double *pdin, /* Return derivative wrt source value */ int luord, /* Number of parameters */ double vv /* Source of value */ ) { double g, din; int i, ord; #ifdef NEVER if (vv < 0.0 || vv > 1.0) { if (vv < 0.0) vv = 0.0; else vv = 1.0; for (ord = 0; ord < luord; ord++) dv[ord] = 0.0; *pdin = 0.0; return vv; } #endif din = 1.0; /* Process all the shaper orders from high to low. */ for (ord = 0; ord < luord; ord++) { double dsv; /* del for del in g */ double ddv; /* del for del in vv */ int nsec; /* Number of sections */ double sec; /* Section */ g = v[ord]; /* Parameter */ nsec = ord + 1; /* Increase sections for each order */ vv *= (double)nsec; sec = floor(vv); if (((int)sec) & 1) { g = -g; /* Alternate action in each section */ } vv -= sec; if (g >= 0.0) { double tt = g - g * vv + 1.0; dsv = (vv * vv - vv)/(tt * tt); ddv = (g + 1.0)/(tt * tt); vv = vv/tt; } else { double tt = 1.0 - g * vv; dsv = (vv * vv - vv)/(tt * tt); ddv = (1.0 - g)/(tt * tt); vv = (vv - g * vv)/tt; } vv += sec; vv /= (double)nsec; dsv /= (double)nsec; if (((int)sec) & 1) dsv = -dsv; dv[ord] = dsv; for (i = ord - 1; i >= 0; i--) dv[i] *= ddv; din *= ddv; } *pdin = din; return vv; } /* Transfer function with offset and scale, and */ /* partial derivative with respect to the */ /* parameters and the input value. */ double icxdpdiSTransFunc( double *v, /* Pointer to first parameter */ double *dv, /* Return derivative wrt each parameter */ double *pdin, /* Return derivative wrt source value */ int luord, /* Number of parameters */ double vv, /* Source of value */ double min, /* Scale values */ double max ) { int i; max -= min; vv = (vv - min)/max; vv = icxdpdiTransFunc(v, dv, pdin, luord, vv); vv = (vv * max) + min; for (i = 0; i < luord; i++) dv[i] *= max; return vv; } /* ------------------------------------------------------ */ /* Multi-plane interpolation, used for device modelling. */ /* Including partial derivative for input and parameters. */ /* A simple flat plane is used for each output. */ /* Multi-plane interpolation - uses base + di slope values. */ /* Parameters are assumed to be fdi groups of di + 1 parameters. */ void icxPlaneInterp( double *v, /* Pointer to first parameter [fdi * (di + 1)] */ int fdi, /* Number of output channels */ int di, /* Number of input channels */ double *out, /* Resulting fdi values */ double *in /* Input di values */ ) { int e, f; for (f = 0; f < fdi; f++) { for (out[f] = 0.0, e = 0; e < di; e++, v++) { out[f] += in[e] * *v; } out[f] += *v; } } /* Multii-plane interpolation with partial derivative */ /* with respect to the input and parameters. */ void icxdpdiPlaneInterp( double *v, /* Pointer to first parameter value [fdi * (di + 1)] */ double *dv, /* Return [1 + di] deriv. wrt each parameter v */ double *din, /* Return [fdi * di] deriv. wrt each input value */ int fdi, /* Number of output channels */ int di, /* Number of input channels */ double *out, /* Resulting fdi values */ double *in /* Input di values */ ) { int e, ee, f, g; int dip2 = (di + 1); /* Output dim increment through parameters */ /* Compute the output values */ for (f = 0; f < fdi; f++) { for (out[f] = 0.0, e = 0; e < di; e++) out[f] += in[e] * v[f * dip2 + e]; out[f] += v[f * dip2 + e]; } /* Since interpolation is verys simple, derivative are also simple */ /* Copy del for parameter to return array */ for (e = 0; e < di; e++) dv[e] = in[e]; dv[e] = 1.0; /* Compute del of out[] from in[] */ for (f = 0; f < fdi; f++) { for (e = 0; e < di; e++) { din[f * di + e] = v[f * dip2 + e]; } } } /* ------------------------------------------------------ */ /* Matrix cube interpolation, used for device modelling. */ /* Including partial derivative for input and parameters. */ /* Matrix cube interpolation - interpolate between 2^di output corner values. */ /* Parameters are assumed to be fdi groups of 2^di parameters. */ void icxCubeInterp( double *v, /* Pointer to first parameter */ int fdi, /* Number of output channels */ int di, /* Number of input channels */ double *out, /* Resulting fdi values */ double *in /* Input di values */ ) { int e, f, g; double gw[1 << MXDI]; /* weight for each matrix grid cube corner */ /* Compute corner weights needed for interpolation */ gw[0] = 1.0; for (e = 0, g = 1; e < di; e++, g *= 2) { int i; for (i = 0; i < g; i++) { gw[g+i] = gw[i] * in[e]; gw[i] *= (1.0 - in[e]); } } /* Now compute the output values */ for (f = 0; f < fdi; f++) { out[f] = 0.0; /* For each output value */ for (e = 0; e < (1 << di); e++) { /* For all corners of cube */ out[f] += gw[e] * *v; v++; } } } /* Matrix cube interpolation. with partial derivative */ /* with respect to the input and parameters. */ void icxdpdiCubeInterp( double *v, /* Pointer to first parameter value [fdi * 2^di] */ double *dv, /* Return [2^di] deriv. wrt each parameter v */ double *din, /* Return [fdi * di] deriv. wrt each input value */ int fdi, /* Number of output channels */ int di, /* Number of input channels */ double *out, /* Resulting fdi values */ double *in /* Input di values */ ) { int e, ee, f, g; int dip2 = (1 << di); double gw[1 << MXDI]; /* weight for each matrix grid cube corner */ /* Compute corner weights needed for interpolation */ gw[0] = 1.0; for (e = 0, g = 1; e < di; e++, g *= 2) { int i; for (i = 0; i < g; i++) { gw[g+i] = gw[i] * in[e]; gw[i] *= (1.0 - in[e]); } } /* Now compute the output values */ for (f = 0; f < fdi; f++) { out[f] = 0.0; /* For each output value */ for (ee = 0; ee < dip2; ee++) { /* For all corners of cube */ out[f] += gw[ee] * v[f * dip2 + ee]; } } /* Copy del for parameter to return array */ for (ee = 0; ee < dip2; ee++) { /* For all other corners of cube */ dv[ee] = gw[ee]; /* del from parameter */ } /* Compute del from in[] value we want */ for (e = 0; e < di; e++) { /* For input we want del wrt */ for (f = 0; f < fdi; f++) din[f * di + e] = 0.0; /* Zero del ready of accumulation */ for (ee = 0; ee < dip2; ee++) { /* For all corners of cube weights, */ int e2; /* accumulate del from in[] we want. */ double vv = 1.0; /* Compute in[] weighted cube corners for all except del of in[] we want */ for (e2 = 0; e2 < di; e2++) { /* const from non del inputs */ if (e2 == e) continue; if (ee & (1 << e2)) vv *= in[e2]; else vv *= (1.0 - in[e2]); } /* Accumulate contribution of in[] we want for corner to out[] we want */ if (ee & (1 << e)) { for (f = 0; f < fdi; f++) din[f * di + e] += v[f * dip2 + ee] * vv; } else { for (f = 0; f < fdi; f++) din[f * di + e] -= v[f * dip2 + ee] * vv; } } } } /* ------------------------------------------------------ */ /* Matrix cube simplex interpolation, used for device modelling. */ /* Matrix cube simplex interpolation - interpolate between 2^di output corner values. */ /* Parameters are assumed to be fdi groups of 2^di parameters. */ void icxCubeSxInterp( double *v, /* Pointer to first parameter */ int fdi, /* Number of output channels */ int di, /* Number of input channels */ double *out, /* Resulting fdi values */ double *in /* Input di values */ ) { int si[MAX_CHAN]; /* in[] Sort index, [0] = smalest */ //{ // double tout[MXDO]; // // icxCubeInterp(v, fdi, di, tout, in); //printf("\n~1 Cube interp result = %f\n",tout[0]); //} //printf("~1 icxCubeSxInterp: %f %f %f\n", in[0], in[1], in[2]); /* Do insertion sort on coordinates, smallest to largest. */ { int ff, vf; unsigned int e; double v; for (e = 0; e < di; e++) si[e] = e; /* Initial unsorted indexes */ for (e = 1; e < di; e++) { ff = e; v = in[si[ff]]; vf = ff; while (ff > 0 && in[si[ff-1]] > v) { si[ff] = si[ff-1]; ff--; } si[ff] = vf; } } //printf("~1 sort order %d %d %d\n", si[0], si[1], si[2]); //printf(" from %f %f %f\n", in[si[0]], in[si[1]], in[si[2]]); /* Now compute the weightings, simplex vertices and output values */ { unsigned int e, f; double w; /* Current vertex weight */ w = 1.0 - in[si[di-1]]; /* Vertex at base of cell */ for (f = 0; f < fdi; f++) { out[f] = w * v[f * (1 << di)]; //printf("~1 out[%d] = %f = %f * %f\n",f,out[f],w,v[f * (1 << di)]); } for (e = di-1; e > 0; e--) { /* Middle verticies */ w = in[si[e]] - in[si[e-1]]; v += (1 << si[e]); /* Move to top of cell in next largest dimension */ for (f = 0; f < fdi; f++) { out[f] += w * v[f * (1 << di)]; //printf("~1 out[%d] = %f += %f * %f\n",f,out[f],w,v[f * (1 << di)]); } } w = in[si[0]]; v += (1 << si[0]); /* Far corner from base of cell */ for (f = 0; f < fdi; f++) { out[f] += w * v[f * (1 << di)]; //printf("~1 out[%d] = %f += %f * %f\n",f,out[f],w,v[f * (1 << di)]); } } } /* ------------------------------------------------------ */ /* Matrix multiplication, used for device modelling. */ /* Including partial derivative for input and parameters. */ /* 3x3 matrix multiplication, with the matrix in a 1D array */ /* with respect to the input and parameters. */ void icxMulBy3x3Parm( double out[3], /* Return input multiplied by matrix */ double mat[9], /* Matrix organised in [slow][fast] order */ double in[3] /* Input values */ ) { double *v = mat, ov[3]; int e, f; /* Compute the output values */ for (f = 0; f < 3; f++) { ov[f] = 0.0; /* For each output value */ for (e = 0; e < 3; e++) { ov[f] += *v++ * in[e]; } } out[0] = ov[0]; out[1] = ov[1]; out[2] = ov[2]; } /* 3x3 matrix multiplication, with partial derivatives */ /* with respect to the input and parameters. */ void icxdpdiMulBy3x3Parm( double out[3], /* Return input multiplied by matrix */ double dv[3][9], /* Return deriv for each [output] with respect to [param] */ double din[3][3], /* Return deriv for each [output] with respect to [input] */ double mat[9], /* Matrix organised in [slow][fast] order */ double in[3] /* Input values */ ) { double *v, ov[3]; int e, f; /* Compute the output values */ v = mat; for (f = 0; f < 3; f++) { ov[f] = 0.0; /* For each output value */ for (e = 0; e < 3; e++) { ov[f] += *v++ * in[e]; } } /* Compute deriv. with respect to the matrix parameter % 3 */ /* This is pretty simple for a matrix ... */ for (f = 0; f < 3; f++) { for (e = 0; e < 9; e++) { if (e/3 == f) dv[f][e] = in[e % 3]; else dv[f][e] = 0.0; } } /* Compute deriv. with respect to the input values */ /* This is pretty simple for a matrix ... */ v = mat; for (f = 0; f < 3; f++) for (e = 0; e < 3; e++) din[f][e] = *v++; out[0] = ov[0]; out[1] = ov[1]; out[2] = ov[2]; } #undef stricmp