2104 lines
59 KiB
C
2104 lines
59 KiB
C
/*
|
|
* 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.
|
|
*
|
|
* Would be nice to be able to specify a specific patch
|
|
* as the white one rather than using heuristic to identify it,
|
|
* since some pathalogical cases don't work.
|
|
*/
|
|
|
|
|
|
|
|
#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.01 /* 0 & 1 harmonic weights */
|
|
#define XSHAPE_HBREAK 3 /* Harmonic that has HWBR */
|
|
#define XSHAPE_HWBR 0.5 /* 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 /* [und] Extra printfs */
|
|
#undef DEBUG_PLOT /* [und] Plot curves */
|
|
#undef DEBUG_SPEC /* [und] Debug some specific cases */
|
|
#define G_DEB 0 /* [und] g_deb default value */
|
|
|
|
/* Debug some specific cases (fwd_relpcs_outpcs, bwd_outpcs_relpcs) */
|
|
#ifdef DEBUG_SPEC
|
|
# undef DBS
|
|
# ifdef CNDTRACE
|
|
# define DBS(xxx) if (p->trace) printf xxx ;
|
|
# else
|
|
# define DBS(xxx) printf xxx ;
|
|
# endif
|
|
#else
|
|
# undef DBS
|
|
# define DBS(xxx)
|
|
#endif
|
|
|
|
/* ========================================================= */
|
|
/* Forward and Backward Matrix type conversion */
|
|
/* Return 0 on success, 1 if clipping occured, 2 on other error */
|
|
|
|
/* Individual components of Fwd conversion: */
|
|
/* Because icm_lu4 sets up component conversions in the requested direction, */
|
|
/* but icxLuMatrix expects component conversions as if the the matrix is */
|
|
/* fwd, we have if (dir) code to reverse things here. */
|
|
static int
|
|
icxLuMatrixFwd_curve (
|
|
icxLuMatrix *p, /* This */
|
|
double *out, /* Vector of output values */
|
|
double *in /* Vector of input values */
|
|
) {
|
|
icmPe_lurv prv;
|
|
if (p->dir) {
|
|
prv = p->plu->output_fmt_bwd(p->plu, out, in); /* Should be NOP */
|
|
prv |= p->plu->output_pch_bwd(p->plu, out, out);
|
|
} else {
|
|
prv = p->plu->input_fmt_fwd(p->plu, out, in); /* Should be NOP */
|
|
prv |= p->plu->input_pch_fwd(p->plu, out, out);
|
|
}
|
|
return LUE2XLUE(prv);
|
|
}
|
|
|
|
static int
|
|
icxLuMatrixFwd_matrix (
|
|
icxLuMatrix *p, /* This */
|
|
double *out, /* Vector of output values */
|
|
double *in /* Vector of input values */
|
|
) {
|
|
icmPe_lurv prv;
|
|
if (p->dir)
|
|
prv = p->plu->core5_bwd(p->plu, out, in);
|
|
else
|
|
prv = p->plu->core5_fwd(p->plu, out, in);
|
|
return LUE2XLUE(prv);
|
|
}
|
|
|
|
static int
|
|
icxLuMatrixFwd_abs (
|
|
icxLuMatrix *p, /* This */
|
|
double *out, /* Vector of output values */
|
|
double *in /* Vector of input values */
|
|
) {
|
|
int rv = 0;
|
|
icmPe_lurv prv;
|
|
if (p->dir) {
|
|
prv = p->plu->input_pch_bwd(p->plu, out, in); /* Should be NOP */
|
|
prv |= p->plu->input_fmt_bwd(p->plu, out, out);
|
|
} else {
|
|
prv = p->plu->output_pch_fwd(p->plu, out, in); /* Should be NOP */
|
|
prv |= p->plu->output_fmt_fwd(p->plu, out, out);
|
|
}
|
|
rv = LUE2XLUE(prv);
|
|
|
|
if (p->pcs == icxSigJabData)
|
|
p->cam->XYZ_to_cam(p->cam, out, out);
|
|
return rv;
|
|
}
|
|
|
|
|
|
/* Overall Fwd conversion */
|
|
/* Note that the overall conversion is in the requested direction, */
|
|
/* as the setup code swaps icxLuMatrixFwd_lookup/icxLuMatrixBwd_lookup */
|
|
/* as needed. */
|
|
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) {
|
|
DBS(("Matrix_fwd_relpcs_outpcs: Lab in = %s\n", icmPdv(p->inputChan, in)));
|
|
icmLab2XYZ(&icmD50, out, in);
|
|
DBS(("Matrix_fwd_relpcs_outpcs: XYZ = %s\n", icmPdv(p->outputChan, out)));
|
|
icxLuMatrixFwd_abs(p, out, out);
|
|
DBS(("Matrix_fwd_relpcs_outpcs: abs XYZ = %s\n", icmPdv(p->outputChan, out)));
|
|
} else if (is == icSigXYZData && p->natpcs == icSigLabData) {
|
|
DBS(("Matrix_fwd_relpcs_outpcs: XYZ in = %s\n", icmPdv(p->inputChan, in)));
|
|
icmXYZ2Lab(&icmD50, out, in);
|
|
DBS(("Matrix_fwd_relpcs_outpcs: Lab = %s\n", icmPdv(p->outputChan, out)));
|
|
icxLuMatrixFwd_abs(p, out, out);
|
|
DBS(("Matrix_fwd_relpcs_outpcs: abs Lab = %s\n", icmPdv(p->outputChan, out)));
|
|
} else {
|
|
DBS(("Matrix_fwd_relpcs_outpcs: rel = %s\n", icmPdv(p->inputChan, in)));
|
|
icxLuMatrixFwd_abs(p, out, in);
|
|
DBS(("Matrix_fwd_relpcs_outpcs: abs = %s\n", icmPdv(p->outputChan, out)));
|
|
}
|
|
}
|
|
|
|
/* - - - - - - - - - - - - - - - - - - - - - */
|
|
/* Individual components of Bwd conversion: */
|
|
|
|
static int
|
|
icxLuMatrixBwd_abs (
|
|
icxLuMatrix *p, /* This */
|
|
double *out, /* Vector of output values */
|
|
double *in /* Vector of input values */
|
|
) {
|
|
icmPe_lurv prv;
|
|
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;
|
|
}
|
|
if (p->dir) {
|
|
prv = p->plu->input_fmt_fwd(p->plu, out, out);
|
|
prv |= p->plu->input_pch_fwd(p->plu, out, out); /* Should be NOP */
|
|
} else {
|
|
prv = p->plu->output_fmt_bwd(p->plu, out, out);
|
|
prv |= p->plu->output_pch_bwd(p->plu, out, out); /* Should be NOP */
|
|
}
|
|
rv = LUE2XLUE(prv);
|
|
} else {
|
|
if (p->dir) {
|
|
prv = p->plu->input_fmt_fwd(p->plu, out, in);
|
|
prv |= p->plu->input_pch_fwd(p->plu, out, out); /* Should be NOP */
|
|
} else {
|
|
prv = p->plu->output_fmt_bwd(p->plu, out, in);
|
|
prv |= p->plu->output_pch_bwd(p->plu, out, out); /* Should be NOP */
|
|
}
|
|
rv = LUE2XLUE(prv);
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
static int
|
|
icxLuMatrixBwd_matrix (
|
|
icxLuMatrix *p, /* This */
|
|
double *out, /* Vector of output values */
|
|
double *in /* Vector of input values */
|
|
) {
|
|
icmPe_lurv prv;
|
|
if (p->dir)
|
|
prv = p->plu->core5_fwd(p->plu, out, in);
|
|
else
|
|
prv = p->plu->core5_bwd(p->plu, out, in);
|
|
return LUE2XLUE(prv);
|
|
}
|
|
|
|
static int
|
|
icxLuMatrixBwd_curve (
|
|
icxLuMatrix *p, /* This */
|
|
double *out, /* Vector of output values */
|
|
double *in /* Vector of input values */
|
|
) {
|
|
icmPe_lurv prv;
|
|
if (p->dir) {
|
|
prv = p->plu->output_pch_fwd(p->plu, out, in);
|
|
prv |= p->plu->output_fmt_fwd(p->plu, out, out); /* Should be NOP */
|
|
} else {
|
|
prv = p->plu->input_pch_bwd(p->plu, out, in);
|
|
prv |= p->plu->input_fmt_bwd(p->plu, out, out); /* Should be NOP */
|
|
}
|
|
return LUE2XLUE(prv);
|
|
}
|
|
|
|
/* Overall Bwd conversion */
|
|
/* Note that the overall conversion is in the requested direction, */
|
|
/* as the setup code swaps icxLuMatrixFwd_lookup/icxLuMatrixBwd_lookup */
|
|
/* as needed. */
|
|
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;
|
|
|
|
DBS(("Matrix_bwd_outpcs_relpcs: rel = %s\n", icmPdv(p->outputChan, in)));
|
|
icxLuMatrixBwd_abs(p, out, in);
|
|
DBS(("Matrix_bwd_outpcs_relpcs: abs = %s\n", icmPdv(p->inputChan, out)));
|
|
if (os == icSigXYZData && p->natpcs == icSigLabData) {
|
|
icmLab2XYZ(&icmD50, out, out);
|
|
DBS(("Matrix_bwd_outpcs_relpcs: XYZ = %s\n", icmPdv(p->inputChan, out)));
|
|
} else if (os == icSigLabData && p->natpcs == icSigXYZData) {
|
|
icmXYZ2Lab(&icmD50, out, out);
|
|
DBS(("Matrix_bwd_outpcs_relpcs: Lab = %s\n", icmPdv(p->inputChan, out)));
|
|
}
|
|
}
|
|
|
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
|
static gamut *icxLuMatrixGamut(icxLuBase *plu, double detail);
|
|
static icxCuspMap *icxLuMatrixCuspMap(icxLuBase *plu, int res) { return NULL; };
|
|
|
|
/* Do the basic icxLuMatrix creation and initialisation */
|
|
static icxLuMatrix *
|
|
alloc_icxLuMatrix(
|
|
xicc *xicp,
|
|
icmLuSpace *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->lutype = icxLuMatrixType;
|
|
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->get_cuspmap = icxLuMatrixCuspMap;
|
|
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->intsep = 0;
|
|
|
|
p->dir = dir;
|
|
|
|
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;
|
|
|
|
|
|
return p;
|
|
}
|
|
|
|
/* We setup fwd and bwd component conversions as if this was a fwd conversion, */
|
|
/* but setup the asked for overal conversion in the resquested direction. */
|
|
static icxLuBase *
|
|
new_icxLuMatrix(
|
|
xicc *xicp,
|
|
int flags, /* clip, merge flags */
|
|
icmLuSpace *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;
|
|
|
|
// ~8 should merge alloc_icxLuMatrix() into new_icxLuMatrix()
|
|
|
|
/* 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_vc(p->cam, &p->vc);
|
|
} else {
|
|
p->cam = NULL;
|
|
}
|
|
|
|
/* Remember the effective intent */
|
|
p->intent = intent;
|
|
|
|
/* In general the native and effective space info of the icx will be the same as the */
|
|
/* underlying icm lookup object. */
|
|
{
|
|
icmCSInfo ini, outi, pcsi;
|
|
|
|
/* Get details of internal, native color space */
|
|
plu->native_spaces(p->plu, &ini, &outi, &pcsi);
|
|
p->natis = ini.sig;
|
|
p->natos = outi.sig;
|
|
p->natpcs = pcsi.sig;
|
|
|
|
icmCpyN(p->ninmin, ini.min, ini.nch);
|
|
icmCpyN(p->ninmax, ini.max, ini.nch);
|
|
icmCpyN(p->noutmin, outi.min, outi.nch);
|
|
icmCpyN(p->noutmax, outi.max, outi.nch);
|
|
|
|
/* Get details of external, effective color spaces */
|
|
plu->spaces(p->plu, &ini, &outi, &pcsi, NULL, NULL, NULL, NULL, NULL, NULL);
|
|
p->ins = ini.sig;
|
|
p->inputChan = ini.nch;
|
|
p->outs = outi.sig;
|
|
p->outputChan = outi.nch;
|
|
p->pcs = pcsi.sig;
|
|
|
|
icmCpyN(p->inmin, ini.min, ini.nch);
|
|
icmCpyN(p->inmax, ini.max, ini.nch);
|
|
icmCpyN(p->outmin, outi.min, outi.nch);
|
|
icmCpyN(p->outmax, outi.max, outi.nch);
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/* 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 gamma or 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 */
|
|
/* 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 * p->smooth;
|
|
}
|
|
tt *= tt;
|
|
tparam += w * tt;
|
|
}
|
|
return XSHAPE_MAG * tparam;
|
|
}
|
|
|
|
/* Input offset 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;
|
|
}
|
|
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;
|
|
}
|
|
|
|
int g_deb = G_DEB;
|
|
|
|
/* 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;
|
|
|
|
if (g_deb) printf("\n");
|
|
|
|
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);
|
|
if (g_deb) printf("%d: %f %f %f -> %f %f %f, target %f %f %f, w %f\n", i, 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],p->points[i].w);
|
|
|
|
/* 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
|
|
if (g_deb)
|
|
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 */
|
|
int wix = -1; /* White patch index */
|
|
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 = 50000;
|
|
stopon = 1e-14;
|
|
} 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;
|
|
|
|
/* Pick a white point for the real Lab conversion */
|
|
{
|
|
double wpy = -1e60;
|
|
double wp[3];
|
|
|
|
/* 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 */
|
|
|
|
wix = -1;
|
|
for (i = 0; i < nodp; i++) {
|
|
double lab[3];
|
|
double yv;
|
|
|
|
/* Get the Lab value of this point */
|
|
if (isLab)
|
|
icmCpy3(lab, ipoints[i].v);
|
|
else
|
|
icmXYZ2Lab(&icmD50, lab, ipoints[i].v);
|
|
|
|
/* Tilt things towards D50 neutral white patches */
|
|
yv = lab[0] - 0.3 * sqrt(lab[1] * lab[1] + lab[2] * lab[2]);
|
|
|
|
if (yv > wpy) {
|
|
wpy = yv;
|
|
wix = i;
|
|
icmCpy3(wp, lab);
|
|
}
|
|
}
|
|
//printf("~1 picked point %d as Lab %f %f %f as white\n",wix,wp[0],wp[1],wp[2]);
|
|
icmLab2XYZ(&icmD50, wp, wp);
|
|
wp[0] /= wp[1]; /* Normalize */
|
|
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;
|
|
|
|
/* Setup points ready for optimisation by converting */
|
|
/* data to Lab relative to our chosen white point */
|
|
for (i = 0; i < nodp; i++) {
|
|
double lch[3];
|
|
|
|
/* Copy input values */
|
|
for (e = 0; e < inputChan; e++)
|
|
points[i].p[e] = ipoints[i].p[e];
|
|
|
|
points[i].w = ipoints[i].w;
|
|
|
|
/* First copy and convert to XYZ */
|
|
if (isLab)
|
|
icmLab2XYZ(&icmD50, points[i].v, ipoints[i].v);
|
|
else
|
|
icmCpy3(points[i].v, ipoints[i].v);
|
|
|
|
/* Then to our wp relative Lab */
|
|
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;
|
|
|
|
/* We try and take a homomorphic approach here, in an attempt */
|
|
/* to avoid getting trapped at a local minimum when a full */
|
|
/* set of shaper parameters are in play. */
|
|
|
|
/* 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) {
|
|
double scgamma;
|
|
|
|
/* Start from linear, which is what was assumed for the matrix fit, */
|
|
/* and fit with a single shared gamma curve. */
|
|
os->isShTRC = 1;
|
|
os->isLinear = 0;
|
|
os->isGamma = 1;
|
|
os->optdim = 10;
|
|
os->v[9] = 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 and single gamma 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);
|
|
|
|
scgamma = os->v[9];
|
|
if (isShTRC && !isGamma) {
|
|
|
|
#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]);
|
|
printf("Gamma = %f\n",os->v[9]);
|
|
}
|
|
#endif /* NEVER */
|
|
|
|
/* Do final optimisation using full curve capability */
|
|
/* and fit first with a single shared curve. */
|
|
os->isShTRC = 1;
|
|
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 shaper 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);
|
|
|
|
scgamma = os->v[9];
|
|
|
|
}
|
|
|
|
/* For multiple curves, continue fitting */
|
|
if (!isShTRC) {
|
|
double mcgamma[3];
|
|
|
|
#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]);
|
|
printf("Gamma = %f\n",os->v[9]);
|
|
}
|
|
#endif /* NEVER */
|
|
|
|
/* Fit matrix + multi gamma curves */
|
|
os->isShTRC = 0;
|
|
os->isLinear = 0;
|
|
os->isGamma = 1;
|
|
os->optdim = 12;
|
|
os->v[9] = os->v[10] = os->v[11] = scgamma; /* Single curve value */
|
|
|
|
/* 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 gamma 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);
|
|
|
|
|
|
mcgamma[0] = os->v[9];
|
|
mcgamma[1] = os->v[10];
|
|
mcgamma[2] = os->v[11];
|
|
|
|
if (!isGamma) {
|
|
|
|
#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]);
|
|
printf("Gamma = %f %f %f\n",os->v[9], os->v[10], os->v[11]);
|
|
}
|
|
#endif /* NEVER */
|
|
|
|
/* Do final curves */
|
|
os->isShTRC = 0;
|
|
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] = mcgamma[0];
|
|
os->v[16] = mcgamma[1];
|
|
os->v[17] = mcgamma[2];
|
|
} 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.1; /* Matrix, Gamma, Offsets, harmonics */
|
|
|
|
if (os->verb)
|
|
printf("Creating matrix and curves...\n");
|
|
|
|
//g_deb = 1;
|
|
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 (os->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);
|
|
if (p->picc != NULL)
|
|
p->picc->chromAdaptMatrix(p->picc, ICM_CAM_NONE, NULL, cmat, _tp, _ap);
|
|
else
|
|
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(
|
|
icc *picc, /* ICC profile used to set cone space matrix, NULL for Bradford. */
|
|
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->picc = picc;
|
|
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;
|
|
}
|
|
|
|
|
|
/* Callback function for create_matrix_xforms() */
|
|
static void matrix_xforms_infunc(void *cbctx, double *out, double *in, int tn) {
|
|
mxopt *p = (mxopt *)cbctx;
|
|
int j;
|
|
|
|
for (j = 0; j < 3; j++)
|
|
mxmfunc1(p, j, p->v, out + j, in + j);
|
|
}
|
|
|
|
|
|
|
|
/* Create icxLuMatrix and undelying tone reproduction curves and */
|
|
/* colorant tags from the supplied scattered data points. */
|
|
|
|
/* The scattered data is assumed to map Device -> native PCS (ie. dir = Fwd) */
|
|
/* Set error code and message in icco on error. */
|
|
/* Return the error code */
|
|
int set_icxLuMatrix(
|
|
icc *icco,
|
|
int flags, /* white/black point flags */
|
|
icxTransformCreateType icctype, /* Profile creation type */
|
|
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 */
|
|
int isShTRC, /* Matrix - NZ if shared TRCs */
|
|
int isGamma, /* Matrix - NZ if gamma rather than shaper */
|
|
int isLinear, /* Matrix - NZ if pure linear, gamma = 1.0 */
|
|
int inputEnt, /* Num of in-table entries */
|
|
int inv_inputEnt /* Num of inverse in-table entries */
|
|
) {
|
|
icmHeader *h = icco->header; /* Pointer to icc header */
|
|
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 */
|
|
mxopt os; /* Optimisation information */
|
|
/* 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;
|
|
|
|
/* Sanity check for matrix */
|
|
if (h->pcs != icSigXYZData) {
|
|
return icm_err(icco, ICX_ERR_BAD_PCS, "Can't create matrix profile with PCS of %s !",
|
|
icm2str(icmColorSpaceSig, h->pcs));
|
|
}
|
|
|
|
/* ------------------------------- */
|
|
|
|
/* 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 < inputChan; e++) {
|
|
dw[e] = 0.0;
|
|
db[e] = 1.0;
|
|
}
|
|
break;
|
|
case icSigRgbData:
|
|
for (e = 0; e < inputChan; e++) {
|
|
dw[e] = 1.0;
|
|
db[e] = 0.0;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
return icm_err(icco,ICX_ERR_BAD_DEV,
|
|
"set_icxLuMatrix: can't handle color space %s",
|
|
icm2str(icmColorSpaceSig, h->colorSpace));
|
|
}
|
|
}
|
|
|
|
/* dw is what we want for dgw[], used for XFIT_OUT_WP_REL_US */
|
|
for (e = 0; e < 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 < 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 < 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 < 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(inputChan, dw), icmPdv(3, wp), icmPLab(wp));
|
|
printf("Picked black patch %d with dev = %s\n XYZ = %s, Lab = %s\n",
|
|
bix+1, icmPdv(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:
|
|
return icm_err(icco,ICX_ERR_BAD_DEV,
|
|
"set_icxLuMatrix: can't handle color space %s",
|
|
icm2str(icmColorSpaceSig, h->colorSpace));
|
|
}
|
|
|
|
if (nw == 0) {
|
|
return icm_err(icco, ICX_ERR_NO_WP,
|
|
"set_icxLuMatrix: can't handle test points without a white patch");
|
|
}
|
|
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, and */
|
|
/* Aproximate relative to absolute conversion matrix */
|
|
icco->chromAdaptMatrix(icco, ICM_CAM_NONE, toAbs, fromAbs, icmD50, _wp);
|
|
}
|
|
|
|
} else {
|
|
icmSetUnity3x3(fromAbs);
|
|
icmSetUnity3x3(toAbs);
|
|
icmCpy3(wp, icmD50_ary3);
|
|
}
|
|
|
|
/* Force do nothing/Absolute white point */
|
|
if ((flags & ICX_SET_WHITE_ABS) == ICX_SET_WHITE_ABS) {
|
|
icmSetUnity3x3(fromAbs);
|
|
icmSetUnity3x3(toAbs);
|
|
icmCpy3(wp, icmD50_ary3);
|
|
}
|
|
|
|
/* Create copy of input points with output converted to white relative */
|
|
/* Allow one extra point for possible bpo value */
|
|
if ((rpoints = (cow *)malloc((nodp+1) * sizeof(cow))) == NULL)
|
|
return icm_err(icco, ICX_ERR_MALLOC, "set_icxLuMatrix: malloc failed");
|
|
|
|
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 ((icco->e.c = createMatrix(icco->e.m, &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)) != ICX_ERR_OK) {
|
|
free(rpoints);
|
|
return icco->e.c;
|
|
}
|
|
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 ((flags & ICX_SET_WHITE) != 0
|
|
&& (flags & ICX_SET_WHITE_ABS) != ICX_SET_WHITE_ABS) {
|
|
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 */
|
|
icco->chromAdaptMatrix(icco, ICM_CAM_NONE, NULL, cmat, icmD50, _wp); /* 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 */
|
|
icco->chromAdaptMatrix(icco, ICM_CAM_NONE, toAbs, fromAbs, icmD50, _wp);
|
|
|
|
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 so important for matrix profiles ?) */
|
|
if ((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 (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 */
|
|
icco->chromAdaptMatrix(icco, ICM_CAM_NONE, toAbs, fromAbs, icmD50, _wp);
|
|
}
|
|
|
|
/* Look up the actual black point */
|
|
if (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;
|
|
|
|
/* Delete any existing tag */
|
|
if (icco->delete_tag_quiet(icco, icSigLuminanceTag) != ICM_ERR_OK)
|
|
return icco->e.c;
|
|
|
|
/* Note that tag types icSigXYZType and icSigXYZArrayType are identical */
|
|
if ((wo = (icmXYZArray *)icco->add_tag(
|
|
icco, icSigLuminanceTag, icSigXYZArrayType)) == NULL)
|
|
return icco->e.c;
|
|
|
|
wo->count = 1;
|
|
wo->allocate(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);
|
|
}
|
|
|
|
/* Add the white and black tags */
|
|
if ((flags & ICX_WRITE_WBL)
|
|
&& (flags & ICX_SET_WHITE)) { /* White Point Tag: */
|
|
icmXYZArray *wo;
|
|
|
|
/* Delete any existing tag */
|
|
if (icco->delete_tag_quiet(icco, icSigMediaWhitePointTag) != ICM_ERR_OK)
|
|
return icco->e.c;
|
|
|
|
/* Note that tag types icSigXYZType and icSigXYZArrayType are identical */
|
|
if ((wo = (icmXYZArray *)icco->add_tag(
|
|
icco, icSigMediaWhitePointTag, icSigXYZArrayType)) == NULL)
|
|
return icco->e.c;
|
|
|
|
wo->count = 1;
|
|
wo->allocate(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;
|
|
|
|
/* Delete any existing tag */
|
|
if (icco->delete_tag_quiet(icco, icSigMediaBlackPointTag) != ICM_ERR_OK)
|
|
return icco->e.c;
|
|
|
|
/* Note that tag types icSigXYZType and icSigXYZArrayType are identical */
|
|
if ((wo = (icmXYZArray *)icco->add_tag(
|
|
icco, icSigMediaBlackPointTag, icSigXYZArrayType)) == NULL)
|
|
return icco->e.c;
|
|
|
|
wo->count = 1;
|
|
wo->allocate(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]);
|
|
}
|
|
|
|
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");
|
|
|
|
{
|
|
int nsigs = 1;
|
|
icmXformSigs sigs[2];
|
|
double mx[3][3]; /* [outn][inn] Matrix values */
|
|
double *gamma = NULL; /* Gamma values if isGamma */
|
|
|
|
switch (icctype) {
|
|
default:
|
|
sigs[0].sig = icmSigShaperMatrix;
|
|
sigs[0].ttype = icmSigShaperMatrixType;
|
|
/* Default ICC Version used */
|
|
break;
|
|
}
|
|
|
|
for (j = e = 0; j < 3; j++) {
|
|
for (i = 0; i < 3; i++, e++) {
|
|
mx[j][i] = os.v[e];
|
|
}
|
|
}
|
|
|
|
if (isGamma)
|
|
gamma = &os.v[9]; /* Gamma values */
|
|
|
|
/* Use icclib helper function to create tags and set their values */
|
|
/* Returns ec */
|
|
if (icco->create_matrix_xforms(
|
|
icco, ICM_CREATE_FLAG_NONE,
|
|
(void *)&os, /* Opaque callback context pointer value */
|
|
nsigs, sigs, /* Signatures and tag types for each table */
|
|
(unsigned int)inputEnt, (unsigned int)inv_inputEnt, /* fwd & rev Table resolutions */
|
|
h->colorSpace, h->pcs, /* ina & out color spaces */
|
|
matrix_xforms_infunc, /* Callback function */
|
|
mx, /* [outn][inn] Matrix values */
|
|
NULL, /* Constants if any */
|
|
isShTRC, gamma, isLinear /* curve flags */
|
|
) != 0) {
|
|
if (flags & ICX_VERBOSE)
|
|
printf("Matrix profile creation failed\n");
|
|
return icco->e.c;
|
|
}
|
|
}
|
|
|
|
if (flags & ICX_VERBOSE)
|
|
printf("Profile done\n");
|
|
|
|
return ICX_ERR_OK;
|
|
}
|
|
|
|
|
|
/* ========================================================= */
|
|
|
|
/* Given an xicc lookup object, returm a gamut object. */
|
|
/* Note that the PCS must be Lab or Jab */
|
|
/* Return NULL on error, check e.c+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->e.c = 1;
|
|
sprintf(p->e.m,"Creating Gamut surface for anything other than Device <-> PCS is not supported.");
|
|
return NULL;
|
|
}
|
|
|
|
if (pcs != icSigLabData && pcs != icxSigJabData) {
|
|
p->e.c = 1;
|
|
sprintf(p->e.m,"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->e.c,p->e.m);
|
|
|
|
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->e.c,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->e.c,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->e.c,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->e.c,p->e.m);
|
|
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
|