diff options
Diffstat (limited to 'xicc/xmatrix.c')
-rw-r--r-- | xicc/xmatrix.c | 2022 |
1 files changed, 2022 insertions, 0 deletions
diff --git a/xicc/xmatrix.c b/xicc/xmatrix.c new file mode 100644 index 0000000..a194314 --- /dev/null +++ b/xicc/xmatrix.c @@ -0,0 +1,2022 @@ +/* + * International Color Consortium color transform expanded support + * + * Author: Graeme W. Gill + * Date: 2/7/00 + * Version: 1.00 + * + * Copyright 2000, 2003 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 provides the expands icclib functionality + * for matrix profiles. + * This file is #included in xicc.c, to keep its functions private. + */ + +/* + * TTBD: + * + * Some of the error handling is crude. Shouldn't use + * error(), should return status. + * + * Should allow for offset in curves - this will greatly improve + * profile quality on non-calibrated displays. See spectro/dispcal.c + * spectro/moncurve.c. Use conjgrad() instead of powell() to speed things up. + * Note that if curves have scale, the scale will have to be + * normalized back to zero by scaling the matrix before storing + * the result in the ICC profile. + * + */ + +#define USE_CIE94_DE /* Use CIE94 delta E measure when creating fit */ + +/* Weights in shaper parameters, to minimise unconstrained "wiggles" */ +#define MXNORDERS 30 /* Maximum shaper harmonic orders to use */ +#define XSHAPE_MAG 1.0 /* Overall shaper parameter magnitide */ + +#define XSHAPE_OFFG 0.1 /* Input offset weights when ord 0 is gamma */ +#define XSHAPE_OFFS 1.0 /* Input offset weights when ord 0 is shaper */ +#define XSHAPE_HW01 0.002 /* 0 & 1 harmonic weights */ +#define XSHAPE_HBREAK 4 /* Harmonic that has HWBR */ +#define XSHAPE_HWBR 0.8 /* Base weight of harmonics HBREAK up */ +#define XSHAPE_HWINC 0.5 /* Increase in weight for each harmonic above HWBR */ + +#define XSHAPE_GAMTHR 0.01 /* Input threshold for linear slope below gamma power */ + +#undef DEBUG /* Extra printfs */ +#undef DEBUG_PLOT /* Plot curves */ + +/* ========================================================= */ +/* Forward and Backward Matrix type conversion */ +/* Return 0 on success, 1 if clipping occured, 2 on other error */ + +/* Individual components of Fwd conversion: */ +static int +icxLuMatrixFwd_curve ( +icxLuMatrix *p, /* This */ +double *out, /* Vector of output values */ +double *in /* Vector of input values */ +) { + return ((icmLuMatrix *)p->plu)->fwd_curve((icmLuMatrix *)p->plu, out, in); +} + +static int +icxLuMatrixFwd_matrix ( +icxLuMatrix *p, /* This */ +double *out, /* Vector of output values */ +double *in /* Vector of input values */ +) { + return ((icmLuMatrix *)p->plu)->fwd_matrix((icmLuMatrix *)p->plu, out, in); +} + +static int +icxLuMatrixFwd_abs ( +icxLuMatrix *p, /* This */ +double *out, /* Vector of output values */ +double *in /* Vector of input values */ +) { + int rv = 0; + rv |= ((icmLuMatrix *)p->plu)->fwd_abs((icmLuMatrix *)p->plu, out, in); + + if (p->pcs == icxSigJabData) { + p->cam->XYZ_to_cam(p->cam, out, out); + } + return rv; +} + + +/* Overall Fwd conversion */ +static int +icxLuMatrixFwd_lookup ( +icxLuBase *pp, /* This */ +double *out, /* Vector of output values */ +double *in /* Vector of input values */ +) { + int rv = 0; + icxLuMatrix *p = (icxLuMatrix *)pp; + rv |= icxLuMatrixFwd_curve(p, out, in); + rv |= icxLuMatrixFwd_matrix(p, out, out); + rv |= icxLuMatrixFwd_abs(p, out, out); + return rv; +} + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - */ +/* Given a relative XYZ or Lab PCS value, convert in the fwd direction into */ +/* the nominated output PCS (ie. Absolute, Jab etc.) */ +/* (This is used in generating gamut compression in B2A tables) */ +void icxLuMatrix_fwd_relpcs_outpcs( +icxLuBase *pp, +icColorSpaceSignature is, /* Input space, XYZ or Lab */ +double *out, double *in) { + icxLuMatrix *p = (icxLuMatrix *)pp; + + if (is == icSigLabData && p->natpcs == icSigXYZData) { + icmLab2XYZ(&icmD50, out, in); + icxLuMatrixFwd_abs(p, out, out); + } else if (is == icSigXYZData && p->natpcs == icSigLabData) { + icmXYZ2Lab(&icmD50, out, in); + icxLuMatrixFwd_abs(p, out, out); + } else { + icxLuMatrixFwd_abs(p, out, in); + } +} + +/* - - - - - - - - - - - - - - - - - - - - - */ +/* Individual components of Bwd conversion: */ + +static int +icxLuMatrixBwd_abs ( +icxLuMatrix *p, /* This */ +double *out, /* Vector of output values */ +double *in /* Vector of input values */ +) { + int rv = 0; + + if (p->pcs == icxSigJabData) { + p->cam->cam_to_XYZ(p->cam, out, in); + /* Hack to prevent CAM02 weirdness being amplified by */ + /* any later per channel clipping. */ + /* Limit -Y to non-stupid values by scaling */ + if (out[1] < -0.1) { + out[0] *= -0.1/out[1]; + out[2] *= -0.1/out[1]; + out[1] = -0.1; + } + rv |= ((icmLuMatrix *)p->plu)->bwd_abs((icmLuMatrix *)p->plu, out, out); + } else { + rv |= ((icmLuMatrix *)p->plu)->bwd_abs((icmLuMatrix *)p->plu, out, in); + } + return rv; +} + +static int +icxLuMatrixBwd_matrix ( +icxLuMatrix *p, /* This */ +double *out, /* Vector of output values */ +double *in /* Vector of input values */ +) { + return ((icmLuMatrix *)p->plu)->bwd_matrix((icmLuMatrix *)p->plu, out, in); +} + +static int +icxLuMatrixBwd_curve ( +icxLuMatrix *p, /* This */ +double *out, /* Vector of output values */ +double *in /* Vector of input values */ +) { + return ((icmLuMatrix *)p->plu)->bwd_curve((icmLuMatrix *)p->plu, out, in); +} + +/* Overall Bwd conversion */ +static int +icxLuMatrixBwd_lookup ( +icxLuBase *pp, /* This */ +double *out, /* Vector of output values */ +double *in /* Vector of input values */ +) { + int rv = 0; + icxLuMatrix *p = (icxLuMatrix *)pp; + rv |= icxLuMatrixBwd_abs(p, out, in); + rv |= icxLuMatrixBwd_matrix(p, out, out); + rv |= icxLuMatrixBwd_curve(p, out, out); + return rv; +} + +static void +icxLuMatrix_free( +icxLuBase *p +) { + p->plu->del(p->plu); + if (p->cam != NULL) + p->cam->del(p->cam); + free(p); +} + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - */ +/* Given a nominated output PCS (ie. Absolute, Jab etc.), convert it in the bwd */ +/* direction into a relative XYZ or Lab PCS value */ +/* (This is used in generating gamut compression in B2A tables) */ +void icxLuMatrix_bwd_outpcs_relpcs( +icxLuBase *pp, +icColorSpaceSignature os, /* Output space, XYZ or Lab */ +double *out, double *in) { + icxLuMatrix *p = (icxLuMatrix *)pp; + + icxLuMatrixFwd_abs(p, out, in); + if (os == icSigXYZData && p->natpcs == icSigLabData) { + icmLab2XYZ(&icmD50, out, out); + } else if (os == icSigXYZData && p->natpcs == icSigLabData) { + icmXYZ2Lab(&icmD50, out, out); + } +} + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +static gamut *icxLuMatrixGamut(icxLuBase *plu, double detail); + +/* Do the basic icxLuMatrix creation and initialisation */ +static icxLuMatrix * +alloc_icxLuMatrix( + xicc *xicp, + icmLuBase *plu, /* Pointer to Lu we are expanding (ours) */ + int dir, /* 0 = fwd, 1 = bwd */ + int flags /* clip, merge flags */ +) { + icxLuMatrix *p; + + if ((p = (icxLuMatrix *) calloc(1,sizeof(icxLuMatrix))) == NULL) + return NULL; + + p->pp = xicp; + p->plu = plu; + p->del = icxLuMatrix_free; + p->lutspaces = icxLutSpaces; + p->spaces = icxLuSpaces; + p->get_native_ranges = icxLu_get_native_ranges; + p->get_ranges = icxLu_get_ranges; + p->efv_wh_bk_points = icxLuEfv_wh_bk_points; + p->get_gamut = icxLuMatrixGamut; + p->fwd_relpcs_outpcs = icxLuMatrix_fwd_relpcs_outpcs; + p->bwd_outpcs_relpcs = icxLuMatrix_bwd_outpcs_relpcs; + + p->nearclip = 0; /* Set flag defaults */ + p->mergeclut = 0; + p->noisluts = 0; + p->noipluts = 0; + p->nooluts = 0; + + p->intsep = 0; + + p->fwd_lookup = icxLuMatrixFwd_lookup; + p->fwd_curve = icxLuMatrixFwd_curve; + p->fwd_matrix = icxLuMatrixFwd_matrix; + p->fwd_abs = icxLuMatrixFwd_abs; + p->bwd_lookup = icxLuMatrixBwd_lookup; + p->bwd_abs = icxLuMatrixBwd_abs; + p->bwd_matrix = icxLuMatrixBwd_matrix; + p->bwd_curve = icxLuMatrixBwd_curve; + + if (dir) { /* Bwd */ + p->lookup = icxLuMatrixBwd_lookup; + p->inv_lookup = icxLuMatrixFwd_lookup; + } else { + p->lookup = icxLuMatrixFwd_lookup; + p->inv_lookup = icxLuMatrixBwd_lookup; + } + + /* There are no matrix specific flags */ + p->flags = flags; + + /* Get details of internal, native color space */ + p->plu->lutspaces(p->plu, &p->natis, NULL, &p->natos, NULL, &p->natpcs); + + /* Get other details of conversion */ + p->plu->spaces(p->plu, NULL, &p->inputChan, NULL, &p->outputChan, NULL, NULL, NULL, NULL, NULL); + + return p; +} + +/* We setup valid fwd and bwd component conversions, */ +/* but setup only the asked for overal conversion. */ +static icxLuBase * +new_icxLuMatrix( +xicc *xicp, +int flags, /* clip, merge flags */ +icmLuBase *plu, /* Pointer to Lu we are expanding */ +icmLookupFunc func, /* Functionality requested */ +icRenderingIntent intent, /* Rendering intent */ +icColorSpaceSignature pcsor, /* PCS override (0 = def) */ +icxViewCond *vc, /* Viewing Condition (NULL if pcsor is not CIECAM) */ +int dir /* 0 = fwd, 1 = bwd */ +) { + icxLuMatrix *p; + + /* Do basic creation and initialisation */ + if ((p = alloc_icxLuMatrix(xicp, plu, dir, flags)) == NULL) + return NULL; + + p->func = func; + + /* Init the CAM model */ + if (pcsor == icxSigJabData) { + if (vc != NULL) /* One has been provided */ + p->vc = *vc; /* Copy the structure */ + else + xicc_enum_viewcond(xicp, &p->vc, -1, NULL, 0, NULL); /* Use a default */ + p->cam = new_icxcam(cam_default); + p->cam->set_view(p->cam, p->vc.Ev, p->vc.Wxyz, p->vc.La, p->vc.Yb, p->vc.Lv, p->vc.Yf, + p->vc.Fxyz, XICC_USE_HK); + } else { + p->cam = NULL; + } + + /* Remember the effective intent */ + p->intent = intent; + + /* Get the effective spaces */ + plu->spaces(plu, &p->ins, NULL, &p->outs, NULL, NULL, NULL, NULL, &p->pcs, NULL); + + /* Override with pcsor */ + if (pcsor == icxSigJabData) { + p->pcs = pcsor; + if (func == icmBwd || func == icmGamut || func == icmPreview) + p->ins = pcsor; + if (func == icmFwd || func == icmPreview) + p->outs = pcsor; + } + + /* In general the native and effective ranges of the icx will be the same as the */ + /* underlying icm lookup object. */ + p->plu->get_lutranges(p->plu, p->ninmin, p->ninmax, p->noutmin, p->noutmax); + p->plu->get_ranges(p->plu, p->inmin, p->inmax, p->outmin, p->outmax); + + /* If we have a Jab PCS override, reflect this in the effective icx range. */ + /* Note that the ab ranges are nominal. They will exceed this range */ + /* for colors representable in L*a*b* PCS */ + if (p->ins == icxSigJabData) { + p->inmin[0] = 0.0; p->inmax[0] = 100.0; + p->inmin[1] = -128.0; p->inmax[1] = 128.0; + p->inmin[2] = -128.0; p->inmax[2] = 128.0; + } else if (p->outs == icxSigJabData) { + p->outmin[0] = 0.0; p->outmax[0] = 100.0; + p->outmin[1] = -128.0; p->outmax[1] = 128.0; + p->outmin[2] = -128.0; p->outmax[2] = 128.0; + } + + return (icxLuBase *)p; +} + +/* ========================================================== */ +/* xicc creation code */ +/* ========================================================== */ + +/* Context for figuring input curves */ +typedef struct { + rspl *r; /* Device -> PCS rspl */ + int linear; /* Flag */ + double nmin, nmax; /* PCS End points to linearise */ + double min, max; /* device End points to linearise */ +} mxinctx; + +#define NPARMS (9 + 6 + 3 * MXNORDERS) + +/* Context for optimising matrix */ +typedef struct { + int verb; /* Verbose */ + int optdim; /* Optimisation dimensions */ + int isLinear; /* NZ if no curves, fixed Gamma = 1.0 */ + int isGamma; /* NZ if gamma + matrix, else shaper */ + int isShTRC; /* NZ if shared TRC */ + int shape0gam; /* NZ if zero'th order shaper should be gamma function */ + int norders; /* Number of shaper orders */ + int clipbw; /* Prevent white > 1 and -ve black */ + int clipprims; /* Prevent primaries going -ve */ + double smooth; /* Shaper smoothing factor (nominal = 1.0) */ + double dscale; /* Scale device values */ + double v[NPARMS]; /* Holder for parameters */ + double sa[NPARMS]; /* Initial search area */ + /* Rest are matrix : */ + /* 0 1 2 R X */ + /* 3 4 5 * G = Y */ + /* 6 7 8 B Z */ + /* For Gamma: */ + /* 9, 10, 11 are gamma */ + /* Else for shaper only: */ + /* 9, 10, 11 are Input Offset */ + /* 12, 13, 14 are Output Offset */ + /* 15, 16, 17 are Gamma or 0th harmonics */ + /* 18, 19, 20 are 1st harmonics */ + /* 21, 22, 23 are 2nd harmonics */ + /* 24, 25, 26 etc. */ + /* For isShTRC there is only one set of offsets & harmonics */ + icmXYZNumber wp; /* Assumed white point for Lab conversion */ + cow *points; /* List of test points as dev->Lab */ + int nodp; /* Number of data points */ +} mxopt; + +/* Per chanel function being optimised */ +static void mxmfunc1(mxopt *p, int j, double *v, double *out, double *in) { + double vv, g; + int ps = 3; /* Parameter spacing */ + + vv = *in * p->dscale; + + if (p->isShTRC) { + j = 0; + ps = 1; /* Only one channel */ + } + + if (p->isLinear) { /* No per channel curve */ + *out = vv; + return; + } + + if (p->isGamma) { /* Pure Gamma */ + /* Apply gamma */ + g = v[9 + j]; + if (g <= 0.0) + vv = 1.0; + else { + if (vv >= 0.0) { + vv = pow(vv, g); + } else { + vv = -pow(-vv, g); + } + } + } else { /* Add extra shaper parameters */ + int ord; + + if (p->shape0gam) { + + /* Apply input offset */ + g = v[9 + j]; /* Offset value */ + + if (g >= 1.0) { + vv = 1.0; + } else { + vv = g + ((1.0 - g) * vv); + } + + /* Apply gamma as order 0 */ + g = v[9 + 2 * ps + j]; + if (g <= 0.0) + vv = 1.0; + else { + /* Power with straight line at small values */ + if (vv >= XSHAPE_GAMTHR) { + vv = pow(vv, g); + } else { + double slope, oth; + + oth = pow(XSHAPE_GAMTHR, g); /* Output at input threshold */ + slope = g * pow(XSHAPE_GAMTHR, g - 1.0); /* Slope at input threshold */ + vv = oth + (vv - XSHAPE_GAMTHR) * slope; /* Straight line */ + } + } + } + + /* Process all the shaper orders 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 */ + /* can't be non-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. ] */ + if (p->shape0gam) + ord = 1; + else + ord = 0; + for (; ord < p->norders; ord++) + { + int nsec; /* Number of sections */ + double sec; /* Section */ + g = v[9 + 2 * ps + ord * ps + j]; /* 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; + } + + /* (For extrapolation it helps to pin 0 & 1) */ + if (p->shape0gam) { + /* Apply output offset */ + g = v[9 + 1 * ps + j]; /* Offset value */ + + if (g >= 1.0) { + vv = 1.0; + } else if (g > 0.0) { + vv = g + ((1.0 - g) * vv); + } + } + } + + *out = vv; +} + +/* Function being optimised */ +static void mxmfunc(mxopt *p, double *v, double *xyz, double *in) { + int j; + double rgb[3]; + + /* Apply per channel processing */ + for (j = 0; j < 3; j++) + mxmfunc1(p, j, v, &rgb[j], &in[j]); + + /* Apply matrix */ + xyz[0] = v[0] * rgb[0] + v[1] * rgb[1] + v[2] * rgb[2]; + xyz[1] = v[3] * rgb[0] + v[4] * rgb[1] + v[5] * rgb[2]; + xyz[2] = v[6] * rgb[0] + v[7] * rgb[1] + v[8] * rgb[2]; + +} + +/* return the sum of the squares of the current shaper parameters */ +static double xshapmag( +mxopt *p, /* Base of optimisation structure */ +double *v /* Pointer to parameters */ +) { + double tt, w, tparam = 0.0; + int f, g; + + if (p->isGamma) { /* Pure Gamma only */ + return 0.0; + } + + if (p->isShTRC) { + /* Input offset value */ + if (p->shape0gam) + w = XSHAPE_OFFG; + else + w = XSHAPE_OFFS; + + tt = v[9]; + tt *= tt; + tparam += w * tt; + + /* Output offset value */ + tt = v[10]; + tt *= tt; + tparam += w * tt; + + /* Shaper values */ + for (f = 0; f < p->norders; f++) { + tt = v[11 + f]; + if (f == 0 && p->shape0gam) + tt -= 1.0; /* default is linear */ + tt *= tt; + /* Weigh to suppress ripples */ + if (f <= 1) { /* Use XSHAPE_HW01 */ + w = XSHAPE_HW01; + } else if (f <= XSHAPE_HBREAK) { /* Blend from XSHAPE_HW01 to XSHAPE_HWBR * smooth */ + double bl = (f - 1.0)/(XSHAPE_HBREAK - 1.0); + w = (1.0 - bl) * XSHAPE_HW01 + bl * XSHAPE_HWBR * p->smooth; + } else { /* Use XSHAPE_HWBR * smooth */ + w = XSHAPE_HWBR + (f-XSHAPE_HBREAK) * XSHAPE_HWINC; + w *= p->smooth; + } + tparam += w * tt; + } + return XSHAPE_MAG * tparam; + } + + /* Input ffset value */ + if (p->shape0gam) + w = XSHAPE_OFFG; + else + w = XSHAPE_OFFS; + + for (g = 0; g < 3; g++) { + tt = v[9 + g]; + tt *= tt; + tparam += w * tt; + } + /* Output ffset value */ + for (g = 0; g < 3; g++) { + tt = v[12 + g]; + tt *= tt; + tparam += w * tt; + } + /* Shaper values */ + for (f = 0; f < p->norders; f++) { + /* Weigh to suppress ripples */ + if (f <= 1) { + w = XSHAPE_HW01; + } else if (f <= XSHAPE_HBREAK) { + double bl = (f - 1.0)/(XSHAPE_HBREAK - 1.0); + w = (1.0 - bl) * XSHAPE_HW01 + bl * XSHAPE_HWBR * p->smooth; + } else { + w = XSHAPE_HWBR + (f-XSHAPE_HBREAK) * XSHAPE_HWINC * p->smooth; + w *= p->smooth; + } + for (g = 0; g < 3; g++) { + tt = v[15 + 3 * f + g]; + if (f == 0 && p->shape0gam) + tt -= 1.0; /* default is linear */ + tt *= tt; + tparam += w * tt; + } + } + return XSHAPE_MAG * tparam/3.0; +} + +/* Matrix optimisation function handed to powell() */ +static double mxoptfunc(void *edata, double *v) { + mxopt *p = (mxopt *)edata; + double err = 0.0, rv = 0.0, smv; + double xyz[3], lab[3]; + int i; + + for (i = 0; i < p->nodp; i++) { + + /* Apply our function */ +//printf("%f %f %f -> %f %f %f\n", p->points[i].p[0], p->points[i].p[1], p->points[i].p[2], xyz[0], xyz[1], xyz[2]); + mxmfunc(p, v, xyz, p->points[i].p); + + /* Convert to Lab */ + icmXYZ2Lab(&p->wp, lab, xyz); +//printf("%f %f %f -> %f %f %f, target %f %f %f\n", p->points[i].p[0], p->points[i].p[1], p->points[i].p[2], lab[0], lab[1], lab[2], p->points[i].v[0], p->points[i].v[1], p->points[i].v[2]); + + /* Accumulate total delta E squared */ +#ifdef USE_CIE94_DE + rv += p->points[i].w * icmCIE94sq(lab, p->points[i].v); +#else + rv += p->points[i].w * icmLabDEsq(lab, p->points[i].v); +#endif + } + + /* Normalise error to be an average delta E squared */ + rv /= (double)p->nodp; + + /* Sum with shaper parameters squared, to */ + /* minimise unsconstrained "wiggles" */ + smv = xshapmag(p, v); + rv += smv; + + /* Penalize if we have white > 1 or -ve black */ + if (p->clipbw) { + double tp[3]; + + tp[0] = tp[1] = tp[2] = 1.0; + mxmfunc(p, v, xyz, tp); + if ((xyz[1] - 1.0) > err) + err = xyz[1] - 1.0; + + tp[0] = tp[1] = tp[2] = 0.0; + mxmfunc(p, v, xyz, tp); + for (i = 0; i < 3; i++) { + if (-xyz[i] > err) + err = -v[i]; + } + } + + /* Penalize if we have -ve primaries */ + if (p->clipprims) { + for (i = 0; i < 9; i++) { + if (-v[i] > err) + err = -v[i]; + } + } + rv += err * 1000.0; + +#ifdef DEBUG +printf("~9(%f)mxoptfunc returning %f\n",smv,rv); +#endif + + return rv; +} + +/* Matrix progress function handed to powell() */ +static void mxprogfunc(void *pdata, int perc) { + mxopt *p = (mxopt *)pdata; + + if (p->verb) { + printf("%c% 3d%%",cr_char,perc); + if (perc == 100) + printf("\n"); + fflush(stdout); + } +} + + +/* Given a correction matrix, transform the matrix values */ +static void mxtransform(mxopt *os, double mat[3][3]) { + double vec[3]; + + vec[0] = os->v[0]; vec[1] = os->v[3]; vec[2] = os->v[6]; + icmMulBy3x3(vec, mat, vec); + os->v[0] = vec[0]; os->v[3] = vec[1]; os->v[6] = vec[2]; + + vec[0] = os->v[1]; vec[1] = os->v[4]; vec[2] = os->v[7]; + icmMulBy3x3(vec, mat, vec); + os->v[1] = vec[0]; os->v[4] = vec[1]; os->v[7] = vec[2]; + + vec[0] = os->v[2]; vec[1] = os->v[5]; vec[2] = os->v[8]; + icmMulBy3x3(vec, mat, vec); + os->v[2] = vec[0]; os->v[5] = vec[1]; os->v[8] = vec[2]; +} + + +/* Setup and then return the optimized matrix fit in the mxopt structure. */ +/* Return 0 on sucess, error code on failure. */ +static int +createMatrix( +char *err, /* Return error message */ +mxopt *os, /* Optimisation information */ +int verb, /* NZ if verbose */ +int nodp, /* Number of points */ +cow *ipoints, /* Array of input points in XYZ space */ +int isLab, /* nz if data points are Lab */ +int quality, /* Quality metric, 0..3 (-1 == 2 orders only) */ +int isLinear, /* NZ if pure linear, gamma = 1.0 */ +int isGamma, /* NZ if gamma rather than shaper */ +int isShTRC, /* NZ if shared TRCs */ +int shape0gam, /* NZ if zero'th order shaper should be gamma function */ +int clipbw, /* Prevent white > 1 and -ve black */ +int clipprims, /* Prevent primaries going -ve */ +double smooth, /* Smoothing factor (nominal 1.0) */ +double scale /* Scale device values */ +) { + double nweight = 1.0; /* Amount to weight neutral patches (make a parameter ?) */ + int inputChan = 3; /* Must be RGB like */ + int outputChan = 3; /* Must be the PCS */ + int rsplflags = 0; /* Flags for scattered data rspl */ + int e, f, i, j; + int maxits = 200; /* Optimisation stop params */ + double stopon = 0.01; /* Absolute delta E change to stop on */ + cow *points; /* Lab copy of ipoints */ + double rerr; + +#ifdef DEBUG_PLOT + #define XRES 100 + double xx[XRES]; + double y1[XRES]; +#endif /* DEBUG_PLOT */ + + if (verb) + rsplflags |= RSPL_VERBOSE; + + /* Allocate the array passed to fit_rspl() */ + if ((points = (cow *)malloc(sizeof(cow) * nodp)) == NULL) { + if (err != NULL) + sprintf(err,"Allocation of scattered coordinate array failed"); + return 2; + } + + /* Setup for optimising run */ + if (verb != 0) + os->verb = verb; + else + os->verb = 0; + os->points = points; + os->nodp = nodp; + os->isShTRC = 0; + os->shape0gam = shape0gam; + os->smooth = smooth; + os->clipbw = clipbw; + os->clipprims = clipprims; + os->dscale = scale; + + /* Set quality/effort factors */ + if (quality >= 3) { /* Ultra high */ + os->norders = 20; + maxits = 10000; + stopon = 5e-7; + } else if (quality == 2) { /* High */ + os->norders = 12; + maxits = 5000; + stopon = 5e-6; + } else if (quality == 1) { /* Medium */ + os->norders = 8; + maxits = 2000; + stopon = 5e-5; + } else if (quality == 0) { /* Low */ + os->norders = 4; + maxits = 1000; + stopon = 5e-4; + } else { /* Ultra Low */ + os->norders = 2; + maxits = 1000; + stopon = 5e-4; + } + if (os->norders > MXNORDERS) + os->norders = MXNORDERS; + + /* Setup points ready for optimisation and do an initial Lab conversion */ + for (i = 0; i < nodp; i++) { + for (e = 0; e < inputChan; e++) + points[i].p[e] = ipoints[i].p[e]; + + for (f = 0; f < outputChan; f++) + points[i].v[f] = ipoints[i].v[f]; + + points[i].w = ipoints[i].w; + } + + /* Pick a white point for the real Lab conversion */ + { + double wp[3]; + double wpy = -1e60; + int wix = -1; + + /* We assume that the input target is well behaved, */ + /* and that it includes a white point patch, */ + /* and that it has an extreme L value */ + + for (i = 0; i < nodp; i++) { + double yv; + + /* Tilt things towards D50 neutral white patches */ + yv = points[i].v[0] - 0.3 * sqrt(points[i].v[1] * points[i].v[1] + + points[i].v[2] * points[i].v[2]); + if (yv > wpy) { + wpy = yv; + wix = i; + } + } +//printf("~1 picked point %d as white\n",wix); + icmLab2XYZ(&icmD50, wp, points[wix].v); + wp[0] /= wp[1]; + wp[2] /= wp[1]; + wp[1] = 1.0; + icmAry2XYZ(os->wp, wp); + + /* We'll use this wp for delta E calculation when creating the matrix */ +// if (os->verb) printf("Switching to L*a*b* white point %f %f %f\n",os->wp.X,os->wp.Y,os->wp.Z); + if (nweight < 1.0) /* Sanity */ + nweight = 1.0; + for (i = 0; i < nodp; i++) { + double lch[3]; + if (isLab) + icmLab2XYZ(&icmD50, points[i].v, points[i].v); + icmXYZ2Lab(&os->wp, points[i].v, points[i].v); + icmLab2LCh(lch, points[i].v); + /* Apply any neutral weighting */ + if (lch[1] < 10.0) { + double w = nweight; + if (lch[1] > 5.0) + w = 1.0 + (nweight - 1.0) * (10.0 - lch[1])/(10.0 - 5.0); + points[i].w = w; + } +//printf("~1 patch %d = Lab %f %f %f, C = %f w = %f\n",i,points[i].v[0], points[i].v[1], points[i].v[2], lch[1],points[i].w); + } + } + + /* Set initial matrix optimisation values */ + os->v[0] = 0.4; os->v[1] = 0.4; os->v[2] = 0.2; /* Matrix */ + os->v[3] = 0.2; os->v[4] = 0.8; os->v[5] = 0.1; + os->v[6] = 0.02; os->v[7] = 0.15; os->v[8] = 1.3; + + /* Do a first pass just setting the matrix values */ + os->isLinear = 1; + os->isGamma = 1; + os->optdim = 9; + os->v[9] = os->v[10] = os->v[11] = 1.0; /* Linear */ + + /* Set search area to starting values */ + for (j = 0; j < os->optdim; j++) + os->sa[j] = 0.2; /* Matrix, Gamma, Offsets, harmonics */ + + if (os->verb) + printf("Creating matrix...\n"); + + if (powell(&rerr, os->optdim, os->v, os->sa, stopon, maxits, + mxoptfunc, (void *)os, mxprogfunc, (void *)os) != 0) + warning("Powell failed to converge, residual error = %f",rerr); + +#ifndef NEVER + if (os->verb) { + printf("Matrix = %f %f %f\n",os->v[0], os->v[1], os->v[2]); + printf(" %f %f %f\n",os->v[3], os->v[4], os->v[5]); + printf(" %f %f %f\n",os->v[6], os->v[7], os->v[8]); + } +#endif /* NEVER */ + + /* Now optimize again with shaper or gamma curves */ + if (!isLinear || isGamma) { + + /* Start from linear, which is what was assumed for the matrix fit, */ + /* and fit first with a single shared curve. */ + os->isShTRC = 1; + if (isGamma) { /* Just gamma curve */ + os->isLinear = 0; + os->isGamma = 1; + os->optdim = 10; + os->v[9] = 1.0; /* Linear */ + } else { /* Creating input curves */ + os->isLinear = 0; + os->isGamma = 0; + os->optdim = 9 + 2 + os->norders; /* Matrix, offset + orders */ + os->v[9] = 0.0; /* Input offset */ + os->v[10] = 0.0; /* Output offset */ + if (shape0gam) + os->v[11] = 1.0; /* Gamma */ + else + os->v[11] = 0.0; /* 0th Harmonic */ + for (i = 12; i < os->optdim; i++) + os->v[i] = 0.0; /* Higher orders */ + } + + /* Set search area to starting values */ + for (j = 0; j < os->optdim; j++) + os->sa[j] = 0.2; /* Matrix, Gamma, Offsets, harmonics */ + + if (os->verb) + printf("Creating matrix and single curve...\n"); + + if (powell(&rerr, os->optdim, os->v, os->sa, stopon, maxits, + mxoptfunc, (void *)os, mxprogfunc, (void *)os) != 0) + warning("Powell failed to converge, residual error = %f",rerr); + +#ifndef NEVER + if (os->verb) { + printf("Matrix = %f %f %f\n",os->v[0], os->v[1], os->v[2]); + printf(" %f %f %f\n",os->v[3], os->v[4], os->v[5]); + printf(" %f %f %f\n",os->v[6], os->v[7], os->v[8]); + if (isGamma) { /* Creating input curves */ + printf("Gamma = %f\n",os->v[9]); + } else { /* Creating input curves */ + printf("Input offset = %f\n",os->v[9]); + printf("Output offset = %f\n",os->v[10]); + for (j = 0; j < os->norders; j++) { + if (shape0gam && j == 0) + printf("gamma = %f\n", os->v[11 + j]); + else + printf("%d harmonics = %f\n",j, os->v[11 + j]); + } + } + } +#endif /* NEVER */ + + /* Now do the final optimisation with all curves */ + if (!isShTRC) { + os->isShTRC = 0; + if (isGamma) { /* Just gamma curves */ + os->isLinear = 0; + os->isGamma = 1; + os->optdim = 12; + os->v[9] = os->v[10] = os->v[11] = 1.0; /* Linear */ + } else { /* Creating input curves */ + os->isLinear = 0; + os->isGamma = 0; + os->optdim = 9 + 6 + 3 * os->norders; /* Matrix, offset + orders */ + os->v[9] = os->v[10] = os->v[11] = 0.0; /* Input offset */ + os->v[12] = os->v[13] = os->v[14] = 0.0; /* Output offset */ + if (shape0gam) + os->v[15] = os->v[16] = os->v[17] = 1.0; /* Gamma */ + else + os->v[15] = os->v[16] = os->v[17] = 0.0; /* 0th Harmonic */ + for (i = 18; i < os->optdim; i++) + os->v[i] = 0.0; /* Higher orders */ + } + + /* Set search area to starting values */ + for (j = 0; j < os->optdim; j++) + os->sa[j] = 0.2; /* Matrix, Gamma, Offsets, harmonics */ + + if (os->verb) + printf("Creating matrix and curves...\n"); + + if (powell(&rerr, os->optdim, os->v, os->sa, stopon, maxits, + mxoptfunc, (void *)os, mxprogfunc, (void *)os) != 0) + warning("Powell failed to converge, residual error = %f",rerr); + } + } + if (os->clipprims) { /* Clip -ve primaries */ + for (i = 0; i < 9; i++) { + if (os->v[i] < 0.0) + os->v[i] = 0.0; + } + } + +#ifndef NEVER + if (os->verb) { + printf("Matrix = %f %f %f\n",os->v[0], os->v[1], os->v[2]); + printf(" %f %f %f\n",os->v[3], os->v[4], os->v[5]); + printf(" %f %f %f\n",os->v[6], os->v[7], os->v[8]); + if (!isLinear) { /* Creating input curves */ + if (isGamma) { /* Creating input curves */ + if (isShTRC) + printf("Gamma = %f\n",os->v[9]); + else + printf("Gamma = %f %f %f\n",os->v[9], os->v[10], os->v[11]); + } else { /* Creating input curves */ + if (isShTRC) { + printf("Input offset = %f\n",os->v[9]); + printf("Output offset = %f\n",os->v[10]); + } else { + printf("Input offset = %f %f %f\n",os->v[9], os->v[10], os->v[11]); + printf("Output offset = %f %f %f\n",os->v[12], os->v[13], os->v[14]); + } + for (j = 0; j < os->norders; j++) { + if (isShTRC) { + if (shape0gam && j == 0) + printf("gamma = %f\n", os->v[11 + j]); + else + printf("%d harmonics = %f\n",j, os->v[11 + j]); + } else { + if (shape0gam && j == 0) + printf("%d gamma = %f %f %f\n",j, os->v[15 + j * 3], + os->v[16 + j * 3], os->v[17 + j * 3]); + else + printf("%d harmonics = %f %f %f\n",j, os->v[15 + j * 3], + os->v[16 + j * 3], os->v[17 + j * 3]); + } + } + } + } + } +#endif /* NEVER */ +#ifdef NEVER /* Check DE of fit */ + { + double xyz[3], txyz[3]; + + for (i = 0; i < nodp; i++) { + + mxmfunc(os, os->v, xyz, ipoints[i].p); + + if (isLab) + icmLab2XYZ(&icmD50, txyz, ipoints[i].v); + else + icmCpy3(txyz, ipoints[i].v); + + printf("~1 point %d DE %f\n", i, icmXYZLabDE(&icmD50, txyz, xyz)); + } + } +#endif + + /* Free the coordinate lists */ + free(points); + + return 0; +} + +/* Apply a chromatic transform to the matrix to force the given */ +/* xyz value (typically white) to be exact */ +static void icxMM_force_exact(icxMatrixModel *p, double *targ, double *rgb) { + mxopt *os = (mxopt *)p->imp; + double txyz[3], axyz[3]; /* Target & actual xyz */ + icmXYZNumber _tp, _ap; + double cmat[3][3]; /* Model transform matrix */ + + if (p->isLab) + icmLab2XYZ(&icmD50, txyz, targ); + else + icmCpy3(txyz, targ); + + mxmfunc(os, os->v, axyz, rgb); + + icmAry2XYZ(_ap, axyz); + icmAry2XYZ(_tp, txyz); + icmChromAdaptMatrix(ICM_CAM_BRADFORD, _tp, _ap, cmat); + + /* Apply correction to fine tune matrix. */ + mxtransform(os, cmat); +} + +static void icxMM_lookup(icxMatrixModel *p, double *out, double *in) { + mxopt *os = (mxopt *)p->imp; + + mxmfunc(os, os->v, out, in); + + if (p->isLab) + icmXYZ2Lab(&icmD50, out, out); +} + +static void icxMM_del(icxMatrixModel *p) { + free(p->imp); + free(p); +} + +/* Create a matrix model of a set of points, and return an object to lookup */ +/* points from the model. Return NULL on error. */ +icxMatrixModel *new_MatrixModel( +int verb, /* NZ if verbose */ +int nodp, /* Number of points */ +cow *ipoints, /* Array of input points in XYZ space */ +int isLab, /* nz if data points are Lab */ +int quality, /* Quality metric, 0..3 (-1 == 2 orders only) */ +int isLinear, /* NZ if pure linear, gamma = 1.0 */ +int isGamma, /* NZ if gamma rather than shaper */ +int isShTRC, /* NZ if shared TRCs */ +int shape0gam, /* NZ if zero'th order shaper should be gamma function */ +int clipbw, /* Prevent white > 1 and -ve black */ +int clipprims, /* Prevent primaries going -ve */ +double smooth, /* Smoothing factor (nominal 1.0) */ +double scale /* Scale device values */ +) { + icxMatrixModel *p; + + if ((p = (icxMatrixModel *) calloc(1,sizeof(icxMatrixModel))) == NULL) + return NULL; + + p->force = icxMM_force_exact; + p->lookup = icxMM_lookup; + p->del = icxMM_del; + + if ((p->imp = (void *) calloc(1,sizeof(mxopt))) == NULL) { + free(p); + return NULL; + } + + if (createMatrix(NULL, (mxopt *)p->imp, verb, nodp, ipoints, isLab, quality, + isLinear, isGamma, isShTRC, shape0gam, + clipbw, clipprims, smooth, scale) != 0) { + free(p->imp); + free(p); + return NULL; + } + + p->isLab = isLab; + + return p; +} + +/* Create icxLuMatrix and undelying tone reproduction curves and */ +/* colorant tags, initialised from the icc, and then overwritten */ +/* by a conversion created from the supplied scattered data points. */ + +/* The scattered data is assumed to map Device -> native PCS (ie. dir = Fwd) */ +/* NOTE:- in theory once this icxLuMatrix is setup, it can be */ +/* called to translate color values. In practice I suspect */ +/* that the icxLuMatrix hasn't been setup completely enough to allows this. */ +/* Might be easier to close it and re-open it ? */ +static icxLuBase * +set_icxLuMatrix( +xicc *xicp, +icmLuBase *plu, /* Pointer to Lu we are expanding (ours) */ +int flags, /* white/black point flags */ +int nodp, /* Number of points */ +int nodpbw, /* Number of points to look for white & black patches in */ +cow *ipoints, /* Array of input points in XYZ space */ +icxMatrixModel *skm, /* Optional skeleton model (not used here) */ +double dispLuminance, /* > 0.0 if display luminance value and is known */ +double wpscale, /* > 0.0 if input white point is to be scaled */ +int quality, /* Quality metric, 0..3 */ +double smooth /* Curve smoothing, nominally 1.0 */ +) { + icxLuMatrix *p; /* Object being created */ + icc *icco = xicp->pp; /* Underlying icc object */ + icmLuMatrix *pmlu = (icmLuMatrix *)plu; /* icc matrix lookup object */ + int luflags = 0; /* icxLuMatrix alloc clip, merge flags */ + int isLinear = 0; /* NZ if pure linear, gamma = 1.0 */ + int isGamma = 0; /* NZ if gamma rather than shaper */ + int isShTRC = 0; /* NZ if shared TRCs */ + int inputChan = 3; /* Must be RGB like */ + int outputChan = 3; /* Must be the PCS */ + icmHeader *h = icco->header; /* Pointer to icc header */ + int rsplflags = 0; /* Flags for scattered data rspl */ + int e, f, i, j; + int maxits = 200; /* Optimisation stop params */ + double stopon = 0.01; /* Absolute delta E change to stop on */ + mxopt os; /* Optimisation information */ + double rerr; + /* If ICX_SET_WHITE | ICX_SET_BLACK: */ + double wp[3]; /* Absolute White point in XYZ */ + double bp[3]; /* Absolute Black point in XYZ */ + double dw[MXDI]; /* Device white value to adjust to be D50 */ + double db[MXDI]; /* Device balck value */ + double dgw[3]; /* Device space gamut boundary white for ICX_SET_WHITE_US */ + double fromAbs[3][3]; /* From abs to relative */ + double toAbs[3][3]; /* To abs from relative */ + cow *rpoints = NULL; /* Aprox. relative in->output values */ + +#ifdef DEBUG_PLOT + #define XRES 100 + double xx[XRES]; + double y1[XRES]; +#endif /* DEBUG_PLOT */ + + if (flags & ICX_VERBOSE) + rsplflags |= RSPL_VERBOSE; + + luflags = flags; /* Transfer straight though ? */ + + /* Check out some things about the profile */ + { + icmCurve *wor, *wog, *wob; + wor = pmlu->redCurve; + wog = pmlu->greenCurve; + wob = pmlu->blueCurve; + + if (wor == wog) { + if (wog != wob) { + xicp->errc = 1; + sprintf(xicp->err,"icx_set_matrix: TRC sharing is inconsistent"); + return NULL; + } + isShTRC = 1; + } + if (wor->flag != wog->flag || wog->flag != wob->flag) { + xicp->errc = 1; + sprintf(xicp->err,"icx_set_matrix: TRC type is inconsistent"); + return NULL; + } + if (wor->flag == icmCurveGamma) { + isGamma = 1; + } + + if (flags & ICX_NO_IN_SHP_LUTS) { + isLinear = 1; + } + } + + /* Do basic icxLu creation and initialisation */ + if ((p = alloc_icxLuMatrix(xicp, plu, 0, luflags)) == NULL) { + xicp->errc = 1; + sprintf(xicp->err,"icx_set_matrix: malloc failed"); + return NULL; + } + + p->func = icmFwd; /* Assumed by caller */ + + /* Get the effective spaces of underlying icm, and set icx the same */ + plu->spaces(plu, &p->ins, NULL, &p->outs, NULL, NULL, &p->intent, NULL, &p->pcs, NULL); + + /* For set_icx the effective pcs has to be the same as the native pcs */ + + /* Sanity check for matrix */ + if (p->pcs != icSigXYZData) { + p->pp->errc = 1; + sprintf(p->pp->err,"Can't create matrix profile with PCS of Lab !"); + p->del((icxLuBase *)p); + return NULL; + } + + /* In general the native and effective ranges of the icx will be the same as the */ + /* underlying icm lookup object. */ + p->plu->get_lutranges(p->plu, p->ninmin, p->ninmax, p->noutmin, p->noutmax); + p->plu->get_ranges(p->plu, p->inmin, p->inmax, p->outmin, p->outmax); + + /* If we have a Jab PCS override, reflect this in the effective icx range. */ + /* Note that the ab ranges are nominal. They will exceed this range */ + /* for colors representable in L*a*b* PCS */ + if (p->ins == icxSigJabData) { + p->inmin[0] = 0.0; p->inmax[0] = 100.0; + p->inmin[1] = -128.0; p->inmax[1] = 128.0; + p->inmin[2] = -128.0; p->inmax[2] = 128.0; + } else if (p->outs == icxSigJabData) { + p->outmin[0] = 0.0; p->outmax[0] = 100.0; + p->outmin[1] = -128.0; p->outmax[1] = 128.0; + p->outmin[2] = -128.0; p->outmax[2] = 128.0; + } + + /* ------------------------------- */ + + /* Choose a white and black point */ + if (flags & (ICX_SET_WHITE | ICX_SET_BLACK)) { + + if (flags & ICX_VERBOSE) + printf("Find white & black points\n"); + + /* Compute device white and black points as if */ + /* we are doing an Output or Display device */ + { + switch (h->colorSpace) { + + case icSigCmyData: + for (e = 0; e < p->inputChan; e++) { + dw[e] = 0.0; + db[e] = 1.0; + } + break; + case icSigRgbData: + for (e = 0; e < p->inputChan; e++) { + dw[e] = 1.0; + db[e] = 0.0; + } + break; + + default: { + xicp->errc = 1; + sprintf(xicp->err,"set_icxLuMatrix: can't handle color space %s", + icm2str(icmColorSpaceSignature, h->colorSpace)); + p->del((icxLuBase *)p); + return NULL; + break; + } + } + } + + /* dw is what we want for dgw[], used for XFIT_OUT_WP_REL_US */ + for (e = 0; e < p->inputChan; e++) + dgw[e] = dw[e]; + + /* If this is actuall an input device, lookup wp & bp */ + /* and override dwhite & dblack */ + if (h->deviceClass == icSigInputClass) { + double wpy = -1e60, bpy = 1e60; + int wix = -1, bix = -1; + + /* We assume that the input target is well behaved, */ + /* and that it includes a white and black point patch, */ + /* and that they have the extreme L/Y values */ + + /* + NOTE that this may not be the best approach ! + It may be better to average the chromaticity + of all the neutral seeming patches, since + the whitest patch may have (for instance) + a blue tint. + */ + + /* Discover the white and black patches */ + for (i = 0; i < nodpbw; i++) { + double labv[3], yv; + + /* Create D50 Lab to allow some chromatic sensitivity */ + /* in picking the white point */ + icmXYZ2Lab(&icmD50, labv, ipoints[i].v); + +#ifdef NEVER + /* Choose Y */ + if (ipoints[i].v[1] > wpy) { + wp[0] = ipoints[i].v[0]; + wp[1] = ipoints[i].v[1]; + wp[2] = ipoints[i].v[2]; + for (e = 0; e < p->inputChan; e++) + dw[e] = ipoints[i].p[e]; + wpy = ipoints[i].v[1]; + wix = i; + } +#else + + /* Tilt things towards D50 neutral white patches */ + yv = labv[0] - 0.3 * sqrt(labv[1] * labv[1] + labv[2] * labv[2]); + if (yv > wpy) { + wp[0] = ipoints[i].v[0]; + wp[1] = ipoints[i].v[1]; + wp[2] = ipoints[i].v[2]; + for (e = 0; e < p->inputChan; e++) + dw[e] = ipoints[i].p[e]; + wpy = yv; + wix = i; + } +#endif + if (ipoints[i].v[1] < bpy) { + bp[0] = ipoints[i].v[0]; + bp[1] = ipoints[i].v[1]; + bp[2] = ipoints[i].v[2]; + for (e = 0; e < p->inputChan; e++) + db[e] = ipoints[i].p[e]; + bpy = ipoints[i].v[1]; + bix = i; + } + } + if (flags & ICX_VERBOSE) { + printf("Picked white patch %d with dev = %s\n XYZ = %s, Lab = %s\n", + wix+1, icmPdv(p->inputChan, dw), icmPdv(3, wp), icmPLab(wp)); + printf("Picked black patch %d with dev = %s\n XYZ = %s, Lab = %s\n", + bix+1, icmPdv(p->inputChan, db), icmPdv(3, bp), icmPLab(bp)); + } + + } else { + /* We assume that the display target is well behaved, */ + /* and that it includes a white point patch. */ + int nw = 0; + + wp[0] = wp[1] = wp[2] = 0.0; + + switch (h->colorSpace) { + + case icSigCmyData: + for (i = 0; i < nodpbw; i++) { + if (ipoints[i].p[0] < 0.001 + && ipoints[i].p[1] < 0.001 + && ipoints[i].p[2] < 0.001) { + wp[0] += ipoints[i].v[0]; + wp[1] += ipoints[i].v[1]; + wp[2] += ipoints[i].v[2]; + nw++; + } + } + break; + case icSigRgbData: + for (i = 0; i < nodpbw; i++) { + if (ipoints[i].p[0] > 0.999 + && ipoints[i].p[1] > 0.999 + && ipoints[i].p[2] > 0.999) { + wp[0] += ipoints[i].v[0]; + wp[1] += ipoints[i].v[1]; + wp[2] += ipoints[i].v[2]; + nw++; + } + } + break; + + default: + xicp->errc = 1; + sprintf(xicp->err,"set_icxLuMatrix: can't handle color space %s", + icm2str(icmColorSpaceSignature, h->colorSpace)); + p->del((icxLuBase *)p); + return NULL; + break; + } + + if (nw == 0) { + xicp->errc = 1; + sprintf(xicp->err,"set_icxLuMatrix: can't handle test points without a white patch"); + p->del((icxLuBase *)p); + return NULL; + } + wp[0] /= (double)nw; + wp[1] /= (double)nw; + wp[2] /= (double)nw; + + if (flags & ICX_VERBOSE) { + printf("Initial white point = %f %f %f\n",wp[0],wp[1],wp[2]); + } + + /* Need to lookup bp[] before we set the tag */ + } + + /* Create some abs<->rel chromatic conversions */ + { + icmXYZNumber _wp; + icmAry2XYZ(_wp, wp); + + /* Absolute->Aprox. Relative Adaptation matrix */ + icmChromAdaptMatrix(ICM_CAM_BRADFORD, icmD50, _wp, fromAbs); + + /* Aproximate relative to absolute conversion matrix */ + icmChromAdaptMatrix(ICM_CAM_BRADFORD, _wp, icmD50, toAbs); + } + + } else { + icmSetUnity3x3(fromAbs); + icmSetUnity3x3(toAbs); + } + + /* Create copy of input points with output converted to white relative */ + if ((rpoints = (cow *)malloc(nodp * sizeof(cow))) == NULL) { + xicp->errc = 1; + sprintf(xicp->err,"set_icxLuMatrix: malloc failed"); + p->del((icxLuBase *)p); + return NULL; + } + for (i = 0; i < nodp; i++) { + rpoints[i].w = ipoints[i].w; + for (e = 0; e < inputChan; e++) + rpoints[i].p[e] = ipoints[i].p[e]; + for (f = 0; f < outputChan; f++) + rpoints[i].v[f] = ipoints[i].v[f]; + + /* abs out -> aprox. rel out */ + icmMulBy3x3(rpoints[i].v, fromAbs, rpoints[i].v); + } + + /* ------------------------------- */ + + /* (Use a gamma curve as 0th order shape) */ + if ((p->pp->errc = createMatrix(p->pp->err, &os, flags & ICX_VERBOSE ? 1 : 0, + nodp, rpoints, 0, quality, + isLinear, isGamma, isShTRC, 1, + flags & ICX_CLIP_WB ? 1 : 0, + flags & ICX_CLIP_PRIMS ? 1 : 0, + smooth, 1.0)) != 0) { + free(rpoints); + p->del((icxLuBase *)p); + return NULL; + } + free(rpoints); rpoints = NULL; + + /* The overall device to absolute conversion is now what we want */ + /* (as dictated by the points, weighting and best fit), */ + /* but we need to adjust the device to relative conversion */ + /* to make device white map exactly to D50, without touching */ + /* the overall absolute behaviour. */ + if (p->flags & ICX_SET_WHITE) { + double aw[3]; /* aprox rel. white */ + icmXYZNumber _wp; /* Uncorrected dw maps to _wp */ + double cmat[3][3]; /* Model correction matrix */ + + if (flags & ICX_VERBOSE) + printf("Doing White point fine tune:\n"); + + + /* See what the aprox. relative white point has turned out to be, */ + /* by looking up the device white in the current conversion */ + mxmfunc(&os, os.v, aw, dw); + + if (flags & ICX_VERBOSE) { + printf("Before fine tune, rel WP = XYZ %s, Lab %s\n", icmPdv(3,aw), icmPLab(aw)); + } + + /* Matrix needed to correct aprox white to target D50 */ + icmAry2XYZ(_wp, aw); /* Aprox relative target white point */ + icmChromAdaptMatrix(ICM_CAM_BRADFORD, icmD50, _wp, cmat); /* Correction */ + + /* Compute the current absolute white point */ + icmMulBy3x3(wp, toAbs, aw); + + /* Apply correction to fine tune matrix. */ + mxtransform(&os, cmat); + + /* Fix relative conversions to leave absolute response unchanged. */ + icmAry2XYZ(_wp, wp); /* Actual white point */ + icmChromAdaptMatrix(ICM_CAM_BRADFORD, icmD50, _wp, fromAbs); + icmChromAdaptMatrix(ICM_CAM_BRADFORD, _wp, icmD50, toAbs); + + if (flags & ICX_VERBOSE) { + double tw[3]; + mxmfunc(&os, os.v, tw, dw); /* Lookup white again */ + printf("After fine tune, rel WP = XYZ %s, Lab %s\n", icmPdv(3, tw), icmPLab(tw)); + printf(" abs WP = XYZ %s, Lab %s\n", icmPdv(3, wp), icmPLab(wp)); + } + } + + /* Create default wpscale */ + if (wpscale < 0.0) { + wpscale = 1.0; + } else { + if (flags & ICX_VERBOSE) { + printf("White manual point scale %f\n", wpscale); + } + } + + /* If we are going to auto scale the WP to avoid clipping */ + /* values above the WP: (not important for matrix profiles ?) */ + if ((p->flags & ICX_SET_WHITE_US) == ICX_SET_WHITE_US) { + double tw[3], bw[3]; + icmXYZNumber _wp; + double uswpscale = 1.0; + double mxd, mxY; + double ndw[3]; + + /* See what device space gamut boundary white (ie. 1,1,1) maps to */ + mxmfunc(&os, os.v, tw, dgw); + icmMulBy3x3(tw, toAbs, tw); /* Convert to absolute */ + + mxY = tw[1]; + icmCpy3(bw, tw); +//printf("~1 1,1,1 Y = %f\n",tw[1]); + + /* See what the device white point value scaled to 1 produces */ + mxd = -1.0; + for (e = 0; e < inputChan; e++) { + if (dw[e] > mxd) + mxd = dw[e]; + } + for (e = 0; e < inputChan; e++) + ndw[e] = dw[e]/mxd; + + mxmfunc(&os, os.v, tw, ndw); + icmMulBy3x3(tw, toAbs, tw); /* Convert to absolute */ + +//printf("~1 ndw = %f %f %f Y = %f\n",ndw[0],ndw[1],ndw[2],tw[1]); + if (tw[1] > mxY) { + mxY = tw[1]; + icmCpy3(bw, tw); + } + + /* Compute WP scale factor needed to fit mxY */ + if (mxY > wp[1]) { + uswpscale = mxY/wp[1]; + wpscale *= uswpscale; + if (flags & ICX_VERBOSE) { + printf("Dev boundary white XYZ %s, scale WP by %f, total WP scale %f\n", + icmPdv(3, bw), uswpscale, wpscale); + } + } + } + + /* If the scaled WP would have Y > 1.0, clip it to 1.0 */ + if (p->flags & ICX_CLIP_WB) { + + if ((wp[1] * wpscale) > 1.0) { + wpscale = 1.0/wp[1]; /* Make wp Y = 1.0 */ + if (flags & ICX_VERBOSE) { + printf("WP Y would ve > 1.0. scale by %f to clip it\n",wpscale); + } + } + } + + /* Apply our total wp scale factor */ + if (wpscale != 1.0) { + icmXYZNumber _wp; + double cmat[3][3]; /* Model correction matrix */ + + /* Create inverse scaling matrix for relative rspl data */ + icmSetUnity3x3(cmat); + icmScale3x3(cmat, cmat, 1.0/wpscale); + + /* Inverse scale the matrix */ + mxtransform(&os, cmat); + + /* Scale the WP */ + icmScale3(wp, wp, wpscale); + + /* Fix absolute conversions to leave absolute response unchanged. */ + icmAry2XYZ(_wp, wp); /* Actual white point */ + icmChromAdaptMatrix(ICM_CAM_BRADFORD, icmD50, _wp, fromAbs); + icmChromAdaptMatrix(ICM_CAM_BRADFORD, _wp, icmD50, toAbs); + } + + /* Look up the actual black point */ + if (p->flags & ICX_SET_BLACK) { + + /* Look black point up in dev->rel model */ + mxmfunc(&os, os.v, bp, db); + + /* Convert from relative to Absolute colorimetric */ + icmMulBy3x3(bp, toAbs, bp); + + + /* Got XYZ black point in bp[] */ + if (flags & ICX_VERBOSE) { + printf("Black point XYZ = %s, Lab = %s\n", icmPdv(3,bp),icmPLab(bp)); + } + + if (flags & ICX_CLIP_WB) { + if (bp[0] < 0.0 || bp[1] < 0.0 || bp[1] < 0.0) { + if (bp[0] < 0.0) + bp[0] = 0.0; + if (bp[1] < 0.0) + bp[1] = 0.0; + if (bp[2] < 0.0) + bp[2] = 0.0; + if (flags & ICX_VERBOSE) + printf("Black point clipped to XYZ = %s, Lab = %s\n",icmPdv(3,bp),icmPLab(bp)); + } + } + } + + if (flags & (ICX_SET_WHITE | ICX_SET_BLACK)) { + + /* If this is a display, adjust the absolute white point to be */ + /* exactly Y = 1.0, and compensate the matrix, dispLuminance */ + /* and black point accordingly. */ + if (h->deviceClass == icSigDisplayClass) { + double cmat[3][3]; /* Model correction matrix */ + double scale = 1.0/wp[1]; + + if (flags & ICX_VERBOSE) + printf("Scaling White Point by %f to make Y = 1.0\n", scale); + + /* Scale the WP & BP*/ + icmScale3(wp, wp, scale); + icmScale3(bp, bp, scale); + + /* Inverse scale the luminance */ + dispLuminance /= scale; + } + + /* Absolute luminance tag */ + if (flags & ICX_WRITE_WBL + && h->deviceClass == icSigDisplayClass + && dispLuminance > 0.0) { + icmXYZArray *wo; + if ((wo = (icmXYZArray *)icco->read_tag( + icco, icSigLuminanceTag)) == NULL) { + xicp->errc = 1; + sprintf(xicp->err,"icx_set_luminance: couldn't find luminance tag"); + p->del((icxLuBase *)p); + return NULL; + } + if (wo->ttype != icSigXYZArrayType) { + xicp->errc = 1; + sprintf(xicp->err,"luminance: tag has wrong type"); + p->del((icxLuBase *)p); + return NULL; + } + + wo->size = 1; + wo->allocate((icmBase *)wo); /* Allocate space */ + wo->data[0].X = 0.0; + wo->data[0].Y = dispLuminance; + wo->data[0].Z = 0.0; + + if (flags & ICX_VERBOSE) + printf("Display Luminance = %f\n", wo->data[0].Y); + } + + /* Write white and black tags */ + if ((flags & ICX_WRITE_WBL) + && (flags & ICX_SET_WHITE)) { /* White Point Tag: */ + icmXYZArray *wo; + if ((wo = (icmXYZArray *)icco->read_tag( + icco, icSigMediaWhitePointTag)) == NULL) { + xicp->errc = 1; + sprintf(xicp->err,"icx_set_white_black: couldn't find white tag"); + p->del((icxLuBase *)p); + return NULL; + } + if (wo->ttype != icSigXYZArrayType) { + xicp->errc = 1; + sprintf(xicp->err,"icx_set_white_black: white tag has wrong type"); + p->del((icxLuBase *)p); + return NULL; + } + + wo->size = 1; + wo->allocate((icmBase *)wo); /* Allocate space */ + wo->data[0].X = wp[0]; + wo->data[0].Y = wp[1]; + wo->data[0].Z = wp[2]; + + if (flags & ICX_VERBOSE) + printf("White point XYZ = %f %f %f\n",wp[0],wp[1],wp[2]); + } + if ((flags & ICX_WRITE_WBL) + && (flags & ICX_SET_BLACK)) { /* Black Point Tag: */ + icmXYZArray *wo; + if ((wo = (icmXYZArray *)icco->read_tag( + icco, icSigMediaBlackPointTag)) == NULL) { + xicp->errc = 1; + sprintf(xicp->err,"icx_set_white_black: couldn't find black tag"); + p->del((icxLuBase *)p); + return NULL; + } + if (wo->ttype != icSigXYZArrayType) { + xicp->errc = 1; + sprintf(xicp->err,"icx_set_white_black: black tag has wrong type"); + p->del((icxLuBase *)p); + return NULL; + } + + wo->size = 1; + wo->allocate((icmBase *)wo); /* Allocate space */ + wo->data[0].X = bp[0]; + wo->data[0].Y = bp[1]; + wo->data[0].Z = bp[2]; + + if (flags & ICX_VERBOSE) + printf("Black point XYZ = %f %f %f\n",bp[0],bp[1],bp[2]); + } + + // ~~99 + if (flags & ICX_CLIP_PRIMS) { + for (i = 0; i < 9; i++) { + if (os.v[i] < 0.0) + os.v[i] = 0.0; + } + } + } + + if (flags & ICX_VERBOSE) + printf("Done gamma/shaper and matrix creation\n"); + + /* Write the gamma/shaper and matrix to the icc memory structures */ + if (!isGamma) { /* Creating input curves */ + unsigned int ui; + icmCurve *wor, *wog, *wob; + wor = pmlu->redCurve; + wog = pmlu->greenCurve; + wob = pmlu->blueCurve; + + for (ui = 0; ui < wor->size; ui++) { + double in, rgb[3]; + + for (j = 0; j < 3; j++) { + + in = (double)ui / (wor->size - 1.0); + + mxmfunc1(&os, j, os.v, &rgb[j], &in); + + if (rgb[j] < 0.0) + rgb[j] = 0.0; + else if (rgb[j] > 1.0) + rgb[j] = 1.0; + } + wor->data[ui] = rgb[0]; /* Curve values 0.0 - 1.0 */ + if (!isShTRC) { + wog->data[ui] = rgb[1]; + wob->data[ui] = rgb[2]; + } + } +#ifdef DEBUG_PLOT + /* Display the result fit */ + for (j = 0; j < 3; j++) { + for (i = 0; i < XRES; i++) { + double x, y; + xx[i] = x = i/(double)(XRES-1); + mxmfunc1(&os, j, os.v, &y, &x); + if (y < 0.0) + y = 0.0; + else if (y > 1.0) + y = 1.0; + y1[i] = y; + } + do_plot(xx,y1,NULL,NULL,XRES); + } +#endif /* DEBUG_PLOT */ + + + } else { /* Gamma */ + icmCurve *wor, *wog, *wob; + wor = pmlu->redCurve; + wog = pmlu->greenCurve; + wob = pmlu->blueCurve; + wor->data[0] = os.v[9]; /* Gamma values */ + if (!isShTRC) { + wog->data[0] = os.v[10]; + wob->data[0] = os.v[11]; + } + } + + /* Matrix values */ + { + icmXYZArray *wor, *wog, *wob; + wor = pmlu->redColrnt; + wog = pmlu->greenColrnt; + wob = pmlu->blueColrnt; + wor->data[0].X = os.v[0]; wor->data[0].Y = os.v[3]; wor->data[0].Z = os.v[6]; + wog->data[0].X = os.v[1]; wog->data[0].Y = os.v[4]; wog->data[0].Z = os.v[7]; + wob->data[0].X = os.v[2]; wob->data[0].Y = os.v[5]; wob->data[0].Z = os.v[8]; + + /* Load into pmlu matrix and inverse ??? */ + } + + if (flags & ICX_VERBOSE) + printf("Profile done\n"); + + return (icxLuBase *)p; +} + +/* ========================================================= */ + +/* Given an xicc lookup object, returm a gamut object. */ +/* Note that the PCS must be Lab or Jab */ +/* Return NULL on error, check errc+err for reason */ +static gamut *icxLuMatrixGamut( +icxLuBase *plu, /* this */ +double detail /* gamut detail level, 0.0 = def */ +) { + xicc *p = plu->pp; /* parent xicc */ + icxLuMatrix *lumat = (icxLuMatrix *)plu; /* Lookup xMatrix type object */ + icColorSpaceSignature pcs; + icmLookupFunc func; + double white[3], black[3], kblack[3]; + gamut *gam; + int res; /* Sample point resolution */ + int i, e; + + if (detail == 0.0) + detail = 10.0; + + /* get some details */ + plu->spaces(plu, NULL, NULL, NULL, NULL, NULL, NULL, &func, &pcs); + + if (func != icmFwd && func != icmBwd) { + p->errc = 1; + sprintf(p->err,"Creating Gamut surface for anything other than Device <-> PCS is not supported."); + return NULL; + } + + if (pcs != icSigLabData && pcs != icxSigJabData) { + p->errc = 1; + sprintf(p->err,"Creating Gamut surface PCS of other than Lab or Jab is not supported."); + return NULL; + } + + gam = new_gamut(detail, pcs == icxSigJabData, 0); + + /* Explore the gamut by itterating through */ + /* it with sample points in device space. */ + + res = (int)(600.0/detail); /* Establish an appropriate sampling density */ + + if (res < 40) + res = 40; + + /* Since matrix profiles can't be non-monotonic, */ + /* just itterate through the surface colors. */ + for (i = 0; i < 3; i++) { + int co[3]; + int ep[3]; + int co_e = 0; + + for (e = 0; e < 3; e++) { + co[e] = 0; + ep[e] = res; + } + ep[i] = 2; + + while (co_e < 3) { + double in[3]; + double out[3]; + + for (e = 0; e < 3; e++) /* Convert count to input value */ + in[e] = co[e]/(ep[e]-1.0); + + /* Always use the device->PCS conversion */ + if (lumat->fwd_lookup((icxLuBase *)lumat, out, in) > 1) + error ("%d, %s",p->errc,p->err); + + gam->expand(gam, out); + + /* Increment the counter */ + for (co_e = 0; co_e < 3; co_e++) { + co[co_e]++; + if (co[co_e] < ep[co_e]) + break; /* No carry */ + co[co_e] = 0; + } + } + } + +#ifdef NEVER + /* Try it twice */ + for (i = 0; i < 3; i++) { + int co[3]; + int ep[3]; + int co_e = 0; + + for (e = 0; e < 3; e++) { + co[e] = 0; + ep[e] = res; + } + ep[i] = 2; + + while (co_e < 3) { + double in[3]; + double out[3]; + + for (e = 0; e < 3; e++) /* Convert count to input value */ + in[e] = co[e]/(ep[e]-1.0); + + /* Always use the device->PCS conversion */ + if (lumat->fwd_lookup((icxLuBase *)lumat, out, in) > 1) + error ("%d, %s",p->errc,p->err); + + gam->expand(gam, out); + + /* Increment the counter */ + for (co_e = 0; co_e < 3; co_e++) { + co[co_e]++; + if (co[co_e] < ep[co_e]) + break; /* No carry */ + co[co_e] = 0; + } + } + } +#endif + +#ifdef NEVER // (doesn't seem to make much difference) + /* run along the primary ridges in more detail too */ + /* just itterate through the surface colors. */ + for (i = 0; i < 3; i++) { + int j; + double in[3]; + double out[3]; + + res *= 4; + + for (j = 0; j < res; j++) { + double vv = i/(res-1.0); + + in[0] = in[1] = in[2] = vv; + in[i] = 0.0; + + if (lumat->fwd_lookup((icxLuBase *)lumat, out, in) > 1) + error ("%d, %s",p->errc,p->err); + gam->expand(gam, out); + + in[0] = in[1] = in[2] = 0.0; + in[i] = vv; + + if (lumat->fwd_lookup((icxLuBase *)lumat, out, in) > 1) + error ("%d, %s",p->errc,p->err); + gam->expand(gam, out); + } + } +#endif + + /* Put the white and black points in the gamut */ + plu->efv_wh_bk_points(plu, white, black, kblack); + gam->setwb(gam, white, black, kblack); + + /* set the cusp points by itterating through the 0 & 100% colorant combinations */ + { + DCOUNT(co, 3, 3, 0, 0, 2); + + gam->setcusps(gam, 0, NULL); + DC_INIT(co); + while(!DC_DONE(co)) { + int e; + double in[3]; + double out[3]; + + if (!(co[0] == 0 && co[1] == 0 && co[2] == 0) + && !(co[0] == 1 && co[1] == 1 && co[2] == 1)) { /* Skip white and black */ + for (e = 0; e < 3; e++) + in[e] = (double)co[e]; + + /* Always use the device->PCS conversion */ + if (lumat->fwd_lookup((icxLuBase *)lumat, out, in) > 1) + error ("%d, %s",p->errc,p->err); + gam->setcusps(gam, 3, out); + } + + DC_INC(co); + } + gam->setcusps(gam, 2, NULL); + } + +#ifdef NEVER /* Not sure if this is a good idea ?? */ + gam->getwb(gam, NULL, NULL, white, black); /* Get the actual gamut white and black points */ + gam->setwb(gam, white, black); /* Put it back as colorspace one */ +#endif + + return gam; +} + +#ifdef DEBUG +#undef DEBUG +#endif |