From 22f703cab05b7cd368f4de9e03991b7664dc5022 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Frings-F=C3=BCrst?= Date: Mon, 1 Sep 2014 13:56:46 +0200 Subject: Initial import of argyll version 1.5.1-8 --- xicc/xicc.c | 3607 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 3607 insertions(+) create mode 100644 xicc/xicc.c (limited to 'xicc/xicc.c') diff --git a/xicc/xicc.c b/xicc/xicc.c new file mode 100644 index 0000000..9b6d867 --- /dev/null +++ b/xicc/xicc.c @@ -0,0 +1,3607 @@ + +/* + * 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 + + + + + + + + + + + + + + + + + + + + + + + -- cgit v1.2.3