4528 lines
134 KiB
C
4528 lines
134 KiB
C
|
|
/*
|
|
* International Color Consortium color transform expanded support
|
|
*
|
|
* Author: Graeme W. Gill
|
|
* Date: 2/7/00
|
|
* Version: 1.00
|
|
*
|
|
* Copyright 2000, 2001, 2014 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.
|
|
*
|
|
* This is the third major version of xlut.c (originally called xlut2.c)
|
|
* Based on the old xlut.c code (preserved as xlut1.c)
|
|
* This version uses xfit.c to do the curve and rspl fitting.
|
|
*/
|
|
|
|
/*
|
|
* This module provides the expanded icclib functionality
|
|
* for lut based profiles.
|
|
* This file is #included in xicc.c, to keep its functions private.
|
|
*
|
|
* This version creates both input and output 1D luts by
|
|
* optimising the accuracy of the profile for a linear clut.
|
|
*
|
|
*/
|
|
|
|
/*
|
|
TTBD:
|
|
|
|
See gamut/gammap.c for notes on the desirability of
|
|
a non-minimum delta E colorimetric intent as the default.
|
|
|
|
Should fix gamut's so that they have enough information
|
|
to spefify a gamut mapping without the need for
|
|
a source colorspace profile, and fix the code
|
|
to work with just a gamut. This would take us further
|
|
towards supporting the PRMG reference gamut interoperability
|
|
as an option.
|
|
|
|
Should the input profile white point determination
|
|
be made a bit smarter about determining the chromaticity ?
|
|
ie. not take on the tint of the whitest patch, but an
|
|
average of neutral patches ?
|
|
|
|
Should xlutfix.c be revived (also adding ICM_CLUT_SET_APXLS support),
|
|
to improve "bumpy black" problem ?
|
|
|
|
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.
|
|
*/
|
|
|
|
/*
|
|
An ICC condrum is that it seems that an unwritten convention for
|
|
V2 profiles is to scale the black point of the perceptual and
|
|
saturation tables to 0 (Part of the V4 spec is to scale to Y = 3.1373).
|
|
|
|
To get better gamut mapping we should therefore unscale the perceptual
|
|
and saturation A2B table to have the same black point as the colorimetric
|
|
table before computing the gamut mapping, and then apply the opposite
|
|
transform to our perceptual B2A and A2B tables.
|
|
|
|
*/
|
|
|
|
/*
|
|
There is interesting behaviour for Jab 0,0,0 in, in that
|
|
it gets mapped to (something like) Lab -1899.019855 -213.574625 -231.914285
|
|
which after per-component clipping of the inv out tables looks like
|
|
0, -128, -128, which may be mapped to (say) RGB 0.085455 1.000000 0.936951,
|
|
which is not black.
|
|
|
|
*/
|
|
|
|
#include "xfit.h"
|
|
|
|
#undef USE_CIE94_DE /* [Undef] Use CIE94 delta E measure when creating in/out curves */
|
|
/* Don't use CIE94 because it makes peak error worse ? */
|
|
|
|
#undef DEBUG /* [Undef] Verbose debug information */
|
|
#undef CNDTRACE /* [Undef] Enable xicc->trace conditional verbosity */
|
|
#undef DEBUG_OLUT /* [Undef] Print steps in overall fwd & rev lookups */
|
|
#undef DEBUG_RLUT /* [Undef] Print values being reverse lookup up */
|
|
#undef DEBUG_SPEC /* [Undef] Debug some specific cases */
|
|
#undef INK_LIMIT_TEST /* [Undef] Turn off input tables for ink limit testing */
|
|
#undef CHECK_ILIMIT /* [Undef] Do sanity checks on meeting ink limit */
|
|
#undef WARN_CLUT_CLIPPING /* [Undef] Print warning if setting clut clips */
|
|
#undef DISABLE_KCURVE_FILTER /* [Undef] don't filter the Kcurve */
|
|
#undef REPORT_LOCUS_SEGMENTS /* [Undef] Examine how many segments there are in aux inversion */
|
|
#undef SKIP_PERCH_LOOKUPS /* [Undef] Skip per channel in overall lookups (for manual trace) */
|
|
|
|
#undef FASTREVSETUP_NON_CAM /* [Undef] Use fast setup on inner non-CAM lookup, if we're */
|
|
/* going to use CAM clip for nn lookup */
|
|
|
|
#define XYZ_EXTRA_SMOOTH 20.0 /* [20] Extra smoothing factor for XYZ profiles */
|
|
/* !!! Note this is mainly due to smoothing being */
|
|
/* scaled by data range in rspl code !!! */
|
|
/* - have fix but all rspl use then needs re-tuning! */
|
|
#define SHP_SMOOTH 1.0 /* [1.0] Input shaper curve smoothing */
|
|
#define OUT_SMOOTH1 1.0 /* [1.0] Output shaper curve smoothing for L*, X,Y,Z */
|
|
#define OUT_SMOOTH2 1.0 /* [1.0] Output shaper curve smoothing for a*, b* */
|
|
|
|
#define CAMCLIPTRANS 1.0 /* [1.0] Cam clipping transition region Delta E */
|
|
/* Should this be smaller ? */
|
|
#undef USECAMCLIPSPLINE /* [Und] use spline blend between PCS and Jab */
|
|
|
|
#define USELCHWEIGHT /* [def] Use LCh weighting for nn clip if possible */
|
|
#define JCCWEIGHT 2.0 /* [2.0] Amount to emphasize J delta E in in computing clip */
|
|
#define CCCWEIGHT 1.0 /* [1.0] Amount to emphasize C delta E in in computing clip */
|
|
#define HCCWEIGHT 2.2 /* [2.2] Amount to emphasize H delta E in in computing clip */
|
|
|
|
#define KLOCUS2BLACKONLY /* [def] Make K locus inking rules from zero to max */
|
|
/* rather than min to max of locus */
|
|
|
|
/*
|
|
* TTBD:
|
|
*
|
|
* Reverse lookup of Lab
|
|
* Make NEARCLIP the default ??
|
|
*
|
|
* XYZ vector clipping isn't implemented.
|
|
*
|
|
* Some of the error handling is crude. Shouldn't use
|
|
* error(), should return status.
|
|
*/
|
|
|
|
static double icxLimitD(icxLuLut *p, double *in); /* For input' */
|
|
#define icxLimitD_void ((double (*)(void *, double *))icxLimitD) /* Cast with void 1st arg */
|
|
static double icxLimit(icxLuLut *p, double *in); /* For input */
|
|
static int icxLuLut_init_clut_camclip(icxLuLut *p);
|
|
|
|
/* Debug overall lookup */
|
|
#ifdef DEBUG_OLUT
|
|
#undef DBOL
|
|
#ifdef CNDTRACE
|
|
#define DBOL(xxx) if (p->trace) printf xxx ;
|
|
#else
|
|
#define DBOL(xxx) printf xxx ;
|
|
#endif
|
|
#else
|
|
#undef DBOL
|
|
#define DBOL(xxx)
|
|
#endif
|
|
|
|
/* Debug reverse lookup */
|
|
#ifdef DEBUG_RLUT
|
|
#undef DBR
|
|
#ifdef CNDTRACE
|
|
#define DBR(xxx) if (p->trace) printf xxx ;
|
|
#else
|
|
#define DBR(xxx) printf xxx ;
|
|
#endif
|
|
#else
|
|
#undef DBR
|
|
#define DBR(xxx)
|
|
#endif
|
|
|
|
/* 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
|
|
|
|
#ifdef SKIP_PERCH_LOOKUPS
|
|
# pragma message("!!!!!!!!!!!! SKIP_PERCH_LOOKUPS is defined !!!!!!!!!")
|
|
#endif
|
|
|
|
/* ========================================================== */
|
|
/* xicc lookup code. */
|
|
/* ========================================================== */
|
|
|
|
/* Forward and Backward Multi-Dimensional Interpolation type conversion */
|
|
/* Return 0 on success, 1 if clipping occured, 2 on other error */
|
|
|
|
/* Components of overall lookup, in order */
|
|
|
|
int icxLuLut_in_abs(icxLuLut *p, double *out, double *in) {
|
|
icmPe_lurv prv = icmPe_lurv_OK;
|
|
int rv = 0;
|
|
|
|
if (p->ins == icxSigJabData) {
|
|
DBOL(("xlut in_abs: CAM in = %s\n", icmPdv(p->inputChan, in)));
|
|
p->cam->cam_to_XYZ(p->cam, out, in);
|
|
DBOL(("xlut in_abs: XYZ = %s\n", icmPdv(p->inputChan, out)));
|
|
/* Hack to prevent CAM02 weirdness being amplified by inv_abs() */
|
|
/* or 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;
|
|
DBOL(("xlut in_abs: after clipping -Y %s\n",icmPdv(p->outputChan, out)));
|
|
}
|
|
prv = p->plu->input_fmt_fwd(p->plu, out, out);
|
|
rv = LUE2XLUE(prv);
|
|
DBOL(("xlut in_abs: XYZ out = %s\n", icmPdv(p->inputChan, out)));
|
|
} else {
|
|
DBOL(("xlut in_abs: PCS in = %s\n", icmPdv(p->inputChan, in)));
|
|
prv = p->plu->input_fmt_fwd(p->plu, out, in);
|
|
rv = LUE2XLUE(prv);
|
|
DBOL(("xlut in_abs: PCS out = %s\n", icmPdv(p->inputChan, out)));
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
/* Possible matrix lookup */
|
|
/* input->input (not distinguishing matrix altered input values) */
|
|
int icxLuLut_matrix(icxLuLut *p, double *out, double *in) {
|
|
int rv = 0;
|
|
/* none. Combined with input & clut */
|
|
icmCpyN(out, in, p->inputChan);
|
|
return rv;
|
|
}
|
|
|
|
/* Do input -> input' lookup */
|
|
int icxLuLut_input(icxLuLut *p, double *out, double *in) {
|
|
icmPe_lurv prv = icmPe_lurv_OK;
|
|
prv = p->plu->input_pch_fwd(p->plu, out, in);
|
|
return LUE2XLUE(prv);
|
|
}
|
|
|
|
/* Do input'->output' lookup, with aux' return */
|
|
/* (The aux' is just extracted from the in' values) */
|
|
int icxLuLut_clut_aux(icxLuLut *p,
|
|
double *out, /* output' value */
|
|
double *oink, /* If not NULL, return amount input is over the ink limit, 0 if not */
|
|
double *auxv, /* If not NULL, return aux value used (packed) */
|
|
double *in /* input' value */
|
|
) {
|
|
int rv = 0;
|
|
int i;
|
|
|
|
/* Use icm lookup for forward direction, since this will be more */
|
|
/* accurate than rspl cLUT if core5 is complex. */
|
|
icmPe_lurv prv = icmPe_lurv_OK;
|
|
|
|
if (p->mergeclut == 0) { /* Do this if it's not merged with clut, */
|
|
prv |= p->plu->core5_fwd(p->plu, out, in);
|
|
} else { /* If mergeclut */
|
|
prv |= p->plu->core5_fwd(p->plu, out, in);
|
|
prv |= p->plu->output_pch_fwd(p->plu, out, out);
|
|
prv |= p->plu->output_fmt_fwd(p->plu, out, out);
|
|
|
|
if (p->outs == icxSigJabData) {
|
|
p->cam->XYZ_to_cam(p->cam, out, out);
|
|
}
|
|
}
|
|
rv = LUE2XLUE(prv);
|
|
|
|
if (auxv != NULL) {
|
|
int ee = 0;
|
|
for (i = 0; i < p->clutTable->di; i++) {
|
|
double v = in[i];
|
|
if (p->auxm[i] != 0) {
|
|
auxv[ee] = v;
|
|
ee++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (oink != NULL) {
|
|
double lim = 0.0;
|
|
|
|
if (p->ink.tlimit >= 0.0 || p->ink.klimit >= 0.0) {
|
|
lim = icxLimitD(p, in);
|
|
if (lim < 0.0)
|
|
lim = 0.0;
|
|
}
|
|
*oink = lim;
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
/* Do input'->output' lookup */
|
|
int icxLuLut_clut(icxLuLut *p, double *out, double *in) {
|
|
#ifdef NEVER
|
|
return ((icmLuLut *)p->plu)->clut((icmLuLut *)p->plu, out, in);
|
|
#else
|
|
return icxLuLut_clut_aux(p, out, NULL, NULL, in);
|
|
#endif
|
|
}
|
|
|
|
/* Do output'->output lookup */
|
|
int icxLuLut_output(icxLuLut *p, double *out, double *in) {
|
|
int rv = 0;
|
|
|
|
if (p->mergeclut == 0) {
|
|
icmPe_lurv prv = icmPe_lurv_OK;
|
|
prv = p->plu->output_pch_fwd(p->plu, out, in);
|
|
rv = LUE2XLUE(prv);
|
|
} else {
|
|
int i;
|
|
for (i = 0; i < p->outputChan; i++)
|
|
out[i] = in[i];
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
/* Relative to absolute conversion + PCS to PCS override (Effective PCS) conversion */
|
|
int icxLuLut_out_abs(icxLuLut *p, double *out, double *in) {
|
|
icmPe_lurv prv = icmPe_lurv_OK;
|
|
int rv = 0;
|
|
if (p->mergeclut == 0) {
|
|
DBOL(("xlut out_abs: PCS in = %s\n", icmPdv(p->outputChan, in)));
|
|
|
|
prv = p->plu->output_fmt_fwd(p->plu, out, in);
|
|
rv = LUE2XLUE(prv);
|
|
|
|
DBOL(("xlut out_abs: ABS PCS out = %s\n", icmPdv(p->outputChan, out)));
|
|
|
|
if (p->outs == icxSigJabData) {
|
|
p->cam->XYZ_to_cam(p->cam, out, out);
|
|
|
|
DBOL(("xlut out_abs: CAM out = %s\n", icmPdv(p->outputChan, out)));
|
|
}
|
|
} else {
|
|
int i;
|
|
for (i = 0; i < p->outputChan; i++)
|
|
out[i] = in[i];
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
/* Overall lookup */
|
|
static int
|
|
icxLuLut_lookup (
|
|
icxLuBase *pp, /* This */
|
|
double *out, /* Vector of output values */
|
|
double *in /* Vector of input values */
|
|
) {
|
|
icxLuLut *p = (icxLuLut *)pp;
|
|
int rv = 0;
|
|
double temp[MAX_CHAN];
|
|
|
|
#ifdef SKIP_PERCH_LOOKUPS
|
|
DBOL(("xicclu: input = %s\n", icmPdv(p->inputChan, in)));
|
|
rv |= p->clut (p, out, in);
|
|
DBOL(("xicclu: output = %s\n", icmPdv(p->outputChan, out)));
|
|
#else
|
|
DBOL(("xicclu: in = %s\n", icmPdv(p->inputChan, in)));
|
|
rv |= p->in_abs (p, temp, in);
|
|
DBOL(("xicclu: after abs = %s\n", icmPdv(p->inputChan, temp)));
|
|
rv |= p->matrix (p, temp, temp);
|
|
DBOL(("xicclu: after matrix = %s\n", icmPdv(p->inputChan, temp)));
|
|
rv |= p->input (p, temp, temp);
|
|
DBOL(("xicclu: after inout = %s\n", icmPdv(p->inputChan, temp)));
|
|
rv |= p->clut (p, out, temp);
|
|
DBOL(("xicclu: after clut = %s\n", icmPdv(p->outputChan, out)));
|
|
if (p->mergeclut == 0) {
|
|
rv |= p->output (p, out, out);
|
|
DBOL(("xicclu: after output = %s\n", icmPdv(p->outputChan, out)));
|
|
rv |= p->out_abs (p, out, out);
|
|
DBOL(("xicclu: after outabs = %s\n", icmPdv(p->outputChan, out)));
|
|
}
|
|
#endif
|
|
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 icxLuLut_fwd_relpcs_outpcs(
|
|
icxLuBase *pp,
|
|
icColorSpaceSignature is, /* Input space, XYZ or Lab */
|
|
double *out, double *in) {
|
|
icmPe_lurv prv = icmPe_lurv_OK;
|
|
icxLuLut *p = (icxLuLut *)pp;
|
|
|
|
/* Convert to the ICC PCS */
|
|
if (is == icSigLabData && p->natpcs == icSigXYZData) {
|
|
DBS(("fwd_relpcs_outpcs: Lab in = %s\n", icmPdv(p->inputChan, in)));
|
|
icmLab2XYZ(&icmD50, out, in);
|
|
DBS(("fwd_relpcs_outpcs: XYZ = %s\n", icmPdv(p->inputChan, out)));
|
|
} else if (is == icSigXYZData && p->natpcs == icSigLabData) {
|
|
DBS(("fwd_relpcs_outpcs: XYZ in = %s\n", icmPdv(p->inputChan, in)));
|
|
icmXYZ2Lab(&icmD50, out, in);
|
|
DBS(("fwd_relpcs_outpcs: Lab = %s\n", icmPdv(p->inputChan, out)));
|
|
} else {
|
|
DBS(("fwd_relpcs_outpcs: PCS in = %s\n", icmPdv(p->inputChan, in)));
|
|
icmCpy3(out, in);
|
|
}
|
|
|
|
/* Convert to final PCS (i.e. absolute intent is set that way) */
|
|
prv = p->plu->output_fmt_fwd(p->plu, out, out);
|
|
|
|
DBS(("fwd_relpcs_outpcs: abs PCS = %s\n", icmPdv(p->inputChan, out)));
|
|
|
|
if (p->outs == icxSigJabData) {
|
|
|
|
/* Convert to CAM */
|
|
p->cam->XYZ_to_cam(p->cam, out, out);
|
|
|
|
DBS(("fwd_relpcs_outpcs: Jab = %s\n", icmPdv(p->inputChan, out)));
|
|
}
|
|
}
|
|
|
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
|
/* Components of inverse lookup, in order */
|
|
|
|
static double sigfunc(double in, double pp) {
|
|
if (in < 0.5)
|
|
return pow(2 * in, pp) * 0.5;
|
|
else
|
|
return 1.0 - (pow(2 * (1.0 - in), pp) * 0.5);
|
|
}
|
|
|
|
/* Utility function - compute the clip vector direction. */
|
|
/* return NULL if vector clip isn't used. */
|
|
double *icxClipVector(
|
|
icxClip *p, /* Clipping setup information */
|
|
double *in, /* Target point */
|
|
double *cdirv, /* Returned clip vector */
|
|
int safe /* Flag - return safe vector */
|
|
) {
|
|
int f;
|
|
if (p->nearclip != 0)
|
|
return NULL; /* Doing nearest clipping, not vector */
|
|
|
|
if (p->cm == NULL) {
|
|
/* Default is simple vector clip */
|
|
for (f = 0; f < p->fdi; f++)
|
|
cdirv[f] = p->ocent[f] - in[f]; /* Clip towards output gamut center */
|
|
|
|
/* Else use CuspMap for more targeted vector */
|
|
} else {
|
|
double Cpow = 2.0; /* [2.0] 4.0 for less L change */
|
|
double Lsig = 2.5; /* [2.5] 1.6 for less L change */
|
|
double Lpow = 0.5; /* [0.5] 0.8 for less L change */
|
|
double Cratio = 0.9; /* [0.9] see cusptest.c */
|
|
double ratio;
|
|
double ss[3];
|
|
double inC;
|
|
double targ[3], cuspLCh[3];
|
|
|
|
inC = sqrt(in[1] * in[1] + in[2] * in[2]);
|
|
|
|
p->cm->getCusp(p->cm, cuspLCh, in);
|
|
|
|
|
|
//icmLCh2Lab(targ, cuspLCh);
|
|
//printf("\n~1 in %f %f %f -> cusp %f %f %f\n", in[0], in[1], in[2], targ[0], targ[1], targ[2]);
|
|
//printf("~1 in %f %f %f -> cuspLCh %f %f %f\n", in[0], in[1], in[2], cuspLCh[0], cuspLCh[1], cuspLCh[2]);
|
|
|
|
/* Constrain clip vector to always be inwards pointing */
|
|
if (cuspLCh[1] > 0.90 * inC) {
|
|
cuspLCh[1] = 0.90 * inC;
|
|
//printf("~1 Constrain cusp C: cuspLCh %f %f %f\n", cuspLCh[0], cuspLCh[1], cuspLCh[2]);
|
|
}
|
|
|
|
ss[0] = in[0];
|
|
ss[1] = in[1];
|
|
ss[2] = in[2];
|
|
|
|
if (ss[0] < p->cm->Lmin[0])
|
|
ss[0] = p->cm->Lmin[0];
|
|
if (ss[0] > p->cm->Lmax[0])
|
|
ss[0] = p->cm->Lmax[0];
|
|
|
|
if (safe) {
|
|
targ[0] = cuspLCh[0]; /* L target is cusp L */
|
|
targ[1] = 0.0; /* Right on the neutral axis */
|
|
|
|
} else {
|
|
if (ss[0] >= cuspLCh[0]) {
|
|
ratio = (p->cm->Lmax[0] - ss[0])/(p->cm->Lmax[0] - cuspLCh[0]);
|
|
targ[0] = p->cm->Lmax[0] - sigfunc(pow(ratio, Lpow), Lsig)
|
|
* (p->cm->Lmax[0] - cuspLCh[0]);
|
|
targ[1] = pow(ratio, Cpow) * Cratio * cuspLCh[1];
|
|
} else {
|
|
ratio = (ss[0] - p->cm->Lmin[0])/(cuspLCh[0] - p->cm->Lmin[0]);
|
|
targ[0] = p->cm->Lmin[0] + sigfunc(pow(ratio, Lpow), Lsig)
|
|
* (cuspLCh[0] - p->cm->Lmin[0]);
|
|
targ[1] = pow(ratio, Cpow) * Cratio * cuspLCh[1];
|
|
}
|
|
}
|
|
targ[2] = cuspLCh[2]; /* h of in */
|
|
|
|
//printf("~1 targLCH %f %f %f\n", targ[0], targ[1], targ[2]);
|
|
icmLCh2Lab(targ, targ);
|
|
//printf("~1 targ %f %f %f\n", targ[0], targ[1], targ[2]);
|
|
|
|
/* line target point up with white and black point ? */
|
|
ratio = (ss[0] - p->cm->Lmin[0])/(p->cm->Lmax[0] - p->cm->Lmin[0]);
|
|
targ[1] += (1.0 - ratio) * p->cm->Lmin[1] + ratio * p->cm->Lmax[1];
|
|
targ[2] += (1.0 - ratio) * p->cm->Lmin[2] + ratio * p->cm->Lmax[2];
|
|
|
|
//printf("~1 in %f %f %f -> targ %f %f %f\n", in[0], in[1], in[2], targ[0], targ[1], targ[2]);
|
|
|
|
/* Compute target clip direction */
|
|
for (f = 0; f < p->fdi; f++)
|
|
cdirv[f] = (targ[f] - in[f]);
|
|
}
|
|
|
|
return cdirv;
|
|
}
|
|
|
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
|
/* Typically inv is used to invert a device->PCS table, */
|
|
/* so it is doing a PCS->device conversion. */
|
|
/* This doesn't always have to be the case though. */
|
|
|
|
/* PCS override (Effective PCS) to PCS conversion + absolute to relative conversion */
|
|
int icxLuLut_inv_out_abs(icxLuLut *p, double *out, double *in) {
|
|
icmPe_lurv prv = icmPe_lurv_OK;
|
|
int rv = 0;
|
|
|
|
DBR(("\nicxLuLut_inv_out_abs got PCS %s\n",icmPdv(p->outputChan, in)));
|
|
|
|
if (p->mergeclut == 0) {
|
|
if (p->outs == icxSigJabData) {
|
|
p->cam->cam_to_XYZ(p->cam, out, in);
|
|
DBR(("icxLuLut_inv_out_abs after cam2XYZ %s\n",icmPdv(p->outputChan, out)));
|
|
/* Hack to prevent CAM02 weirdness being amplified by inv_out_abs() */
|
|
/* or 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;
|
|
DBR(("icxLuLut_inv_out_abs after clipping -Y %s\n",icmPdv(p->outputChan, out)));
|
|
}
|
|
prv = p->plu->output_fmt_bwd(p->plu, out, out);
|
|
rv = LUE2XLUE(prv);
|
|
DBR(("icxLuLut_inv_out_abs after icmLut inv_out_abs %s\n",icmPdv(p->outputChan, out)));
|
|
} else {
|
|
prv = p->plu->output_fmt_bwd(p->plu, out, in);
|
|
rv = LUE2XLUE(prv);
|
|
DBR(("icxLuLut_inv_out_abs after icmLut inv_out_abs %s\n",icmPdv(p->outputChan, out)));
|
|
}
|
|
} else {
|
|
int i;
|
|
for (i = 0; i < p->outputChan; i++)
|
|
out[i] = in[i];
|
|
}
|
|
DBR(("icxLuLut_inv_out_abs returning PCS %f %f %f\n",out[0],out[1],out[2]))
|
|
return rv;
|
|
}
|
|
|
|
/* Do output->output' inverse lookup */
|
|
int icxLuLut_inv_output(icxLuLut *p, double *out, double *in) {
|
|
icmPe_lurv prv = icmPe_lurv_OK;
|
|
int rv = 0;
|
|
DBR(("icxLuLut_inv_output got PCS %f %f %f\n",in[0],in[1],in[2]))
|
|
if (p->mergeclut == 0) {
|
|
prv = p->plu->output_pch_bwd(p->plu, out, in);
|
|
rv = LUE2XLUE(prv);
|
|
} else {
|
|
int i;
|
|
for (i = 0; i < p->outputChan; i++)
|
|
out[i] = in[i];
|
|
}
|
|
DBR(("icxLuLut_inv_output returning PCS' %f %f %f\n",out[0],out[1],out[2]))
|
|
return rv;
|
|
}
|
|
|
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
|
|
|
/* Convert input values from linearized to (potentially) */
|
|
/* final values using the xcal curves */
|
|
static void xicc_to_final(icxLuLut *p, double *out, double *in) {
|
|
xicc *pp = p->pp;
|
|
|
|
if (out != in)
|
|
vect_cpy(out, in, p->inputChan);
|
|
|
|
if (pp->cal != NULL)
|
|
pp->cal->interp(pp->cal, out, out);
|
|
|
|
}
|
|
|
|
/* Convert input values from (potentially) final values */
|
|
/* to linearized device values using the xcal curves */
|
|
static void xicc_from_final(icxLuLut *p, double *out, double *in) {
|
|
xicc *pp = p->pp;
|
|
|
|
if (out != in)
|
|
vect_cpy(out, in, p->inputChan);
|
|
|
|
|
|
if (pp->cal != NULL)
|
|
pp->cal->inv_interp(pp->cal, out, out);
|
|
}
|
|
|
|
#ifdef NEVER /* Not currently used ?? */
|
|
|
|
/* Convert input value from linearized to (potentially) */
|
|
/* final values using the xcal curves */
|
|
static double xicc_to_final_ch(icxLuLut *p, int ch, double in) {
|
|
xicc *pp = p->pp;
|
|
double out = in;
|
|
|
|
if (pp->cal != NULL)
|
|
out = pp->cal->interp_ch(pp->cal, ch, out);
|
|
|
|
|
|
return out;
|
|
}
|
|
|
|
/* Convert input values from (potentially) final values */
|
|
/* to linearized device values using the xcal curves */
|
|
static double xicc_from_final_ch(icxLuLut *p, int ch, double in) {
|
|
xicc *pp = p->pp;
|
|
double out = in;
|
|
|
|
|
|
if (pp->cal != NULL)
|
|
out = pp->cal->inv_interp_ch(pp->cal, ch, out);
|
|
|
|
return out;
|
|
}
|
|
#endif /* NEVER */
|
|
|
|
/* Ink limit+gamut limit calculation function for xLuLut. */
|
|
/* (Same functionality as xfitLimit() below, but use by bfindfunc(()) */
|
|
/* Returns < 0.0 if input value is within limits, */
|
|
/* > 0.0 if outside limits. Value is proportinal to distance to limits. */
|
|
/* We implement device gamut check to improve utility outside rspl, */
|
|
/* in optimisation routines. */
|
|
/* The limits are assumed to be post calibrated device values */
|
|
/* (i.e. final device values) */
|
|
static double icxLimit(
|
|
icxLuLut *p,
|
|
double *in
|
|
) {
|
|
double cin[MAX_CHAN]; /* final raw input values */
|
|
double tlim, klim;
|
|
double ovr, val;
|
|
int e;
|
|
|
|
/* Compute amount outside device value limits 0.0 - 1.0 */
|
|
for (ovr = -1.0, e = 0; e < p->inputChan; e++) {
|
|
cin[e] = in[e];
|
|
if (in[e] < 0.0) { /* ! Not cin[] */
|
|
cin[e] = 0.0;
|
|
if (-in[e] > ovr)
|
|
ovr = -in[e];
|
|
} else if (in[e] > 1.0) {
|
|
cin[e] = 1.0;
|
|
if ((in[e] - 1.0) > ovr)
|
|
ovr = in[e] - 1.0;
|
|
}
|
|
}
|
|
|
|
xicc_to_final(p, cin, cin);
|
|
|
|
if ((tlim = p->ink.tlimit) < 0.0)
|
|
tlim = (double)p->inputChan; /* Default is no limit */
|
|
|
|
if ((klim = p->ink.klimit) < 0.0)
|
|
klim = 1.0;
|
|
|
|
/* Compute amount outside total limit */
|
|
{ /* No calibration */
|
|
double sum;
|
|
for (sum = 0.0, e = 0; e < p->inputChan; e++)
|
|
sum += cin[e];
|
|
val = sum - tlim;
|
|
}
|
|
|
|
/* Compute amount outside black limit */
|
|
if (p->ink.klimit >= 0.0) {
|
|
double kval = 0.0;
|
|
switch(p->natis) {
|
|
case icSigCmykData:
|
|
kval = cin[3] - klim;
|
|
break;
|
|
default:
|
|
/* NOTE !!! p->kch isn't being initialized !!! */
|
|
if (p->kch >= 0) {
|
|
kval = cin[p->kch] - klim;
|
|
} else {
|
|
error("xlut: Unknown colorspace when black limit specified");
|
|
}
|
|
}
|
|
if (kval > val)
|
|
val = kval;
|
|
}
|
|
|
|
if (ovr > val)
|
|
val = ovr;
|
|
|
|
return val;
|
|
}
|
|
|
|
/* Same as above, but works with input' values */
|
|
/* (If an ink limit is being used we assume that the */
|
|
/* input space is not PCS, hence inv_in_abs() is doing nothing) */
|
|
static double icxLimitD(
|
|
icxLuLut *p,
|
|
double *ind
|
|
) {
|
|
double in[MAX_CHAN], val;
|
|
int e;
|
|
co tc;
|
|
|
|
/* Convert input' to input through revinput Luts (for speed) */
|
|
for (e = 0; e < p->inputChan; e++) {
|
|
tc.p[0] = ind[e];
|
|
p->revinputTable[e]->interp(p->revinputTable[e], &tc);
|
|
in[e] = tc.v[0];
|
|
}
|
|
val = icxLimit(p, in);
|
|
|
|
return val;
|
|
}
|
|
|
|
#ifdef NEVER
|
|
#undef DBK
|
|
#define DBK(xxx) printf xxx ;
|
|
#else
|
|
#undef DBK
|
|
#define DBK(xxx)
|
|
#endif
|
|
|
|
/* helper function that creates our standard K locus curve value, */
|
|
/* given the curve parameters, and the normalised L 0.0 - 1.0 value. */
|
|
/* No filtering version. */
|
|
/* !!! Should add K limit in here so that smoothing takes it into account !!! */
|
|
static double icxKcurveNF(double L, icxInkCurve *c) {
|
|
double Kstpo, Kenpo, Kstle, Kenle;
|
|
double rv;
|
|
|
|
DBK(("icxKcurve got L = %f, smth %f skew %f, Parms %f %f %f %f %f\n",L, c->Ksmth, c->Kskew, c->Kstle, c->Kstpo, c->Kenpo, c->Kenle, c->Kshap));
|
|
|
|
/* Invert sense of L, so that 0.0 = white, 1.0 = black */
|
|
L = 1.0 - L;
|
|
|
|
/* Clip L, just in case */
|
|
if (L < 0.0) {
|
|
L = 0.0;
|
|
} else if (L > 1.0) {
|
|
L = 1.0;
|
|
}
|
|
DBK(("Clipped inverted L = %f\n",L));
|
|
|
|
/* Make sure breakpoints are ordered */
|
|
if (c->Kstpo < c->Kenpo) {
|
|
Kstle = c->Kstle;
|
|
Kstpo = c->Kstpo;
|
|
Kenpo = c->Kenpo;
|
|
Kenle = c->Kenle;
|
|
} else { /* They're swapped */
|
|
Kstle = c->Kenle;
|
|
Kstpo = c->Kenpo;
|
|
Kenpo = c->Kstpo;
|
|
Kenle = c->Kstle;
|
|
}
|
|
|
|
if (L <= Kstpo) {
|
|
/* We are at white level */
|
|
rv = Kstle;
|
|
DBK(("At white level %f\n",rv));
|
|
} else if (L >= Kenpo) {
|
|
/* We are at black level */
|
|
rv = Kenle;
|
|
DBK(("At black level %f\n",rv));
|
|
} else {
|
|
double Lp, g;
|
|
/* We must be on the curve from start to end levels */
|
|
|
|
Lp = (L - Kstpo)/(Kenpo - Kstpo);
|
|
|
|
DBK(("Curve position %f\n",Lp));
|
|
|
|
Lp = pow(Lp, c->Kskew);
|
|
|
|
DBK(("Skewed curve position %f\n",Lp));
|
|
|
|
g = c->Kshap/2.0;
|
|
DBK(("Curve bf %f, g %g\n",Lp,g));
|
|
|
|
/* A value of 0.5 will be tranlated to g */
|
|
Lp = Lp/((1.0/g - 2.0) * (1.0 - Lp) + 1.0);
|
|
|
|
DBK(("Skewed shaped %f\n",Lp));
|
|
|
|
Lp = pow(Lp, 1.0/c->Kskew);
|
|
|
|
DBK(("Shaped %f\n",Lp));
|
|
|
|
/* Transition between start end end levels */
|
|
rv = Lp * (Kenle - Kstle) + Kstle;
|
|
|
|
DBK(("Scaled to start and end levele %f\n",rv));
|
|
}
|
|
|
|
DBK(("Returning %f\n",rv));
|
|
return rv;
|
|
}
|
|
|
|
#ifdef DBK
|
|
#undef DBK
|
|
#define DBK(xxx)
|
|
#endif
|
|
|
|
|
|
#ifdef NEVER
|
|
#undef DBK
|
|
#define DBK(xxx) printf xxx ;
|
|
#else
|
|
#undef DBK
|
|
#define DBK(xxx)
|
|
#endif
|
|
|
|
/* Same as above, but implement transition filters across inflection points. */
|
|
/* (The convolultion filter previously used could be */
|
|
/* re-instituted if something was done about compressing */
|
|
/* the filter at the boundaries so that the levels are met.) */
|
|
static double icxKcurve(double L, icxInkCurve *c) {
|
|
|
|
#ifdef DISABLE_KCURVE_FILTER
|
|
return icxKcurveNF(L, c);
|
|
|
|
#else /* !DISABLE_KCURVE_FILTER */
|
|
|
|
double Kstpo, Kenpo, Kstle, Kenle, Ksmth;
|
|
double rv;
|
|
|
|
DBK(("icxKcurve got L = %f, smth %f skew %f, Parms %f %f %f %f %f\n",L, c->Ksmth, c->Kskew, c->Kstle, c->Kstpo, c->Kenpo, c->Kenle, c->Kshap));
|
|
|
|
/* Invert sense of L, so that 0.0 = white, 1.0 = black */
|
|
L = 1.0 - L;
|
|
|
|
/* Clip L, just in case */
|
|
if (L < 0.0) {
|
|
L = 0.0;
|
|
} else if (L > 1.0) {
|
|
L = 1.0;
|
|
}
|
|
DBK(("Clipped inverted L = %f\n",L));
|
|
|
|
/* Make sure breakpoints are ordered */
|
|
if (c->Kstpo < c->Kenpo) {
|
|
Kstle = c->Kstle;
|
|
Kstpo = c->Kstpo;
|
|
Kenpo = c->Kenpo;
|
|
Kenle = c->Kenle;
|
|
} else { /* They're swapped */
|
|
Kstle = c->Kenle;
|
|
Kstpo = c->Kenpo;
|
|
Kenpo = c->Kstpo;
|
|
Kenle = c->Kstle;
|
|
}
|
|
Ksmth = c->Ksmth;
|
|
|
|
/* Curve value at point */
|
|
rv = icxKcurveNF(1.0 - L, c);
|
|
|
|
DBK(("Raw output at iL = %f, rv\n",L));
|
|
|
|
/* Create filtered value */
|
|
{
|
|
double wbs, wbe; /* White transitioin start, end */
|
|
double wbl, wfv; /* White blend factor, value at filter band */
|
|
|
|
double mt; /* Middle of the two transitions */
|
|
|
|
double bbs, bbe; /* Black transitioin start, end */
|
|
double bbl, bfv; /* Black blend factor, value at filter band */
|
|
|
|
wbs = Kstpo - Ksmth;
|
|
wbe = Kstpo + Ksmth;
|
|
|
|
bbs = Kenpo - 1.0 * Ksmth;
|
|
bbe = Kenpo + 1.0 * Ksmth;
|
|
|
|
mt = 0.5 * (wbe + bbs);
|
|
|
|
/* Make sure that the whit & black transition regions */
|
|
/* don't go out of bounts or overlap */
|
|
if (wbs < 0.0) {
|
|
wbe += wbs;
|
|
wbs = 0.0;
|
|
}
|
|
if (bbe > 1.0) {
|
|
bbs += (bbe - 1.0);
|
|
bbe = 1.0;
|
|
}
|
|
|
|
if (wbe > mt) {
|
|
wbs += (wbe - mt);
|
|
wbe = mt;
|
|
}
|
|
|
|
if (bbs < mt) {
|
|
bbe += (mt - bbs);
|
|
bbs = mt;
|
|
}
|
|
|
|
DBK(("Transition windows %f - %f, %f - %f\n",wbs, wbe, bbw, bbe));
|
|
if (wbs < wbe) {
|
|
wbl = (L - wbe)/(wbs - wbe);
|
|
|
|
if (wbl > 0.0 && wbl < 1.0) {
|
|
wfv = icxKcurveNF(1.0 - wbe, c);
|
|
DBK(("iL = %f, wbl = %f, wfv = %f\n",L,Kstpo,wbl,wfv));
|
|
|
|
wbl = 1.0 - pow(1.0 - wbl, 2.0);
|
|
rv = wbl * Kstle + (1.0 - wbl) * wfv;
|
|
}
|
|
}
|
|
if (bbs < bbe) {
|
|
bbl = (L - bbe)/(bbs - bbe);
|
|
|
|
if (bbl > 0.0 && bbl < 1.0) {
|
|
bfv = icxKcurveNF(1.0 - bbs, c);
|
|
DBK(("iL = %f, bbl = %f, bfv = %f\n",L,Kstpo,bbl,bfv));
|
|
|
|
bbl = pow(bbl, 2.0);
|
|
rv = bbl * bfv + (1.0 - bbl) * Kenle;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* To be safe */
|
|
if (rv < 0.0)
|
|
rv = 0.0;
|
|
else if (rv > 1.0)
|
|
rv = 1.0;
|
|
|
|
DBK(("Returning %f\n",rv));
|
|
return rv;
|
|
#endif /* !DISABLE_KCURVE_FILTER */
|
|
}
|
|
|
|
#ifdef DBK
|
|
#undef DBK
|
|
#define DBK(xxx)
|
|
#endif
|
|
|
|
/* Do output'->input' lookup with aux details. */
|
|
/* Note that out[] will be used as the inking value if icxKrule is */
|
|
/* icxKvalue, icxKlocus, icxKl5l or icxKl5lk, and that the auxiliar values, PCS ranges */
|
|
/* and icxKrule value will all be evaluated in output->input space (not ' space). */
|
|
/* Note that the ink limit will be computed after converting input' to input, auxt */
|
|
/* will override the inking rule, and auxr[] reflects the available auxiliary range */
|
|
/* that the locus was to choose from, and auxv[] was the actual auxiliary used. */
|
|
/* Returns clip status. */
|
|
int icxLuLut_inv_clut_aux(
|
|
icxLuLut *p,
|
|
double *out, /* Function return values, plus aux value or locus target input if auxt == NULL */
|
|
double *auxv, /* If not NULL, return aux value used (packed) */
|
|
double *auxr, /* If not NULL, return aux locus range (packed, 2 at a time) */
|
|
double *auxt, /* If not NULL, specify the aux target for this lookup (override ink) */
|
|
double *clipd, /* If not NULL, return DE to gamut on clipi, 0 for not clip */
|
|
double *in /* Function input values to invert (== clut output' values) */
|
|
) {
|
|
co pp[MAX_INVSOLN]; /* Room for all the solutions found */
|
|
co upp; /* pp[0] value sent to rev_interp() for replay. */
|
|
int nsoln; /* Number of solutions found */
|
|
double *cdir, cdirv[MXDO]; /* Clip vector direction and length/LCh weighting */
|
|
int e,f,i;
|
|
int fdi = p->clutTable->fdi;
|
|
int flags = 0; /* reverse interp flags */
|
|
int xflags = 0; /* extra clip/noclip flags */
|
|
int uflags = 0; /* flags value sent to rev_interp() for replay */
|
|
double tin[MXDO]; /* PCS value to be inverted */
|
|
double cdist = 0.0; /* clip DE */
|
|
int crv = 0; /* Return value - set to 1 if clipped */
|
|
|
|
if (p->nearclip != 0)
|
|
flags |= RSPL_NEARCLIP; /* Use nearest clipping rather than clip vector */
|
|
|
|
DBR(("inv_clut_aux input is %f %f %f\n",in[0], in[1], in[2]))
|
|
|
|
if (auxr != NULL) { /* Set a default locus range */
|
|
int ee = 0;
|
|
for (e = 0; e < p->clutTable->di; e++) {
|
|
if (p->auxm[e] != 0) {
|
|
auxr[ee++] = 1e60;
|
|
auxr[ee++] = -1e60;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Setup for reverse lookup */
|
|
for (f = 0; f < fdi; f++)
|
|
upp.v[f] = pp[0].v[f] = in[f]; /* Target value */
|
|
|
|
/* Compute clip vector, if any */
|
|
cdir = icxClipVector(&p->clip, in, cdirv, 0);
|
|
|
|
if (p->clutTable->di > fdi) { /* ie. CMYK->Lab, there will be ambiguity */
|
|
double min[MXDI], max[MXDI]; /* Auxiliary locus range */
|
|
|
|
#ifdef REPORT_LOCUS_SEGMENTS /* Examine how many segments there are */
|
|
{ /* ie. CMYK->Lab, there will be ambiguity */
|
|
double smin[10][MXRI], smax[10][MXRI]; /* Auxiliary locus segment ranges */
|
|
double min[MXRI], max[MXRI]; /* Auxiliary min and max locus range */
|
|
|
|
nsoln = p->clutTable->rev_locus_segs(
|
|
p->clutTable, /* rspl object */
|
|
p->auxm, /* Auxiliary mask */
|
|
pp, /* Input target and output solutions */
|
|
10, /* Maximum number of solutions to return */
|
|
smin, smax); /* Returned locus of valid auxiliary values */
|
|
|
|
if (nsoln != 0) {
|
|
/* Convert the locuses from input' -> input space */
|
|
/* and get overall min/max locus range */
|
|
for (e = 0; e < p->clutTable->di; e++) {
|
|
co tc;
|
|
/* (Is speed more important than precision ?) */
|
|
if (p->auxm[e] != 0) {
|
|
for (i = 0; i < nsoln; i++) {
|
|
tc.p[0] = smin[i][e];
|
|
p->revinputTable[e]->interp(p->revinputTable[e], &tc);
|
|
smin[i][e] = tc.v[0];
|
|
tc.p[0] = smax[i][e];
|
|
p->revinputTable[e]->interp(p->revinputTable[e], &tc);
|
|
smax[i][e] = tc.v[0];
|
|
printf(" Locus seg %d:[%d] %f -> %f\n",i, e, smin[i][e], smax[i][e]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif /* REPORT_LOCUS_SEGMENTS */
|
|
|
|
/* Compute auxiliary locus on the fly. This is in dev' == input' space. */
|
|
nsoln = p->clutTable->rev_locus(
|
|
p->clutTable, /* rspl object */
|
|
p->auxm, /* Auxiliary mask */
|
|
pp, /* Input target and output solutions */
|
|
min, max); /* Returned locus of valid auxiliary values */
|
|
|
|
if (nsoln == 0) {
|
|
xflags |= RSPL_WILLCLIP; /* No valid locus, so we expect to have to clip */
|
|
#ifdef DEBUG_RLUT
|
|
printf("inv_clut_aux: no valid locus, expect clip\n");
|
|
#endif
|
|
/* Make sure that the auxiliar value is initialized, */
|
|
/* even though it won't have any effect. */
|
|
for (e = 0; e < p->clutTable->di; e++) {
|
|
if (p->auxm[e] != 0) {
|
|
upp.p[e] = pp[0].p[e] = 0.5;
|
|
}
|
|
}
|
|
|
|
} else { /* Got a valid locus */
|
|
|
|
/* Convert the locuses from input' -> input space */
|
|
for (e = 0; e < p->clutTable->di; e++) {
|
|
co tc;
|
|
/* (Is speed more important than precision ?) */
|
|
if (p->auxm[e] != 0) {
|
|
tc.p[0] = min[e];
|
|
p->revinputTable[e]->interp(p->revinputTable[e], &tc);
|
|
min[e] = tc.v[0];
|
|
tc.p[0] = max[e];
|
|
p->revinputTable[e]->interp(p->revinputTable[e], &tc);
|
|
max[e] = tc.v[0];
|
|
}
|
|
}
|
|
|
|
if (auxr != NULL) { /* Report the locus range */
|
|
int ee = 0;
|
|
for (e = 0; e < p->clutTable->di; e++) {
|
|
if (p->auxm[e] != 0) {
|
|
auxr[ee++] = min[e];
|
|
auxr[ee++] = max[e];
|
|
}
|
|
}
|
|
}
|
|
|
|
if (auxt != NULL) { /* overiding auxiliary target */
|
|
int ee = 0;
|
|
for (e = 0; e < p->clutTable->di; e++) {
|
|
if (p->auxm[e] != 0) {
|
|
double iv = auxt[ee++];
|
|
if (iv < min[e])
|
|
iv = min[e];
|
|
else if (iv > max[e])
|
|
iv = max[e];
|
|
upp.p[e] = pp[0].p[e] = iv;
|
|
}
|
|
}
|
|
DBR(("inv_clut_aux: aux %f from auxt[] %f\n",pp[0].p[3],auxt[0]))
|
|
} else if (p->ink.k_rule == icxKvalue) {
|
|
/* Implement the auxiliary inking rule */
|
|
/* Target auxiliary values are provided in out[] K value */
|
|
for (e = 0; e < p->clutTable->di; e++) {
|
|
if (p->auxm[e] != 0) {
|
|
double iv = out[e]; /* out[] holds aux target value */
|
|
if (iv < min[e])
|
|
iv = min[e];
|
|
else if (iv > max[e])
|
|
iv = max[e];
|
|
upp.p[e] = pp[0].p[e] = iv;
|
|
}
|
|
}
|
|
DBR(("inv_clut_aux: aux %f from out[0] K target %f min %f max %f\n",pp[0].p[3],out[3],min[3],max[3]))
|
|
} else if (p->ink.k_rule == icxKlocus) {
|
|
/* Set target auxliary input values from values in out[] and locus */
|
|
for (e = 0; e < p->clutTable->di; e++) {
|
|
if (p->auxm[e] != 0) {
|
|
double ii, iv;
|
|
ii = out[e]; /* Input ink locus */
|
|
iv = min[e] + ii * (max[e] - min[e]); /* Output ink from locus */
|
|
if (iv < min[e])
|
|
iv = min[e];
|
|
else if (iv > max[e])
|
|
iv = max[e];
|
|
upp.p[e] = pp[0].p[e] = iv;
|
|
}
|
|
}
|
|
DBR(("inv_clut_aux: aux %f from out[0] locus %f min %f max %f\n",pp[0].p[3],out[3],min[3],max[3]))
|
|
} else { /* p->ink.k_rule == icxKluma5 || icxKluma5k || icxKl5l || icxKl5lk */
|
|
/* Auxiliaries are driven by a rule and the output values */
|
|
double rv, L;
|
|
|
|
/* If we've got a mergeclut, then the PCS' is the same as the */
|
|
/* effective PCS, and we need to convert to native PCS */
|
|
if (p->mergeclut) {
|
|
p->mergeclut = 0; /* Hack to be able to use inv_out_abs() */
|
|
icxLuLut_inv_out_abs(p, tin, in);
|
|
p->mergeclut = 1;
|
|
|
|
} else {
|
|
/* Convert native PCS' to native PCS values */
|
|
p->output(p, tin, in);
|
|
}
|
|
|
|
/* Figure out Luminance number */
|
|
if (p->natos == icSigXYZData) {
|
|
icmXYZ2Lab(&icmD50, tin, tin);
|
|
} else if (p->natos != icSigLabData) { /* Hmm. that's unexpected */
|
|
error("Assert: xlut K locus, unexpected native pcs of 0x%x\n",p->natos);
|
|
}
|
|
L = 0.01 * tin[0];
|
|
DBR(("inv_clut_aux: aux from Luminance, raw L = %f\n",L));
|
|
|
|
/* Normalise L to its possible range from min to max */
|
|
L = (L - p->Lmin)/(p->Lmax - p->Lmin);
|
|
DBR(("inv_clut_aux: Normalize L = %f\n",L));
|
|
|
|
/* Convert L to curve value */
|
|
rv = icxKcurve(L, &p->ink.c);
|
|
DBR(("inv_clut_aux: icxKurve lookup returns = %f\n",rv));
|
|
|
|
if (p->ink.k_rule == icxKluma5) { /* Curve is locus value */
|
|
|
|
/* Set target black as K fraction within locus */
|
|
|
|
for (e = 0; e < p->clutTable->di; e++) {
|
|
if (p->auxm[e] != 0) {
|
|
#ifdef KLOCUS2BLACKONLY
|
|
/* Select between zero to max locus */
|
|
upp.p[e] = pp[0].p[e] = rv * max[e];
|
|
#else
|
|
/* Select between min to max locus */
|
|
upp.p[e] = pp[0].p[e] = min[e] + rv * (max[e] - min[e]);
|
|
#endif
|
|
}
|
|
}
|
|
DBR(("inv_clut_aux: aux %f from locus %f min %f max %f\n",pp[0].p[3],rv,min[3],max[3]))
|
|
|
|
} else if (p->ink.k_rule == icxKluma5k) { /* Curve is K value */
|
|
|
|
for (e = 0; e < p->clutTable->di; e++) {
|
|
if (p->auxm[e] != 0) {
|
|
double iv = rv;
|
|
if (iv < min[e]) /* Clip to locus */
|
|
iv = min[e];
|
|
else if (iv > max[e])
|
|
iv = max[e];
|
|
upp.p[e] = pp[0].p[e] = iv;
|
|
}
|
|
}
|
|
DBR(("inv_clut_aux: aux %f from out[0] K target %f min %f max %f\n",pp[0].p[3],rv,min[3],max[3]))
|
|
|
|
} else { /* icxKl5l || icxKl5lk */
|
|
/* Create second curve, and use input locus to */
|
|
/* blend between */
|
|
|
|
double rv2; /* Upper limit */
|
|
|
|
/* Convert L to max curve value */
|
|
rv2 = icxKcurve(L, &p->ink.x);
|
|
|
|
if (rv2 < rv) { /* Ooops - better swap. */
|
|
double tt;
|
|
tt = rv;
|
|
rv = rv2;
|
|
rv2 = tt;
|
|
}
|
|
|
|
for (e = 0; e < p->clutTable->di; e++) {
|
|
if (p->auxm[e] != 0) {
|
|
if (p->ink.k_rule == icxKl5l) {
|
|
double ii;
|
|
ii = out[e]; /* Input K locus */
|
|
if (ii < 0.0)
|
|
ii = 0.0;
|
|
else if (ii > 1.0)
|
|
ii = 1.0;
|
|
ii = (1.0 - ii) * rv + ii * rv2;/* Blend between locus rule curves */
|
|
/* Out ink from output locus */
|
|
#ifdef KLOCUS2BLACKONLY
|
|
/* Select between zero to max locus */
|
|
upp.p[e] = pp[0].p[e] = ii * max[e];
|
|
#else
|
|
/* Select between min to max locus */
|
|
upp.p[e] = pp[0].p[e] = min[e] + ii * (max[e] - min[e]);
|
|
#endif
|
|
} else {
|
|
double iv;
|
|
iv = out[e]; /* Input K level */
|
|
if (iv < rv) /* Constrain to curves */
|
|
iv = rv;
|
|
else if (iv > rv2)
|
|
iv = rv2;
|
|
upp.p[e] = pp[0].p[e] = iv;
|
|
}
|
|
}
|
|
}
|
|
DBR(("inv_clut_aux: aux %f from 2 curves\n",pp[0].p[3]))
|
|
}
|
|
}
|
|
|
|
/* Convert to input/dev aux target to input'/dev' space for rspl inversion */
|
|
{
|
|
double clp[MAX_CHAN];
|
|
for (e = 0; e < p->clutTable->di; e++) {
|
|
double tv = pp[0].p[e];
|
|
/* Clip to overall locus range (belt and braces) */
|
|
if (tv < min[e])
|
|
tv = min[e];
|
|
if (tv > max[e])
|
|
tv = max[e];
|
|
clp[e] = tv;
|
|
}
|
|
p->plu->input_pch_fwd(p->plu, pp[0].p, clp);
|
|
icmCpyN(upp.p, pp[0].p, p->clutTable->di);
|
|
}
|
|
|
|
xflags |= RSPL_EXACTAUX; /* Since we confine aux to locus */
|
|
|
|
#ifdef DEBUG_RLUT
|
|
printf("inv_clut_aux computed aux values ");
|
|
for (e = 0; e < p->clutTable->di; e++) {
|
|
if (p->auxm[e] != 0)
|
|
printf("%d: %f ",e,pp[0].p[e]);
|
|
}
|
|
printf("\n");
|
|
#endif /* DEBUG_RLUT */
|
|
}
|
|
|
|
if (clipd != NULL) { /* Copy pp.v[] to compute clip DE */
|
|
for (f = 0; f < fdi; f++)
|
|
tin[f] = pp[0].v[f];
|
|
}
|
|
|
|
uflags = RSPL_MAXAUX | flags | xflags; /* Combine all the flags */
|
|
|
|
/* Find reverse solution with target auxiliaries */
|
|
/* We choose the closest aux at or above the target */
|
|
/* to try and avoid glitches near black due to */
|
|
/* possible forked black locuses. */
|
|
nsoln = p->clutTable->rev_interp(
|
|
p->clutTable, /* rspl object */
|
|
uflags,
|
|
MAX_INVSOLN, /* Maxumum solutions to return */
|
|
p->auxm, /* Auxiliary input chanel mask */
|
|
cdir, /* Clip vector direction/LCh weighting */
|
|
pp); /* Input target and output solutions */
|
|
/* returned solutions in pp[0..retval-1].p[] */
|
|
|
|
} else {
|
|
DBR(("inv_clut_aux needs no aux value\n"))
|
|
|
|
if (clipd != NULL) { /* Copy pp.v[] to compute clip DE */
|
|
for (f = 0; f < fdi; f++)
|
|
tin[f] = pp[0].v[f];
|
|
}
|
|
|
|
uflags = flags; /* No extra flags */
|
|
|
|
/* Color spaces don't need auxiliaries to choose from solution locus */
|
|
nsoln = p->clutTable->rev_interp(
|
|
p->clutTable, /* rspl object */
|
|
uflags,
|
|
MAX_INVSOLN, /* Maxumum solutions to return */
|
|
NULL, /* No auxiliary input targets */
|
|
cdir, /* Clip vector direction/LCh weighting */
|
|
pp); /* Input target and output solutions */
|
|
/* returned solutions in pp[0..retval-1].p[] */
|
|
}
|
|
if (nsoln & RSPL_DIDCLIP)
|
|
crv = 1; /* Clipped on PCS inverse lookup */
|
|
|
|
if (crv && clipd != NULL) {
|
|
|
|
|
|
/* Compute the clip DE */
|
|
for (cdist = 0.0, f = 0; f < fdi; f++) {
|
|
double tt;
|
|
tt = pp[0].v[f] - tin[f];
|
|
cdist += tt * tt;
|
|
}
|
|
cdist = sqrt(cdist);
|
|
DBR(("Targ PCS %f %f %f Clipped %f %f %f cdist %f\n",tin[0],tin[1],tin[2],pp[0].v[0],pp[0].v[1],pp[0].v[2],cdist))
|
|
}
|
|
|
|
nsoln &= RSPL_NOSOLNS; /* Get number of solutions */
|
|
|
|
DBR(("inv_clut_aux got %d rev_interp solutions, clipflag = %d\n",nsoln,crv))
|
|
|
|
/* If we clipped and we should clip in CAM Jab space, compute reverse */
|
|
/* clip solution using our additional CAM space rspl. */
|
|
/* Note that we don't support vector clip in CAM space at the moment */
|
|
/* - icxLuLut_init_clut_camclip() doesn't setup CAM rspl for vector clip. */
|
|
if (crv != 0 && p->camclip && p->nearclip) {
|
|
co cpp; /* Alternate CAM space solution */
|
|
double bf; /* Blend factor */
|
|
|
|
DBR(("inv_clut_aux got clip, compute CAM clip\n"))
|
|
|
|
if (nsoln != 1) { /* This would be unexpected */
|
|
error("Unexpected failure to return 1 solution on clip for input to output table");
|
|
}
|
|
|
|
if (p->cclutTable == NULL) { /* we haven't created this yet, so do so */
|
|
if (icxLuLut_init_clut_camclip(p))
|
|
error("Creating CAM rspl for camclip failed");
|
|
}
|
|
|
|
/* Setup for reverse lookup */
|
|
DBR(("inv_clut_aux cam clip PCS in %f %f %f\n",in[0],in[1],in[2]))
|
|
|
|
/* Convert from PCS' to (XYZ) PCS */
|
|
p->absxyzlu->output_pch_fwd(p->absxyzlu, tin, in);
|
|
DBR(("inv_clut_aux cam clip PCS' -> PCS %f %f %f\n",tin[0],tin[1],tin[2]))
|
|
|
|
p->absxyzlu->output_fmt_fwd(p->absxyzlu, tin, tin);
|
|
DBR(("inv_clut_aux cam clip abs XYZ PCS %f %f %f\n",tin[0],tin[1],tin[2]))
|
|
|
|
p->cam->XYZ_to_cam(p->cam, tin, tin);
|
|
DBR(("inv_clut_aux cam clip PCS after XYZtoCAM %f %f %f\n",tin[0],tin[1],tin[2]))
|
|
|
|
for (f = 0; f < fdi; f++) /* Transfer CAM targ */
|
|
cpp.v[f] = tin[f];
|
|
|
|
/* Make sure that the auxiliar value is initialized, */
|
|
/* even though it shouldn't have any effect, since should clipp. */
|
|
for (e = 0; e < p->clutTable->di; e++) {
|
|
if (p->auxm[e] != 0) {
|
|
cpp.p[e] = 0.5;
|
|
}
|
|
}
|
|
if (p->clutTable->di > fdi) { /* ie. CMYK->Lab, there will be ambiguity */
|
|
|
|
nsoln = p->cclutTable->rev_interp(
|
|
p->cclutTable, /* rspl object */
|
|
flags | xflags | RSPL_WILLCLIP, /* Combine all the flags + clip ?? */
|
|
1, /* Maximum solutions to return */
|
|
p->auxm, /* Auxiliary input chanel mask */
|
|
cdir, /* Clip vector direction/ LCh weighting */
|
|
&cpp); /* Input target and output solutions */
|
|
|
|
} else {
|
|
nsoln = p->cclutTable->rev_interp(
|
|
p->cclutTable, /* rspl object */
|
|
flags | RSPL_WILLCLIP, /* Because we know it will clip ?? */
|
|
1, /* Maximum solutions to return */
|
|
NULL, /* No auxiliary input targets */
|
|
cdir, /* Clip vector direction/LCh weighting */
|
|
&cpp); /* Input target and output solutions */
|
|
}
|
|
|
|
nsoln &= RSPL_NOSOLNS; /* Get number of solutions */
|
|
|
|
if (nsoln != 1) { /* This would be unexpected */
|
|
error("Unexpected failure to return 1 solution on CAM clip for input to output table");
|
|
}
|
|
|
|
/* Compute the CAM clip distances */
|
|
for (cdist = 0.0, f = 0; f < fdi; f++) {
|
|
double tt;
|
|
tt = cpp.v[f] - tin[f];
|
|
cdist += tt * tt;
|
|
}
|
|
cdist = sqrt(cdist);
|
|
DBR(("Targ CAM %f %f %f Clipped %f %f %f cdist %f\n",tin[0],tin[1],tin[2],cpp.v[0],cpp.v[1],cpp.v[2],cdist))
|
|
|
|
/* Use magic number to set blend distance, and compute a blend factor. */
|
|
/* Blend over 1 delta E, otherwise the Lab & CAM02 divergence can result */
|
|
/* in reversals. */
|
|
bf = cdist/CAMCLIPTRANS; /* 0.0 for PCS result, 1.0 for CAM result */
|
|
if (bf > 1.0)
|
|
bf = 1.0;
|
|
#ifdef USECAMCLIPSPLINE
|
|
bf = bf * bf * (3.0 - 2.0 * bf); /* Convert to spline blend */
|
|
#endif
|
|
DBR(("cdist %f, spline blend %f\n",cdist,bf))
|
|
|
|
/* Blend between solution values for PCS and CAM clip result. */
|
|
/* We're hoping that the solutions are close, and expect them to be */
|
|
/* that way when we're close to the gamut surface (since the cell */
|
|
/* vertex values should be exact, irrespective of which interpolation */
|
|
/* space they're in), but weird things could happen away from the surface. */
|
|
/* Weird things can happen anyway with "clip to nearest", since this is not */
|
|
/* guaranteed to be a smooth function, depending on the gamut surface */
|
|
/* geometry, without taking some precaution such as clipping to a */
|
|
/* convex hull "wrapper" to create a clip vector, which we're not */
|
|
/* current doing. */
|
|
DBR(("Clip blend between device:\n"))
|
|
DBR(("Lab: %s & ",icmPdv(p->clutTable->di, pp[0].p)))
|
|
DBR(("Jab: %s ",icmPdv(p->clutTable->di, cpp.p)))
|
|
|
|
for (e = 0; e < p->clutTable->di; e++) {
|
|
out[e] = (1.0 - bf) * pp[0].p[e] + bf * cpp.p[e];
|
|
}
|
|
DBR((" = %s\n",icmPdv(p->clutTable->di, out)))
|
|
|
|
/* Not CAM clip case */
|
|
} else {
|
|
|
|
/* If vector clip, replay with simple vector */
|
|
if (nsoln == 0 && p->nearclip == 0) {
|
|
//printf("~1 !!!!!!!!!!!!!! Vector clip failed - trying safe replay !!!!!!!!!!!!!!!1\n");
|
|
|
|
// Reset pp[0] target values and auiliaries
|
|
for (e = 0; e < p->clutTable->di; e++)
|
|
pp[0].p[e] = upp.p[e];
|
|
for (f = 0; f < fdi; f++)
|
|
pp[0].v[f] = upp.v[f];
|
|
|
|
/* Compute safe clip vector */
|
|
cdir = icxClipVector(&p->clip, in, cdirv, 1);
|
|
|
|
/* Replay the lookup using safer vector */
|
|
nsoln = p->clutTable->rev_interp(
|
|
p->clutTable, /* rspl object */
|
|
uflags,
|
|
MAX_INVSOLN, /* Maxumum solutions to return */
|
|
NULL, /* No auxiliary input targets */
|
|
cdir, /* Clip vector direction and length */
|
|
pp); /* Input target and output solutions */
|
|
/* returned solutions in pp[0..retval-1].p[] */
|
|
nsoln &= RSPL_NOSOLNS; /* Get number of solutions */
|
|
|
|
if (nsoln == 0) { /* Hmm */
|
|
//printf("~1 !!!!!!!!!!!!!! Vector clip failed again - using nn replay !!!!!!!!!!!!!!!1\n");
|
|
// Reset pp[0] target values and auiliaries
|
|
for (e = 0; e < p->clutTable->di; e++)
|
|
pp[0].p[e] = upp.p[e];
|
|
for (f = 0; f < fdi; f++)
|
|
pp[0].v[f] = upp.v[f];
|
|
|
|
/* Replay the lookup as a nearclip, to guarantee a solution */
|
|
nsoln = p->clutTable->rev_interp(
|
|
p->clutTable, /* rspl object */
|
|
RSPL_NEARCLIP | RSPL_NONNSETUP,
|
|
MAX_INVSOLN, /* Maxumum solutions to return */
|
|
NULL, /* No auxiliary input targets */
|
|
NULL, /* No LCh weighting */
|
|
pp); /* Input target and output solutions */
|
|
/* returned solutions in pp[0..retval-1].p[] */
|
|
nsoln &= RSPL_NOSOLNS; /* Get number of solutions */
|
|
}
|
|
}
|
|
|
|
if (nsoln == 1) { /* Exactly one solution */
|
|
i = 0;
|
|
} else if (nsoln == 0) { /* Zero solutions. This is unexpected. */
|
|
double in_v[MXDO];
|
|
p->output(p, in_v, pp[0].v); /* Get ICC inverse input values */
|
|
p->out_abs(p, in_v, in_v);
|
|
if (p->nearclip == 0)
|
|
a1logd(g_log,0,"Clip dst %f %f %f\n",pp[0].v[0]+cdir[0], pp[0].v[1]+cdir[1], pp[0].v[2]+cdir[2]);
|
|
error("Unexpected failure to find reverse solution for input to output table for value %f %f %f (ICC input %f %f %f)",pp[0].v[0],pp[0].v[1],pp[0].v[2], in_v[0], in_v[1], in_v[2]);
|
|
return 2;
|
|
} else { /* Multiple solutions */
|
|
/* Use a simple minded resolution - choose the one closest to the center */
|
|
double bdist = 1e300;
|
|
int bsoln = 0;
|
|
DBR(("got multiple reverse solutions\n"));
|
|
for (i = 0; i < nsoln; i++) {
|
|
double ss;
|
|
|
|
DBR(("Soln %d: %s\n",i, icmPdv(p->clutTable->di, pp[i].p)))
|
|
for (ss = 0.0, e = 0; e < p->clutTable->di; e++) {
|
|
double tt;
|
|
tt = pp[i].p[e] - p->licent[e];
|
|
tt *= tt;
|
|
if (tt < bdist) { /* Better solution */
|
|
bdist = tt;
|
|
bsoln = i;
|
|
}
|
|
}
|
|
}
|
|
#ifndef NEVER
|
|
// ~~99 average them
|
|
for (i = 1; i < nsoln; i++) {
|
|
for (e = 0; e < p->clutTable->di; e++)
|
|
pp[0].p[e] += pp[i].p[e];
|
|
}
|
|
for (e = 0; e < p->clutTable->di; e++)
|
|
pp[0].p[e] /= (double)nsoln;
|
|
bsoln = 0;
|
|
#endif
|
|
//printf("~1 chose %d\n",bsoln);
|
|
i = bsoln;
|
|
}
|
|
for (e = 0; e < p->clutTable->di; e++) {
|
|
/* Save solution as atractor for next one, on the basis */
|
|
/* that it might have better continuity given pesudo-hilbert inversion path. */
|
|
p->licent[e] = out[e] = pp[i].p[e]; /* Solution */
|
|
}
|
|
}
|
|
|
|
/* Sanitise auxiliary locus range and auxiliary value return */
|
|
if (auxr != NULL || auxv != NULL) {
|
|
int ee = 0;
|
|
for (e = 0; e < p->clutTable->di; e++) {
|
|
double v = out[e]; /* Solution */
|
|
if (p->auxm[e] != 0) {
|
|
if (auxr != NULL) { /* Make sure locus encloses actual value */
|
|
if (auxr[2 * ee] > v)
|
|
auxr[2 * ee] = v;
|
|
if (auxr[2 * ee + 1] < v)
|
|
auxr[2 * ee + 1] = v;
|
|
}
|
|
if (auxv != NULL) {
|
|
auxv[ee] = v;
|
|
}
|
|
ee++;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef CHECK_ILIMIT /* Do sanity checks on meeting ink limit */
|
|
if (p->ink.tlimit >= 0.0 || p->ink.klimit >= 0.0) {
|
|
double sum = icxLimitD(p, out);
|
|
if (sum > 0.0)
|
|
printf("xlut assert%s: icxLuLut_inv_clut returned outside limits by %f > tlimit %f\n",crv ? " (clip)" : "", sum, p->ink.tlimit);
|
|
}
|
|
#endif
|
|
|
|
if (clipd != NULL) {
|
|
*clipd = cdist;
|
|
DBR(("inv_clut_aux returning clip DE %f\n",cdist))
|
|
}
|
|
|
|
DBR(("inv_clut_aux returning %f %f %f %f\n",out[0],out[1],out[2],out[3]))
|
|
return crv;
|
|
}
|
|
|
|
/* Do output'->input' lookup, simple version */
|
|
/* Note than out[] will carry inking value if icxKrule is icxKvalue of icxKlocus */
|
|
/* and that the icxKrule value will be in the input (NOT input') space. */
|
|
/* Note that the ink limit will be computed after converting input' to input */
|
|
int icxLuLut_inv_clut(icxLuLut *p, double *out, double *in) {
|
|
return icxLuLut_inv_clut_aux(p, out, NULL, NULL, NULL, NULL, in);
|
|
}
|
|
|
|
/* Given the proposed auxiliary input values in in[di], */
|
|
/* and the target output' (ie. PCS') values in out[fdi], */
|
|
/* return the auxiliary input (NOT input' space) values as a proportion of their */
|
|
/* possible locus in locus[di]. */
|
|
/* This is generally used on a source CMYK profile to convey the black intent */
|
|
/* to destination CMYK profile. */
|
|
int icxLuLut_clut_aux_locus(icxLuLut *p, double *locus, double *out, double *in) {
|
|
co pp[1]; /* Room for all the solutions found */
|
|
int nsoln; /* Number of solutions found */
|
|
int e,f;
|
|
|
|
if (p->clutTable->di > p->clutTable->fdi) { /* ie. CMYK->Lab, there will be ambiguity */
|
|
double min[MXDI], max[MXDI]; /* Auxiliary locus range */
|
|
|
|
/* Setup for auxiliary locus lookup */
|
|
for (f = 0; f < p->clutTable->fdi; f++) {
|
|
pp[0].v[f] = out[f]; /* Target output' (i.e. PCS) value */
|
|
}
|
|
|
|
/* Compute auxiliary locus */
|
|
nsoln = p->clutTable->rev_locus(
|
|
p->clutTable, /* rspl object */
|
|
p->auxm, /* Auxiliary mask */
|
|
pp, /* Input target and output solutions */
|
|
min, max); /* Returned locus of valid auxiliary values */
|
|
|
|
if (nsoln == 0) {
|
|
for (e = 0; e < p->clutTable->di; e++)
|
|
locus[e] = 0.0; /* Return some safe values */
|
|
} else { /* Got a valid locus */
|
|
|
|
/* Convert the locus from input' -> input space */
|
|
for (e = 0; e < p->clutTable->di; e++) {
|
|
co tc;
|
|
/* (Is speed more important than precision ?) */
|
|
if (p->auxm[e] != 0) {
|
|
tc.p[0] = min[e];
|
|
p->revinputTable[e]->interp(p->revinputTable[e], &tc);
|
|
min[e] = tc.v[0];
|
|
tc.p[0] = max[e];
|
|
p->revinputTable[e]->interp(p->revinputTable[e], &tc);
|
|
max[e] = tc.v[0];
|
|
}
|
|
}
|
|
|
|
/* Figure out the proportion of the locus */
|
|
for (e = 0; e < p->clutTable->di; e++) {
|
|
if (p->auxm[e] != 0) {
|
|
double iv = in[e];
|
|
if (iv <= min[e])
|
|
locus[e] = 0.0;
|
|
else if (iv >= max[e])
|
|
locus[e] = 1.0;
|
|
else {
|
|
double lpl = max[e] - min[e]; /* Locus path length */
|
|
if (lpl > 1e-6)
|
|
locus[e] = (iv - min[e])/lpl;
|
|
else
|
|
locus[e] = 0.0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
/* There should be no auxiliaries */
|
|
for (e = 0; e < p->clutTable->di; e++)
|
|
locus[e] = 0.0; /* Return some safe values */
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Do input' -> input inverse lookup */
|
|
int icxLuLut_inv_input(icxLuLut *p, double *out, double *in) {
|
|
icmPe_lurv prv = icmPe_lurv_OK;
|
|
prv = p->plu->input_pch_bwd(p->plu, out, in);
|
|
DBR(("inv_input returning DEV %f %f %f %f\n",out[0],out[1],out[2],out[3]))
|
|
return LUE2XLUE(prv);
|
|
}
|
|
|
|
/* Possible inverse matrix lookup */
|
|
/* (Will do nothing if input is device space) */
|
|
int icxLuLut_inv_matrix(icxLuLut *p, double *out, double *in) {
|
|
int rv = 0;
|
|
/* none. Combined with input & clut */
|
|
icmCpyN(out, in, p->inputChan);
|
|
return rv;
|
|
}
|
|
|
|
/* Inverse input absolute intent conversion */
|
|
/* (Will do nothing if input is device space) */
|
|
int icxLuLut_inv_in_abs(icxLuLut *p, double *out, double *in) {
|
|
int rv = 0;
|
|
icmPe_lurv prv;
|
|
|
|
prv = p->plu->input_fmt_bwd(p->plu, out, in);
|
|
rv = LUE2XLUE(prv);
|
|
|
|
if (p->ins == icxSigJabData) {
|
|
p->cam->XYZ_to_cam(p->cam, out, out);
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
/* Overall inverse lookup */
|
|
/* Note that all auxiliary values are in input (NOT input') space */
|
|
static int
|
|
icxLuLut_inv_lookup(
|
|
icxLuBase *pp, /* This */
|
|
double *out, /* Vector of output values/input auxiliary values */
|
|
double *in /* Vector of input values */
|
|
) {
|
|
icxLuLut *p = (icxLuLut *)pp;
|
|
int rv = 0;
|
|
int i;
|
|
double temp[MAX_CHAN];
|
|
|
|
#ifdef SKIP_PERCH_LOOKUPS
|
|
DBOL(("xiccilu: input = %s\n", icmPdv(p->outputChan, in)));
|
|
rv |= p->inv_clut (p, out, in);
|
|
DBOL(("xiccilu: output = %s\n", icmPdv(p->inputChan, out)));
|
|
#else
|
|
DBOL(("xiccilu: input = %s\n", icmPdv(p->outputChan, in)));
|
|
if (p->mergeclut == 0) { /* Do this if it's not merger with clut */
|
|
rv |= p->inv_out_abs (p, temp, in);
|
|
DBOL(("xiccilu: after inv abs = %s\n", icmPdv(p->outputChan, temp)));
|
|
rv |= p->inv_output (p, temp, temp);
|
|
DBOL(("xiccilu: after inv out = %s\n", icmPdv(p->outputChan, temp)));
|
|
} else {
|
|
for (i = 0; i < p->outputChan; i++)
|
|
temp[i] = in[i];
|
|
}
|
|
DBOL(("xiccilu: aux targ = %s\n", icmPdv(p->inputChan,out)));
|
|
rv |= p->inv_clut (p, out, temp);
|
|
DBOL(("xiccilu: after inv clut = %s\n", icmPdv(p->inputChan,out)));
|
|
rv |= p->inv_input (p, out, out);
|
|
DBOL(("xiccilu: after inv input = %s\n", icmPdv(p->inputChan,out)));
|
|
rv |= p->inv_matrix (p, out, out);
|
|
DBOL(("xiccilu: after inv matrix = %s\n", icmPdv(p->inputChan,out)));
|
|
rv |= p->inv_in_abs (p, out, out);
|
|
DBOL(("xiccilu: after inv abs = %s\n", icmPdv(p->inputChan,out)));
|
|
#endif
|
|
return rv;
|
|
}
|
|
|
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
|
|
|
/* 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 icxLuLut_bwd_outpcs_relpcs(
|
|
icxLuBase *pp,
|
|
icColorSpaceSignature os, /* Output space, XYZ or Lab */
|
|
double *out, double *in) {
|
|
icxLuLut *p = (icxLuLut *)pp;
|
|
|
|
if (p->outs == icxSigJabData) {
|
|
DBS(("bwd_outpcs_relpcs: Jab in = %s\n", icmPdv(3, in)));
|
|
p->cam->cam_to_XYZ(p->cam, out, in);
|
|
DBS(("bwd_outpcs_relpcs: abs XYZ = %s\n", icmPdv(3, out)));
|
|
/* Hack to prevent CAM02 weirdness being amplified by */
|
|
/* 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;
|
|
DBS(("bwd_outpcs_relpcs: after clipping -Y %s\n",icmPdv(p->outputChan, out)));
|
|
}
|
|
} else {
|
|
DBS(("bwd_outpcs_relpcs: abs PCS in = %s\n", icmPdv(3, out)));
|
|
icmCpy3(out, in);
|
|
}
|
|
|
|
p->plu->output_fmt_bwd(p->plu, out, out);
|
|
DBS(("bwd_outpcs_relpcs: rel PCS = %s\n", icmPdv(3, out)));
|
|
|
|
if (os == icSigXYZData && p->natpcs == icSigLabData) {
|
|
icmLab2XYZ(&icmD50, out, out);
|
|
DBS(("bwd_outpcs_relpcs: rel XYZ = %s\n", icmPdv(3, out)));
|
|
} else if (os == icSigXYZData && p->natpcs == icSigLabData) {
|
|
icmXYZ2Lab(&icmD50, out, out);
|
|
DBS(("bwd_outpcs_relpcs: rel Lab = %s\n", icmPdv(3, out)));
|
|
}
|
|
}
|
|
|
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
|
|
|
|
|
static void
|
|
icxLuLut_free(
|
|
icxLuBase *pp
|
|
) {
|
|
icxLuLut *p = (icxLuLut *)pp;
|
|
int i;
|
|
|
|
for (i = 0; i < p->inputChan; i++) {
|
|
if (p->revinputTable[i] != NULL)
|
|
p->revinputTable[i]->del(p->revinputTable[i]);
|
|
}
|
|
|
|
if (p->clutTable != NULL)
|
|
p->clutTable->del(p->clutTable);
|
|
|
|
if (p->cclutTable != NULL)
|
|
p->cclutTable->del(p->cclutTable);
|
|
|
|
|
|
if (p->plu != NULL)
|
|
p->plu->del(p->plu);
|
|
|
|
if (p->cam != NULL)
|
|
p->cam->del(p->cam);
|
|
|
|
if (p->absxyzlu != NULL)
|
|
p->absxyzlu->del(p->absxyzlu);
|
|
|
|
free(p);
|
|
}
|
|
|
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
|
|
|
static gamut *icxLuLutGamut(icxLuBase *plu, double detail);
|
|
static icxCuspMap *icxLuLutCuspMap(icxLuBase *plu, int res);
|
|
|
|
/* Do the basic icxLuLut creation and initialisation */
|
|
static icxLuLut *
|
|
alloc_icxLuLut(
|
|
xicc *xicp,
|
|
icmLuSpace *plu, /* Pointer to Lu we are expanding (ours) */
|
|
int flags /* clip, merge flags */
|
|
) {
|
|
icxLuLut *p; /* Object being created */
|
|
icmLuLut *luluto = (icmLuLut *)plu; /* Lookup Lut type object */
|
|
|
|
if ((p = (icxLuLut *) calloc(1,sizeof(icxLuLut))) == NULL)
|
|
return NULL;
|
|
|
|
p->lutype = icxLuLutType;
|
|
p->pp = xicp;
|
|
p->plu = plu;
|
|
p->del = icxLuLut_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 = icxLuLutGamut;
|
|
p->get_cuspmap = icxLuLutCuspMap;
|
|
p->fwd_relpcs_outpcs = icxLuLut_fwd_relpcs_outpcs;
|
|
p->bwd_outpcs_relpcs = icxLuLut_bwd_outpcs_relpcs;
|
|
p->nearclip = 0; /* Set flag defaults */
|
|
p->mergeclut = 0;
|
|
p->intsep = 0;
|
|
|
|
p->lookup = icxLuLut_lookup;
|
|
p->in_abs = icxLuLut_in_abs;
|
|
p->matrix = icxLuLut_matrix;
|
|
p->input = icxLuLut_input;
|
|
p->clut = icxLuLut_clut;
|
|
p->clut_aux = icxLuLut_clut_aux;
|
|
p->output = icxLuLut_output;
|
|
p->out_abs = icxLuLut_out_abs;
|
|
|
|
p->inv_lookup = icxLuLut_inv_lookup;
|
|
p->inv_in_abs = icxLuLut_inv_in_abs;
|
|
p->inv_matrix = icxLuLut_inv_matrix;
|
|
p->inv_input = icxLuLut_inv_input;
|
|
p->inv_clut = icxLuLut_inv_clut;
|
|
p->inv_clut_aux = icxLuLut_inv_clut_aux;
|
|
p->inv_output = icxLuLut_inv_output;
|
|
p->inv_out_abs = icxLuLut_inv_out_abs;
|
|
|
|
p->clut_locus = icxLuLut_clut_aux_locus;
|
|
|
|
/* Setup all the rspl analogs of the icc Lut */
|
|
/* NOTE: We assume that none of this relies on the flag settings, */
|
|
/* since they will be set on our return. */
|
|
|
|
/* 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;
|
|
|
|
/* Get details of external, effective color spaces */
|
|
plu->spaces(p->plu, &ini, &outi, &pcsi, NULL, NULL, NULL, NULL, NULL, NULL);
|
|
p->inputChan = ini.nch;
|
|
p->outputChan = outi.nch;
|
|
}
|
|
|
|
/* Remember flags */
|
|
p->flags = flags;
|
|
|
|
/* Sanity check */
|
|
if (p->inputChan > MXDI) {
|
|
sprintf(p->pp->e.m,"xicc can only handle input channels of %d or less",MXDI);
|
|
p->inputChan = MXDI; /* Avoid going outside array bounds */
|
|
p->pp->e.c = 1;
|
|
p->del((icxLuBase *)p);
|
|
return NULL;
|
|
}
|
|
if (p->outputChan > MXDO) {
|
|
sprintf(p->pp->e.m,"xicc can only handle output channels of %d or less",MXDO);
|
|
p->outputChan = MXDO; /* Avoid going outside array bounds */
|
|
p->pp->e.c = 1;
|
|
p->del((icxLuBase *)p);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
return p;
|
|
}
|
|
|
|
/* Initialise the clut ink limiting and black */
|
|
/* generation information. */
|
|
/* return 0 or error code */
|
|
static int
|
|
setup_ink_icxLuLut(
|
|
icxLuLut *p, /* Object being initialised */
|
|
icxInk *ink, /* inking details (NULL for default) */
|
|
int setLminmax /* Figure the L locus for inking rule */
|
|
) {
|
|
int devchan = p->func == icmFwd ? p->inputChan : p->outputChan;
|
|
|
|
if (ink) {
|
|
p->ink = *ink; /* Copy the structure */
|
|
} else {
|
|
p->ink.tlimit = 3.0; /* default ink limit of 300% */
|
|
p->ink.klimit = -1.0; /* default no black limit */
|
|
p->ink.KonlyLmin = 0; /* default don't use K only black as Locus Lmin */
|
|
p->ink.k_rule = icxKluma5; /* default K generation rule */
|
|
p->ink.c.Ksmth = ICXINKDEFSMTH; /* Default smoothing */
|
|
p->ink.c.Kskew = ICXINKDEFSKEW; /* Default shape skew */
|
|
p->ink.c.Kstle = 0.0; /* Min K at white end */
|
|
p->ink.c.Kstpo = 0.0; /* Start of transition is at white */
|
|
p->ink.c.Kenle = 1.0; /* Max K at black end */
|
|
p->ink.c.Kenpo = 1.0; /* End transition at black */
|
|
p->ink.c.Kshap = 1.0; /* Linear transition */
|
|
}
|
|
|
|
/* Normalise total and black ink limits */
|
|
if (p->ink.tlimit <= 1e-4 || p->ink.tlimit >= (double)devchan)
|
|
p->ink.tlimit = -1.0; /* Turn ink limit off if not effective */
|
|
if (devchan < 4 || p->ink.klimit < 0.0 || p->ink.klimit >= 1.0)
|
|
p->ink.klimit = -1.0; /* Turn black limit off if not effective */
|
|
|
|
/* Set the ink limit information for any reverse interpolation. */
|
|
/* Calling this will clear the reverse interpolaton cache. */
|
|
p->clutTable->rev_set_limit(
|
|
p->clutTable, /* this */
|
|
p->ink.tlimit >= 0.0 || p->ink.klimit >= 0.0 ? icxLimitD_void : NULL,
|
|
/* Optional input space limit() function. */
|
|
/* Function should evaluate in[0..di-1], and return */
|
|
/* number that is not to exceed 0.0. NULL if not used. */
|
|
(void *)p, /* Context passed to limit() */
|
|
0.0 /* Value that limit() is not to exceed */
|
|
);
|
|
|
|
/* Duplicate in the CAM clip rspl if it exists */
|
|
if (p->cclutTable != NULL) {
|
|
p->cclutTable->rev_set_limit(
|
|
p->cclutTable, /* this */
|
|
p->ink.tlimit >= 0.0 || p->ink.klimit >= 0.0 ? icxLimitD_void : NULL,
|
|
/* Optional input space limit() function. */
|
|
/* Function should evaluate in[0..di-1], and return */
|
|
/* number that is not to exceed 0.0. NULL if not used. */
|
|
(void *)p, /* Context passed to limit() */
|
|
0.0 /* Value that limit() is not to exceed */
|
|
);
|
|
}
|
|
|
|
/* Figure Lmin and Lmax for icxKluma5 curve basis */
|
|
if (setLminmax
|
|
&& p->clutTable->di > p->clutTable->fdi) { /* If K generation makes sense */
|
|
double wh[3], bk[3], kk[3];
|
|
int mergeclut; /* Save/restore mergeclut value */
|
|
|
|
/* Get white/black in effective xlu PCS space */
|
|
p->efv_wh_bk_points((icxLuBase *)p, wh, bk, kk);
|
|
|
|
/* Convert from effective PCS (ie. Jab) to native XYZ or Lab PCS */
|
|
mergeclut = p->mergeclut; /* Hack to be able to use inv_out_abs() */
|
|
p->mergeclut = 0; /* if mergeclut is active. */
|
|
icxLuLut_inv_out_abs(p, wh, wh);
|
|
icxLuLut_inv_out_abs(p, bk, bk);
|
|
icxLuLut_inv_out_abs(p, kk, kk);
|
|
p->mergeclut = mergeclut; /* Restore */
|
|
|
|
/* Convert to Lab PCS */
|
|
if (p->natos == icSigXYZData) { /* Always do K rule in L space */
|
|
icmXYZ2Lab(&icmD50, wh, wh);
|
|
icmXYZ2Lab(&icmD50, bk, bk);
|
|
icmXYZ2Lab(&icmD50, kk, kk);
|
|
}
|
|
p->Lmax = 0.01 * wh[0];
|
|
if (p->ink.KonlyLmin != 0)
|
|
p->Lmin = 0.01 * kk[0];
|
|
else
|
|
p->Lmin = 0.01 * bk[0];
|
|
} else {
|
|
|
|
/* Some sane defaults */
|
|
p->Lmax = 1.0;
|
|
p->Lmin = 0.0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* Initialise the clut clipping information, ink limiting */
|
|
/* and auxiliary parameter settings for all the rspl. */
|
|
/* return 0 or error code */
|
|
static int
|
|
setup_clip_icxLuLut(
|
|
icxLuLut *p /* Object being initialised */
|
|
) {
|
|
double tmin[MXDIDO], tmax[MXDIDO];
|
|
int i;
|
|
|
|
/* Setup for inversion of multi-dim clut */
|
|
|
|
p->kch = -1; /* Not known yet */
|
|
|
|
/* Set auxiliaries */
|
|
for (i = 0; i < p->inputChan; i++)
|
|
p->auxm[i] = 0;
|
|
|
|
if (p->inputChan > p->outputChan) {
|
|
switch(p->natis) {
|
|
case icSigCmykData:
|
|
/* Should fix icm/xicc to remember K channel */
|
|
p->auxm[3] = 1; /* K is the auxiliary channel */
|
|
break;
|
|
default:
|
|
if (p->kch >= 0) /* It's been discovered */
|
|
p->auxm[p->kch] = 1;
|
|
else {
|
|
p->pp->e.c = 2;
|
|
sprintf(p->pp->e.m,"Unknown colorspace %s when setting auxliaries",
|
|
icm2str(icmColorSpaceSig, p->natis));
|
|
return p->pp->e.c;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// p->auxlinf = NULL; /* Input space auxiliary linearisation function - not implemented */
|
|
// p->auxlinf_ctx = NULL; /* Opaque context for auxliliary linearisation function */
|
|
|
|
/* Aproximate center of clut input gamut - used for */
|
|
/* resolving multiple reverse solutions. */
|
|
p->clutTable->get_in_range(p->clutTable, tmin, tmax);
|
|
for (i = 0; i < p->clutTable->di; i++) {
|
|
p->licent[i] = p->icent[i] = (tmin[i] + tmax[i])/2.0;
|
|
}
|
|
|
|
/* Compute clip setup information relating to clut output gamut. */
|
|
if (p->nearclip != 0 /* Near clip requested */
|
|
|| p->inputChan == 1) { /* or vector clip won't work */
|
|
p->clip.nearclip = 1;
|
|
|
|
} else { /* Vector clip */
|
|
icColorSpaceSignature clutos = p->natos;
|
|
|
|
p->clip.nearclip = 0;
|
|
p->clip.LabLike = 0;
|
|
p->clip.fdi = p->clutTable->fdi;
|
|
|
|
switch((int)clutos) {
|
|
case icxSigJabData:
|
|
case icSigLabData: {
|
|
|
|
p->clip.LabLike = 1;
|
|
|
|
/* Create a CuspMap to point vectors towards */
|
|
/* (Don't make it too fine, or there will be dips) */
|
|
p->clip.cm = p->get_cuspmap((icxLuBase *)p, 30);
|
|
break;
|
|
}
|
|
case icSigXYZData:
|
|
// ~~~~~~1 need to add this.
|
|
warning("xlut.c: setup_clip_icxLuLut() icSigXYZData case not implemented!");
|
|
/* Fall through */
|
|
|
|
default:
|
|
/* Do a crude approximation, that may not work. */
|
|
p->clutTable->get_out_range(p->clutTable, tmin, tmax);
|
|
for (i = 0; i < p->clutTable->fdi; i++)
|
|
p->clip.ocent[i] = (tmin[i] + tmax[i])/2.0;
|
|
|
|
break;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Function to pass to rspl to set secondary input/output transfer functions */
|
|
static void
|
|
icxLuLut_inout_func(
|
|
void *pp, /* icxLuLut */
|
|
double *out, /* output value */
|
|
double *in /* inut value */
|
|
) {
|
|
icxLuLut *p = (icxLuLut *)pp; /* this */
|
|
icmLuLut *luluto = (icmLuLut *)p->plu; /* Get icmLuLut object */
|
|
double tin[MAX_CHAN];
|
|
double tout[MAX_CHAN];
|
|
int i;
|
|
|
|
if (p->iol_out == 0) { /* fwd input */
|
|
#ifdef INK_LIMIT_TEST
|
|
tout[p->iol_ch] = in[0];
|
|
#else
|
|
for (i = 0; i < p->inputChan; i++)
|
|
tin[i] = 0.0;
|
|
tin[p->iol_ch] = in[0];
|
|
luluto->input_pch_fwd(luluto, tout, tin);
|
|
#endif
|
|
} else if (p->iol_out == 1) { /* fwd output */
|
|
for (i = 0; i < p->outputChan; i++)
|
|
tin[i] = 0.0;
|
|
tin[p->iol_ch] = in[0];
|
|
luluto->output_pch_fwd(luluto, tout, tin);
|
|
} else { /* bwd input */
|
|
#ifdef INK_LIMIT_TEST
|
|
tout[p->iol_ch] = in[0];
|
|
#else
|
|
for (i = 0; i < p->inputChan; i++)
|
|
tin[i] = 0.0;
|
|
tin[p->iol_ch] = in[0];
|
|
luluto->input_pch_bwd(luluto, tout, tin);
|
|
/* This won't be valid if matrix is used or there is a PCS conversion !!! */
|
|
/* Shouldn't be a problem because this is only used for inverting dev->PCS ? */
|
|
luluto->input_fmt_bwd(luluto, tout, tout);
|
|
#endif
|
|
}
|
|
out[0] = tout[p->iol_ch];
|
|
}
|
|
|
|
/* Function to pass to rspl to set clut up */
|
|
static void
|
|
icxLuLut_clut_func(
|
|
void *pp, /* icxLuLut */
|
|
double *out, /* output value */
|
|
double *in /* input value */
|
|
) {
|
|
icxLuLut *p = (icxLuLut *)pp; /* this */
|
|
icmLuLut *luluto = (icmLuLut *)p->plu; /* Get icmLuLut object */
|
|
|
|
if (p->mergeclut == 0) { /* Do this if it's not merged with clut, */
|
|
luluto->core5_fwd(luluto, out, in);
|
|
} else { /* If mergeclut */
|
|
luluto->core5_fwd(luluto, out, in);
|
|
luluto->output_pch_fwd(luluto, out, out);
|
|
luluto->output_fmt_fwd(luluto, out, out);
|
|
|
|
if (p->outs == icxSigJabData) {
|
|
p->cam->XYZ_to_cam(p->cam, out, out);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Implimenation of Lut create from icc. */
|
|
/* Note that xicc_get_luobj() will have set the pcsor & */
|
|
/* intent to consistent values if Jab and/or icxAppearance */
|
|
/* has been requested. */
|
|
/* It will also have created the underlying icm lookup object */
|
|
/* that is used to create and implement the icx one. The icm */
|
|
/* will be used to translate from native to effective PCS, unless */
|
|
/* the effective PCS is Jab, in which case the icm will be set to */
|
|
/* have an effective PCS of XYZ. Since native<->effecive PCS conversion */
|
|
/* is done at the to/from_abs() stage, none of it affects the individual */
|
|
/* conversion steps, which will all talk the native PCS (unless merged). */
|
|
static icxLuBase *
|
|
new_icxLuLut(
|
|
xicc *xicp,
|
|
int flags, /* clip, merge flags */
|
|
icmLuSpace *plu, /* Pointer to Lu we are expanding (ours) */
|
|
icmLookupFunc func, /* Functionality requested */
|
|
icRenderingIntent intent, /* Rendering intent */
|
|
icColorSpaceSignature pcsor, /* PCS override (0 = def) */
|
|
icxViewCond *vc, /* Viewing Condition (NULL if not using CAM) */
|
|
icxInk *ink /* inking details (NULL for default) */
|
|
) {
|
|
icxLuLut *p; /* Object being created */
|
|
icmLuLut *luluto = (icmLuLut *)plu; /* Lookup Lut type object */
|
|
icmLookupFunc fnc;
|
|
|
|
int i;
|
|
|
|
/* Do basic creation and initialisation */
|
|
if ((p = alloc_icxLuLut(xicp, plu, flags)) == NULL)
|
|
return NULL;
|
|
|
|
p->func = func;
|
|
|
|
/* Set LuLut "use" specific creation flags: */
|
|
if (flags & ICX_CLIP_NEAREST)
|
|
p->nearclip = 1;
|
|
|
|
if (flags & ICX_MERGE_CLUT)
|
|
p->mergeclut = 1;
|
|
|
|
if (flags & ICX_FAST_SETUP)
|
|
p->fastsetup = 1;
|
|
|
|
/* We're only implementing this under specific conditions. */
|
|
if (flags & ICX_CAM_CLIP
|
|
&& func == icmFwd
|
|
&& !(p->mergeclut != 0 && pcsor == icxSigJabData)) /* Don't need camclip if merged Jab */
|
|
p->camclip = 1;
|
|
|
|
if (flags & ICX_INT_SEPARATE) {
|
|
fprintf(stderr,"~1 Internal optimised 4D separations not yet implemented!\n");
|
|
p->intsep = 1;
|
|
}
|
|
|
|
/* Init the CAM model if it will be used */
|
|
if (pcsor == icxSigJabData || p->camclip) {
|
|
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 */
|
|
p->plu->native_spaces(p->plu, &ini, &outi, &pcsi);
|
|
|
|
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 */
|
|
p->plu->spaces(p->plu, &ini, &outi, &pcsi, NULL, NULL, &fnc, NULL, NULL, NULL);
|
|
|
|
p->ins = ini.sig;
|
|
p->outs = outi.sig;
|
|
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 */
|
|
/* We assume that any profile that has a CIE color as a "device" color */
|
|
/* intends it to stay that way, and not be overridden. */
|
|
if (pcsor == icxSigJabData) {
|
|
p->pcs = pcsor;
|
|
|
|
if (xicp->pp->header->deviceClass == icSigAbstractClass) {
|
|
p->ins = pcsor;
|
|
p->outs = pcsor;
|
|
|
|
} else if (xicp->pp->header->deviceClass != icSigLinkClass) {
|
|
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;
|
|
}
|
|
|
|
/* If we have a merged clut, reflect this in the icx native PCS range. */
|
|
/* Merging merges output processing (irrespective of whether we are using */
|
|
/* the forward or backward cluts) */
|
|
if (p->mergeclut != 0) {
|
|
int i;
|
|
for (i = 0; i < p->outputChan; i++) {
|
|
p->noutmin[i] = p->outmin[i];
|
|
p->noutmax[i] = p->outmax[i];
|
|
}
|
|
}
|
|
|
|
|
|
/* Create rspl based reverse input lookups used in ink limit and locus range functions. */
|
|
for (i = 0; i < p->inputChan; i++) {
|
|
int gres = 256;
|
|
if ((p->revinputTable[i] = new_rspl(RSPL_NOFLAGS, 1, 1)) == NULL) {
|
|
p->pp->e.c = 2;
|
|
sprintf(p->pp->e.m,"Creation of reverse input table rspl failed");
|
|
p->del((icxLuBase *)p);
|
|
return NULL;
|
|
}
|
|
p->iol_out = 2; /* Inveted input lookup */
|
|
p->iol_ch = i; /* Chanel */
|
|
p->revinputTable[i]->set_rspl(p->revinputTable[i], RSPL_NOFLAGS,
|
|
(void *)p, icxLuLut_inout_func,
|
|
&p->ninmin[i], &p->ninmax[i], &gres, &p->ninmin[i], &p->ninmax[i]);
|
|
}
|
|
|
|
/* ------------------------------- */
|
|
/* Create rspl based core transform */
|
|
{
|
|
int gres[MAX_CHAN];
|
|
int xflags = 0;
|
|
|
|
if (p->plu->max_clut_res(p->plu, gres) == 0) { /* Hmm. */
|
|
int res;
|
|
if (p->inputChan <= 3)
|
|
res = 33;
|
|
else if (p->inputChan <= 4)
|
|
res = 17;
|
|
else
|
|
res = 5;
|
|
for (i = 0; i < p->inputChan; i++)
|
|
gres[i] = res;
|
|
}
|
|
|
|
/* If what we're imitating is complex, use a high resolution */
|
|
if (p->plu->core5->attr.op == icmPeOp_complex) {
|
|
if (p->inputChan <= 3) {
|
|
for (i = 0; i < p->inputChan; i++) {
|
|
if (gres[i] < 33)
|
|
gres[i] = 33;
|
|
}
|
|
} else if (p->inputChan <= 4) {
|
|
for (i = 0; i < p->inputChan; i++) {
|
|
if (gres[i] < 17)
|
|
gres[i] = 17;
|
|
}
|
|
} else {
|
|
for (i = 0; i < p->inputChan; i++) {
|
|
if (gres[i] < 9)
|
|
gres[i] = 9;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef FASTREVSETUP_NON_CAM
|
|
# pragma message("!!!!!!!!!!!! FASTREVSETUP_NON_CAM is on !!!!!!!!!")
|
|
|
|
/* Don't fill in nnrev array if we aren't going to use it */
|
|
if (p->camclip && p->nearclip)
|
|
xflags = RSPL_FASTREVSETUP;
|
|
#endif
|
|
|
|
/* Create rspl based multi-dim table */
|
|
if ((p->clutTable = new_rspl((p->fastsetup ? RSPL_FASTREVSETUP : RSPL_NOFLAGS)
|
|
| (flags & ICX_VERBOSE ? RSPL_VERBOSE : RSPL_NOFLAGS)
|
|
| xflags,
|
|
p->inputChan, p->outputChan)) == NULL) {
|
|
p->pp->e.c = 2;
|
|
sprintf(p->pp->e.m,"Creation of clut table rspl failed");
|
|
p->del((icxLuBase *)p);
|
|
return NULL;
|
|
}
|
|
|
|
/* Setup for normal or merged */
|
|
p->clutTable->set_rspl(p->clutTable, RSPL_NOFLAGS,
|
|
(void *)p, icxLuLut_clut_func,
|
|
p->ninmin, p->ninmax, gres, p->noutmin, p->noutmax);
|
|
|
|
#ifdef USELCHWEIGHT
|
|
/* If we are not doing camclip, but our output is an Lab like space, */
|
|
/* then apply lchw weighting anyway. */
|
|
if (!p->camclip && (p->outs == icSigLabData || p->outs == icxSigJabData)) {
|
|
double lchw[MXRO] = { JCCWEIGHT, CCCWEIGHT, HCCWEIGHT };
|
|
|
|
/* Set the Nearest Neighbor clipping Weighting */
|
|
p->clutTable->rev_set_lchw(p->clutTable, lchw);
|
|
}
|
|
#else
|
|
# pragma message("!!!!!!!!!!!! USELCHWEIGHT is off !!!!!!!!!")
|
|
#endif /* USELCHWEIGHT */
|
|
|
|
/* clut clipping is setup separately */
|
|
}
|
|
|
|
/* ------------------------------- */
|
|
/* ------------------------------- */
|
|
|
|
/* Setup all the clipping, ink limiting and auxiliary stuff, */
|
|
/* in case a reverse call is used. Only do this if we know */
|
|
/* the reverse stuff isn't going to fail due to channel limits. */
|
|
if (fnc != icmGamut && fnc != icmPreview
|
|
&& p->clutTable->within_restrictedsize(p->clutTable)) {
|
|
|
|
if (setup_ink_icxLuLut(p, ink, 1) != 0) {
|
|
p->del((icxLuBase *)p);
|
|
return NULL;
|
|
}
|
|
|
|
if (setup_clip_icxLuLut(p) != 0) {
|
|
p->del((icxLuBase *)p);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
return (icxLuBase *)p;
|
|
}
|
|
|
|
|
|
/* Function to pass to rspl to set clut up, when camclip is going to be used. */
|
|
/* We use the temporary icm fwd absolute xyz lookup, then convert to CAM Jab. */
|
|
/* This is similar to icxLuLut_clut_merge_func() except that XYZ_to_cam is forced.) */
|
|
static void
|
|
icxLuLut_clut_camclip_func(
|
|
void *pp, /* icxLuLut */
|
|
double *out, /* output value */
|
|
double *in /* inut value */
|
|
) {
|
|
icxLuLut *p = (icxLuLut *)pp; /* this */
|
|
icmLuLut *luluto = (icmLuLut *)p->absxyzlu;
|
|
|
|
luluto->core5_fwd(luluto, out, in);
|
|
luluto->output_pch_fwd(luluto, out, out);
|
|
luluto->output_fmt_fwd(luluto, out, out);
|
|
p->cam->XYZ_to_cam(p->cam, out, out);
|
|
}
|
|
|
|
/* Initialise the additional CAM space clut rspl, used to compute */
|
|
/* reverse lookup CAM clipping results when the camclip flag is set. */
|
|
/* We weight the CAM nn clipping, to give a more L* and H* preserving clip direction. */
|
|
/* Return error code. */
|
|
/* (We are assuming nearest clipping - we aren't setting up properly for */
|
|
/* vector clipping) */
|
|
static int
|
|
icxLuLut_init_clut_camclip(
|
|
icxLuLut *p) {
|
|
int e, gres[MAX_CHAN];
|
|
double lchw[MXRO] = { JCCWEIGHT, CCCWEIGHT, HCCWEIGHT };
|
|
|
|
/* Setup so clut contains transform to CAM Jab */
|
|
/* (camclip is only used in fwd or invfwd direction lookup) */
|
|
double cmin[3], cmax[3];
|
|
cmin[0] = 0.0; cmax[0] = 100.0; /* Nominal Jab output ranges */
|
|
cmin[1] = -128.0; cmax[1] = 128.0;
|
|
cmin[2] = -128.0; cmax[2] = 128.0;
|
|
|
|
/* Get icm lookup we need for seting up and using CAM icx clut */
|
|
if ((p->absxyzlu = (icmLuSpace *)p->pp->pp->get_luobj(p->pp->pp, icmFwd,
|
|
icAbsoluteColorimetric, icSigXYZData, icmLuOrdNorm)) == NULL) {
|
|
p->pp->e.c = p->pp->pp->e.c; /* Copy error to xicc */
|
|
strcpy(p->pp->e.m, p->pp->pp->e.m);
|
|
return p->pp->e.c;
|
|
}
|
|
|
|
/* Create CAM rspl based multi-dim table */
|
|
if ((p->cclutTable = new_rspl((p->fastsetup ? RSPL_FASTREVSETUP : RSPL_NOFLAGS)
|
|
| (p->flags & ICX_VERBOSE ? RSPL_VERBOSE : RSPL_NOFLAGS),
|
|
p->inputChan, p->outputChan)) == NULL) {
|
|
p->pp->e.c = 2;
|
|
sprintf(p->pp->e.m,"Creation of clut table rspl failed");
|
|
return p->pp->e.c;
|
|
}
|
|
|
|
#ifdef USELCHWEIGHT
|
|
/* Set the Nearest Neighbor clipping Weighting */
|
|
p->cclutTable->rev_set_lchw(p->cclutTable, lchw);
|
|
#endif /* USELCHWEIGHT */
|
|
|
|
if (p->plu->max_clut_res(p->plu, gres) == 0) { /* Hmm. */
|
|
int res;
|
|
if (p->inputChan <= 3)
|
|
res = 33;
|
|
else if (p->inputChan <= 4)
|
|
res = 17;
|
|
else
|
|
res = 5;
|
|
for (e = 0; e < p->inputChan; e++)
|
|
gres[e] = res;
|
|
}
|
|
|
|
/* Setup our special CAM space rspl */
|
|
p->cclutTable->set_rspl(p->cclutTable, RSPL_NOFLAGS, (void *)p,
|
|
icxLuLut_clut_camclip_func,
|
|
p->ninmin, p->ninmax, gres, cmin, cmax);
|
|
|
|
/* Duplicate the ink limit information for any reverse interpolation. */
|
|
p->cclutTable->rev_set_limit(
|
|
p->cclutTable, /* this */
|
|
p->ink.tlimit >= 0.0 || p->ink.klimit >= 0.0 ? icxLimitD_void : NULL,
|
|
/* Optional input space limit() function. */
|
|
/* Function should evaluate in[0..di-1], and return */
|
|
/* number that is not to exceed 0.0. NULL if not used. */
|
|
(void *)p, /* Context passed to limit() */
|
|
0.0 /* Value that limit() is not to exceed */
|
|
);
|
|
return 0;
|
|
}
|
|
|
|
/* ========================================================== */
|
|
/* xicc creation code */
|
|
/* ========================================================== */
|
|
|
|
/* Callback for computing delta E squared for two output (PCS) */
|
|
/* values, passed as a callback to xfit */
|
|
static double xfit_to_de2(void *cntx, double *in1, double *in2) {
|
|
icmHeader *h = (icmHeader *)cntx;
|
|
double rv;
|
|
|
|
if (h->pcs == icSigLabData) {
|
|
#ifdef USE_CIE94_DE
|
|
rv = icmCIE94sq(in1, in2);
|
|
#else
|
|
rv = icmLabDEsq(in1, in2);
|
|
#endif
|
|
} else {
|
|
double lab1[3], lab2[3];
|
|
icmXYZ2Lab(&icmD50, lab1, in1);
|
|
icmXYZ2Lab(&icmD50, lab2, in2);
|
|
#ifdef USE_CIE94_DE
|
|
rv = icmCIE94sq(lab1, lab2);
|
|
#else
|
|
rv = icmLabDEsq(lab1, lab2);
|
|
#endif
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
/* Same as above plus partial derivatives */
|
|
static double xfit_to_dde2(void *cntx, double dout[2][MXDIDO], double *in1, double *in2) {
|
|
icmHeader *h = (icmHeader *)cntx;
|
|
double rv;
|
|
|
|
if (h->pcs == icSigLabData) {
|
|
int j,k;
|
|
double tdout[2][3];
|
|
#ifdef USE_CIE94_DE
|
|
rv = icxdCIE94sq(tdout, in1, in2);
|
|
#else
|
|
rv = icxdLabDEsq(tdout, in1, in2);
|
|
#endif
|
|
for (k = 0; k < 2; k++) {
|
|
for (j = 0; j < 3; j++)
|
|
dout[k][j] = tdout[k][j];
|
|
}
|
|
} else {
|
|
double lab1[3], lab2[3];
|
|
double dout12[2][3][3];
|
|
double tdout[2][3];
|
|
int i,j,k;
|
|
|
|
icxdXYZ2Lab(&icmD50, lab1, dout12[0], in1);
|
|
icxdXYZ2Lab(&icmD50, lab2, dout12[1], in2);
|
|
#ifdef USE_CIE94_DE
|
|
rv = icxdCIE94sq(tdout, lab1, lab2);
|
|
#else
|
|
rv = icxdLabDEsq(tdout, lab1, lab2);
|
|
#endif
|
|
/* Compute partial derivative (is this correct ??) */
|
|
for (k = 0; k < 2; k++) {
|
|
for (j = 0; j < 3; j++) {
|
|
dout[k][j] = 0.0;
|
|
for (i = 0; i < 3; i++) {
|
|
dout[k][j] += tdout[k][i] * dout12[k][i][j];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
#ifdef NEVER
|
|
/* Check partial derivative function within xfit_to_dde2() */
|
|
|
|
static double _xfit_to_dde2(void *cntx, double dout[2][MXDIDO], double *in1, double *in2) {
|
|
int k, i;
|
|
double rv, drv;
|
|
double trv;
|
|
|
|
rv = xfit_to_de2(cntx, in1, in2);
|
|
drv = xfit_to_dde2(cntx, dout, in1, in2);
|
|
|
|
if (fabs(rv - drv) > 1e-6)
|
|
printf("######## DDE2: RV MISMATCH is %f should be %f ########\n",rv,drv);
|
|
|
|
/* Check each parameter delta */
|
|
for (k = 0; k < 2; k++) {
|
|
for (i = 0; i < 3; i++) {
|
|
double *in;
|
|
double del;
|
|
|
|
if (k == 0)
|
|
in = in1;
|
|
else
|
|
in = in2;
|
|
|
|
in[i] += 1e-9;
|
|
trv = xfit_to_de2(cntx, in1, in2);
|
|
in[i] -= 1e-9;
|
|
|
|
/* Check that del is correct */
|
|
del = (trv - rv)/1e-9;
|
|
if (fabs(dout[k][i] - del) > 0.04) {
|
|
printf("######## DDE2: EXCESSIVE at in[%d][%d] is %f should be %f ########\n",k,i,dout[k][i],del);
|
|
}
|
|
}
|
|
}
|
|
return rv;
|
|
} /* NEVER */
|
|
|
|
#define xfit_to_dde2 _xfit_to_dde2
|
|
|
|
#endif
|
|
|
|
|
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
|
/* Context to pass to icc settables() to setup icc A2B Lut: */
|
|
typedef struct {
|
|
int noisluts; /* Flag - If LuLut: Don't create input (Device) shaper curves. */ \
|
|
int noipluts; /* Flag - If LuLut: Don't create input (Device) position curves. */ \
|
|
int nooluts; /* Flag - If LuLut: Don't create output (PCS) curves. */ \
|
|
xfit *xf; /* Curve fitting class instance */
|
|
|
|
/* Infor for black point finding */
|
|
icxInk *ink; /* inking details */
|
|
int kch; /* Black ink channel, -1 if n/a */
|
|
xcal *cal; /* Optional cal Will override any */
|
|
icmHeader *h; /* icc profile header */
|
|
} set_cntx;
|
|
|
|
/* Unility functions within set_cmtx */
|
|
|
|
/* Convert input values from linearized to (potentially) */
|
|
/* final values using the xcal curves */
|
|
static void sc_to_final(set_cntx *p, double *out, double *in) {
|
|
xfit *xf = p->xf;
|
|
|
|
if (out != in)
|
|
vect_cpy(out, in, xf->di);
|
|
|
|
if (p->cal != NULL)
|
|
p->cal->interp(p->cal, out, out);
|
|
|
|
}
|
|
|
|
/* Convert input values from (potentially) final values */
|
|
/* to linearized device values using the xcal curves */
|
|
static void sc_from_final(set_cntx *p, double *out, double *in) {
|
|
xfit *xf = p->xf;
|
|
|
|
if (out != in)
|
|
vect_cpy(out, in, xf->di);
|
|
|
|
|
|
if (p->cal != NULL)
|
|
p->cal->inv_interp(p->cal, out, out);
|
|
}
|
|
|
|
#ifdef NEVER /* Not currently used ?? */
|
|
|
|
/* Convert input value from linearized to (potentially) */
|
|
/* final values using the xcal curves */
|
|
static double sc_to_final_ch(set_cntx *p, int ch, double in) {
|
|
double out = in;
|
|
|
|
if (p->cal != NULL)
|
|
out = p->cal->interp_ch(p->cal, ch, out);
|
|
|
|
|
|
return out;
|
|
}
|
|
|
|
/* Convert input values from (potentially) final values */
|
|
/* to linearized device values using the xcal curves */
|
|
static double sc_from_final_ch(set_cntx *p, int ch, double in) {
|
|
double out = in;
|
|
|
|
|
|
if (p->cal != NULL)
|
|
out = p->cal->inv_interp_ch(p->cal, ch, out);
|
|
|
|
return out;
|
|
}
|
|
#endif /* NEVER */
|
|
|
|
/* Functions to pass to icc settables() to setup icc A2B Lut: */
|
|
|
|
/* Input table */
|
|
static void set_input(void *cntx, double *out, double *in, int ch) {
|
|
set_cntx *p = (set_cntx *)cntx;
|
|
xfit *xf = p->xf;
|
|
int e;
|
|
|
|
if (p->noisluts != 0 && p->noipluts != 0) { /* Input table must be linear */
|
|
icmCpyN(out, in, xf->di);
|
|
} else {
|
|
for (e = 0; e < xf->di; e++)
|
|
out[e] = xf->incurve(xf, in[e], e);
|
|
}
|
|
}
|
|
|
|
/* clut */
|
|
static void set_clut(void *cntx, double *out, double *in, int ch) {
|
|
set_cntx *p = (set_cntx *)cntx;
|
|
xfit *xf = p->xf;
|
|
co tp;
|
|
|
|
icmCpyN(tp.p, in, xf->di);
|
|
xf->clut->interp(xf->clut, &tp);
|
|
icmCpyN(out, tp.v, xf->fdi);
|
|
}
|
|
|
|
/* output */
|
|
static void set_output(void *cntx, double *out, double *in, int ch) {
|
|
set_cntx *p = (set_cntx *)cntx;
|
|
xfit *xf = p->xf;
|
|
int f;
|
|
|
|
if (p->nooluts != 0) { /* Output table must be linear */
|
|
icmCpyN(out, in, xf->fdi);
|
|
} else {
|
|
for (f = 0; f < xf->fdi; f++)
|
|
out[f] = xf->outcurve(xf, in[f], f);
|
|
}
|
|
}
|
|
|
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
|
|
|
/* Ink limit+gamut limit calculation function using xfit information */
|
|
/* (Same functionality as icxLimit() above, but not depending on xLuLut) */
|
|
/* Returns < 0.0 if input value is within limits, */
|
|
/* > 0.0 if outside limits. Value is proportinal to distance to limits. */
|
|
/* We implement device gamut check to improve utility outside rspl, */
|
|
/* in optimisation routines. */
|
|
/* The limits are assumed to be post calibrated device values (ie. real */
|
|
/* final device values) */
|
|
static double xfitLimit(
|
|
set_cntx *p,
|
|
double *in
|
|
) {
|
|
double cin[MAX_CHAN]; /* final input values */
|
|
double tlim, klim;
|
|
double ovr, val;
|
|
int e;
|
|
|
|
/* Compute amount outside device value limits 0.0 - 1.0 */
|
|
for (ovr = -1.0, e = 0; e < p->xf->di; e++) {
|
|
cin[e] = in[e];
|
|
if (in[e] < 0.0) { /* ! Not cin[] */
|
|
cin[e] = 0.0;
|
|
if (-in[e] > ovr)
|
|
ovr = -in[e];
|
|
} else if (in[e] > 1.0) {
|
|
cin[e] = 1.0;
|
|
if ((in[e] - 1.0) > ovr)
|
|
ovr = in[e] - 1.0;
|
|
}
|
|
}
|
|
|
|
//double lsum, sum;
|
|
//for (lsum = 0.0, e = 0; e < p->xf->di; e++) lsum += cin[e];
|
|
|
|
sc_to_final(p, cin, cin);
|
|
|
|
if ((tlim = p->ink->tlimit) < 0.0)
|
|
tlim = (double)p->xf->di; /* Default is no limit */
|
|
|
|
if ((klim = p->ink->klimit) < 0.0)
|
|
klim = 1.0;
|
|
|
|
/* Compute amount outside total limit */
|
|
{ /* No calibration */
|
|
double sum;
|
|
for (sum = 0.0, e = 0; e < p->xf->di; e++)
|
|
sum += cin[e];
|
|
val = sum - tlim;
|
|
}
|
|
|
|
/* Compute amount outside black limit */
|
|
if (p->ink->klimit >= 0.0) {
|
|
double kval = 0.0;
|
|
switch(p->h->colorSpace) {
|
|
case icSigCmykData:
|
|
kval = cin[3] - klim;
|
|
break;
|
|
default:
|
|
/* NOTE !!! p->ink->kch isn't being initialized !!! */
|
|
if (p->kch >= 0) {
|
|
kval = cin[p->kch] - klim;
|
|
} else {
|
|
error("xlut: Unknown colorspace when black limit specified");
|
|
}
|
|
}
|
|
if (kval > val)
|
|
val = kval;
|
|
}
|
|
|
|
if (ovr > val)
|
|
val = ovr;
|
|
|
|
return val;
|
|
}
|
|
|
|
/* Ink limit+gamut limit clipping function for xLuLut (CMYK). */
|
|
/* Return nz if there was clipping */
|
|
static int xfitDoLimit(
|
|
set_cntx *p,
|
|
double *out,
|
|
double *in
|
|
) {
|
|
double tlim, klim = -1.0;
|
|
double sum;
|
|
int clip = 0, e;
|
|
int kch = -1;
|
|
|
|
sc_to_final(p, out, in);
|
|
|
|
if ((tlim = p->ink->tlimit) < 0.0)
|
|
tlim = (double)p->xf->di;
|
|
|
|
if ((klim = p->ink->klimit) < 0.0)
|
|
klim = 1.0;
|
|
|
|
/* Clip black */
|
|
if (p->h->colorSpace == icSigCmykData)
|
|
kch = 3;
|
|
else
|
|
kch = p->kch;
|
|
|
|
if (kch >= 0) {
|
|
if (out[p->kch] > klim) {
|
|
out[p->kch] = klim;
|
|
clip = 1;
|
|
}
|
|
}
|
|
|
|
/* Compute amount outside total limit */
|
|
for (sum = 0.0, e = 0; e < p->xf->di; e++)
|
|
sum += out[e];
|
|
|
|
if (sum > tlim) {
|
|
clip = 1;
|
|
sum /= (double)p->xf->di;
|
|
for (e = 0; e < p->xf->di; e++)
|
|
out[e] -= sum;
|
|
}
|
|
|
|
sc_from_final(p, out, out);
|
|
|
|
return clip;
|
|
}
|
|
|
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
|
/* Routine to figure out a suitable black point for CMYK */
|
|
|
|
/* Structure to hold optimisation information */
|
|
typedef struct {
|
|
set_cntx *p; /* Transfer function fitted */
|
|
double toAbs[3][3]; /* To abs from aprox relative */
|
|
// ~8 could replace with p->xf->toAbs[][]
|
|
double p1[3]; /* white pivot point in abs Lab */
|
|
double p2[3]; /* Point on vector towards black */
|
|
double toll; /* Tollerance of black direction */
|
|
} xfit_finds;
|
|
|
|
/* Optimise device values to minimise L, while remaining */
|
|
/* within the ink limit, and staying in line between p1 (white) and p2 (black dir) */
|
|
static double bfindfunc(void *adata, double pv[]) {
|
|
xfit_finds *b = (xfit_finds *)adata;
|
|
double rv = 0.0;
|
|
double tt[MAX_CHAN], Lab[3];
|
|
double lr, ta, tb, terr; /* L ratio, target a, target b, target error */
|
|
double ovr;
|
|
|
|
//printf("~1 bfindfunc got %f %f %f %f\n", pv[0], pv[1], pv[2], pv[3]);
|
|
/* See if over ink limit or outside device range */
|
|
ovr = xfitLimit(b->p, pv); /* > 0.0 if outside device gamut or ink limit */
|
|
if (ovr < 0.0)
|
|
ovr = 0.0;
|
|
//printf("~1 bfindfunc got ovr %f\n", ovr);
|
|
|
|
/* Compute the absolute Lab value using xfit model: */
|
|
set_input((void *)b->p, tt, pv, 0);
|
|
set_clut((void *)b->p, tt, tt, 0);
|
|
set_output((void *)b->p, tt, tt, 0);
|
|
|
|
if (b->p->h->pcs != icSigXYZData) /* Convert PCS to XYZ */
|
|
icmLab2XYZ(&icmD50, tt, tt);
|
|
|
|
/* Convert from relative to Absolute colorimetric */
|
|
icmMulBy3x3(tt, b->toAbs, tt);
|
|
icmXYZ2Lab(&icmD50, Lab, tt); /* Convert to Lab */
|
|
|
|
#ifdef DEBUG
|
|
printf("~1 p1 = %f %f %f, p2 = %f %f %f\n",b->p1[0],b->p1[1],b->p1[2],b->p2[0],b->p2[1],b->p2[2]);
|
|
printf("~1 device value %f %f %f %f, Lab = %f %f %f\n",pv[0],pv[1],pv[2],pv[3],Lab[0],Lab[1],Lab[2]);
|
|
#endif
|
|
|
|
/* Primary aim is to minimise L value */
|
|
rv = Lab[0];
|
|
|
|
/* See how out of line from p1 to p2 we are */
|
|
lr = (Lab[0] - b->p1[0])/(b->p2[0] - b->p1[0]); /* Distance towards p2 from p1 */
|
|
ta = lr * (b->p2[1] - b->p1[1]) + b->p1[1]; /* Target a value */
|
|
tb = lr * (b->p2[2] - b->p1[2]) + b->p1[2]; /* Target b value */
|
|
|
|
terr = (ta - Lab[1]) * (ta - Lab[1])
|
|
+ (tb - Lab[2]) * (tb - Lab[2]);
|
|
|
|
if (terr < b->toll) /* Tollerance error doesn't count until it's over tollerance */
|
|
terr = 0.0;
|
|
|
|
#ifdef DEBUG
|
|
printf("~1 target error %f\n",terr);
|
|
#endif
|
|
rv += XICC_BLACK_FIND_ABERR_WEIGHT * terr; /* Make ab match more important than min. L */
|
|
|
|
#ifdef DEBUG
|
|
printf("~1 out of range error %f\n",ovr);
|
|
#endif
|
|
rv += 200 * ovr;
|
|
|
|
#ifdef DEBUG
|
|
printf("~1 black find tc ret %f\n",rv);
|
|
#endif
|
|
return rv;
|
|
}
|
|
|
|
/* Create icxLuLut and underlying fwd Lut from scattered data */
|
|
/* The scattered data is assumed to map Device -> native PCS */
|
|
int set_icxLuLut(
|
|
icc *icco,
|
|
int flags, /* ICX white/black point flags etc. */
|
|
icxTransformCreateType icctype, /* Profile creation type */
|
|
int nodp, /* Number of data points */
|
|
int nodpbw, /* Number of data points to look for white & black patches in */
|
|
cow *ipoints, /* Array of input points (Lab or XYZ normalized to 1.0) */
|
|
icxMatrixModel *skm, /* Optional skeleton model (used for input profiles) */
|
|
double dispLuminance, /* > 0.0 if display luminance value and is known */
|
|
double wpscale, /* > 0.0 if white point is to be scaled */
|
|
double smooth, /* RSPL smoothing factor, -ve if raw */
|
|
double avgdev, /* reading Average Deviation as a prop. of the input range */
|
|
double demph, /* dark emphasis factor for cLUT grid res. */
|
|
icxViewCond *vc, /* Viewing Condition (NULL if not using CAM) */
|
|
icxInk *ink, /* inking details (NULL for default) */
|
|
xcal *cal, /* Optional cal used in setting white/black (NULL for none) */
|
|
int quality, /* Quality metric, 0..3 */
|
|
int ires, /* Num of in-table entries */
|
|
int gres[MXDI], /* RSPL/CLUT resolution */
|
|
int ores /* Num of out-table entries */
|
|
) {
|
|
icmHeader *h = icco->header; /* Pointer to icc header */
|
|
int inputChan; /* Device channels */
|
|
int outputChan = 3; /* Must be the PCS */
|
|
int pcsy; /* Effective PCS L or Y chanel index */
|
|
double pcsymax; /* Effective PCS L or Y maximum value */
|
|
int maxchan; /* max(inputChan, outputChan) */
|
|
int rsplflags = RSPL_NOFLAGS; /* Flags for scattered data rspl */
|
|
int singleintent = 0; /* nz if only a single intent (i.e. 0 tag rather than 1) */
|
|
int e, f, i, j;
|
|
double dwhite[MXDI], dblack[MXDI]; /* Device white and black values */
|
|
double wp[3]; /* Absolute White point in XYZ */
|
|
double bp[3]; /* Absolute Black point in XYZ */
|
|
double dgwhite[MXDI]; /* Device space gamut boundary white (ie. RGB 1,1,1) */
|
|
double oavgdev[MXDO]; /* Average output value deviation */
|
|
xfit *xf = NULL; /* Curve fitting class instance */
|
|
set_cntx _cntx = { 0 }, *cntx = &_cntx; /* Context for setting transform */
|
|
|
|
if (flags & ICX_VERBOSE)
|
|
rsplflags |= RSPL_VERBOSE;
|
|
|
|
// if (flags & ICX_2PASSSMTH)
|
|
// rsplflags |= RSPL_2PASSSMTH; /* Smooth data using Gaussian */
|
|
|
|
// if (flags & ICX_EXTRA_FIT)
|
|
// rsplflags |= RSPL_EXTRAFIT2; /* Try extra hard to fit data */
|
|
|
|
/* Set create specific flags: */
|
|
if (flags & ICX_NO_IN_SHP_LUTS)
|
|
cntx->noisluts = 1;
|
|
|
|
if (flags & ICX_NO_IN_POS_LUTS)
|
|
cntx->noipluts = 1;
|
|
|
|
if (flags & ICX_NO_OUT_LUTS)
|
|
cntx->nooluts = 1;
|
|
|
|
if (h->pcs == icSigXYZData) {
|
|
pcsy = 1; /* Y of XYZ */
|
|
pcsymax = 1.0;
|
|
} else {
|
|
pcsy = 0; /* L or Lab */
|
|
pcsymax = 100.0;
|
|
}
|
|
|
|
inputChan = icmCSSig2nchan(h->colorSpace);
|
|
maxchan = inputChan > outputChan ? inputChan : outputChan;
|
|
|
|
/* Translate overall average deviation into output channel deviation */
|
|
/* (This is for rspl scattered data fitting smoothness adjustment) */
|
|
/* (This could do with more tuning) */
|
|
|
|
/* XYZ display models are under-smoothed, because the mapping is typically */
|
|
/* very "straight", and the lack of tension reduces any noise reduction effect. */
|
|
/* !!! This probably means that we should switch to 3rd order smoothness criteria !! */
|
|
/* We apply an arbitrary correction here */
|
|
/* !!!! There is also a bug in the rspl code, where smoothness is */
|
|
/* scaled by data range. This is making Lab smoothing ~100 times */
|
|
/* more than XYZ smoothing. Fix this with SMOOTH2 changes ?? */
|
|
if (h->pcs == icSigXYZData) {
|
|
oavgdev[0] = XYZ_EXTRA_SMOOTH * 0.70 * avgdev;
|
|
oavgdev[1] = XYZ_EXTRA_SMOOTH * 1.00 * avgdev;
|
|
oavgdev[2] = XYZ_EXTRA_SMOOTH * 0.70 * avgdev;
|
|
} else if (h->pcs == icSigLabData) {
|
|
oavgdev[0] = 1.00 * avgdev;
|
|
oavgdev[1] = 0.70 * avgdev;
|
|
oavgdev[2] = 0.70 * avgdev;
|
|
} else { /* Hmmm */
|
|
for (f = 0; f < outputChan; f++)
|
|
oavgdev[f] = avgdev;
|
|
}
|
|
|
|
/* ------------------------------- */
|
|
|
|
if (flags & ICX_VERBOSE)
|
|
printf("Estimating white point\n");
|
|
|
|
icmXYZ2Ary(wp, icmD50); /* Set a default value - D50 */
|
|
icmXYZ2Ary(bp, icmBlack); /* Set a default value - absolute black */
|
|
|
|
{
|
|
/* Figure out as best we can the device white and black points */
|
|
|
|
/* Compute device white and black points as if */
|
|
/* we are doing an Output or Display device */
|
|
{
|
|
switch (h->colorSpace) {
|
|
|
|
case icSigCmykData:
|
|
for (e = 0; e < inputChan; e++) {
|
|
dwhite[e] = 0.0;
|
|
dblack[e] = 1.0;
|
|
}
|
|
break;
|
|
case icSigCmyData:
|
|
for (e = 0; e < inputChan; e++) {
|
|
dwhite[e] = 0.0;
|
|
dblack[e] = 1.0;
|
|
}
|
|
break;
|
|
case icSigRgbData:
|
|
for (e = 0; e < inputChan; e++) {
|
|
dwhite[e] = 1.0;
|
|
dblack[e] = 0.0;
|
|
}
|
|
break;
|
|
|
|
case icSigGrayData: { /* Could be additive or subtractive */
|
|
double maxY, minY; /* Y min and max */
|
|
double maxd, mind; /* Corresponding device values */
|
|
|
|
maxY = -1e8, minY = 1e8;
|
|
|
|
/* Figure out if it's additive or subtractive */
|
|
for (i = 0; i < nodpbw; i++) {
|
|
double Y;
|
|
if (h->pcs != icSigXYZData) { /* Convert white point to XYZ */
|
|
double xyz[3];
|
|
icmLab2XYZ(&icmD50, xyz, ipoints[i].v);
|
|
Y = xyz[1];
|
|
} else {
|
|
Y = ipoints[i].v[1];
|
|
}
|
|
|
|
if (Y > maxY) {
|
|
maxY = Y;
|
|
maxd = ipoints[i].p[0];
|
|
}
|
|
if (Y < minY) {
|
|
minY = Y;
|
|
mind = ipoints[i].p[0];
|
|
}
|
|
}
|
|
if (maxd < mind) { /* Subtractive */
|
|
for (e = 0; e < inputChan; e++) {
|
|
dwhite[e] = 0.0;
|
|
dblack[e] = 1.0;
|
|
}
|
|
} else { /* Additive */
|
|
for (e = 0; e < inputChan; e++) {
|
|
dwhite[e] = 1.0;
|
|
dblack[e] = 0.0;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
return icm_err(icco,ICX_ERR_BAD_DEV,
|
|
"set_icxLuLut: can't handle color space %s",
|
|
icm2str(icmColorSpaceSig, h->colorSpace));
|
|
}
|
|
}
|
|
/* dwhite is what we want for dgwhite[], used for XFIT_OUT_WP_REL_US */
|
|
for (e = 0; e < inputChan; e++)
|
|
dgwhite[e] = dwhite[e];
|
|
|
|
/* If this is actualy 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 */
|
|
if (h->pcs == icSigXYZData)
|
|
icmXYZ2Lab(&icmD50, labv, ipoints[i].v);
|
|
else
|
|
icmCpy3(labv,ipoints[i].v);
|
|
|
|
#ifdef NEVER
|
|
/* Choose largest Y or L* */
|
|
if (ipoints[i].v[pcsy] > 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++)
|
|
dwhite[e] = ipoints[i].p[e];
|
|
wpy = ipoints[i].v[pcsy];
|
|
wix = i;
|
|
}
|
|
#else
|
|
|
|
/* Tilt things towards D50 neutral white patches */
|
|
yv = labv[0] - 0.3 * sqrt(labv[1] * labv[1] + labv[2] * labv[2]);
|
|
//printf("~1 patch %d Lab = %s, yv = %f\n",i+1,icmPdv(3,labv),yv);
|
|
if (yv > wpy) {
|
|
//printf("~1 best so far\n");
|
|
wp[0] = ipoints[i].v[0];
|
|
wp[1] = ipoints[i].v[1];
|
|
wp[2] = ipoints[i].v[2];
|
|
for (e = 0; e < inputChan; e++)
|
|
dwhite[e] = ipoints[i].p[e];
|
|
wpy = yv;
|
|
wix = i;
|
|
}
|
|
#endif
|
|
if (ipoints[i].v[pcsy] < 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++)
|
|
dblack[e] = ipoints[i].p[e];
|
|
bpy = ipoints[i].v[pcsy];
|
|
bix = i;
|
|
}
|
|
}
|
|
/* Make sure values are XYZ */
|
|
if (h->pcs != icSigXYZData) {
|
|
icmLab2XYZ(&icmD50, wp, wp);
|
|
icmLab2XYZ(&icmD50, bp, bp);
|
|
}
|
|
|
|
if (flags & ICX_VERBOSE) {
|
|
printf("Picked white patch %d with dev = %s\n XYZ = %s, Lab = %s\n",
|
|
wix+1, icmPdv(inputChan, dwhite), icmPdv(3, wp), icmPLab(wp));
|
|
printf("Picked black patch %d with dev = %s\n XYZ = %s, Lab = %s\n",
|
|
bix+1, icmPdv(inputChan, dblack), icmPdv(3, bp), icmPLab(bp));
|
|
}
|
|
|
|
/* else Output or Display device */
|
|
} else {
|
|
/* We assume that the output target is well behaved, */
|
|
/* and that it includes a white point patch. */
|
|
/* Avarage together all the white patches values. */
|
|
int nw = 0;
|
|
|
|
wp[0] = wp[1] = wp[2] = 0.0;
|
|
|
|
switch (h->colorSpace) {
|
|
|
|
case icSigCmykData:
|
|
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
|
|
&& ipoints[i].p[3] < 0.001) {
|
|
wp[0] += ipoints[i].v[0];
|
|
wp[1] += ipoints[i].v[1];
|
|
wp[2] += ipoints[i].v[2];
|
|
nw++;
|
|
}
|
|
}
|
|
break;
|
|
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++;
|
|
}
|
|
}
|
|
/* Setup bpo device value in case we need it */
|
|
// bpop.p[0] = bpop.p[1] = bpop.p[2] = 0.0;
|
|
break;
|
|
|
|
case icSigGrayData: { /* Could be additive or subtractive */
|
|
double minwp[3], maxwp[3];
|
|
int nminwp = 0, nmaxwp = 0;
|
|
|
|
minwp[0] = minwp[1] = minwp[2] = 0.0;
|
|
maxwp[0] = maxwp[1] = maxwp[2] = 0.0;
|
|
|
|
/* Look for both min & max */
|
|
for (i = 0; i < nodpbw; i++) {
|
|
if (ipoints[i].p[0] < 0.001) {
|
|
minwp[0] += ipoints[i].v[0];
|
|
minwp[1] += ipoints[i].v[1];
|
|
minwp[2] += ipoints[i].v[2];
|
|
nminwp++;
|
|
}
|
|
if (ipoints[i].p[0] > 0.999) {
|
|
maxwp[0] += ipoints[i].v[0];
|
|
maxwp[1] += ipoints[i].v[1];
|
|
maxwp[2] += ipoints[i].v[2];
|
|
nmaxwp++;
|
|
}
|
|
}
|
|
/* And use the one we need */
|
|
if (nminwp > 0) { /* Subtractive */
|
|
wp[0] = minwp[0];
|
|
wp[1] = minwp[1];
|
|
wp[2] = minwp[2];
|
|
nw = nminwp;
|
|
if (minwp[pcsy]/nminwp < (0.5 * pcsymax))
|
|
nw = 0; /* Looks like a mistake */
|
|
// bpop.p[0] = 1.0;
|
|
}
|
|
if (nmaxwp > 0 /* Additive */
|
|
&& (nminwp == 0 || maxwp[pcsy]/nmaxwp > minwp[pcsy]/nminwp)) {
|
|
wp[0] = maxwp[0];
|
|
wp[1] = maxwp[1];
|
|
wp[2] = maxwp[2];
|
|
nw = nmaxwp;
|
|
if (maxwp[pcsy]/nmaxwp < (0.5 * pcsymax))
|
|
nw = 0; /* Looks like a mistake */
|
|
// bpop.p[0] = 0.0;
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
return icm_err(icco,ICX_ERR_BAD_DEV,
|
|
"set_icxLuLut: can't handle color space %s",
|
|
icm2str(icmColorSpaceSig, h->colorSpace));
|
|
}
|
|
|
|
if (nw == 0) {
|
|
return icm_err(icco, ICX_ERR_NO_WP,
|
|
"set_icxLuLut: can't handle test points without a white patch");
|
|
}
|
|
|
|
/* Compute average */
|
|
wp[0] /= (double)nw;
|
|
wp[1] /= (double)nw;
|
|
wp[2] /= (double)nw;
|
|
if (h->pcs != icSigXYZData) /* Convert white point to XYZ */
|
|
icmLab2XYZ(&icmD50, wp, wp);
|
|
}
|
|
|
|
if (flags & ICX_VERBOSE) {
|
|
printf("Approximate White point XYZ = %s, Lab = %s\n", icmPdv(3,wp),icmPLab(wp));
|
|
}
|
|
}
|
|
|
|
if (h->colorSpace == icSigGrayData) { /* Don't use device or PCS curves for monochrome */
|
|
cntx->noisluts = cntx->noipluts = cntx->nooluts = 1;
|
|
}
|
|
|
|
if ((flags & ICX_VERBOSE) && (cntx->noisluts == 0 || cntx->noipluts == 0 || cntx->nooluts == 0))
|
|
printf("Creating optimised per channel curves\n");
|
|
|
|
/* Setup and then create xfit object that does most of the work */
|
|
{
|
|
int xfflags = 0; /* xfit flags */
|
|
double in_min[MXDI]; /* Input value scaling minimum */
|
|
double in_max[MXDI]; /* Input value scaling maximum */
|
|
double out_min[MXDO]; /* Output value scaling minimum */
|
|
double out_max[MXDO]; /* Output value scaling maximum */
|
|
int iluord, sluord, oluord;
|
|
int iord[MXDI]; /* Input curve orders */
|
|
int sord[MXDI]; /* Input sub-grid curve orders */
|
|
int oord[MXDO]; /* Output curve orders */
|
|
double shp_smooth[MXDI];/* Smoothing factors for each curve, nom = 1.0 */
|
|
double out_smooth[MXDO];
|
|
|
|
optcomb tcomb = oc_ipo; /* Create all by default */
|
|
|
|
if ((xf = new_xfit(icco)) == NULL) {
|
|
return icm_err(icco, ICX_ERR_XFIT_FAIL,
|
|
"set_icxLuLut: creation of xfit object failed");
|
|
}
|
|
|
|
cntx->xf = xf;
|
|
|
|
/* Setup for optimising run */
|
|
if (cntx->noisluts)
|
|
tcomb &= ~oc_i;
|
|
|
|
if (cntx->noipluts)
|
|
tcomb &= ~oc_p;
|
|
|
|
if (cntx->nooluts)
|
|
tcomb &= ~oc_o;
|
|
|
|
if (flags & ICX_VERBOSE)
|
|
xfflags |= XFIT_VERB;
|
|
|
|
if (flags & ICX_SET_WHITE) {
|
|
|
|
if ((flags & ICX_SET_WHITE_ABS) != ICX_SET_WHITE_ABS) {
|
|
|
|
xfflags |= XFIT_OUT_WP_REL;
|
|
|
|
if ((flags & ICX_SET_WHITE_C) == ICX_SET_WHITE_C)
|
|
xfflags |= XFIT_OUT_WP_REL_C;
|
|
|
|
else if ((flags & ICX_SET_WHITE_US) == ICX_SET_WHITE_US)
|
|
xfflags |= XFIT_OUT_WP_REL_US;
|
|
}
|
|
|
|
if (h->pcs != icSigXYZData)
|
|
xfflags |= XFIT_OUT_LAB;
|
|
}
|
|
|
|
if (flags & ICX_SINGLE_INTENT)
|
|
singleintent = 1;
|
|
|
|
/* If asked, clip the absolute white point to have Y <= 1.0 ? */
|
|
if (flags & ICX_CLIP_WB)
|
|
xfflags |= XFIT_CLIP_WP;
|
|
|
|
/* With current B2A code, make sure a & b curves */
|
|
/* pass through zero. */
|
|
if (h->pcs == icSigLabData) {
|
|
xfflags |= XFIT_OUT_ZERO;
|
|
}
|
|
|
|
/* Let xfit create the clut */
|
|
xfflags |= XFIT_MAKE_CLUT;
|
|
|
|
/* Set the curve orders for input (device) and output (PCS) */
|
|
if (quality >= 3) { /* Ultra high */
|
|
iluord = 25;
|
|
sluord = 4;
|
|
oluord = 25;
|
|
} else if (quality == 2) { /* High */
|
|
iluord = 20;
|
|
sluord = 3;
|
|
oluord = 20;
|
|
} else if (quality == 1) { /* Medium */
|
|
iluord = 17;
|
|
sluord = 2;
|
|
oluord = 17;
|
|
} else { /* Low */
|
|
iluord = 10;
|
|
sluord = 1;
|
|
oluord = 10;
|
|
}
|
|
|
|
icmGetDefaultFullRange(icco, in_min, in_max, h->colorSpace);
|
|
for (e = 0; e < inputChan; e++) {
|
|
iord[e] = iluord;
|
|
sord[e] = sluord;
|
|
shp_smooth[e] = SHP_SMOOTH;
|
|
}
|
|
|
|
icmGetDefaultFullRange(icco, out_min, out_max, h->pcs);
|
|
for (f = 0; f < outputChan; f++) {
|
|
oord[f] = oluord;
|
|
|
|
/* Hack to prevent a convex L curve pushing */
|
|
/* the clut L values above the maximum value */
|
|
/* that can be represented, causing clipping. */
|
|
/* Do this by making sure that the L curve pivots */
|
|
/* through 100.0 to 100.0 */
|
|
if (f == 0 && h->pcs == icSigLabData) {
|
|
if (out_min[f] < 0.0001 && out_max[f] > 100.0) {
|
|
out_max[f] = 100.0;
|
|
}
|
|
}
|
|
out_smooth[f] = OUT_SMOOTH1;
|
|
if (f != 0 && h->pcs == icSigLabData) /* a* & b* */
|
|
out_smooth[f] = OUT_SMOOTH2;
|
|
}
|
|
//printf("~1 wp before xfit %f %f %f\n", wp[0], wp[1], wp[2]);
|
|
|
|
/* Create input, sub and output per channel curves (if configured), */
|
|
/* adjust for white point to make relative (if configured), */
|
|
/* and create clut rspl using xfit class. */
|
|
/* The true white point for the returned curves and rspl is returned. */
|
|
if (xf->fit(xf, xfflags, inputChan, outputChan,
|
|
rsplflags, wp, dwhite, wpscale, dgwhite,
|
|
ipoints, nodp, skm, in_min, in_max, gres, out_min, out_max,
|
|
smooth, oavgdev, demph, iord, sord, oord, shp_smooth, out_smooth, tcomb,
|
|
(void *)h, xfit_to_de2, xfit_to_dde2) != 0) {
|
|
xf->del(xf);
|
|
return icm_err(icco, ICX_ERR_XFIT_FIT_FAIL, "xfit fitting failed");
|
|
}
|
|
//printf("~1 wp after xfit %f %f %f\n", wp[0], wp[1], wp[2]);
|
|
}
|
|
|
|
/* ------------------------------- */
|
|
|
|
/* Deal with finalizing white/black points */
|
|
{
|
|
|
|
if ((flags & ICX_SET_WHITE) && (flags & ICX_VERBOSE)) {
|
|
printf("White point XYZ = %s, Lab = %s\n", icmPdv(3,wp),icmPLab(wp));
|
|
}
|
|
|
|
/* Lookup the black point */
|
|
{ /* Black Point Tag: */
|
|
double devv[MAX_CHAN];
|
|
double pcsv[MAX_CHAN];
|
|
|
|
if (flags & ICX_VERBOSE)
|
|
printf("Find black point\n");
|
|
|
|
/* !!! Hmm. For CMY and RGB we are simply using the device */
|
|
/* combination values as the black point. In reality we might */
|
|
/* want to have the option of using a neutral black point, */
|
|
/* just like CMYK ?? */
|
|
|
|
/* For CMYK devices, we choose a black that has minumum L within */
|
|
/* the ink limits, and if XICC_NEUTRAL_CMYK_BLACK it will in the direction */
|
|
/* that has the same chromaticity as the white point, else choose the same */
|
|
/* Lab vector direction as K, with the minimum L value. */
|
|
/* (Note this is duplicated in xicc.c icxLu_comp_bk_point() !!!) */
|
|
if (h->deviceClass != icSigInputClass
|
|
&& h->colorSpace == icSigCmykData) {
|
|
xfit_finds bfs; /* Callback context */
|
|
double sr[MXDO]; /* search radius */
|
|
double tt[MXDO]; /* Temporary */
|
|
double rs0[MXDO], rs1[MXDO]; /* Random start candidates */
|
|
int trial;
|
|
double brv;
|
|
int kch = 3;
|
|
|
|
/* Setup callback function context */
|
|
bfs.p = cntx; /* xfit contxt */
|
|
bfs.p->ink = ink;
|
|
bfs.p->kch = -1; /* Not set - only used if not cmyk */
|
|
bfs.p->cal = cal;
|
|
bfs.p->h = h;
|
|
bfs.toll = XICC_BLACK_POINT_TOLL;
|
|
|
|
/* !!! we should use an accessor funcion of xfit !!! */
|
|
for (i = 0; i < 3; i++) {
|
|
for (j = 0; j < 3; j++) {
|
|
bfs.toAbs[i][j] = xf->toAbs[i][j];
|
|
}
|
|
}
|
|
|
|
/* Lookup abs Lab value of white point */
|
|
icmXYZ2Lab(&icmD50, bfs.p1, wp);
|
|
|
|
#ifdef XICC_NEUTRAL_CMYK_BLACK
|
|
icmScale3(tt, wp, 0.02); /* Scale white XYZ towards 0,0,0 */
|
|
icmXYZ2Lab(&icmD50, bfs.p2, tt); /* Convert black direction to Lab */
|
|
if (flags & ICX_VERBOSE)
|
|
printf("Neutral black direction (Lab) = %f %f %f\n",bfs.p2[0], bfs.p2[1], bfs.p2[2]);
|
|
|
|
#else /* K direction black */
|
|
/* Now figure abs Lab value of K only, as the direction */
|
|
/* to use for the rich black. */
|
|
for (e = 0; e < inputChan; e++)
|
|
devv[e] = 0.0;
|
|
if (p->ink.klimit <= 0.0)
|
|
devv[kch] = 1.0;
|
|
else
|
|
devv[kch] = p->ink.klimit; /* K value */
|
|
|
|
set_input((void *)cntx, devv, devv, 0); /* Through input curves */
|
|
set_clut((void *)cntx, pcsv, devv, 0); /* Through clut */
|
|
set_output((void *)cntx, pcsv, pcsv, 0); /* Through the output curves */
|
|
|
|
if (h->pcs != icSigXYZData) /* Convert PCS to XYZ */
|
|
icmLab2XYZ(&icmD50, pcsv, pcsv);
|
|
|
|
/* Convert from relative to Absolute colorimetric */
|
|
icmMulBy3x3(tt, xf->toAbs, pcsv);
|
|
icmXYZ2Lab(&icmD50, bfs.p2, tt); /* Convert K only black point to Lab */
|
|
|
|
if (flags & ICX_VERBOSE)
|
|
printf("K only black direction (Lab) = %f %f %f\n",bfs.p2[0], bfs.p2[1], bfs.p2[2]);
|
|
#endif
|
|
/* Set the random start 0 location as 000K */
|
|
/* and the random start 1 location as CMY0 */
|
|
{
|
|
double tv;
|
|
|
|
// !!!!! this should take into account possible xcal effect on limits !!!!
|
|
for (e = 0; e < inputChan; e++)
|
|
rs0[e] = 0.0;
|
|
if (ink->klimit <= 0.0)
|
|
rs0[kch] = 1.0;
|
|
else
|
|
rs0[kch] = ink->klimit; /* K value */
|
|
|
|
if (ink->tlimit <= 0.0)
|
|
tv = 1.0;
|
|
else
|
|
tv = ink->tlimit/(inputChan - 1.0);
|
|
for (e = 0; e < inputChan; e++)
|
|
rs1[e] = tv;
|
|
rs1[kch] = 0.0; /* K value */
|
|
}
|
|
|
|
/* Start with the K only as the current best value */
|
|
for (e = 0; e < inputChan; e++)
|
|
devv[e] = rs0[e];
|
|
brv = bfindfunc((void *)&bfs, devv);
|
|
//printf("~1 initial brv for K only = %f\n",brv);
|
|
|
|
/* Find the device black point using optimization */
|
|
/* Do several trials to avoid local minima. */
|
|
rand32(0x12345678); /* Make trial values deterministic */
|
|
for (trial = 0; trial < 200; trial++) {
|
|
double rv; /* Temporary */
|
|
|
|
/* Start first trial at 000K */
|
|
if (trial == 0) {
|
|
for (j = 0; j < inputChan; j++) {
|
|
tt[j] = rs0[j];
|
|
sr[j] = 0.1;
|
|
}
|
|
|
|
} else {
|
|
/* Base is random between 000K and CMY0: */
|
|
if (trial < 100) {
|
|
rv = d_rand(0.0, 1.0);
|
|
for (j = 0; j < inputChan; j++) {
|
|
tt[j] = rv * rs0[j] + (1.0 - rv) * rs1[j];
|
|
sr[j] = 0.1;
|
|
}
|
|
/* Base on current best */
|
|
} else {
|
|
for (j = 0; j < inputChan; j++) {
|
|
tt[j] = devv[j];
|
|
sr[j] = 0.1;
|
|
}
|
|
}
|
|
|
|
/* Then add random start offset */
|
|
for (rv = 0.0, j = 0; j < inputChan; j++) {
|
|
tt[j] += d_rand(-0.5, 0.5);
|
|
if (tt[j] < 0.0)
|
|
tt[j] = 0.0;
|
|
else if (tt[j] > 1.0)
|
|
tt[j] = 1.0;
|
|
}
|
|
}
|
|
|
|
/* Clip to be within ink limit */
|
|
xfitDoLimit(cntx, tt, tt);
|
|
|
|
if (powell(&rv, inputChan, tt, sr, 0.000001, 1000,
|
|
bfindfunc, (void *)&bfs, NULL, NULL) == 0) {
|
|
//printf("~1 trial %d, rv %f bp %f %f %f %f\n",trial,rv,tt[0],tt[1],tt[2],tt[3]);
|
|
if (rv < brv) {
|
|
//printf("~1 new best\n");
|
|
brv = rv;
|
|
for (j = 0; j < inputChan; j++)
|
|
devv[j] = tt[j];
|
|
}
|
|
}
|
|
}
|
|
if (brv > 1000.0)
|
|
error ("set_icxLuLut: Black point powell failed");
|
|
|
|
/* Make sure resulting device values are strictly in range */
|
|
for (j = 0; j < inputChan; j++) {
|
|
if (devv[j] < 0.0)
|
|
devv[j] = 0.0;
|
|
else if (devv[j] > 1.0)
|
|
devv[j] = 1.0;
|
|
}
|
|
/* Now have device black in devv[] */
|
|
//printf("~1 Black point is CMYK %f %f %f %f\n", devv[0], devv[1], devv[2], devv[3]);
|
|
|
|
/* Else not a CMYK output device, */
|
|
/* use the previously determined device black value. */
|
|
} else {
|
|
for (e = 0; e < inputChan; e++)
|
|
devv[e] = dblack[e];
|
|
}
|
|
|
|
/* Lookup the PCS for the device black: */
|
|
set_input((void *)cntx, devv, devv, 0); /* Through input curves */
|
|
set_clut((void *)cntx, pcsv, devv, 0); /* Through clut */
|
|
set_output((void *)cntx, pcsv, pcsv, 0); /* Through the output curves */
|
|
|
|
if (h->pcs != icSigXYZData) /* Convert PCS to XYZ */
|
|
icmLab2XYZ(&icmD50, pcsv, pcsv);
|
|
|
|
/* Convert from relative to Absolute colorimetric */
|
|
icmMulBy3x3(bp, xf->toAbs, pcsv);
|
|
|
|
/* Got XYZ black point in bp[] */
|
|
if (flags & ICX_VERBOSE) {
|
|
printf("Black point XYZ = %s, Lab = %s\n", icmPdv(3,bp),icmPLab(bp));
|
|
}
|
|
|
|
/* Some ICC sw gets upset if the bp is at all -ve. */
|
|
/* Clip it if this is the case */
|
|
/* (Or we could get xfit to rescale the rspl instead ??) */
|
|
if ((flags & ICX_CLIP_WB)
|
|
&& (bp[0] < 0.0 || bp[1] < 0.0 || bp[2] < 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 this is a display, adjust the white point to be */
|
|
/* exactly Y = 1.0, and compensate the dispLuminance */
|
|
/* and black point accordingly. The Lut is already set to */
|
|
/* assume device white maps to perfect PCS white. */
|
|
if (h->deviceClass == icSigDisplayClass) {
|
|
double scale = 1.0/wp[1];
|
|
int i;
|
|
|
|
dispLuminance /= scale;
|
|
|
|
for (i = 0; i < 3; i++) {
|
|
wp[i] *= scale;
|
|
bp[i] *= scale;
|
|
}
|
|
}
|
|
|
|
if (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 * wp[1];
|
|
wo->data[0].Z = 0.0;
|
|
|
|
if (flags & ICX_VERBOSE)
|
|
printf("Display Luminance = %f\n", wo->data[0].Y);
|
|
}
|
|
|
|
/* Write white and black points */
|
|
if (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_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];
|
|
}
|
|
}
|
|
|
|
/* ------------------------------- */
|
|
|
|
//Debugging clipping of clut
|
|
//printf("~1 xlut.c: noutmin = %f %f %f\n", p->noutmin[0], p->noutmin[1], p->noutmin[2]);
|
|
//printf("~1 xlut.c: noutmax = %f %f %f\n", p->noutmax[0], p->noutmax[1], p->noutmax[2]);
|
|
//printf("~1 xlut.c: outmin = %f %f %f\n", p->outmin[0], p->outmin[1], p->outmin[2]);
|
|
//printf("~1 xlut.c: outmax = %f %f %f\n", p->outmax[0], p->outmax[1], p->outmax[2]);
|
|
|
|
/* Do a specific test for out of Lab encoding range RGB primaries */
|
|
/* (A more general check seems to get false positives - why ??) */
|
|
if (h->pcs == icSigLabData
|
|
&& ( h->deviceClass == icSigDisplayClass
|
|
|| h->deviceClass == icSigOutputClass)
|
|
&& h->colorSpace == icSigRgbData) {
|
|
double dev[3] = { 0.0, 0.0, 0.0 };
|
|
double pcs[3];
|
|
double clip = 0;
|
|
|
|
for (i = 0; i < 3; i++) {
|
|
dev[i] = 1.0;
|
|
|
|
set_clut((void *)cntx, pcs, dev, 0); /* Through clut */
|
|
|
|
/* Convert from efective pcs to natural pcs */
|
|
if (h->pcs != icSigLabData)
|
|
icmXYZ2Lab(&icmD50, pcs, pcs);
|
|
|
|
if (pcs[1] < -128.0 || pcs[1] > 128.0
|
|
|| pcs[2] < -128.0 || pcs[2] > 128.0) {
|
|
warning("\n *** %s primary value can't be encoded in L*a*b* PCS (%f %f %f)",
|
|
i == 0 ? "Red" : i == 1 ? "Green" : "Blue", pcs[0],pcs[1],pcs[2]);
|
|
clip = 1;
|
|
}
|
|
dev[i] = 0.0;
|
|
}
|
|
if (clip)
|
|
a1logw(g_log," *** Try switching to XYZ PCS ***\n");
|
|
}
|
|
|
|
{
|
|
int nsigs = 1;
|
|
icmXformSigs sigs[2];
|
|
|
|
switch (icctype) {
|
|
default:
|
|
sigs[0].sig = singleintent ? icSigAToB0Tag : icSigAToB1Tag;
|
|
sigs[0].ttype = icSigLut16Type;
|
|
/* Default ICC Version used */
|
|
break;
|
|
}
|
|
|
|
/* Use our xfit to set the icc Lut AtoB table values. */
|
|
/* Use icc helper function to do the hard work. */
|
|
if (icco->create_lut_xforms(icco, ICM_CLUT_SET_EXACT, (void *)cntx,
|
|
nsigs, sigs, /* No. of tables + signatures */
|
|
2, /* Bytes per value of CLUT, 1 or 2 */
|
|
(unsigned int)ires, (unsigned int *)gres, (unsigned int)ores,
|
|
h->colorSpace, h->pcs, /* Input, output color spaces */
|
|
NULL, NULL, /* Input space range */
|
|
set_input, /* Input transfer function, Dev->Dev' */
|
|
NULL, NULL, /* Use default Maximum range of Dev' values */
|
|
set_clut, /* Dev' -> PCS' transfer function */
|
|
NULL, NULL, /* Use default Maximum range of PCS' values */
|
|
set_output, /* Linear output transform PCS'->PCS */
|
|
NULL, NULL /* No APXLS */
|
|
)) {
|
|
xf->del(xf);
|
|
// printf("Setting %s->%s Lut failed: %d, %s",
|
|
// icm2str(icmColorSpaceSig, h->colorSpace),
|
|
// icm2str(icmColorSpaceSig, h->pcs),
|
|
// p->pp->pp->e.c,p->pp->pp->e.m);
|
|
if (flags & ICX_VERBOSE)
|
|
printf("cLut profile creation failed\n");
|
|
return icco->e.c; /* create_lut_xforms will have set error */
|
|
}
|
|
}
|
|
|
|
if (flags & ICX_VERBOSE)
|
|
printf("Done A to B table creation\n");
|
|
|
|
/* Done with xfit now */
|
|
xf->del(xf);
|
|
|
|
return ICX_ERR_OK;
|
|
}
|
|
|
|
|
|
|
|
/* ========================================================== */
|
|
/* Gamut boundary code. */
|
|
/* ========================================================== */
|
|
|
|
|
|
/* Context for creating gamut boundary points from, xicc */
|
|
typedef struct {
|
|
gamut *g; /* Gamut being created */
|
|
icxLuLut *x; /* xLut we are working from */
|
|
icxLuBase *flu; /* Forward xlookup */
|
|
double in[MAX_CHAN]; /* Device input values */
|
|
} lutgamctx;
|
|
|
|
/* Function to hand to zbrent to find a clut input' value at the ink limit */
|
|
/* Returns value < 0.0 when within gamut, > 0.0 when out of gamut */
|
|
static double icxLimitFind(void *fdata, double tp) {
|
|
int i;
|
|
double in[MAX_CHAN];
|
|
lutgamctx *p = (lutgamctx *)fdata;
|
|
double tt;
|
|
|
|
for (i = 0; i < p->x->inputChan; i++) {
|
|
in[i] = tp * p->in[i]; /* Scale given input value */
|
|
}
|
|
|
|
tt = icxLimitD(p->x, in);
|
|
|
|
return tt;
|
|
}
|
|
|
|
/* Function to pass to rspl to create gamut boundary from */
|
|
/* forward xLut transform grid points. */
|
|
static void
|
|
lutfwdgam_func(
|
|
void *pp, /* lutgamctx structure */
|
|
double *out, /* output' value at clut grid point (ie. PCS' value) */
|
|
double *in /* input' value at clut grid point (ie. device' value) */
|
|
) {
|
|
lutgamctx *p = (lutgamctx *)pp;
|
|
double pcso[3]; /* PCS output value */
|
|
|
|
/* Figure if we are over the ink limit. */
|
|
if ( (p->x->ink.tlimit >= 0.0 || p->x->ink.klimit >= 0.0)
|
|
&& icxLimitD(p->x, in) > 0.0) {
|
|
int i;
|
|
double sf;
|
|
|
|
/* We are, so use the bracket search to discover a scale */
|
|
/* for the clut input' value that will put us on the ink limit. */
|
|
|
|
for (i = 0; i < p->x->inputChan; i++)
|
|
p->in[i] = in[i];
|
|
|
|
if (zbrent(&sf, 0.0, 1.0, 1e-4, icxLimitFind, pp) != 0) {
|
|
return; /* Give up */
|
|
}
|
|
|
|
/* Compute ink limit value */
|
|
for (i = 0; i < p->x->inputChan; i++)
|
|
p->in[i] = sf * in[i];
|
|
|
|
/* Compute the clut output for this clut input */
|
|
p->x->clut(p->x, pcso, p->in);
|
|
p->x->output(p->x, pcso, pcso);
|
|
p->x->out_abs(p->x, pcso, pcso);
|
|
} else { /* No ink limiting */
|
|
/* Convert the clut PCS' values to PCS output values */
|
|
p->x->output(p->x, pcso, out);
|
|
p->x->out_abs(p->x, pcso, pcso);
|
|
}
|
|
|
|
/* Expand the gamut surface with this point */
|
|
p->g->expand(p->g, pcso);
|
|
|
|
/* Leave out[] unchanged */
|
|
}
|
|
|
|
|
|
/* Function to pass to rspl to create gamut boundary from */
|
|
/* backwards Lut transform. This is called for every node in the */
|
|
/* B2A grid. */
|
|
static void
|
|
lutbwdgam_func(
|
|
void *pp, /* lutgamctx structure */
|
|
double *out, /* output value */
|
|
double *in /* input value */
|
|
) {
|
|
lutgamctx *p = (lutgamctx *)pp;
|
|
double devo[MAX_CHAN]; /* Device output value */
|
|
double pcso[3]; /* PCS output value */
|
|
|
|
/* Convert the clut values to device output values */
|
|
p->x->output(p->x, devo, out); /* (Device never uses out_abs()) */
|
|
|
|
/* Convert from device values to PCS values */
|
|
p->flu->lookup(p->flu, pcso, devo);
|
|
|
|
/* Expand the gamut surface with this point */
|
|
p->g->expand(p->g, pcso);
|
|
|
|
/* Leave out[] unchanged */
|
|
}
|
|
|
|
/* Given an xicc lookup object, return a gamut object. */
|
|
/* Note that the PCS must be Lab or Jab */
|
|
/* An icxLuLut type must be icmFwd or icmBwd, */
|
|
/* and for icmFwd, the ink limit (if supplied) will be applied. */
|
|
/* Return NULL on error, check e.c+err for reason */
|
|
static gamut *icxLuLutGamut(
|
|
icxLuBase *plu, /* this */
|
|
double detail /* gamut detail level, 0.0 = def */
|
|
) {
|
|
xicc *p = plu->pp; /* parent xicc */
|
|
icxLuLut *luluto = (icxLuLut *)plu; /* Lookup xLut type object */
|
|
icColorSpaceSignature ins, pcs, outs;
|
|
icmLookupFunc func;
|
|
icRenderingIntent intent;
|
|
double white[3], black[3], kblack[3];
|
|
int inn, outn;
|
|
gamut *gam;
|
|
|
|
/* get some details */
|
|
plu->spaces(plu, &ins, &inn, &outs, &outn, NULL, &intent, &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;
|
|
}
|
|
|
|
if (func == icmFwd) {
|
|
lutgamctx cx;
|
|
|
|
cx.g = gam = new_gamut(detail, pcs == icxSigJabData, 0);
|
|
cx.x = luluto;
|
|
|
|
/* Scan through grid. */
|
|
/* (Note this can give problems for a strange input space - ie. Lab */
|
|
/* and a low grid resolution - ie. 2) */
|
|
luluto->clutTable->scan_rspl(
|
|
luluto->clutTable, /* this */
|
|
RSPL_NOFLAGS, /* Combination of flags */
|
|
(void *)&cx, /* Opaque function context */
|
|
lutfwdgam_func /* Function to set from */
|
|
);
|
|
|
|
/* Make sure the white and point goes in too, if it isn't in the grid */
|
|
plu->efv_wh_bk_points(plu, white, NULL, NULL);
|
|
//printf("~1 file %s line %d efv_wh_bk_points() set white = %s\n",__FILE__,__LINE__,icmPdv(3,white));
|
|
gam->expand(gam, white);
|
|
|
|
if (detail == 0.0)
|
|
detail = 10.0;
|
|
|
|
/* If the gamut is more than cursary, add some more detail surface points */
|
|
if (detail < 20.0 || luluto->clutTable->g.mres < 4) {
|
|
int res;
|
|
DCOUNT(co, MAX_CHAN, inn, 0, 0, 2);
|
|
|
|
res = (int)(500.0/detail); /* Establish an appropriate sampling density */
|
|
if (res < 10)
|
|
res = 10;
|
|
|
|
/* Itterate over all the faces in the device space */
|
|
DC_INIT(co);
|
|
while(!DC_DONE(co)) { /* Count through the corners of hyper cube */
|
|
int e, m1, m2;
|
|
double in[MAX_CHAN];
|
|
double out[3];
|
|
|
|
for (e = 0; e < inn; e++) { /* Base value */
|
|
in[e] = (double)co[e]; /* Base value */
|
|
in[e] = in[e] * (luluto->inmax[e] - luluto->inmin[e])
|
|
+ luluto->inmin[e];
|
|
}
|
|
|
|
/* Figure if we are over the ink limit. */
|
|
if ((luluto->ink.tlimit >= 0.0 || luluto->ink.klimit >= 0.0)
|
|
&& icxLimit(luluto, in) > 0.0) {
|
|
DC_INC(co);
|
|
continue; /* Skip points over limit */
|
|
}
|
|
|
|
/* Scan only device surface */
|
|
for (m1 = 0; m1 < (inn-1); m1++) { /* Choose first coord to scan */
|
|
if (co[m1] != 0)
|
|
continue; /* Not at lower corner */
|
|
for (m2 = m1 + 1; m2 < inn; m2++) { /* Choose second coord to scan */
|
|
int x, y;
|
|
|
|
if (co[m2] != 0)
|
|
continue; /* Not at lower corner */
|
|
|
|
for (x = 0; x < res; x++) { /* step over surface */
|
|
in[m1] = x/(res - 1.0);
|
|
in[m1] = in[m1] * (luluto->inmax[m1] - luluto->inmin[m1])
|
|
+ luluto->inmin[m1];
|
|
for (y = 0; y < res; y++) {
|
|
in[m2] = y/(res - 1.0);
|
|
in[m2] = in[m2] * (luluto->inmax[m2] - luluto->inmin[m2])
|
|
+ luluto->inmin[m2];
|
|
|
|
/* Figure if we are over the ink limit. */
|
|
if ( (luluto->ink.tlimit >= 0.0 || luluto->ink.klimit >= 0.0)
|
|
&& icxLimit(luluto, in) > 0.0) {
|
|
continue; /* Skip points over limit */
|
|
}
|
|
|
|
luluto->lookup((icxLuBase *)luluto, out, in);
|
|
gam->expand(gam, out);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/* Increment index within block */
|
|
DC_INC(co);
|
|
}
|
|
}
|
|
|
|
/* Now set the cusp points by itterating through colorant 0 & 100% combinations */
|
|
/* If we know what sort of space it is: */
|
|
if (ins == icSigRgbData || ins == icSigCmyData || ins == icSigCmykData) {
|
|
DCOUNT(co, 3, 3, 0, 0, 2);
|
|
|
|
gam->setcusps(gam, 0, NULL);
|
|
DC_INIT(co);
|
|
while(!DC_DONE(co)) {
|
|
int e;
|
|
double in[MAX_CHAN];
|
|
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];
|
|
in[e] = 0; /* K */
|
|
|
|
/* Always use the device->PCS conversion */
|
|
if (luluto->lookup((icxLuBase *)luluto, 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);
|
|
|
|
/* Do all ink combinations and hope we can sort it out */
|
|
/* (This may not be smart, since bodgy cusp values will cause gamut mapping to fail...) */
|
|
} else if (ins != icSigXYZData
|
|
&& ins != icSigLabData
|
|
&& ins != icSigLuvData
|
|
&& ins != icSigYxyData) {
|
|
DCOUNT(co, MAX_CHAN, inn, 0, 0, 2);
|
|
|
|
gam->setcusps(gam, 0, NULL);
|
|
DC_INIT(co);
|
|
while(!DC_DONE(co)) {
|
|
int e;
|
|
double in[MAX_CHAN];
|
|
double out[3];
|
|
|
|
for (e = 0; e < inn; e++) {
|
|
in[e] = (double)co[e];
|
|
in[e] = in[e] * (luluto->inmax[e] - luluto->inmin[e])
|
|
+ luluto->inmin[e];
|
|
}
|
|
|
|
/* Figure if we are over the ink limit. */
|
|
if ((luluto->ink.tlimit >= 0.0 || luluto->ink.klimit >= 0.0)
|
|
&& icxLimit(luluto, in) > 0.0) {
|
|
DC_INC(co);
|
|
continue; /* Skip points over limit */
|
|
}
|
|
|
|
luluto->lookup((icxLuBase *)luluto, out, in);
|
|
gam->setcusps(gam, 1, out);
|
|
|
|
DC_INC(co);
|
|
}
|
|
gam->setcusps(gam, 2, NULL);
|
|
}
|
|
|
|
} else { /* Must be icmBwd */
|
|
lutgamctx cx;
|
|
|
|
/* Get an appropriate device to PCS conversion for the fwd conversion */
|
|
/* we use after bwd conversion in lutbwdgam_func() */
|
|
switch ((int)intent) {
|
|
/* If it is relative */
|
|
case icmDefaultIntent: /* Shouldn't happen */
|
|
case icPerceptual:
|
|
case icRelativeColorimetric:
|
|
case icSaturation:
|
|
intent = icRelativeColorimetric; /* Choose relative */
|
|
break;
|
|
/* If it is absolute */
|
|
case icAbsoluteColorimetric:
|
|
case icxAppearance:
|
|
case icxAbsAppearance:
|
|
break; /* Leave unchanged */
|
|
default:
|
|
break;
|
|
}
|
|
if ((cx.flu = p->get_luobj(p, ICX_CLIP_NEAREST, icmFwd, intent, pcs, icmLuOrdNorm,
|
|
&plu->vc, NULL)) == NULL) {
|
|
return NULL; /* oops */
|
|
}
|
|
|
|
cx.g = gam = new_gamut(detail, pcs == icxSigJabData, 0);
|
|
|
|
cx.x = luluto;
|
|
|
|
luluto->clutTable->scan_rspl(
|
|
luluto->clutTable, /* this */
|
|
RSPL_NOFLAGS, /* Combination of flags */
|
|
(void *)&cx, /* Opaque function context */
|
|
lutbwdgam_func /* Function to set from */
|
|
);
|
|
|
|
/* Now set the cusp points by using the fwd conversion and */
|
|
/* itterating through colorant 0 & 100% combinations. */
|
|
/* If we know what sort of space it is: */
|
|
if (outs == icSigRgbData || outs == icSigCmyData || outs == icSigCmykData) {
|
|
DCOUNT(co, 3, 3, 0, 0, 2);
|
|
|
|
gam->setcusps(gam, 0, NULL);
|
|
DC_INIT(co);
|
|
while(!DC_DONE(co)) {
|
|
int e;
|
|
double in[MAX_CHAN];
|
|
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];
|
|
in[e] = 0; /* K */
|
|
|
|
/* Always use the device->PCS conversion */
|
|
if (cx.flu->lookup((icxLuBase *)cx.flu, 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);
|
|
|
|
/* Do all ink combinations and hope we can sort it out */
|
|
/* (This may not be smart, since bodgy cusp values will cause gamut mapping to fail...) */
|
|
} else if (ins != icSigXYZData
|
|
&& ins != icSigLabData
|
|
&& ins != icSigLuvData
|
|
&& ins != icSigYxyData) {
|
|
DCOUNT(co, MAX_CHAN, inn, 0, 0, 2);
|
|
|
|
gam->setcusps(gam, 0, NULL);
|
|
DC_INIT(co);
|
|
while(!DC_DONE(co)) {
|
|
int e;
|
|
double in[MAX_CHAN];
|
|
double out[3];
|
|
|
|
for (e = 0; e < inn; e++) {
|
|
in[e] = (double)co[e];
|
|
in[e] = in[e] * (luluto->inmax[e] - luluto->inmin[e])
|
|
+ luluto->inmin[e];
|
|
}
|
|
|
|
/* Figure if we are over the ink limit. */
|
|
if ((luluto->ink.tlimit >= 0.0 || luluto->ink.klimit >= 0.0)
|
|
&& icxLimit(luluto, in) > 0.0) {
|
|
DC_INC(co);
|
|
continue; /* Skip points over limit */
|
|
}
|
|
|
|
cx.flu->lookup((icxLuBase *)cx.flu, out, in);
|
|
gam->setcusps(gam, 1, out);
|
|
|
|
DC_INC(co);
|
|
}
|
|
gam->setcusps(gam, 2, NULL);
|
|
}
|
|
cx.flu->del(cx.flu); /* Done with the fwd conversion */
|
|
}
|
|
|
|
/* Set the white and black points */
|
|
plu->efv_wh_bk_points(plu, white, black, kblack);
|
|
gam->setwb(gam, white, black, kblack);
|
|
|
|
//printf("~1 icxLuLutGamut: set black %f %f %f and kblack %f %f %f\n", black[0], black[1], black[2], kblack[0], kblack[1], kblack[2]);
|
|
|
|
#ifdef NEVER /* Not sure if this is a good idea ?? */
|
|
/* Since we know this is a profile, force the space and gamut points to be the same */
|
|
gam->getwb(gam, NULL, NULL, white, black, kblack); /* Get the actual gamut white and black points */
|
|
gam->setwb(gam, white, black, kblack); /* Put it back as colorspace one */
|
|
#endif
|
|
|
|
return gam;
|
|
}
|
|
|
|
/* ========================================================== */
|
|
/* Cusp Map finding code. */
|
|
/* ========================================================== */
|
|
|
|
/* Context for creating Cusp Map points from, xicc */
|
|
typedef struct {
|
|
icxCuspMap *cm; /* Cusp Map being created */
|
|
icxLuLut *x; /* xLut we are working from */
|
|
icxLuBase *flu; /* Forward xlookup */
|
|
double in[MAX_CHAN]; /* Device input values */
|
|
} lutcuspmapctx;
|
|
|
|
/* Function to pass to rspl to create cusp map from */
|
|
/* forward xLut transform grid points. */
|
|
static void
|
|
lutfwdcuspmap_func(
|
|
void *pp, /* lutcuspmapctx structure */
|
|
double *out, /* output' value at clut grid point (ie. PCS' value) */
|
|
double *in /* input' value at clut grid point (ie. device' value) */
|
|
) {
|
|
lutcuspmapctx *p = (lutcuspmapctx *)pp;
|
|
double pcso[3]; /* PCS output value */
|
|
|
|
/* Figure if we are over the ink limit. */
|
|
if ( (p->x->ink.tlimit >= 0.0 || p->x->ink.klimit >= 0.0)
|
|
&& icxLimitD(p->x, in) > 0.0) {
|
|
int i;
|
|
double sf;
|
|
|
|
/* We are, so use the bracket search to discover a scale */
|
|
/* for the clut input' value that will put us on the ink limit. */
|
|
|
|
for (i = 0; i < p->x->inputChan; i++)
|
|
p->in[i] = in[i];
|
|
|
|
if (zbrent(&sf, 0.0, 1.0, 1e-4, icxLimitFind, pp) != 0) {
|
|
return; /* Give up */
|
|
}
|
|
|
|
/* Compute ink limit value */
|
|
for (i = 0; i < p->x->inputChan; i++)
|
|
p->in[i] = sf * in[i];
|
|
|
|
/* Compute the clut output for this clut input */
|
|
p->x->clut(p->x, pcso, p->in);
|
|
p->x->output(p->x, pcso, pcso);
|
|
p->x->out_abs(p->x, pcso, pcso);
|
|
} else { /* No ink limiting */
|
|
/* Convert the clut PCS' values to PCS output values */
|
|
p->x->output(p->x, pcso, out);
|
|
p->x->out_abs(p->x, pcso, pcso);
|
|
}
|
|
|
|
/* Expand the usp map surface with this point */
|
|
p->cm->expand(p->cm, pcso);
|
|
|
|
/* Leave out[] unchanged */
|
|
}
|
|
|
|
/* Expand cusp map with given point */
|
|
/* (We use a segmentned maxima approach) */
|
|
static void cuspmap_expand(icxCuspMap *p, double lab[3]) {
|
|
double h, C;
|
|
int ix;
|
|
|
|
/* Hue angle 0.0 .. 1.0 */
|
|
h = (0.5/3.14159265359) * atan2(lab[2], lab[1]);
|
|
h = (h < 0.0) ? h + 1.0 : h;
|
|
|
|
/* Slot index 0..res-1 */
|
|
ix = (int)floor(h * p->res + 0.5);
|
|
if (ix >= p->res)
|
|
ix -= p->res;
|
|
|
|
/* Chromanance */
|
|
C = sqrt(lab[1] * lab[1] + lab[2] * lab[2]);
|
|
|
|
/* New point at this angle with largest chromanance */
|
|
if (C > p->C[ix]) {
|
|
p->C[ix] = C;
|
|
p->L[ix] = lab[0];
|
|
}
|
|
|
|
/* Tracl min * max L */
|
|
if (lab[0] > p->Lmax[0])
|
|
icmCpy3(p->Lmax, lab);
|
|
if (lab[0] < p->Lmin[0])
|
|
icmCpy3(p->Lmin, lab);
|
|
}
|
|
|
|
/* Interpolate over any gaps in map */
|
|
static void cuspmap_complete(icxCuspMap *p) {
|
|
int i, j, k;
|
|
|
|
// printf("cuspmap list before fixups:\n");
|
|
// for (i = 0; i < p->res; i++) {
|
|
// printf(" %d: C = %f, L = %f\n",i, p->C[i], p->L[i]);
|
|
// }
|
|
|
|
/* First check if there are any entries at all */
|
|
for (i = 0; i < p->res; i++) {
|
|
if (p->C[i] >= 0.0)
|
|
break;
|
|
}
|
|
|
|
if (i >= p->res) /* Nothing there - give up */
|
|
return;
|
|
|
|
/* See if there are any gaps */
|
|
j = -1;
|
|
for (i = 0; i < p->res; i++) {
|
|
//printf("~1 checking %d\n",i);
|
|
if (p->C[i] >= 0.0) {
|
|
j = i; /* Last valid slot */
|
|
//printf("~1 last valid %d\n",j);
|
|
|
|
/* Got a gap */
|
|
} else {
|
|
int ii = i;
|
|
//printf("~1 found gap at %d\n",i);
|
|
if (j < 0) { /* No previous */
|
|
//printf("~1 no previous\n");
|
|
for (j = p->res-1; j >= 0; j--) {
|
|
if (p->C[j] >= 0.0)
|
|
break;
|
|
}
|
|
}
|
|
//printf("~1 got previous %d\n",j);
|
|
/* Find next, even if it's behind us */
|
|
for (k = i+1; k != i; k = (k+1) % p->res) {
|
|
if (p->C[k] >= 0.0)
|
|
break;
|
|
}
|
|
//printf("~1 got next %d\n",k);
|
|
|
|
/* Interpolate between them */
|
|
for (; i != k; i = (i+1) % p->res) {
|
|
double prop;
|
|
int bb, tt;
|
|
bb = k > i ? k - i : k + p->res - i;
|
|
tt = k > j ? k - j : k + p->res - j;
|
|
prop = (double)bb/(double)tt;
|
|
p->C[i] = prop * p->C[j] + (1.0 - prop) * p->C[k];
|
|
p->L[i] = prop * p->L[j] + (1.0 - prop) * p->L[k];
|
|
//printf("~1 interpolating %d with weight %f from %d and %d\n",i,prop,j,k);
|
|
}
|
|
if (k > ii) {
|
|
i = k; /* Continue from next valid */
|
|
//printf("~1 continuing from %d\n",i);
|
|
} else {
|
|
//printf("~1 we've looped back\n");
|
|
break; /* We've looped back */
|
|
}
|
|
}
|
|
}
|
|
|
|
// printf("cuspmap list after fixups:\n");
|
|
// for (i = 0; i < p->res; i++) {
|
|
// printf(" %d: C = %f, L = %f\n",i, p->C[i], p->L[i]);
|
|
// }
|
|
}
|
|
|
|
/* Return the corresponding cusp location, given the source point */
|
|
static void cuspmap_getCusp(icxCuspMap *p,double cuspLCh[3], double srcLab[3]) {
|
|
double h, C;
|
|
int ix, ix0, ix1;
|
|
|
|
/* Hue angle 0.0 .. 1.0 */
|
|
h = (0.5/3.14159265359) * atan2(srcLab[2], srcLab[1]);
|
|
h = (h < 0.0) ? h + 1.0 : h;
|
|
|
|
//printf("~1 getCusp in %f %f %f, h = %f\n", srcLab[0], srcLab[1], srcLab[2],h);
|
|
|
|
/* Slot index 0..res-1 */
|
|
ix = (int)floor(h * p->res + 0.5);
|
|
if (ix >= p->res)
|
|
ix -= p->res;
|
|
|
|
/* Indexes each side */
|
|
ix0 = ix > 0 ? ix-1 : p->res-1;
|
|
ix1 = ix < (p->res-1) ? ix+1 : 0;
|
|
|
|
cuspLCh[0] = p->L[ix];
|
|
cuspLCh[1] = p->C[ix];
|
|
if (cuspLCh[1] > p->C[ix0]) /* Be conservative with C value */
|
|
cuspLCh[1] = p->C[ix0];
|
|
if (cuspLCh[1] > p->C[ix1])
|
|
cuspLCh[1] = p->C[ix1];
|
|
cuspLCh[2] = 360.0 * h;
|
|
|
|
//printf("~1 index %d, L %f C %f h %f\n", ix, cuspLCh[0], cuspLCh[1], cuspLCh[2]);
|
|
}
|
|
|
|
/* We're done with CuspMap */
|
|
static void cuspmap_del(icxCuspMap *p) {
|
|
if (p != NULL) {
|
|
if (p->L != NULL)
|
|
free(p->L);
|
|
if (p->C != NULL)
|
|
free(p->C);
|
|
free(p);
|
|
}
|
|
}
|
|
|
|
/* Given an xicc lookup object, return an icxCuspMap object. */
|
|
/* Note that the PCS must be Lab or Jab. */
|
|
/* An icxLuLut type must be icmFwd, and the ink limit (if supplied) */
|
|
/* will be applied. */
|
|
/* Return NULL on error, check e.c+err for reason */
|
|
static icxCuspMap *icxLuLutCuspMap(
|
|
icxLuBase *plu, /* this */
|
|
int res /* Hue resolution */
|
|
) {
|
|
xicc *p = plu->pp; /* parent xicc */
|
|
icxLuLut *luluto = (icxLuLut *)plu; /* Lookup xLut type object */
|
|
icColorSpaceSignature ins, pcs, outs;
|
|
icmLookupFunc func;
|
|
icRenderingIntent intent;
|
|
int inn, outn;
|
|
lutcuspmapctx cx;
|
|
icxCuspMap *cm;
|
|
int i;
|
|
|
|
/* get some details */
|
|
plu->spaces(plu, &ins, &inn, &outs, &outn, NULL, &intent, &func, &pcs);
|
|
|
|
if (func != icmFwd) {
|
|
p->e.c = 1;
|
|
sprintf(p->e.m,"Creating CuspMap 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 CuspMap PCS of other than Lab or Jab is not supported.");
|
|
return NULL;
|
|
}
|
|
|
|
cx.cm = cm = (icxCuspMap *) calloc(1, sizeof(icxCuspMap));
|
|
cx.x = luluto;
|
|
|
|
if (cx.cm == NULL) {
|
|
p->e.c = 2;
|
|
sprintf(p->e.m,"Malloc of icxCuspMap failed");
|
|
return NULL;
|
|
}
|
|
|
|
if ((cm->L = (double *)malloc(sizeof(double) * res)) == NULL) {
|
|
free(cm);
|
|
p->e.c = 2;
|
|
sprintf(p->e.m,"Malloc of icxCuspMap failed");
|
|
return NULL;
|
|
}
|
|
|
|
if ((cm->C = (double *)malloc(sizeof(double) * res)) == NULL) {
|
|
free(cm->L);
|
|
free(cm);
|
|
p->e.c = 2;
|
|
sprintf(p->e.m,"Malloc of icxCuspMap failed");
|
|
return NULL;
|
|
}
|
|
|
|
cm->res = res;
|
|
cm->expand = cuspmap_expand;
|
|
cm->getCusp = cuspmap_getCusp;
|
|
cm->del = cuspmap_del;
|
|
|
|
for (i = 0; i < res; i++) {
|
|
cm->L[i] = -1.0;
|
|
cm->C[i] = -1.0;
|
|
}
|
|
cm->Lmax[0] = -1.0;
|
|
cm->Lmin[0] = 101.0;
|
|
|
|
/* Scan through grid, expanding the CuspMap. */
|
|
luluto->clutTable->scan_rspl(
|
|
luluto->clutTable, /* this */
|
|
RSPL_NOFLAGS, /* Combination of flags */
|
|
(void *)&cx, /* Opaque function context */
|
|
lutfwdcuspmap_func /* Function to set from */
|
|
);
|
|
|
|
cm->Lmax[0] -= 0.1; /* Make sure tips intersect */
|
|
cm->Lmin[0] += 0.1;
|
|
|
|
for (i = 0; i < res; i++) {
|
|
if (cm->L[i] > cm->Lmax[0])
|
|
cm->L[i] = cm->Lmax[0];
|
|
if (cm->L[i] < cm->Lmin[0])
|
|
cm->L[i] = cm->Lmin[0];
|
|
}
|
|
|
|
/* Fill in any gaps */
|
|
cuspmap_complete(cm);
|
|
|
|
return cm;
|
|
}
|
|
|
|
/* ----------------------------------------------- */
|
|
#ifdef DEBUG
|
|
#undef DEBUG /* Limit extent to this file */
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
|