From 22f703cab05b7cd368f4de9e03991b7664dc5022 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Frings-F=C3=BCrst?= Date: Mon, 1 Sep 2014 13:56:46 +0200 Subject: Initial import of argyll version 1.5.1-8 --- link/collink.c | 3298 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 3298 insertions(+) create mode 100644 link/collink.c (limited to 'link/collink.c') diff --git a/link/collink.c b/link/collink.c new file mode 100644 index 0000000..6c40efd --- /dev/null +++ b/link/collink.c @@ -0,0 +1,3298 @@ + +/* + * collink + * + * Link two device profiles to create a Device Link Profile + * + * Author: Graeme W. Gill + * Date: 25/11/00 + * Version: 2.10 + * + * Copyright 2000 - 2005 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. + */ + +/* TTBD: + * + * Device curve resolution should be taken from the device profiles, + * rather than depending on the quality settings (scRGB compression curves) + * + * Abstract link support intent doesn't work properly for anything + * other than absolute. This should really be fixed. + */ + +/* NOTES: + + Normally the device side per channel curves are copied from + the source profiles to the link profile on the assumption that + the raw linearisation they do is good, and should be maintained + for best overall transform accuracy. Since the intermediate + link is done in an Lab like space, then this linearisation + will even out quantisation error introduced by the Lut + in perceptual space. In the case of a Matrix profile with a native + XYZ PCS, these assumptions break, since the device curves + would generally be linearising the device to Y, not a perceptual + space. For this reason we add a Y to L* type curve to the input + linearisation curves. For XYZ CLUT based profiles, we just have + to hope that the profile has been created well, and that the + input curves distribute the indexes reasonably perceptually + evenly. For an output (XYZ) Matrix profile, we also + use an L* style mixing space. + + In general the per channel curves should be: + + No curve, or + + An optimized curve, or + + The source profile per channel curve plus + a Y to L* curve it's a Matrix profile. + + + Colorspace representations are a bit of a mess. It's hard to know what space + color is in at any point, and difficult to transform to match + some other element. Putting the different colorspace support within + the profile transforms is neat, but may not be flexible enough, since the + needed information to do a transform (white point, viewing conditions) + might be a bit too closely bound into the profile. An alternative might be + to expand the colorspace definition from a tag to include all the + other needed information, and create a general colorspace adapter + to transform from one to the other, or to limit connections + to a cannonical spaces such as XYZ. + One big cause of confusion for user and implimentor is how to + handle the conflicting intents. What does it mean to link + a relative source to absolute abstract to CAM destination ??? + + A non-absolute abstract profile is poorly defined (just like + the device profiles) if it doesn't define the viewing conditions + within which the transform is defined. + + Having separated creating a gamut surface for a raster out + (for better modularity), it then creates problems in applying + an abstact transform. The abstract transform can't really be + applied to a gamut surface, it really needs to be applied + to the image data before gamut surface extraction. This is yet + another avenue for user & implementor confusion, with regard + to intents, even without allowing the user to set the intent + of the abstract profile. + + */ + +#undef USE_MERGE_CLUT_OPT /* [Undef] When using inverse A2B table, merge the output luts */ + /* with the clut for faster operation, and clipping in */ + /* Jab space. Turned off because it affects the accuracy too much, */ + /* and xicc handles Jab clip without this now. */ + +#define USE_CAM_CLIP_OPT /* [Define] Clip out of gamut in CAM space rather than XYZ or L*a*b* */ +#define ENKHACK /* [Define] Enable K hack code */ +#undef WARN_CLUT_CLIPPING /* [Undef] Print warning if setting clut clips */ + +#undef DEBUG /* Report values of each sample transformed */ +#undef DEBUGC /* ie "if (tt)" */ /* Debug condition */ +#undef DEBUG_ONE /* test a single value out. Look for DBGNO to set value. */ +#undef NEUTKDEBUG /* print info about neutral L -> K mapping */ + + +#include +#include +#include +#include +#include +#include +#include "copyright.h" +#include "aconfig.h" +#include "numlib.h" +#include "icc.h" +#include "xicc.h" +#include "gamut.h" +#include "gammap.h" +// ~~~99 +#include "vrml.h" + +void usage(char *diag, ...) { + int i; + fprintf(stderr,"Link ICC profiles, Version %s\n",ARGYLL_VERSION_STR); + fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n"); + if (diag != NULL) { + va_list args; + fprintf(stderr," Diagnostic: "); + va_start(args, diag); + vfprintf(stderr, diag, args); + va_end(args); + fprintf(stderr,"\n"); + } + fprintf(stderr,"usage: collink [options] srcprofile dstprofile linkedprofile\n"); + fprintf(stderr," -v Verbose\n"); + fprintf(stderr," -A manufacturer Manufacturer description string\n"); + fprintf(stderr," -M model Model description string\n"); + fprintf(stderr," -D description Profile Description string (Default \"inoutfile\")\n"); + fprintf(stderr," -C copyright Copyright string\n"); + fprintf(stderr," -V Verify existing profile, rather than link\n"); + fprintf(stderr," -q lmhu Quality - Low, Medium (def), High, Ultra\n"); +// fprintf(stderr," -q fmsu Speed - Fast, Medium (def), Slow, Ultra Slow\n"); + fprintf(stderr," -r res Override clut res. set by -q\n"); + fprintf(stderr," -n Don't preserve device linearization curves in result\n"); + fprintf(stderr," -f Special :- Force neutral colors to be K only output\n"); + fprintf(stderr," -fk Special :- Force K only neutral colors to be K only output\n"); + fprintf(stderr," -F Special :- Force all colors to be K only output\n"); + fprintf(stderr," -fcmy Special :- Force 100%% C,M or Y only to stay pure \n"); + fprintf(stderr," -p absprof Include abstract profile in link\n"); + fprintf(stderr," -s Simple Mode (default)\n"); + fprintf(stderr," -g [src.gam] Gamut Mapping Mode [optional source image gamut]\n"); + fprintf(stderr," -G [src.gam] Gamut Mapping Mode using inverse outprofile A2B\n"); + fprintf(stderr," Simple Mode Options:\n"); + fprintf(stderr," -i in_intent p = perceptual, r = relative colorimetric,\n"); + fprintf(stderr," s = saturation, a = absolute colorimetric\n"); + fprintf(stderr," -o out_intent p = perceptual, r = relative colorimetric,\n"); + fprintf(stderr," s = saturation, a = absolute colorimetric\n"); + fprintf(stderr," Gamut Mapping Mode Options:\n"); + fprintf(stderr," -i intent set linking intent from the following choice:\n"); + for (i = 0; ; i++) { + icxGMappingIntent gmi; + if (xicc_enum_gmapintent(&gmi, i, NULL) == icxIllegalGMIntent) + break; + + fprintf(stderr," %s\n",gmi.desc); + } + fprintf(stderr," -w [J,a,b] Use forced whitepoint hack [optional target point]\n"); +// fprintf(stderr," -W J,a,b Forced whitepoint adjustment by delta Jab\n"); + fprintf(stderr," -c viewcond set source viewing conditions for %s,\n",icxcam_description(cam_default)); + fprintf(stderr," either an enumerated choice, or a parameter\n"); + fprintf(stderr," -d viewcond set destination viewing conditions for %s,\n",icxcam_description(cam_default)); + fprintf(stderr," either an enumerated choice, or parameter:value changes\n"); + for (i = 0; ; i++) { + icxViewCond vc; + if (xicc_enum_viewcond(NULL, &vc, i, NULL, 1, NULL) == -999) + break; + + fprintf(stderr," %s\n",vc.desc); + } + fprintf(stderr," s:surround n = auto, a = average, m = dim, d = dark,\n"); + fprintf(stderr," c = transparency (default average)\n"); + fprintf(stderr," w:X:Y:Z Adapted white point as XYZ (default media white)\n"); + fprintf(stderr," w:x:y Adapted white point as x, y\n"); + fprintf(stderr," a:adaptation Adaptation luminance in cd.m^2 (default 50.0)\n"); + fprintf(stderr," b:background Background %% of image luminance (default 20)\n"); + fprintf(stderr," l:scenewhite Scene white in cd.m^2 if surround = auto (default 250)\n"); + fprintf(stderr," f:flare Flare light %% of image luminance (default 1)\n"); + fprintf(stderr," f:X:Y:Z Flare color as XYZ (default media white)\n"); + fprintf(stderr," f:x:y Flare color as x, y\n"); + fprintf(stderr," -t tlimit set source total ink limit, 0 - 400%% (estimate by default)\n"); + fprintf(stderr," -T klimit set source black ink limit, 0 - 100%% (estimate by default)\n"); + fprintf(stderr," Inverse outprofile A2B Options:\n"); + fprintf(stderr," -k tezhxr CMYK Black generation\n"); + fprintf(stderr," t = transfer K from source to destination, e = retain K of destination B2A table\n"); + fprintf(stderr," z = zero K, h = 0.5 K, x = maximum K, r = ramp K (default)\n"); + fprintf(stderr," -k p stle stpo enpo enle shape\n"); + fprintf(stderr," p = black target generation curve parameters\n"); + fprintf(stderr," -k q stle0 stpo0 enpo0 enle0 shape0 stle2 stpo2 enpo2 enle2 shape2\n"); + fprintf(stderr," q = transfer source K to dual curve limits\n"); + fprintf(stderr," -K parameters Same as -k, but target is K locus rather than K value itself\n"); + fprintf(stderr," -l tlimit set destination total ink limit, 0 - 400%% (estimate by default)\n"); + fprintf(stderr," -L klimit set destination black ink limit, 0 - 100%% (estimate by default)\n"); + fprintf(stderr," -P Create gamut gammap.wrl diagostic\n"); + exit(1); +} + +/* ------------------------------------------- */ +/* structures to support icc calbacks */ + +/* Information needed from a profile */ +struct _profinfo { + /* Setup parameters */ + icRenderingIntent intent; /* Selected ICC rendering intent */ + icxViewCond vc; /* Viewing Condition for CAM */ + int inking; /* k inking algorithm, 0 = input, 1 = min, */ + /* 2 = 0.5, 3 = max, 4 = ramp, 5 = curve, 6 = dual curve */ + /* 7 = outpupt profile K value */ + int locus; /* 0 = K target value, 1 = K locus value */ + icxInk ink; /* Ink parameters */ + + /* Operational parameters */ + icc *c; + icmHeader *h; + xicc *x; + icxLuBase *luo; /* Base XLookup type object */ + icmLuAlgType alg; /* Type of lookup algorithm */ + icColorSpaceSignature csp; /* Colorspace */ + int chan; /* Channels */ + int nocurve; /* NZ to not use device curve in per channel curve */ + int lcurve; /* 1 to apply a Y like to L* curve for XYZ Matrix profiles */ + /* 2 to apply a Y to L* curve for XYZ space */ + double wp[3]; /* Lab/Jab white point for profile used by wphack & xyzscale */ + icxLuBase *b2aluo; /* B2A lookup for inking == 7 */ +}; typedef struct _profinfo profinfo; + +/* Structure that holds all the color lookup information */ +struct _clink { + /* Overall options */ + int verb; + int gamdiag; /* nz, create gammap.wrl diagnostic */ + int total, count, last; /* Progress count information */ + int mode; /* 0 = simple mode, 1 = mapping mode, 2 = mapping mode with inverse A2B */ + int quality; /* 0 = low, 1 = medium, 2 = high, 3 = ultra */ + int clutres; /* 0 = quality default, !0 = override, then actual during link */ + int src_kbp; /* nz = Use K only black point as src gamut black point */ + int dst_kbp; /* nz = Use K only black point as dst gamut black point */ + int dst_cmymap; /* masks C = 1, M = 2, Y = 4 to force 100% cusp map */ + + icColorSpaceSignature pcsor; /* PCS to use between in & out profiles */ + + int nhack; /* 0 = off, 1 = hack to map input neutrals to output K only, */ + /* 2 = map 000K to output K only, 3 = all to K */ + int cmyhack; /* CMY 100% colorant map though hack, 1 = C, 2 = M, 4 = Y */ + rspl *pcs2k; /* PCS L to K' lookup for nhack */ + int wphack; /* 0 = off, 1 = hack to map input wp to output wp, 2 = to hwp[] */ + double hwp[3]; /* hack destination white point in PCS space */ + int wphacked; /* Operation flag, set nz if white point was translated */ + int rel_oride; /* Relative override flag */ + + icmFile *abs_fp; /* Abstract profile transform */ + icRenderingIntent abs_intent; + icc *abs_icc; + xicc *abs_xicc; + icxLuBase *abs_luo; /* NULL if none */ + + /* (We current assume that xyzscale can't be used with gmi) */ + double xyzscale; /* < 1.0 if Y is to be scaled in destination XYZ space */ + double swxyz[3]; /* Source white point in XYZ */ + + icxGMappingIntent gmi; + gammap *map; /* Gamut mapping */ + gammap *Kmap; /* Gamut mapping K in to K out nhack == 2 and K in to K out */ + + + /* Per profile setup information */ + profinfo in; + profinfo out; + +}; typedef struct _clink clink; + + +/* ------------------------------------------- */ +//#define YSCALE 1.0 +#define YSCALE (2.0/1.3) + +/* Extra non-linearity applied to BtoA XYZ PCS */ +/* This distributes the LUT indexes more evenly in */ +/* perceptual space, greatly improving the B2A accuracy of XYZ LUT */ +/* Since typically XYZ doesn't use the full range of 0-2.0 allowed */ +/* for in the encoding, we scale the cLUT index values to use the 0-1.3 range */ + +/* (For these functions the encoded XYZ 0.0 - 2.0 range is 0.0 - 1.0 ??) */ + +/* Y to L* */ +static void y2l_curve(double *out, double *in, int isXYZ) { + int i; + double val; + double isc = 1.0, osc = 1.0; + + /* Scale from 0.0 .. 1.999969 to 0.0 .. 1.0 and back */ + /* + range adjustment */ + if (isXYZ) { + isc = 32768.0/65535.0 * YSCALE; + osc = 65535.0/32768.0; + } + + for (i = 0; i < 3; i++) { + val = in[i] * isc; + if (val > 0.008856451586) + val = 1.16 * pow(val,1.0/3.0) - 0.16; + else + val = 9.032962896 * val; + if (val > 1.0) + val = 1.0; + out[i] = val * osc; + } +} + +/* L* to Y */ +static void l2y_curve(double *out, double *in, int isXYZ) { + int i; + double val; + double isc = 1.0, osc = 1.0; + + /* Scale from 0.0 .. 1.999969 to 0.0 .. 1.0 and back */ + /* + range adjustment */ + if (isXYZ) { + isc = 32768.0/65535.0; + osc = 65535.0/32768.0 / YSCALE; + } + + /* Use an L* like curve, scaled to the maximum XYZ value */ + for (i = 0; i < 3; i++) { + val = in[i] * isc; + if (val > 0.08) + val = pow((val + 0.16)/1.16, 3.0); + else + val = val/9.032962896; + out[i] = val * osc; + } +} + +/* ------------------------------------------- */ +/* Functions called back in setting up the transform table */ + +#ifdef DEBUGC + +static int tt = 0; + +#endif /* DEBUGC */ + +/* Input table, DevIn -> DevIn' */ +void devi_devip(void *cntx, double *out, double *in) { + int rv = 0; + clink *p = (clink *)cntx; + +#ifdef DEBUGC + if (in[0] == 1.0 && in[1] == 1.0 && in[2] == 1.0 && in[3]) + tt = 1; +#endif + +#ifdef DEBUG +#ifdef DEBUGC + DEBUGC +#endif + printf("DevIn->DevIn' got %f %f %f %f\n",in[0], in[1], in[2], in[3]); fflush(stdout); +#endif + + if (p->in.nocurve) { /* Don't use profile per channel curves */ + int i; + for (i = 0; i < p->in.chan; i++) + out[i] = in[i]; + } else { /* Use input profile per channel curves */ + switch(p->in.alg) { + case icmMonoFwdType: { + icxLuMono *lu = (icxLuMono *)p->in.luo; /* Safe to coerce */ + rv |= lu->fwd_curve(lu, out, in); + break; + } + case icmMatrixFwdType: { + icxLuMatrix *lu = (icxLuMatrix *)p->in.luo; /* Safe to coerce */ + rv |= lu->fwd_curve(lu, out, in); + break; + } + case icmLutType: { + icxLuLut *lu = (icxLuLut *)p->in.luo; /* Safe to coerce */ + /* Since not PCS, in_abs and matrix cannot be valid, */ + /* so input curve on own is ok to use. */ + rv |= lu->input(lu, out, in); + break; + } + default: + error("Unexpected algorithm type %d in devi_devip()",p->in.alg); + } + if (rv >= 2) + error("icc lookup failed: %d, %s",p->in.c->errc,p->in.c->err); + } + + if (p->in.lcurve) { /* Apply Y to L* */ +//printf("~1 y2l_curve got %f %f %f, isXYZ %d\n",in[0],in[1],in[2],p->in.lcurve == 2); + y2l_curve(out, out, p->in.lcurve == 2); + } + +#ifdef DEBUG +#ifdef DEBUGC + DEBUGC +#endif + printf("DevIn->DevIn' ret %f %f %f %f\n",out[0], out[1], out[2], in[3]); fflush(stdout); +#endif +} + + +/* - - - - - - - - - - - - */ +/* clut, DevIn' -> DevOut' */ +void devip_devop(void *cntx, double *out, double *in) { + double win[MAX_CHAN]; /* working input values */ + double pcsv[MAX_CHAN]; /* PCS intermediate value, pre-gamut map */ + double pcsvm[MAX_CHAN]; /* PCS intermediate value, post-gamut map */ + double locus[MAX_CHAN]; /* Auxiliary locus values */ + int wptrig = 0; /* White point hack triggered */ + double konlyness = 0.0; /* Degree of K onlyness */ + int ntrig = 0; /* K only output hack triggered */ + int cmytrig = 0; /* CMY output hack triggered */ + int i, rv = 0; + clink *p = (clink *)cntx; + +#ifdef DEBUG +#ifdef DEBUGC + DEBUGC +#endif + printf("DevIn'->DevOut' got %f %f %f %f\n",in[0], in[1], in[2], in[3]); fflush(stdout); +#endif + +#ifdef ENKHACK + /* Handle neutral recognition/output K only hack */ + /* (see discussion at top of file for generalization of this idea) */ + if (p->nhack == 1 || p->nhack == 2) { + double thr = (0.5)/(p->clutres-1.0); /* Match threshold */ + + if (p->nhack == 1) { + /* We want to see if the input colors are equal (Or a=b= 0.0 ??) */ + /* li.nhack should have set p->in.nocurve, so we should be getting raw */ + /* input space device values here. It also made sure that there are at */ + /* least 3 input channels. */ + + if (fabs(in[0] - in[1]) < thr + && fabs(in[1] - in[2]) < thr + && fabs(in[2] - in[0]) < thr) + ntrig = 1; /* K only output triggered flag */ + + } else if (p->nhack == 2) { + double maxcmy; /* Comute a degree of source "K onlyness" */ + double maxcmyk; + + maxcmy = in[0]; /* Compute minimum of CMY */ + if (in[1] > maxcmy) + maxcmy = in[1]; + if (in[2] > maxcmy) + maxcmy = in[2]; + + maxcmyk = maxcmy; /* Compute minimum of all inks */ + if (in[3] > maxcmyk) + maxcmyk = in[3]; + +//printf("~1 maxcmy = %f, maxcmyk = %f, in[3] = %f\n",maxcmy,maxcmyk,in[3]); + if (in[3] <= 0.0 || maxcmy > in[3]) { + konlyness = 0.0; + } else { + konlyness = (in[3] - maxcmy)/in[3]; + } + + /* As we approach no colorant, blend towards no Konlyness */ + if (maxcmyk < 0.2) + konlyness *= maxcmyk/0.2; + + /* We want to see if the input colors are exactly K only. */ + if (in[0] < thr + && in[1] < thr + && in[2] < thr) + ntrig = 1; /* K only output triggered flag */ +#ifdef DEBUG +#ifdef DEBUGC + DEBUGC +#endif + printf("konlyness set to %f\n",konlyness); +#endif + + } + } + /* Handle 100% CMY hack */ + if (p->cmyhack != 0) { + double thr = (0.5)/(p->clutres-1.0); /* Match threshold */ + + if (p->cmyhack & 1) { + if (in[0] > (1.0 - thr) + && in[1] < thr + && in[2] < thr + && (p->in.chan < 4 || in[3] < thr)) + cmytrig |= 1; + } + if (p->cmyhack & 2) { + if (in[0] < thr + && in[1] > (1.0 - thr) + && in[2] < thr + && (p->in.chan < 4 || in[3] < thr)) + cmytrig |= 2; + } + if (p->cmyhack & 4) { + if (in[0] < thr + && in[1] < thr + && in[2] > (1.0 - thr) + && (p->in.chan < 4 || in[3] < thr)) + cmytrig |= 4; + } + } +#endif /* ENKHACK */ + + if (p->in.lcurve) { /* Apply L* to Y */ + l2y_curve(win, in, p->in.lcurve == 2); +#ifdef DEBUG +#ifdef DEBUGC + DEBUGC +#endif + printf("win[] set to L* value %f %f %f %f\n",win[0], win[1], win[2], win[3]); fflush(stdout); +#endif + + } else { + for (i = 0; i < p->in.chan; i++) + win[i] = in[i]; +#ifdef DEBUG +#ifdef DEBUGC + DEBUGC +#endif + printf("win[] set to in[] value %f %f %f %f\n",win[0], win[1], win[2], win[3]); fflush(stdout); +#endif + } + + /* Do DevIn' -> PCS */ + switch(p->in.alg) { + case icmMonoFwdType: { + icxLuMono *lu = (icxLuMono *)p->in.luo; /* Safe to coerce */ + + if (p->in.nocurve) { /* No explicit curve, so do implicit here */ + rv |= lu->fwd_curve(lu, pcsv, win); + rv |= lu->fwd_map(lu, pcsv, pcsv); + } else { + rv |= lu->fwd_map(lu, pcsv, win); + } + rv |= lu->fwd_abs(lu, pcsv, pcsv); + break; + } + case icmMatrixFwdType: { + icxLuMatrix *lu = (icxLuMatrix *)p->in.luo; /* Safe to coerce */ + + if (p->in.nocurve) { /* No explicit curve, so do implicit here */ + rv |= lu->fwd_curve(lu, pcsv, win); + rv |= lu->fwd_matrix(lu, pcsv, pcsv); + } else { + rv |= lu->fwd_matrix(lu, pcsv, win); + } + rv |= lu->fwd_abs(lu, pcsv, pcsv); + break; + } + case icmLutType: { + icxLuLut *lu = (icxLuLut *)p->in.luo; /* Safe to coerce */ + if (p->in.nocurve) { /* No explicit curve, so we've got Dev */ + /* Since not PCS, in_abs and matrix cannot be valid, */ + /* so input curve on own is ok to use. */ + rv |= lu->input(lu, pcsv, win); /* Dev -> Dev' */ + rv |= lu->clut(lu, pcsv, pcsv); /* Dev' -> PCS' */ + } else { /* We've got Dev' */ + rv |= lu->clut(lu, pcsv, win); /* Dev' -> PCS' */ + } + /* We've got the input profile PCS' at this point. */ + + /* If we're transfering the K value from the input profile to the */ + /* output, copy it into locus[], which will be given to the inverse */ + /* lookup function, else the inverse lookup will generate a K using */ + /* the curve parameters. */ +//printf("~1 out.inking = %d\n",p->out.inking); + if (p->out.inking == 0 || p->out.inking == 6) { + if (p->out.locus) { + /* Converts PCS' to K locus proportion */ + lu->clut_locus(lu, locus, pcsv, win); /* Compute possible locus values */ +//printf("~1 looked up locus value\n"); + } else { + for (i = 0; i < p->in.chan; i++) /* Target is K input value */ + locus[i] = win[i]; + /* Convert K' to K value ready for aux target */ + if (!p->in.nocurve) { /* we have an input curve, so convert Dev' -> Dev */ + lu->inv_input(lu, locus, locus); + } +//printf("~1 copied win to locus\n"); + } +#ifdef DEBUG +#ifdef DEBUGC + DEBUGC +#endif + printf("Got possible K %s of %f %f %f %f\n",p->out.locus ? "locus" : "value", locus[0],locus[1],locus[2],locus[3]); +#endif + } + rv |= lu->output(lu, pcsv, pcsv); /* PCS' -> */ + rv |= lu->out_abs(lu, pcsv, pcsv); /* PCS */ + break; + } + default: + error("Unexpected algorithm type %d in devip of devip_devop()",p->in.alg); + } + + /* At this point, the PCS is: + * + * If not gamut mapped: + * Lab in the intent selected for the source profile + * If gamut mapped: + * either + * Absolute Lab + * or + * Jab derived from absolute XYZ via the in/out viewing conditions + * + * and locus[] contains any auxiliar target values if the + * auxiliary is not being created by a rule applied to the PCS. + */ + + /* + * The order to do this intermediate processing is hard to figure out, + * as is the interaction between such elements. How should the + * abstract profile be properly handled ? + * what should we do if the wphack is on and Y scaling is on ? + */ + +#ifdef DEBUG +#ifdef DEBUGC + DEBUGC +#endif + printf("PCS before map %f %f %f\n",pcsv[0], pcsv[1], pcsv[2]); fflush(stdout); +#endif + + if (p->wphack) { + int e; + double dd = 0.0; + for (e = 0; e < 3; e++) { /* Does this match the input white point ? */ + double tt; + tt = pcsv[e] - p->in.wp[e]; + dd += tt * tt; + } + dd = sqrt(dd); + + if (dd < 1.0) { /* Triggered withing 1 delta E */ + p->wphacked++; + wptrig = 1; + if (p->wphack == 2) { + for (e = 0; e < 3; e++) /* Map input white to given white */ + pcsv[e] = p->hwp[e]; + } else { + for (e = 0; e < 3; e++) /* Map input white to output white */ + pcsv[e] = p->out.wp[e]; + } + +#ifndef DEBUG + if (p->verb) +#endif + { + printf("White point hack mapped %f %f %f to %f %f %f, hit withing %f\n", + p->in.wp[0],p->in.wp[1],p->in.wp[2],pcsv[0], pcsv[1], pcsv[2],dd); + fflush(stdout); + } + } + } + + /* Do luminence scaling if requested */ + if (wptrig == 0 && p->xyzscale < 1.0) { + double xyz[3]; + +//printf("~1 got xyzscale = %f\n",p->xyzscale); +//printf("PCS %f %f %f\n",pcsv[0], pcsv[1], pcsv[2]); + + /* Convert our PCS to XYZ */ + if (p->pcsor == icxSigJabData) { + /* We're being bad in delving inside the xluo, but we'll fix it latter */ + p->out.luo->cam->cam_to_XYZ(p->out.luo->cam, xyz, pcsv); + } else + error("Internal :- not setup to handle Y scaling and non-Jab PCS"); + +//printf("XYZ %f %f %f\n",xyz[0], xyz[1], xyz[2]); + /* Scale it */ + xyz[0] *= p->xyzscale; + xyz[1] *= p->xyzscale; + xyz[2] *= p->xyzscale; + +//printf("scaled XYZ %f %f %f\n",xyz[0], xyz[1], xyz[2]); + /* Convert back to PCS */ + if (p->pcsor == icxSigJabData) { + /* We're being bad in delving inside the xluo, but we'll fix it latter */ + p->out.luo->cam->XYZ_to_cam(p->out.luo->cam, pcsv, xyz); + } else + error("Internal :- not setup to handle Y scaling and non-Jab PCS"); + +//printf("scaled PCS %f %f %f\n",pcsv[0], pcsv[1], pcsv[2]); +#ifdef DEBUG +#ifdef DEBUGC + DEBUGC +#endif + printf("PCS after Y scale %f %f %f\n",pcsv[0], pcsv[1], pcsv[2]); fflush(stdout); +#endif + } + + + /* Do gamut mapping */ + if (wptrig == 0 && p->mode > 0 && p->gmi.usemap) { + /* We've used pcsor to ensure PCS space is appropriate */ + + /* Doing XXXK -> XXXK */ + if (p->nhack == 2) { + /* Ideally we would create a 4D PCSK -> PCSK gamut mapping */ + /* to smoothly and accurately cope with the changing source */ + /* and destination gamuts acording to their degree of "K onlyness". */ + /* In practice we're going to simply interpolated between */ + /* two extremes: unrestricted gamut and K only black gamut. */ + double map0[3], map1[3]; + + /* Compute blend of normal gamut map and Konly to Konly gamut map */ + { + p->map->domap(p->map, map0, pcsv); + p->Kmap->domap(p->Kmap, map1, pcsv); + icmBlend3(pcsvm, map0, map1, konlyness); + } + +#ifdef DEBUG +#ifdef DEBUGC + DEBUGC +#endif + printf("PCS after map0 %f %f %f map1 %f %f %f\n", map0[0], map0[1], map0[2], map1[0], map1[1], map1[2]); +#endif + + /* Normal gamut mapping */ + } else { + { + p->map->domap(p->map, pcsvm, pcsv); + } + } + + +#ifdef DEBUG +#ifdef DEBUGC + DEBUGC +#endif + printf("PCS after map %f %f %f\n",pcsvm[0], pcsvm[1], pcsvm[2]); fflush(stdout); +#endif + } else { + pcsvm[0] = pcsv[0]; + pcsvm[1] = pcsv[1]; + pcsvm[2] = pcsv[2]; + } + + /* Gamut mapped PCS value is now in pcsvm[] */ + + /* Abstract profile transform, PCS -> PCS */ + /* pcsor -> abstract -> pcsor conversion */ + /* We're applying any abstract profile after gamut mapping, */ + /* on the assumption is is primarily being used to "correct" the */ + /* output device. Ideally the gamut mapping should take the change */ + /* the abstract profile has on the output device into account, but */ + /* currently we're not doing this.. */ + if (wptrig == 0 && p->abs_luo != NULL) { + /* Abstract profile is either absolute or relative. */ + /* We need to convert the current PCS into something compatible. */ + /* This is more ugly than it really should be, so we're ignoring it. */ + /* We should really run the source through the abstract profile before */ + /* creating the gamut mapping, to be able to use abstract with gamut */ + /* mapping properly. */ + p->abs_luo->lookup(p->abs_luo, pcsvm, pcsvm); +#ifdef DEBUG +#ifdef DEBUGC + DEBUGC +#endif + printf("PCS after abstract %f %f %f\n",pcsvm[0], pcsvm[1], pcsvm[2]); fflush(stdout); +#endif + } + + /* If we're using the existing B2A inking to determine K, */ + /* lookup the output profiles K value for this PCS */ + if (p->mode >= 2 && p->out.inking == 7) { + double tdevv[MAX_CHAN]; + +//printf("~1 dealing with out.inking = %d\n",p->out.inking); + if (p->out.alg != icmLutType || p->out.c->header->colorSpace != icSigCmykData) + error ("Attempting to use non-CMYK output profile to determine K inking"); + + /* Lookup PCS in B2A of output profile to get target K value */ +//printf("~1 looking up pcs %f %f %f in B2A\n", pcsvm[0], pcsvm[1], pcsvm[2]); + p->out.b2aluo->lookup(p->out.b2aluo, tdevv, pcsvm); +//printf("~1 resulting dev %f %f %f %f\n", tdevv[0], tdevv[1], tdevv[2], tdevv[3]); + + if (p->out.locus) { + double tpcsv[MAX_CHAN]; + icxLuLut *lu = (icxLuLut *)p->out.luo; /* Safe to coerce */ + + /* Convert PCS to PCS' ready for locus lookup */ + lu->in_abs(lu, tpcsv, pcsvm); + lu->matrix(lu, tpcsv, tpcsv); + lu->input(lu, tpcsv, tpcsv); + lu->clut_locus(lu, locus, tpcsv, tdevv); /* Compute locus values */ + } else { + for (i = 0; i < p->out.chan; i++) /* Target is K value */ + locus[i] = tdevv[i]; + } +#ifdef DEBUG +#ifdef DEBUGC + DEBUGC +#endif + printf("Got possible K %s of %f %f %f %f\n",p->out.locus ? "locus" : "value", locus[0],locus[1],locus[2],locus[3]); +#endif + } + + /* Do PCS -> DevOut' */ + if (p->nhack == 3 /* All to K only */ + || ntrig /* Neutral or K only to K only hack has triggered */ + || cmytrig) { /* 100% CMY rough hack has triggered */ + + if (p->nhack == 3 || ntrig) { /* Neutral to K only hack has triggered */ + co pp; + pp.p[0] = pcsvm[0]; /* Input L value */ + p->pcs2k->interp(p->pcs2k, &pp); /* L -> K' */ + if (pp.v[0] < 0.0) /* rspl might have extrapolated */ + pp.v[0] = 0.0; + else if (pp.v[0] > 1.0) + pp.v[0] = 1.0; + out[0] = out[1] = out[2] = 0.0; /* We know output is CMYK' */ + out[3] = pp.v[0]; + +#ifndef DEBUG + if (p->verb) +#endif + if (ntrig) { + printf("Neutral hack mapped %s to 0 0 0 %f\n", icmPdv(p->in.chan,win), out[3]); + fflush(stdout); + } + } else if (cmytrig) { /* 100% CMY rough hack has triggered */ + if (cmytrig & 1) { + out[0] = 1.0; + out[1] = out[2] = out[3] = 0.0; + } + if (cmytrig & 2) { + out[1] = 1.0; + out[0] = out[2] = out[3] = 0.0; + } + if (cmytrig & 4) { + out[2] = 1.0; + out[0] = out[1] = out[3] = 0.0; + } + +#ifndef DEBUG + if (p->verb) +#endif + if (cmytrig != 0) { + if (p->in.chan == 4) + printf("CMY hack mapped %s to %s\n",icmPdv(p->in.chan, win), icmPdv(p->out.chan, out)); + fflush(stdout); + } + } + } else { /* Neutral to K hack has NOT triggered */ + switch(p->out.alg) { + case icmMonoBwdType: { + icxLuMono *lu = (icxLuMono *)p->out.luo; /* Safe to coerce */ + + rv |= lu->bwd_abs(lu, pcsvm, pcsvm); + rv |= lu->bwd_map(lu, out, pcsvm); + if (p->out.nocurve) { /* No explicit curve, so do implicit here */ + rv |= lu->bwd_curve(lu, out, out); + } + break; + } + case icmMatrixBwdType: { + icxLuMatrix *lu = (icxLuMatrix *)p->out.luo; /* Safe to coerce */ + + rv |= lu->bwd_abs(lu, pcsvm, pcsvm); + rv |= lu->bwd_matrix(lu, out, pcsvm); + if (p->out.nocurve) { /* No explicit curve, so do implicit here */ + rv |= lu->bwd_curve(lu, out, out); + } + break; + } + case icmLutType: { + icxLuLut *lu = (icxLuLut *)p->out.luo; /* Safe to coerce */ + + if (p->mode < 2) { /* Using B2A table */ + rv |= lu->in_abs(lu, pcsvm, pcsvm); + rv |= lu->matrix(lu, pcsvm, pcsvm); + rv |= lu->input(lu, pcsvm, pcsvm); + rv |= lu->clut(lu, out, pcsvm); + if (p->out.nocurve) { /* No explicit curve, so do implicit here */ + rv |= lu->output(lu, out, out); + } + + } else { /* Use inverse A2B table */ + int i; +#ifdef USE_MERGE_CLUT_OPT + /* Because we have used the ICX_MERGE_CLUT flag, we don't need */ + /* to call inv_out_abs() and inv_output() */ +#else + rv |= lu->inv_out_abs(lu, pcsvm, pcsvm); + rv |= lu->inv_output(lu, pcsvm, pcsvm); +#endif + +#ifdef DEBUG +#ifdef DEBUGC + DEBUGC +#endif + printf("Calling inv_clut with K aux targets %f %f %f %f and pcsvm %f %f %f %f\n", + locus[0],locus[1],locus[2],locus[3],pcsvm[0],pcsvm[1],pcsvm[2],pcsvm[3]); +#endif + + /* locus[] contains possible K target or locus value, */ + /* so copy it to out[] so that inv_clut will use it. */ + for (i = 0; i < p->out.chan; i++) + out[i] = locus[i]; + + rv |= lu->inv_clut(lu, out, pcsvm); +#ifdef DEBUG +#ifdef DEBUGC + DEBUGC +#endif + printf("Got result %f %f %f %f\n", out[0],out[1],out[2],out[3]); +#endif + + + if (p->out.nocurve) { /* No explicit curve, so do implicit here */ + rv |= lu->inv_input(lu, out, out); + } + } + break; + } + + default: + error("Unexpected algorithm type %d in devop of devip_devop()",p->out.alg); + } + if (rv >= 2) + error("icc lookup failed: %d, %s",p->in.c->errc,p->in.c->err); + } + + if (p->out.lcurve) /* Apply Y to L* */ + y2l_curve(out, out, p->out.lcurve == 2); + +#ifdef DEBUG +#ifdef DEBUGC + DEBUGC +#endif + printf("DevIn'->DevOut' ret %f %f %f %f\n\n",out[0], out[1], out[2], out[3]); fflush(stdout); +#endif + + + if (p->verb) { /* Output percent intervals */ + int pc; + p->count++; + pc = (int)(p->count * 100.0/p->total + 0.5); + if (pc != p->last) { + printf("%c%2d%%",cr_char,pc); fflush(stdout); + p->last = pc; + } + } +} + +/* - - - - - - - - - - - - - - - - */ +/* Output table, DevOut' -> DevOut */ +void devop_devo(void *cntx, double *out, double *in) { + int rv = 0; + clink *p = (clink *)cntx; + int i; + +#ifdef DEBUG +#ifdef DEBUGC + DEBUGC +#endif + printf("DevOut'->DevOut got %f %f %f %f\n",in[0], in[1], in[2], in[4]); fflush(stdout); +#endif + + for (i = 0; i < p->out.chan; i++) + out[i] = in[i]; + + if (p->out.lcurve) /* Apply L* to Y */ + l2y_curve(out, out, p->out.lcurve == 2); + + if (p->out.nocurve == 0) { /* Using per channel curves */ + + switch(p->out.alg) { + case icmMonoBwdType: { + icxLuMono *lu = (icxLuMono *)p->out.luo; /* Safe to coerce */ + rv |= lu->bwd_curve(lu, out, out); + break; + } + case icmMatrixBwdType: { + icxLuMatrix *lu = (icxLuMatrix *)p->out.luo; /* Safe to coerce */ + rv |= lu->bwd_curve(lu, out, out); + break; + } + case icmLutType: { + if (p->mode < 2) { /* Using B2A table */ + icxLuLut *lu = (icxLuLut *)p->out.luo; /* Safe to coerce */ + rv |= lu->output(lu, out, out); + /* Since not PCS, out_abs is never used */ + break; + } else { /* Use inverse A2B table */ + icxLuLut *lu = (icxLuLut *)p->out.luo; /* Safe to coerce */ + rv |= lu->inv_input(lu, out, out); + /* Since not PCS, inv_matrix and inv_in_abs is never used */ + break; + } + } + default: + error("Unexpected algorithm type in devop_devo()"); + } + if (rv >= 2) + error("icc lookup failed: %d, %s",p->in.c->errc,p->in.c->err); + } +#ifdef DEBUG +#ifdef DEBUGC + DEBUGC +#endif + printf("DevOut'->DevOut ret %f %f %f %f\n",out[0], out[1], out[2], out[3]); fflush(stdout); +#endif +#ifdef DEBUGC + tt = 0; +#endif +} + +/* ------------------------------------------- */ +/* Fixup L -> K only lookup table white and black values, */ +/* to compensate for inexact rspl fitting */ + +/* Context for fixup */ +typedef struct { + double kmax; + double kmin; +} pcs2k_ctx; + +/* Function to pass to rspl to re-set output values, */ +/* to make them relative to the white and black points */ +static void +fix_pcs2k_white( + void *pp, /* relativectx structure */ + double *out, /* output value */ + double *in /* input value */ +) { + double f; + pcs2k_ctx *p = (pcs2k_ctx *)pp; + + /* Scale so that kmin->hmax becomes 0 to 1 */ + f = (out[0] - p->kmin)/(p->kmax - p->kmin); + + out[0] = f; +} + +/* ------------------------------------------- */ +/* powell() callback to set XYZ scaling factor */ + +static double xyzoptfunc(void *cntx, double *v) { + clink *p = (clink *)cntx; + double swxyz[3], jab[3], dev[MAX_CHAN]; + double rv; + int rc = 0; + + rv = 2.0 - v[0]; /* Make Y as large as possible */ + + /* If we wanted to use this function to maximise the brightness */ + /* we would not limit the scale to 1.0 */ + if (v[0] > 1.0) { + rv += 1000.0; + return rv; + } + if (v[0] < 0.0) { + rv += 100.0; + return rv; + } + swxyz[0] = v[0] * p->swxyz[0]; + swxyz[1] = v[0] * p->swxyz[1]; + swxyz[2] = v[0] * p->swxyz[2]; + +//printf("~1 scaled white XYZ = %f %f %f\n", swxyz[0], swxyz[1], swxyz[2]); + + if (p->pcsor == icxSigJabData) { + /* We're being bad in delving inside the xluo, but we'll fix it latter */ + p->out.luo->cam->XYZ_to_cam(p->out.luo->cam, jab, swxyz); + } else + error("Internal :- not setup to handle Y scaling and non-Jab PCS"); + +//printf("~1 scaled white Jab = %f %f %f\n", jab[0], jab[1], jab[2]); + + /* Run the target PCS backwards through the output space to see if it clips */ + switch(p->out.alg) { + case icmMonoBwdType: { + icxLuMono *lu = (icxLuMono *)p->out.luo; /* Safe to coerce */ + + rc = lu->bwd_lookup(p->out.luo, dev, jab); + break; + } + case icmMatrixBwdType: { + icxLuMatrix *lu = (icxLuMatrix *)p->out.luo; /* Safe to coerce */ + + rc = lu->bwd_lookup(p->out.luo, dev, jab); + break; + } + case icmLutType: { + icxLuLut *lu = (icxLuLut *)p->out.luo; /* Safe to coerce */ + + if (p->mode < 2) /* Using B2A table */ + rc = lu->lookup(p->out.luo, dev, jab); + else /* Use inverse A2B table */ + rc = lu->inv_lookup(p->out.luo, dev, jab); + break; + } + default: + error("Unexpected algorithm type %d in devop of devip_devop()",p->out.alg); + } +//printf("~1 device = %f %f %f, rc = %d\n", dev[0], dev[1], dev[2], rc); + if (rc != 0) + rv += 500.0; + +//printf("~1 xyzoptfunc rv %f from xyzscale %f\n\n",rv,v[0]); + return rv; +} + +/* ------------------------------------------- */ + +int +main(int argc, char *argv[]) { + int fa, nfa, mfa; /* argument we're looking at */ + char in_name[MAXNAMEL+1]; + char sgam_name[MAXNAMEL+1] = "\000"; /* Source gamut name */ + char abs_name[MAXNAMEL+1] = "\000"; /* Abstract profile name */ + char out_name[MAXNAMEL+1]; + char link_name[MAXNAMEL+1]; + int verify = 0; /* Do verify pass */ + int outinkset = 0; /* The user specfied an output inking */ + int intentset = 0; /* The user specified an intent */ + int vcset = 0; /* Viewing conditions were set by user */ + int modeset = 0; /* The gamut mapping mode was set by the user */ + int rv = 0; + icxViewCond ivc, ovc; /* Viewing Condition Overrides for in and out profiles */ + int ivc_e = -1, ovc_e = -1; /* Enumerated viewing condition */ + clink li; /* Linking information structure */ + int isJab = 0; /* (Derived from li.mode & li.gmi) NZ if Jab link space */ + int in_curve_res = 0; /* Input profile A2B input curve resolution (if known) */ + int out_curve_res = 0; /* Output profile B2A output curve resolution (if known) */ + profxinf xpi; /* Extra profile information */ + int i; + + error_program = argv[0]; + check_if_not_interactive(); + memset((void *)&xpi, 0, sizeof(profxinf)); /* Init extra profile info to defaults */ + memset((void *)&li, 0, sizeof(clink)); + + /* Set defaults */ + li.verb = 0; + li.count = 0; + li.last = -1; + li.mode = 0; /* Default simple link mode */ + li.quality = 1; /* Medium quality */ + li.clutres = 0; /* No resolution override */ + li.nhack = 0; + li.cmyhack = 0; /* Mask for 100% purity through mapping of CMY */ + li.pcs2k = NULL; + li.wphack = 0; + li.wphacked = 0; + li.abs_luo = NULL; /* No abstract */ + li.xyzscale = 1.0; /* No XYZ scaling */ + li.hwp[0] = li.hwp[1] = li.hwp[2] = 0.0; + li.map = NULL; + li.Kmap = NULL; + li.in.intent = icmDefaultIntent; /* Default */ + li.in.ink.tlimit = -1.0; /* Default no total limit */ + li.in.ink.klimit = -1.0; /* Default no black limit */ + li.in.inking = 4; /* Inking algorithm default = ramp */ + li.in.locus = 0; /* Default K value target */ + li.in.nocurve = 0; /* Preserve device linearisation curve */ + li.in.lcurve = 0; /* Don't apply a Y to L* curve after device curve */ + li.out.intent = icmDefaultIntent; /* Default */ + li.out.ink.tlimit = -1.0; /* Default no total limit */ + li.out.ink.klimit = -1.0; /* Default no black limit */ + li.out.ink.KonlyLmin = 0; /* Use normal black Lmin for locus */ + li.out.ink.c.Ksmth = ICXINKDEFSMTH; /* default curve smoothing */ + li.out.ink.c.Kskew = ICXINKDEFSKEW; /* default curve skew */ + li.out.ink.x.Ksmth = ICXINKDEFSMTH; + li.out.ink.x.Kskew = ICXINKDEFSKEW; + li.out.inking = 4; /* Default ramp K */ + li.out.locus = 0; /* Default K value target */ + li.out.nocurve = 0; /* Preserve device linearisation curve */ + li.out.lcurve = 0; /* Don't apply an L* to Y curve before device curve */ + li.out.b2aluo = NULL; /* B2A lookup for inking == 7 */ + + xicc_enum_gmapintent(&li.gmi, icxDefaultGMIntent, NULL); /* Set default overall intent */ + + /* Init VC overrides so that we know when the've been set */ + ivc.Ev = -1; + ivc.Wxyz[0] = -1.0; ivc.Wxyz[1] = -1.0; ivc.Wxyz[2] = -1.0; + ivc.La = -1.0; + ivc.Yb = -1.0; + ivc.Lv = -1.0; + ivc.Yf = -1.0; + ivc.Fxyz[0] = -1.0; ivc.Fxyz[1] = -1.0; ivc.Fxyz[2] = -1.0; + + ovc.Ev = -1; + ovc.Wxyz[0] = -1.0; ovc.Wxyz[1] = -1.0; ovc.Wxyz[2] = -1.0; + ovc.La = -1.0; + ovc.Yb = -1.0; + ovc.Lv = -1.0; + ovc.Yf = -1.0; + ovc.Fxyz[0] = -1.0; ovc.Fxyz[1] = -1.0; ovc.Fxyz[2] = -1.0; + + if (argc < 4) + usage("Too few arguments, got %d expect at least 3",argc-1); + + /* Process the arguments */ + mfa = 3; /* Minimum final arguments */ + for(fa = 1;fa < argc;fa++) { + nfa = fa; /* skip to nfa if next argument is used */ + + if (argv[fa][0] == '-') { /* Look for any flags */ + char *na = NULL; /* next argument after flag, null if none */ + + if (argv[fa][2] != '\000') + na = &argv[fa][2]; /* next is directly after flag */ + else { + if ((fa+1+mfa) < argc) { + if (argv[fa+1][0] != '-') { + nfa = fa + 1; + na = argv[nfa]; /* next is seperate non-flag argument */ + } + } + } + + if (argv[fa][1] == '?') + usage("Requested usage"); + + /* Verbosity */ + else if (argv[fa][1] == 'v') { + li.verb = 1; + } + + /* Manufacturer description string */ + else if (argv[fa][1] == 'A') { + fa = nfa; + if (na == NULL) usage("Expect argument to manufacturer description flag -A"); + xpi.deviceMfgDesc = na; + } + + /* Model description string */ + else if (argv[fa][1] == 'M') { + fa = nfa; + if (na == NULL) usage("Expect argument to model description flag -M"); + xpi.modelDesc = na; + } + + /* Profile Description */ + else if (argv[fa][1] == 'D') { + fa = nfa; + if (na == NULL) usage("Expect argument to profile description flag -D"); + xpi.profDesc = na; + } + + /* Copyright string */ + else if (argv[fa][1] == 'C') { + fa = nfa; + if (na == NULL) usage("Expect argument to copyright flag -C"); + xpi.copyright = na; + } + + /* Verify rather than link */ + else if (argv[fa][1] == 'V') + verify = 1; + + /* Disable profile per channel curve use in device link output */ + else if (argv[fa][1] == 'n' || argv[fa][1] == 'N') { + li.in.nocurve = 1; + li.out.nocurve = 1; + } + + /* Hack to force input neutrals to K only output */ + else if (argv[fa][1] == 'f' || argv[fa][1] == 'F') { + + if (argv[fa][1] == 'f') { + if (na != NULL) { /* XXXK -> XXXK hack */ + int j; + fa = nfa; + for (j = 0; ; j++) { + if (na[j] == '\000') + break; + if (na[j] == 'k' || na[j] == 'K') + li.nhack = 2; + else if (na[j] == 'c' || na[j] == 'C') + li.cmyhack |= 0x1; + else if (na[j] == 'm' || na[j] == 'M') + li.cmyhack |= 0x2; + else if (na[j] == 'y' || na[j] == 'Y') + li.cmyhack |= 0x4; + else + usage("Unexpected argument '%c' to -f flag",na[j]); + } + + } else { /* Neutral -> 000K hack */ + li.nhack = 1; + li.in.nocurve = 1; /* Disable input curve to preserve input equality */ + } + } else { + li.nhack = 3; /* All -> 000K Hack */ + } + } + + /* Quality */ + else if (argv[fa][1] == 'q' || argv[fa][1] == 'Q') { + fa = nfa; + if (na == NULL) usage("Quality flag (-q) needs an argument"); + switch (na[0]) { + case 'f': /* fast */ + case 'l': + case 'L': + li.quality = 0; + break; + case 'm': /* medium */ + case 'M': + li.quality = 1; + break; + case 's': /* slow */ + case 'h': + case 'H': + li.quality = 2; + break; + case 'u': /* ultra slow */ + case 'U': + li.quality = 3; + break; + default: + usage("Unrecognised quality flag (-q) argument '%c'",na[0]); +// usage("Unrecognised speed flag (-q) argument '%c'",na[0]); + } + } + + /* CLUT resolution override */ + else if (argv[fa][1] == 'r' || argv[fa][1] == 'R') { + int rr; + fa = nfa; + if (na == NULL) usage("Resolution flag (-r) needs an argument"); + rr = atoi(na); + if (rr < 1 || rr > 255) usage("Resolution flag (-r) argument out of range (%d)",rr); + li.clutres = rr; + } + + /* Abstract profile */ + else if (argv[fa][1] == 'p') { + if (na == NULL) usage("Expected abstract profile filename after -a"); + fa = nfa; + strncpy(abs_name,na,MAXNAMEL); abs_name[MAXNAMEL] = '\000'; + } + + /* Simple mode */ + else if (argv[fa][1] == 's') { + li.mode = 0; + modeset = 1; + } + + /* Maping mode */ + else if (argv[fa][1] == 'g' || argv[fa][1] == 'G') { + li.mode = 1; + if (argv[fa][1] == 'G') { + li.mode = 2; + } + + if (na != NULL) { /* Found an optional source gamut */ + fa = nfa; + strncpy(sgam_name,na,MAXNAMEL); sgam_name[MAXNAMEL] = '\000'; + } + modeset = 1; + } + + /* White point hack */ + else if (argv[fa][1] == 'w' || argv[fa][1] == 'W') { + li.wphack = 1; + if (na != NULL) { // To a particular white point + fa = nfa; + if (sscanf(na, " %lf , %lf , %lf ",&li.hwp[0], &li.hwp[1], &li.hwp[2]) == 3) { + li.wphack = 2; + } else + usage("Couldn't parse hack white point (-w) value '%s'",na); + } + } + /* Input profile Intent or Mapping mode intent */ + else if (argv[fa][1] == 'i' || argv[fa][1] == 'I') { + fa = nfa; + if (na == NULL) usage("Input intent flag (-i) needs an argument"); + /* Record it for simple mode */ + switch (na[0]) { + case 'p': + case 'P': + li.in.intent = icPerceptual; + break; + case 'r': + case 'R': + li.in.intent = icRelativeColorimetric; + break; + case 's': + case 'S': + li.in.intent = icSaturation; + break; + case 'a': + case 'A': + li.in.intent = icAbsoluteColorimetric; + break; + default: + li.in.intent = icMaxEnumIntent; /* Detect error later */ + } + /* Record it for gamut mapping mode */ + if (xicc_enum_gmapintent(&li.gmi, icxNoGMIntent, na) == -999) + usage("Input intent (-i) argument '%s' isn't recognised",na); + intentset = 1; + } + + /* Output profile Intent */ + else if (argv[fa][1] == 'o' || argv[fa][1] == 'O') { + fa = nfa; + if (na == NULL) usage("Output intent flag (-o) needs an argument"); + switch (na[0]) { + case 'p': + case 'P': + li.out.intent = icPerceptual; + break; + case 'r': + case 'R': + li.out.intent = icRelativeColorimetric; + break; + case 's': + case 'S': + li.out.intent = icSaturation; + break; + case 'a': + case 'A': + li.out.intent = icAbsoluteColorimetric; + break; + default: + usage("Output intent (-o) argument '%s' not recognised",na); + } + } + + /* Viewing conditions */ + else if (argv[fa][1] == 'c' || argv[fa][1] == 'C' + || argv[fa][1] == 'd' || argv[fa][1] == 'D') { + icxViewCond *vc; + + if (argv[fa][1] == 'c' || argv[fa][1] == 'C') { + vc = &ivc; + } else { + vc = &ovc; + } + + fa = nfa; + if (na == NULL) usage("Viewing conditions flag (-[cd]) needs an argument"); +#ifdef NEVER + if (na[0] >= '0' && na[0] <= '9') { + if (vc == &ivc) + ivc_e = atoi(na); + else + ovc_e = atoi(na); + } else +#endif + if (na[1] != ':') { + /* Enumerated condition index */ + if (vc == &ivc) { + if ((ivc_e = xicc_enum_viewcond(NULL, NULL, -2, na, 1, NULL)) == -999) + usage("Unrecognised viewing condition enumeration '%s'",na); + } else { + if ((ovc_e = xicc_enum_viewcond(NULL, NULL, -2, na, 1, NULL)) == -999) + usage("Unrecognised viewing condition enumeration '%s'",na); + } + } else if (na[0] == 's' || na[0] == 'S') { + if (na[1] != ':') + usage("Viewing conditions (-[cd]s) missing ':'"); + if (na[2] == 'n' || na[2] == 'N') { + vc->Ev = vc_none; /* Automatic */ + } else if (na[2] == 'a' || na[2] == 'A') { + vc->Ev = vc_average; + } else if (na[2] == 'm' || na[2] == 'M') { + vc->Ev = vc_dim; + } else if (na[2] == 'd' || na[2] == 'D') { + vc->Ev = vc_dark; + } else if (na[2] == 'c' || na[2] == 'C') { + vc->Ev = vc_cut_sheet; + } else + usage("Viewing condition (-[cd]) unrecognised surround '%c'",na[2]); + } else if (na[0] == 'w' || na[0] == 'W') { + double x, y, z; + if (sscanf(na+1,":%lf:%lf:%lf",&x,&y,&z) == 3) { + vc->Wxyz[0] = x; vc->Wxyz[1] = y; vc->Wxyz[2] = z; + } else if (sscanf(na+1,":%lf:%lf",&x,&y) == 2) { + vc->Wxyz[0] = x; vc->Wxyz[1] = y; + } else + usage("Viewing condition (-[cd]w) unrecognised white point '%s'",na+1); + } else if (na[0] == 'a' || na[0] == 'A') { + if (na[1] != ':') + usage("Viewing conditions (-[cd]a) missing ':'"); + vc->La = atof(na+2); + } else if (na[0] == 'b' || na[0] == 'B') { + if (na[1] != ':') + usage("Viewing conditions (-[cd]b) missing ':'"); + vc->Yb = atof(na+2)/100.0; + } else if (na[0] == 'l' || na[0] == 'L') { + if (na[1] != ':') + usage("Viewing conditions (-[cd]l) missing ':'"); + vc->Lv = atof(na+2); + } else if (na[0] == 'f' || na[0] == 'F') { + double x, y, z; + if (sscanf(na+1,":%lf:%lf:%lf",&x,&y,&z) == 3) { + vc->Fxyz[0] = x; vc->Fxyz[1] = y; vc->Fxyz[2] = z; + } else if (sscanf(na+1,":%lf:%lf",&x,&y) == 2) { + vc->Fxyz[0] = x; vc->Fxyz[1] = y; + } else if (sscanf(na+1,":%lf",&x) == 1) { + vc->Yf = x/100.0; + } else + usage("Viewing condition (-[cd]f) unrecognised flare '%s'",na+1); + } else + usage("Viewing condition (-[cd]) unrecognised sub flag '%c'",na[0]); + vcset = 1; /* Viewing conditions were set by user */ + } + + /* Inking rule */ + else if (argv[fa][1] == 'k' || argv[fa][1] == 'K') { + fa = nfa; + if (na == NULL) usage("Inking rule flag (-k) needs an argument"); + if (argv[fa][1] == 'k') + li.out.locus = 0; /* Use K value target */ + else + li.out.locus = 1; /* Use K locus target */ + switch (na[0]) { + case 't': + case 'T': + li.out.inking = 0; /* Use input K value for output */ + break; + case 'e': + case 'E': + li.out.inking = 7; /* Use output K value as guide */ + break; + case 'z': + case 'Z': + li.out.inking = 1; /* Use minimum k */ + break; + case 'h': + case 'H': + li.out.inking = 2; /* Use half k */ + break; + case 'x': + case 'X': + li.out.inking = 3; /* Use maximum k */ + break; + case 'r': + case 'R': + li.out.inking = 4; /* Use ramp k */ + break; + case 'p': + case 'P': + case 'q': + case 'Q': + li.out.inking = 5; /* Use curve parameter */ + + ++fa; + if (fa >= argc) usage("Inking rule (-kp) expects more parameters"); + li.out.ink.c.Kstle = atof(argv[fa]); + + ++fa; + if (fa >= argc) usage("Inking rule (-kp) expects more parameters"); + li.out.ink.c.Kstpo = atof(argv[fa]); + + ++fa; + if (fa >= argc || argv[fa][0] == '-') usage("Inking rule (-kp) expects more parameters"); + li.out.ink.c.Kenpo = atof(argv[fa]); + + ++fa; + if (fa >= argc || argv[fa][0] == '-') usage("Inking rule (-kp) expects more parameters"); + li.out.ink.c.Kenle = atof(argv[fa]); + + ++fa; + if (fa >= argc || argv[fa][0] == '-') usage("Inking rule (-kp) expects more parameters"); + li.out.ink.c.Kshap = atof(argv[fa]); + + if (na[0] == 'q' || na[0] == 'Q') { + li.out.inking = 6; /* Use transfer to dual curve parameter */ + + ++fa; + if (fa >= argc) usage("Inking rule (-kq) expects more parameters"); + li.out.ink.x.Kstle = atof(argv[fa]); + + ++fa; + if (fa >= argc) usage("Inking rule (-kq) expects more parameters"); + li.out.ink.x.Kstpo = atof(argv[fa]); + + ++fa; + if (fa >= argc || argv[fa][0] == '-') usage("Inking rule (-kq) expects more parameters"); + li.out.ink.x.Kenpo = atof(argv[fa]); + + ++fa; + if (fa >= argc) usage("Inking rule (-kq) expects more parameters"); + li.out.ink.x.Kenle = atof(argv[fa]); + + ++fa; + if (fa >= argc || argv[fa][0] == '-') usage("Inking rule (-kq) expects more parameters"); + li.out.ink.x.Kshap = atof(argv[fa]); + + } + break; + default: + usage("Inking rule (-k) unknown sub flag '%c'",na[0]); + } + outinkset = 1; /* The user set an inking */ + } + /* Input ink limits */ + else if (argv[fa][1] == 't') { + int tlimit; + fa = nfa; + if (na == NULL) usage("No parameter after flag -t"); + tlimit = atoi(na); + if (tlimit >= 0) + li.in.ink.tlimit = tlimit/100.0; + else + li.in.ink.tlimit = -1.0; + } + else if (argv[fa][1] == 'T') { + int klimit; + fa = nfa; + if (na == NULL) usage("No parameter after flag -T"); + klimit = atoi(na); + if (klimit >= 0) + li.in.ink.klimit = klimit/100.0; + else + li.in.ink.klimit = -1.0; + } + /* Output ink limits */ + else if (argv[fa][1] == 'l') { + int tlimit; + fa = nfa; + if (na == NULL) usage("No parameter after flag -l"); + tlimit = atoi(na); + if (tlimit >= 0) + li.out.ink.tlimit = tlimit/100.0; + else + li.out.ink.tlimit = -1.0; + if (li.mode < 2) /* Set minimum link mode */ + li.mode = 2; + } + else if (argv[fa][1] == 'L') { + int klimit; + fa = nfa; + if (na == NULL) usage("No parameter after flag -L"); + klimit = atoi(na); + if (klimit >= 0) + li.out.ink.klimit = klimit/100.0; + else + li.out.ink.klimit = -1.0; + if (li.mode < 2) /* Set minimum link mode */ + li.mode = 2; + } + + /* Gammut mapping diagnostic plots */ + else if (argv[fa][1] == 'P') + li.gamdiag = 1; + + else + usage("Unknown flag '%c'",argv[fa][1]); + } else + break; + } + + if (fa >= argc || argv[fa][0] == '-') usage("Missing input profile"); + strncpy(in_name,argv[fa++],MAXNAMEL); in_name[MAXNAMEL] = '\000'; + + if (fa >= argc || argv[fa][0] == '-') usage("Missing output profile"); + strncpy(out_name,argv[fa++],MAXNAMEL); out_name[MAXNAMEL] = '\000'; + + if (fa >= argc || argv[fa][0] == '-') usage("Missing result profile"); + strncpy(link_name,argv[fa++],MAXNAMEL); link_name[MAXNAMEL] = '\000'; + + if (xpi.profDesc == NULL) + xpi.profDesc = link_name; /* Default description */ + + if (li.verb) + printf("Got options\n"); + + /* - - - - - - - - - - - - - - - - - - - */ +#ifndef ENKHACK /* Enable K hack code */ + warning("!!!!!! linkl/collink.c ENKHACK not enabled !!!!!!"); +#endif + /* - - - - - - - - - - - - - - - - - - - */ + /* Sanity checking/defaulting of options */ + + /* Deal with options that need link mode -g */ + if (li.mode < 1 + && (li.in.intent == icMaxEnumIntent /* User set a smart linking intent */ + || vcset /* Viewing conditions were set by user */ + || li.wphack)) { + if (modeset) { + if (li.in.intent == icMaxEnumIntent) + warning("Complex intent can't work with -s linking mode"); + else if (vcset) + warning("Viewing conditions are ignored with -s linking mode"); + else if (li.wphack) + warning("White point hack is ignored with -s linking mode"); + } else { + if (li.verb) { + if (li.in.intent == icMaxEnumIntent) + printf("Setting -g to enable Gamut Mapping mode intent\n"); + else if (vcset) + printf("Setting -g to enable viewing conditions\n"); + else if (li.wphack) + printf("Setting -g to enable white point hack\n"); + } + li.mode = 1; + } + } + + /* Deal with options that need link mode -G */ + if (li.mode < 2 + && (outinkset /* The user set a K inking rule */ + || li.out.ink.tlimit >= 0.0 /* The user set an output total limit */ + || li.out.ink.klimit >= 0.0)) { /* The user set an output black limit */ + if (modeset) { + if (outinkset) + warning("Black inking can't work with -s or -g linking mode"); + else if (li.out.ink.tlimit >= 0.0 || li.out.ink.klimit >= 0.0) + warning("Ink limiting can't work with -s linking mode"); + } else { + if (li.verb) { + if (outinkset) + printf("Setting -G to enable black inking\n"); + else if (li.out.ink.tlimit >= 0.0 || li.out.ink.klimit >= 0.0) + printf("Setting -G to enable ink limiting\n"); + } + li.mode = 2; + } + } + + /* Deal with options that complement -f -F */ + if (li.nhack || li.cmyhack) { + + /* Ideally we need to set K inking and map to K only black point, which require -G mode */ + if (li.mode < 2) { + if (li.nhack == 1) { /* All neutrals to K only */ + if (modeset) { + warning("-f will give best result with -G mode"); + } else { + if (li.verb) + printf("Setting -G mode to complement -f option\n"); + li.mode = 2; + } + } else if (li.nhack == 2) { /* K only in to K only out */ + if (modeset) { + warning("For better results use -G mode with -fk option"); + } else { + if (li.verb) + printf("Setting -G mode to complement -fk option\n"); + li.mode = 2; + } + } else if (li.nhack == 3) { /* All to K only out */ + if (modeset) { + warning("For better results use -G mode with -F option"); + } else { + if (li.verb) + printf("Setting -G mode to complement -F option\n"); + li.mode = 2; + } + } + if (li.cmyhack != 0) { /* Map pure 100% CMY to pure CMY */ + if (modeset) { + warning("For better results use -G mode with -fcmy options"); + } else { + if (li.verb) + printf("Setting -G mode to complement -fcmy options\n"); + li.mode = 2; + } + } + } + + /* Ideally we should use an appropriate K inking */ + if (li.mode >= 2) { /* Gammut mapping mode */ + if (li.nhack == 1 && li.out.inking != 3) { /* All neutrals to K only */ + if (outinkset) { + warning("For better results use -kx with -f option"); + } else { + if (li.verb) + printf("Setting -kx to complement -f option\n"); + li.out.inking = 3; /* Use maximum K */ + } + } else if (li.nhack == 2 && li.out.inking != 0) { /* K only in to K only out */ + if (outinkset) { + warning("For better results use -kt with -fk option"); + } else { + if (li.verb) + printf("Setting -kt to complement -fk option\n"); + li.out.inking = 0; /* Use input K value for output */ + } + } else if (li.nhack == 3 && li.out.inking != 3) { /* All colors to K only */ + if (modeset) { + warning("For better results use -kx with -F option"); + } else { + if (li.verb) + printf("Setting -kx to complement -f option\n"); + li.out.inking = 3; /* Use maximum K */ + } + } + } + + /* Ideally we should use an appropriate gamut mapping */ + if (li.mode >= 1) { /* Gammut mapping mode */ + + if (li.gmi.usemap == 0 || li.gmi.greymf < 1.0 /* Not mapping black point */ + || li.gmi.glumbcpf < 1.0 || li.gmi.glumbexf < 1.0) { + if (li.nhack == 1) { /* All neutrals to K only */ + if (intentset) { + warning("For better results use an intent that maps black point with -f option"); + } else { + if (li.verb) + printf("Setting -ip intent to complement -f option\n"); + if (xicc_enum_gmapintent(&li.gmi, icxNoGMIntent, "p") == -999) + usage("Internal, intent 'p' isn't recognised"); + li.dst_kbp = 1; /* Map to K only black point */ + li.out.ink.KonlyLmin = 1; /* Use K only black Lmin for locus */ + } + } else if (li.nhack == 3) { /* All to K only out */ + if (intentset) { + warning("For better results use an intent that maps black point with -F option"); + } else { + if (li.verb) + printf("Setting -ip intent to complement -F option\n"); + if (xicc_enum_gmapintent(&li.gmi, icxNoGMIntent, "p") == -999) + usage("Internal, intent 'p' isn't recognised"); + li.dst_kbp = 1; /* Map to K only black point */ + li.out.ink.KonlyLmin = 1; /* Use K only black Lmin for locus */ + } + } + + /* Got an appropriate intent, so set mapping to K only black point */ + } else if (li.nhack == 1 || li.nhack == 3) { + li.dst_kbp = 1; /* Map to K only black point */ + li.out.ink.KonlyLmin = 1; /* Use K only black Lmin for locus */ + } + if (li.cmyhack != 0) { /* Map pure 100% CMY to pure CMY */ + if (intentset) { + if (strcmp(li.gmi.as, "s") != 0) + warning("For better results use -is with -fcmy options"); + } else { + if (li.verb) + printf("Setting -is intent to complement -fcmy options\n"); + if (xicc_enum_gmapintent(&li.gmi, icxNoGMIntent, "s") == -999) + usage("Internal, intent 's' isn't recognised"); + li.dst_cmymap = li.cmyhack; + } + } + } + } + + if (li.mode == 0) { + if (li.in.intent == icMaxEnumIntent) + usage("Input intent (-i) argument isn't recognised for simple mapping mode"); + } + + if (li.wphack && (li.gmi.usecas & 0x100) != 0) + usage("Can't use 'white point hack' and Luminence scaling intent together"); + + /* - - - - - - - - - - - - - - - - - - - */ + /* Open up the input device profile for reading, and read header etc. */ + if ((li.in.c = read_embedded_icc(in_name)) == NULL) + error ("Can't open file '%s'",in_name); + li.in.h = li.in.c->header; + + /* Check that it is a suitable device input icc */ + if (li.in.h->deviceClass != icSigInputClass + && li.in.h->deviceClass != icSigDisplayClass + && li.in.h->deviceClass != icSigOutputClass + && li.in.h->deviceClass != icSigColorSpaceClass) /* For sRGB etc. */ + error("Input profile '%s' isn't a device profile",in_name); + + /* Wrap with an expanded icc */ + if ((li.in.x = new_xicc(li.in.c)) == NULL) + error ("Creation of input profile xicc failed"); + + /* Set the default ink limits if not set on command line */ + icxDefaultLimits(li.in.x, &li.in.ink.tlimit, li.in.ink.tlimit, &li.in.ink.klimit, li.in.ink.klimit); + + if (li.verb) { + if (li.in.ink.tlimit >= 0.0) + printf("Input total ink limit assumed is %3.0f%%\n",100.0 * li.in.ink.tlimit); + if (li.in.ink.klimit >= 0.0) + printf("Input black ink limit assumed is %3.0f%%\n",100.0 * li.in.ink.klimit); + } + + /* - - - - - - - - - - - - - - - - - - - */ + /* Open up the abstract profile if requested */ + if (abs_name[0] != '\000') { + if ((li.abs_fp = new_icmFileStd_name(abs_name,"r")) == NULL) + error ("Can't open abstract profile file '%s'",abs_name); + + if ((li.abs_icc = new_icc()) == NULL) + error ("Creation of Abstract profile ICC object failed"); + + /* Read header etc. */ + if ((rv = li.abs_icc->read(li.abs_icc,li.abs_fp,0)) != 0) + error ("%d, %s",rv,li.abs_icc->err); + + if (li.abs_icc->header->deviceClass != icSigAbstractClass) + error("Abstract profile isn't an abstract profile"); + + /* Take intended abstract intent from profile itself */ + if ((li.abs_intent = li.abs_icc->header->renderingIntent) != icAbsoluteColorimetric) + li.abs_intent = icRelativeColorimetric; + + /* Wrap with an expanded icc */ + if ((li.abs_xicc = new_xicc(li.abs_icc)) == NULL) + error ("Creation of abstract profile xicc failed"); + } + /* - - - - - - - - - - - - - - - - - - - */ + /* Open up the output device output profile for reading, and read header etc. */ + if ((li.out.c = read_embedded_icc(out_name)) == NULL) + error ("Can't open file '%s'",out_name); + li.out.h = li.out.c->header; + + if (li.out.h->deviceClass != icSigInputClass + && li.out.h->deviceClass != icSigDisplayClass + && li.out.h->deviceClass != icSigOutputClass + && li.out.h->deviceClass != icSigColorSpaceClass) /* For sRGB etc. */ + error("Output profile isn't a device profile"); + + /* Wrap with an expanded icc */ + if ((li.out.x = new_xicc(li.out.c)) == NULL) + error ("Creation of output profile xicc failed"); + + /* Set the default ink limits if not set on command line */ + icxDefaultLimits(li.out.x, &li.out.ink.tlimit, li.out.ink.tlimit, &li.out.ink.klimit, li.out.ink.klimit); + + if (li.verb) { + if (li.out.ink.tlimit >= 0.0) + printf("Output total ink limit assumed is %3.0f%%\n",100.0 * li.out.ink.tlimit); + if (li.out.ink.klimit >= 0.0) + printf("Output black ink limit assumed is %3.0f%%\n",100.0 * li.out.ink.klimit); + } + + /* deal with output black generation. */ + /* Ink limits will have been set in option parsing */ + + switch (li.out.inking) { + case 0: /* Use input profile K level or locus */ + /* Sanity check */ + if (li.in.h->colorSpace != li.out.h->colorSpace) + error("Can't transfer black ink in & out unless the same colorspaces"); + li.out.ink.k_rule = li.out.locus ? icxKlocus : icxKvalue; /* Given as aux parameter in PCS -> Device */ + break; + case 7: /* Use output profile K level or locus */ + li.out.ink.k_rule = li.out.locus ? icxKlocus : icxKvalue; /* Given as aux parameter in PCS -> Device */ + break; + case 1: /* Minimum K */ + li.out.ink.k_rule = li.out.locus ? icxKluma5 : icxKluma5k; + li.out.ink.c.Kstle = 0.0; + li.out.ink.c.Kstpo = 0.0; + li.out.ink.c.Kenpo = 1.0; + li.out.ink.c.Kenle = 0.0; + li.out.ink.c.Kshap = 1.0; + break; + case 2: /* 0.5 K */ + li.out.ink.k_rule = li.out.locus ? icxKluma5 : icxKluma5k; + li.out.ink.c.Kstle = 0.5; + li.out.ink.c.Kstpo = 0.0; + li.out.ink.c.Kenpo = 1.0; + li.out.ink.c.Kenle = 0.5; + li.out.ink.c.Kshap = 1.0; + break; + case 3: /* Maximum K */ + li.out.ink.k_rule = li.out.locus ? icxKluma5 : icxKluma5k; + li.out.ink.c.Kstle = 1.0; + li.out.ink.c.Kstpo = 0.0; + li.out.ink.c.Kenpo = 1.0; + li.out.ink.c.Kenle = 1.0; + li.out.ink.c.Kshap = 1.0; + break; + case 4: /* Ramp K */ + li.out.ink.k_rule = li.out.locus ? icxKluma5 : icxKluma5k; + li.out.ink.c.Kstle = 0.0; + li.out.ink.c.Kstpo = 0.0; + li.out.ink.c.Kenpo = 1.0; + li.out.ink.c.Kenle = 1.0; + li.out.ink.c.Kshap = 1.0; + break; + case 5: /* Curve */ + li.out.ink.k_rule = li.out.locus ? icxKluma5 : icxKluma5k; + break; /* Other params already set by options */ + case 6: /* Use input profile K locus + dual curve limits */ + /* Sanity check */ + if (li.in.h->colorSpace != li.out.h->colorSpace) + error("Can't transfer black ink in & out unless the same colorspaces"); + li.out.ink.k_rule = li.out.locus ? icxKl5l : icxKl5lk; /* Aux param in PCS -> Device */ + break; /* Other params already set by options */ + } + + for (i = 0; i < 2; i++) { + xicc *x; + icxViewCond *v, *vc; + int es; + + if (i == 0) { + v = &ivc; /* Override parameters */ + vc = &li.in.vc; /* Target parameters */ + es = ivc_e; + x = li.in.x; /* xicc */ + } else { + v = &ovc; /* Override parameters */ + vc = &li.out.vc; /* Target parameters */ + es = ovc_e; + x = li.out.x; /* xicc */ + } + + /* Set the default viewing conditions */ + xicc_enum_viewcond(x, vc, -1, NULL, 0, NULL); + + /* Override the default viewing conditions. */ + /* (?? Could move this code into xicc_enum_viewcond() as an option ??) */ + /* First any enumerated selection */ + if (es != -1) { + if (xicc_enum_viewcond(x, vc, es, NULL, 0, NULL) == -999) + error ("%d, %s",x->errc, x->err); + } + /* Then any individual paramaters */ + if (v->Ev >= 0) + vc->Ev = v->Ev; + if (v->Wxyz[0] >= 0.0 && v->Wxyz[1] > 0.0 && v->Wxyz[2] >= 0.0) { + /* Normalise XYZ to current media white */ + vc->Wxyz[0] = v->Wxyz[0]/v->Wxyz[1] * vc->Wxyz[1]; + vc->Wxyz[2] = v->Wxyz[2]/v->Wxyz[1] * vc->Wxyz[1]; + } + if (v->Wxyz[0] >= 0.0 && v->Wxyz[1] >= 0.0 && v->Wxyz[2] < 0.0) { + /* Convert Yxy to XYZ */ + double x = v->Wxyz[0]; + double y = v->Wxyz[1]; /* If Y == 1.0, then X+Y+Z = 1/y */ + double z = 1.0 - x - y; + vc->Wxyz[0] = x/y * vc->Wxyz[1]; + vc->Wxyz[2] = z/y * vc->Wxyz[1]; + } + if (v->La >= 0.0) + vc->La = v->La; + if (v->Yb >= 0.0) + vc->Yb = v->Yb; + if (v->Lv >= 0.0) + vc->Lv = v->Lv; + if (v->Yf >= 0.0) + vc->Yf = v->Yf; + if (v->Fxyz[0] >= 0.0 && v->Fxyz[1] > 0.0 && v->Fxyz[2] >= 0.0) { + /* Normalise XYZ to current media white */ + vc->Fxyz[0] = v->Fxyz[0]/v->Fxyz[1] * vc->Fxyz[1]; + vc->Fxyz[2] = v->Fxyz[2]/v->Fxyz[1] * vc->Fxyz[1]; + } + if (v->Fxyz[0] >= 0.0 && v->Fxyz[1] >= 0.0 && v->Fxyz[2] < 0.0) { + /* Convert Yxy to XYZ */ + double x = v->Fxyz[0]; + double y = v->Fxyz[1]; /* If Y == 1.0, then X+Y+Z = 1/y */ + double z = 1.0 - x - y; + vc->Fxyz[0] = x/y * vc->Fxyz[1]; + vc->Fxyz[2] = z/y * vc->Fxyz[1]; + } + } + + if (li.verb) + printf("Configured options\n"); + + /* - - - - - - - - - - - - - - - - - - - */ + /* Setup the profile color lookup information */ + { + icmLuAlgType oalg; /* Native output algorithm */ + icColorSpaceSignature natpcs; /* Underlying native output PCS */ + int flb = 0, fl = 0; /* luobj flags */ + + li.pcsor = icSigLabData; /* Default use Lab as PCS */ + + /* If we are using the gamut map mode, then setup */ + /* the intents and pcsor appropriately. */ + if (li.mode > 0) { + + if ((li.gmi.usecas & 0xff) != 0) { + li.pcsor = icxSigJabData; /* Use CAM as PCS */ + isJab = 1; + + if ((li.gmi.usecas & 0xff) == 0x2) { /* Absolute Appearance space */ + double mxw; + + li.in.intent = li.out.intent = li.abs_intent = icxAbsAppearance; + + /* Make absolute common white point average between the two */ + li.in.vc.Wxyz[0] = 0.5 * (li.in.vc.Wxyz[0] + li.out.vc.Wxyz[0]); + li.in.vc.Wxyz[1] = 0.5 * (li.in.vc.Wxyz[1] + li.out.vc.Wxyz[1]); + li.in.vc.Wxyz[2] = 0.5 * (li.in.vc.Wxyz[2] + li.out.vc.Wxyz[2]); + + /* And scale it Y to be equal to 1.0 */ + mxw = 1.0/li.in.vc.Wxyz[1]; + li.in.vc.Wxyz[0] *= mxw; + li.in.vc.Wxyz[1] *= mxw; + li.in.vc.Wxyz[2] *= mxw; + + /* Set the output vc to be the same as the input */ + li.out.vc = li.in.vc; /* Structure copy */ + } else { + /* Not Abs Appearance space */ + li.in.intent = li.out.intent = li.abs_intent = icxAppearance; + } + } else { + /* Not Appearance space */ + li.in.intent = li.out.intent = li.abs_intent = icAbsoluteColorimetric; + } + } + + if (li.verb) + printf("Loading input A2B table\n"); + + /* default flags for all xicc luobj's */ + flb = ICX_CLIP_NEAREST; + if (li.verb) + flb |= ICX_VERBOSE; + + /* Get an input profile xicc conversion object */ + fl = flb; +#ifdef USE_MERGE_CLUT_OPT + fl |= ICX_MERGE_CLUT; +#endif + +#ifdef NEVER + printf("~1 input space flags = 0x%x\n",fl); + printf("~1 input space intent = %s\n",icx2str(icmRenderingIntent,li.in.intent)); + printf("~1 input space pcs = %s\n",icx2str(icmColorSpaceSignature,li.pcsor)); + printf("~1 input space viewing conditions =\n"); xicc_dump_viewcond(&li.in.vc); + printf("~1 input space inking =\n"); xicc_dump_inking(&li.in.ink); +#endif + if ((li.in.luo = li.in.x->get_luobj(li.in.x, fl, icmFwd, li.in.intent, + li.pcsor, icmLuOrdNorm, &li.in.vc, &li.in.ink)) == NULL) { + error("get xlookup object failed: %d, %s",li.in.x->errc,li.in.x->err); + } + + /* Get details of overall conversion */ + li.in.luo->spaces(li.in.luo, &li.in.csp, &li.in.chan, NULL, NULL, &li.in.alg, + NULL, NULL, NULL); + + /* Get the input profile A2B input curve resolution */ + /* (This is pretty rough - this should work for non LUT types as well!!) */ + { + if (li.in.alg== icmLutType) { + icmLut *lut; + icxLuLut *luluo = (icxLuLut *)li.in.luo; /* Safe to coerce */ + luluo->get_info(luluo, &lut, NULL, NULL, NULL); /* Get some details */ + in_curve_res = lut->inputEnt; + } + } + + /* Grab the white point in case the wphack or xyzscale needs it */ + li.in.luo->efv_wh_bk_points(li.in.luo, li.in.wp, NULL, NULL); + + /* Get native PCS space */ + li.in.luo->lutspaces(li.in.luo, NULL, NULL, &natpcs, NULL, NULL); + + if (li.in.nocurve == 0 && natpcs == icSigXYZData + && (li.in.alg == icmMatrixFwdType || li.in.alg == icmMatrixBwdType + || li.in.csp == icSigXYZData)) { + li.in.lcurve = 1; /* Use Y to L* and L* to Y for input */ + if (li.in.csp == icSigXYZData) { + li.in.lcurve = 2; /* Use real Y to L* and L* to Y for input */ + li.in.nocurve = 1; /* Don't trust the curve that comes with it */ + } + if (li.verb) + printf("Using Y to L* and L* to Y curves for input\n"); + } + + /* Setup any abstract profile to match the chosen PCS */ + /* We aren't checking whether the input/abstract/output profile */ + /* intents really make any sense. It's assumed at the moment */ + /* that the user knows what they're doing! */ + if (abs_name[0] != '\000') { + + if ((li.abs_luo = li.abs_xicc->get_luobj(li.abs_xicc, flb, icmFwd, li.abs_intent, + li.pcsor, icmLuOrdNorm, &li.out.vc, NULL)) == NULL) + error ("%d, %s",li.abs_icc->errc, li.abs_icc->err); + } + + // Figure out whether the output profile is a Lut profile or not */ + { + icmLuBase *plu; + + /* Get temporary icm lookup object */ + /* (Use Fwd just in case profile is missing B2A !!!!) */ + if ((plu = li.out.c->get_luobj(li.out.c, icmFwd, icmDefaultIntent, icmSigDefaultData, + icmLuOrdNorm)) == NULL) { + error("get icm lookup object failed: on '%s' %d, %s",out_name,li.out.c->errc,li.out.c->err); + } + + /* Check what the algorithm is */ + plu->spaces(plu, NULL, NULL, NULL, NULL, &oalg, NULL, NULL, NULL, NULL); + + /* release the icm lookup */ + plu->del(plu); + + } + + if (oalg != icmLutType || li.mode < 2) { /* Using B2A table or inv. mono/matrix */ + if (li.verb) + printf("Loading output B2A table\n"); + + if ((li.out.luo = li.out.x->get_luobj(li.out.x, flb, icmBwd, li.out.intent, + li.pcsor, icmLuOrdNorm, &li.out.vc, &li.out.ink)) == NULL) { + error("get xlookup object failed: %d, %s",li.out.x->errc,li.out.x->err); + } + /* Get details of overall conversion */ + li.out.luo->spaces(li.out.luo, NULL, NULL, NULL, &li.out.chan, &li.out.alg, + NULL, NULL, NULL); + + /* Get the output profile B2A output curve resolution */ + /* (This is pretty rough - this should work for non LUT types as well!!) */ + { + if (li.out.alg== icmLutType) { + icmLut *lut; + icxLuLut *luluo = (icxLuLut *)li.out.luo; /* Safe to coerce */ + luluo->get_info(luluo, &lut, NULL, NULL, NULL); /* Get some details */ + out_curve_res = lut->outputEnt; + } + } + + /* Grab the white point in case the wphack or xyzscale needs it */ + li.out.luo->efv_wh_bk_points(li.out.luo, li.out.wp, NULL, NULL); + + /* Get native PCS space */ + li.out.luo->lutspaces(li.out.luo, &natpcs, NULL, NULL, NULL, NULL); + + } else { /* Using inverse A2B Lut for output conversion */ + + fl = flb; +#ifdef USE_MERGE_CLUT_OPT + fl |= ICX_MERGE_CLUT; +#endif +#ifdef USE_CAM_CLIP_OPT + fl |= ICX_CAM_CLIP; +#endif + if (li.verb) + printf("Loading output inverse A2B table\n"); + +#ifdef NEVER + printf("~1 output space flags = 0x%x\n",fl); + printf("~1 output space intent = %s\n",icx2str(icmRenderingIntent,li.out.intent)); + printf("~1 output space pcs = %s\n",icx2str(icmColorSpaceSignature,li.pcsor)); + printf("~1 output space viewing conditions =\n"); xicc_dump_viewcond(&li.out.vc); + printf("~1 output space inking =\n"); xicc_dump_inking(&li.out.ink); +#endif + + if ((li.out.luo = li.out.x->get_luobj(li.out.x, fl, icmFwd, + li.out.intent, li.pcsor, icmLuOrdNorm, &li.out.vc, + &li.out.ink)) == NULL) { + error("get xlookup object failed: %d, %s",li.out.x->errc,li.out.x->err); + } + + /* Get details of overall conversion */ + li.out.luo->spaces(li.out.luo, &li.out.csp, &li.out.chan, NULL, NULL, &li.out.alg, + NULL, NULL, NULL); + + /* Get the output profile A2B input curve resolution */ + /* (This is pretty rough - this should work for non LUT types as well!!) */ + { + if (li.out.alg== icmLutType) { + icmLut *lut; + icxLuLut *luluo = (icxLuLut *)li.out.luo; /* Safe to coerce */ + luluo->get_info(luluo, &lut, NULL, NULL, NULL); /* Get some details */ + out_curve_res = lut->inputEnt; + } + } + + /* Grab the white point in case the wphack or xyzscale needs it */ + li.out.luo->efv_wh_bk_points(li.out.luo, li.out.wp, NULL, NULL); + + /* Get native PCS space */ + li.out.luo->lutspaces(li.out.luo, NULL, NULL, &natpcs, NULL, NULL); + + /* If we need a B2A lookup to get the existing K */ + if (li.out.inking == 7) { + if ((li.out.b2aluo = li.out.x->get_luobj(li.out.x, flb, icmBwd, + li.out.intent, li.pcsor, icmLuOrdNorm, &li.out.vc, NULL)) == NULL) { + error("get B2A xlookup object failed: %d, %s",li.out.x->errc,li.out.x->err); + } + } + } + + /* If we need an PCS->K' mapping for the neutral axis to K hack. */ + /* What we do is lookup the L for K values from 0 to 1, */ + /* and then invert this to create an L to K lookup. */ + /* If the gamut mapping is set to map to the K only black point, */ + /* it should all work well... */ + if (li.nhack) { + icxLuBase *luo; /* Base XLookup type object */ + icmLuAlgType alg; /* Type of lookup algorithm */ + co ips[256]; /* Initialisation points */ + datai glow; /* Grid low scale */ + datai ghigh; /* Grid high scale */ + datao vlow; /* Data value low normalize */ + datao vhigh; /* Data value high normalize */ + double Lmax, Lmin; /* Max and Min L values that result */ + int grres; + double avgdev[MXDO]; + + if (li.out.h->colorSpace != icSigCmykData) + error("Neutral Axis K only requested with non CMYK output profile"); + + if (li.in.chan < 3) + error("Neutral Axis K only requested with input profile with less than 3 channels"); + + if (li.nhack == 2 && li.in.h->colorSpace != icSigCmykData) + error("Neutral Axis 000K only requested with input profile that is not CMYK"); + + if ((li.pcs2k = new_rspl(RSPL_NOFLAGS, 1, 1)) == NULL) { + error("Failed to create an rspl object"); + } + + /* Get a device to PCS lookup object to use to lookup K->PCS */ + if ((luo = li.out.x->get_luobj(li.out.x, flb, + icmFwd, li.out.intent, li.pcsor, icmLuOrdNorm, &li.out.vc, + NULL)) == NULL) { + error("get xlookup object failed: %d, %s",li.out.x->errc,li.out.x->err); + } + /* Get details of overall conversion */ + luo->spaces(luo, NULL, NULL, NULL, NULL, &alg, NULL, NULL, NULL); + if (alg != icmLutType) + error ("Unexpected algorithm type for CMYK output profile"); + + /* Setup the initialisation points */ + Lmax = -100.0; + Lmin = 1000.0; + for (i = 0; i < 256; i++) { + icxLuLut *lu = (icxLuLut *)luo; /* Safe to coerce */ + double in[4], pcsv[4]; + in[0] = in[1] = in[2] = 0.0; + in[3] = i/(255.0); + + /* Want to do dev' -> PCS conversion to match the */ + /* normal inverse PCS-> dev' used in devip_devop() */ + if (li.out.nocurve) { /* No explicit curve, so do implicit here */ + /* Since not PCS, in_abs and matrix cannot be valid, */ + /* so input curve on own is ok to use. */ + lu->input(lu, pcsv, in); + lu->clut(lu, pcsv, pcsv); + } else { + lu->clut(lu, pcsv, in); + } + lu->output(lu, pcsv, pcsv); + lu->out_abs(lu, pcsv, pcsv); + + /* We force the rspl to be a forward conversion by swapping K and PCS */ + ips[i].p[0] = pcsv[0]; /* PCS as input */ + ips[i].v[0] = in[3]; /* K as output */ +#ifdef NEUTKDEBUG + printf("L %f -> K' %f\n",pcsv[0], in[3]); +#endif /* NEUTKDEBUG */ + + if (pcsv[0] > Lmax) /* Track min and max L values */ + Lmax = pcsv[0]; + if (pcsv[0] < Lmin) + Lmin = pcsv[0]; + } + + glow[0] = 0.0; + ghigh[0] = 100.0; + vlow[0] = 0.0; + vhigh[0] = 1.0; + grres = 256; + avgdev[0] = 0.005; + + li.pcs2k->fit_rspl(li.pcs2k, 0, ips, 256, glow, ghigh, &grres, vlow, vhigh, 1.0, avgdev, NULL); + + /* Fixup the white and black points for neutral axis to K hack. */ + /* This is to make sure that they exactly match the fwd mapping */ + /* after the rspl is fitted. */ + { + pcs2k_ctx cx; /* White point fixup context */ + co pp; /* Lookup the min and max K values */ + + pp.p[0] = Lmax; + li.pcs2k->interp(li.pcs2k, &pp); + cx.kmin = pp.v[0]; /* Ideally would be 0 */ + pp.p[0] = Lmin; + li.pcs2k->interp(li.pcs2k, &pp); + cx.kmax = pp.v[0]; /* Ideally would be 1 */ + +#ifdef NEUTKDEBUG + printf("Before fix: Lmax %f, Lmin %f, Kmin %f, Kmax %f\n",Lmax, Lmin, cx.kmin, cx.kmax); +#endif /* NEUTKDEBUG */ + + li.pcs2k->re_set_rspl(li.pcs2k, 0, (void *)&cx, fix_pcs2k_white); +#ifdef NEUTKDEBUG + pp.p[0] = Lmax; + li.pcs2k->interp(li.pcs2k, &pp); + cx.kmin = pp.v[0]; /* Ideally would be 0 */ + pp.p[0] = Lmin; + li.pcs2k->interp(li.pcs2k, &pp); + cx.kmax = pp.v[0]; /* Ideally would be 1 */ + printf("After fix: Lmax %f, Lmin %f, Kmin %f, Kmax %f\n",Lmax, Lmin, cx.kmin, cx.kmax); +#endif /* NEUTKDEBUG */ + } + + } /* end if neutral axis to K hack */ + + if (li.cmyhack != 0) { + if (li.in.h->colorSpace != icSigCmyData + && li.in.h->colorSpace != icSigCmykData) + error("100% CMY mapping requested with non CMY or CMYK input profile"); + + if (li.out.h->colorSpace != icSigCmyData + && li.out.h->colorSpace != icSigCmykData) + error("100% CMY mapping requested with non CMY or CMYK output profile"); + } + + if (li.out.nocurve == 0 && natpcs == icSigXYZData + && (li.out.alg == icmMatrixFwdType || li.out.alg == icmMatrixBwdType + || li.out.csp == icSigXYZData)) { + li.out.lcurve = 1; /* Use Y to L* and L* to Y for output */ + if (li.out.csp == icSigXYZData) { + li.out.lcurve = 2; /* Use real Y to L* and L* to Y for output */ + li.out.nocurve = 1; /* Don't trust the curve that comes with it */ + } + if (li.verb) + printf("Using Y to L* and L* to Y curves for output\n"); + } + } + + /* - - - - - - - - - - - - - - - - - - - */ + /* Setup the gamut mapping */ +// ~~~~ need to account for possible abstract profile after source !!!! +// ~~~~ also need to fix tiffgamut to allow for abstract profile !!!! + + if (li.verb) + printf("Gamut mapping mode is '%s'\n",li.mode == 0 ? "Simple" : li.mode == 1 ? "Mapping" : "Mapping inverse A2B"); + if (li.verb && li.mode > 0) + printf("Gamut mapping intent is '%s'\n",li.gmi.desc); + + /* In gamut mapping mode, the PCS used will always be absolute */ + /* intent from the input and output profiles, and either */ + /* lab or Jab space, with the given in/out viewing conditions */ + /* for the latter. The xluo->get_gamut work in the set li.pcsor */ + /* for each xluo. */ + if (li.mode > 0 && li.gmi.usemap) { + gamut *csgam, *igam, *ogam; + double sgres; /* Source gamut surface feature resolution */ + double dgres; /* Destination gamut surface feature resolution */ + int mapres; /* Mapping rspl resolution */ + + if (li.verb) + printf("Creating Gamut Mapping\n"); + + /* Gamut mapping will extend given grid res to encompas */ + /* source gamut by a margin. */ + if (li.quality == 3) { /* Ultra High */ + sgres = 7.0; + dgres = 7.0; + mapres = 41; + } else if (li.quality == 2) { /* High */ + sgres = 8.0; + dgres = 8.0; + mapres = 33; + } else if (li.quality == 1) { /* Medium */ + sgres = 10.0; + dgres = 10.0; + mapres = 25; + } else { /* Low quality */ + sgres = 12.0; + dgres = 12.0; + mapres = 17; + } + + /* Creat the source colorspace gamut surface */ + if (li.verb) + printf(" Finding Source Colorspace Gamut with res %f\n",sgres); + + /* Creat the source image gamut surface in the selected li.pcsor space */ + if ((csgam = li.in.luo->get_gamut(li.in.luo, sgres)) == NULL) + error ("%d, %s",li.in.x->errc, li.in.x->err); + + /* Grab a given source image gamut. */ + if (sgam_name[0] != '\000') { /* Optional source gamut - ie. from an images */ + + if (li.verb) + printf(" Loading Image Source Gamut '%s'\n",sgam_name); + + igam = new_gamut(sgres, isJab, 0); /* isJab will be overriden by gamut file */ + + if (igam->read_gam(igam, sgam_name)) + error("Reading source gamut '%s' failed",sgam_name); + + if (igam->getisjab(igam) != isJab) { + /* Should really convert to/from Jab here! */ + warning("Image gamut is wrong colorspace for link (Lab != Jab)"); + + /* This will actually error in the gamut mapping code */ + /* Note that we're not checking relative/absolute colorspace here. */ + /* At the moment it's up to the user to get this right. */ + } + + } else { + igam = NULL; /* NULL signals no source image gamut */ + } + + /* Creat the destination gamut surface */ + if (li.verb) + printf(" Finding Destination Gamut with res %f\n",dgres); + + if ((ogam = li.out.luo->get_gamut(li.out.luo, dgres)) == NULL) + error ("%d, %s",li.out.x->errc, li.out.x->err); + + if (li.verb) + printf(" Creating Gamut match\n"); + + li.map = new_gammap(li.verb, csgam, igam, ogam, &li.gmi, + li.src_kbp, li.dst_kbp, li.cmyhack, li.rel_oride, + mapres, NULL, NULL, li.gamdiag ? "gammap.wrl" : NULL + ); + if (li.map == NULL) + error ("Failed to make gamut map transform"); + + if (li.nhack == 2) { + if (li.verb) + printf(" Creating K only black to K only black Gamut match\n"); + + li.Kmap = new_gammap(li.verb, csgam, igam, ogam, &li.gmi, + 1, 1, li.cmyhack, li.rel_oride, + mapres, NULL, NULL, li.gamdiag ? "gammap.wrl" : NULL + ); + if (li.Kmap == NULL) + error ("Failed to make K only gamut map transform"); + } + + ogam->del(ogam); + if (igam != NULL) + igam->del(igam); + csgam->del(csgam); + } + + /* If we've got a request for Absolute Appearance mode with scaling */ + /* to avoid clipping the source white point, compute the needed XYZ scaling factor. */ + /* We assume that the white point hack can't be used at the same time. */ + if (li.mode > 0 && li.wphack == 0 && (li.gmi.usecas & 0x100) != 0) { + double xyzscale[1], sa[1]; + + /* We already have the source space white point in li.in.wp[] */ + + /* Convert it to destination XYZ */ + if (li.pcsor == icxSigJabData) { + /* We're being bad in delving inside the xluo, but we'll fix it latter */ + li.out.luo->cam->cam_to_XYZ(li.out.luo->cam, li.swxyz, li.in.wp); + } else + error("Internal :- not setup to handle Y scaling and non-Jab PCS"); + +//printf("~1 Source white Jab = %f %f %f\n", li.in.wp[0], li.in.wp[1], li.in.wp[2]); +//printf("~1 Source white XYZ = %f %f %f\n", li.swxyz[0], li.swxyz[1], li.swxyz[2]); + + /* Compute the bigest scale factor less than or equal to 1.0, */ + /* that doesn't clip the li.swxyz[] on the destination gamut */ + sa[0] = 0.1; + xyzscale[0] = 0.5; + if (powell(NULL, 1, xyzscale, sa, 1e-6, 2000, xyzoptfunc, (void *)&li, NULL, NULL) != 0) { + warning("set_icxLuLut: XYZ scale powell failed to converge - set scale to 1.0"); + } else { + li.xyzscale = xyzscale[0]; + if (li.verb) + printf("Set XYZ scale factor to %f\n",li.xyzscale); + } + } + + /* Create the link profile */ + if (verify == 0) { + icmFile *wr_fp; + icc *wr_icc; + + if (li.verb) + printf("Creating link profile\n"); + + /* Open up the link file for writing */ + if ((wr_fp = new_icmFileStd_name(link_name,"w")) == NULL) + error ("Write: Can't open file '%s'",link_name); + + if ((wr_icc = new_icc()) == NULL) + error ("Write: Creation of ICC object failed"); + + /* Add all the tags required */ + + /* The header: */ + { + icmHeader *wh = wr_icc->header; + + /* Values that must be set before writing */ + wh->deviceClass = icSigLinkClass; /* We are creating a link ! */ + wh->colorSpace = li.in.h->colorSpace; /* Input profile device space */ + wh->pcs = li.out.h->colorSpace; /* Output profile device space */ + if (li.mode > 0) { + wh->renderingIntent = li.gmi.icci; /* Closest ICC intent */ + } else { + wh->renderingIntent = li.out.intent; /* Output intent chosen */ + } + + /* Values that should be set before writing */ + if (xpi.manufacturer != 0L) + wh->manufacturer = xpi.manufacturer; + else + wh->manufacturer = icmSigUnknownType; + + if (xpi.model != 0L) + wh->model = xpi.model; + else + wh->model = icmSigUnknownType; + + /* Values that may be set before writing */ + if (xpi.creator != 0L) + wh->creator = xpi.creator; + + wh->attributes.l = 0; + wh->flags = 0; +#ifdef NT + wh->platform = icSigMicrosoft; +#endif +#ifdef __APPLE__ + wh->platform = icSigMacintosh; +#endif +#if defined(UNIX) && !defined(__APPLE__) + wh->platform = icmSig_nix; +#endif + } + /* Profile Description Tag: */ + { + icmTextDescription *wo; + char *dst, dstm[200]; /* description */ + + if (xpi.profDesc != NULL) + dst = xpi.profDesc; + else { + dst = "Device Link profile - See ProfileSequenceDescTag for more information"; + dst = dstm; + } + + if ((wo = (icmTextDescription *)wr_icc->add_tag( + wr_icc, icSigProfileDescriptionTag, icSigTextDescriptionType)) == NULL) + error("add_tag failed: %d, %s",wr_icc->errc,wr_icc->err); + + wo->size = strlen(dst)+1; /* Allocated and used size of desc, inc null */ + wo->allocate((icmBase *)wo);/* Allocate space */ + strcpy(wo->desc, dst); /* Copy the string in */ + } + /* Copyright Tag: */ + { + icmText *wo; + char *crt; + + if (xpi.copyright != NULL) + crt = xpi.copyright; + else + crt = "Copyright, the creator of this profile"; + + if ((wo = (icmText *)wr_icc->add_tag( + wr_icc, icSigCopyrightTag, icSigTextType)) == NULL) + error("add_tag failed: %d, %s",wr_icc->errc,wr_icc->err); + + wo->size = strlen(crt)+1; /* Allocated and used size of text, inc null */ + wo->allocate((icmBase *)wo);/* Allocate space */ + strcpy(wo->data, crt); /* Copy the text in */ + } + /* Device Manufacturers Description Tag: */ + if (xpi.deviceMfgDesc != NULL) { + icmTextDescription *wo; + char *dst = xpi.deviceMfgDesc; + + if ((wo = (icmTextDescription *)wr_icc->add_tag( + wr_icc, icSigDeviceMfgDescTag, icSigTextDescriptionType)) == NULL) + error("add_tag failed: %d, %s",wr_icc->errc,wr_icc->err); + + wo->size = strlen(dst)+1; /* Allocated and used size of desc, inc null */ + wo->allocate((icmBase *)wo);/* Allocate space */ + strcpy(wo->desc, dst); /* Copy the string in */ + } + /* Model Description Tag: */ + if (xpi.modelDesc != NULL) { + icmTextDescription *wo; + char *dst = xpi.modelDesc; + + if ((wo = (icmTextDescription *)wr_icc->add_tag( + wr_icc, icSigDeviceModelDescTag, icSigTextDescriptionType)) == NULL) + error("add_tag failed: %d, %s",wr_icc->errc,wr_icc->err); + + wo->size = strlen(dst)+1; /* Allocated and used size of desc, inc null */ + wo->allocate((icmBase *)wo);/* Allocate space */ + strcpy(wo->desc, dst); /* Copy the string in */ + } + /* ProfileSequenceDescTag: */ + { + unsigned int i; + icmProfileSequenceDesc *wo; + if ((wo = (icmProfileSequenceDesc *)wr_icc->add_tag( + wr_icc, icSigProfileSequenceDescTag, icSigProfileSequenceDescType)) == NULL) + return 1; + + wo->count = 2; /* Number of descriptions in sequence */ + if (wo->allocate((icmBase *)wo) != 0) /* Allocate space for all the DescStructures */ + error("allocate failed: %d, %s",wr_icc->errc,wr_icc->err); + + /* Fill in each description structure in sequence */ + + /* For each profile in the chain */ + for (i = 0; i < wo->count; i++) { + icc *iccs = NULL; + icmHeader *sh = NULL; + icmSignature *tsig; + icmTextDescription *ddesc; + icmTextDescription *mdesc; + + if (i == 0) { + iccs = li.in.c; /* Input profile */ + sh = li.in.h; /* Input profile header */ + } else if (i == (wo->count-1)) { + iccs = li.out.c; /* Output profile */ + sh = li.out.h; /* Output profile header */ + } else { + error("Abstract profiles in link not implemented yet!"); + } + + /* Try and read the technology tag */ + if ((tsig = (icmSignature *)iccs->read_tag(iccs, icSigTechnologyTag)) != NULL) { + if (tsig->ttype != icSigSignatureType) /* oops */ + tsig = NULL; + } + + /* Try and read the Device Manufacturers Description Tag */ + if ((ddesc = (icmTextDescription *)iccs->read_tag( + iccs, icSigDeviceMfgDescTag)) != NULL) { + if (ddesc->ttype != icSigTextDescriptionType) /* oops */ + ddesc = NULL; + } + + /* Try and read the Model Manufacturers Description Tag */ + if ((mdesc = (icmTextDescription *)iccs->read_tag( + iccs, icSigDeviceModelDescTag)) != NULL) { + if (mdesc->ttype != icSigTextDescriptionType) /* oops */ + mdesc = NULL; + } + + /* Header information */ + wo->data[i].deviceMfg = sh->manufacturer; + wo->data[i].deviceModel = sh->model; + wo->data[i].attributes = sh->attributes; + + /* Technology signature */ + if (tsig != NULL) + wo->data[i].technology = tsig->sig; + + if (ddesc != NULL) { + wo->data[i].device.size = ddesc->size; + if (wo->data[i].allocate(&wo->data[i]) != 0) /* Allocate space */ + error("allocate failed: %d, %s",wr_icc->errc,wr_icc->err); + strcpy(wo->data[i].device.desc, ddesc->desc); + + wo->data[i].device.ucLangCode = ddesc->ucLangCode; + wo->data[i].device.ucSize = ddesc->ucSize; + if (wo->data[i].allocate(&wo->data[i]) != 0) /* Allocate space */ + error("allocate failed: %d, %s",wr_icc->errc,wr_icc->err); + memmove(wo->data[i].device.ucDesc, ddesc->ucDesc, 2 * ddesc->ucSize); + + wo->data[i].device.scCode = ddesc->scCode; + wo->data[i].device.scSize = ddesc->scSize; + strcpy((char *)wo->data[i].device.scDesc, (char *)ddesc->scDesc); + } + + /* model Text description */ + if (mdesc != NULL) { + wo->data[i].model.size = mdesc->size; + if (wo->data[i].allocate(&wo->data[i])!= 0) /* Allocate space */ + error("allocate failed: %d, %s",wr_icc->errc,wr_icc->err); + strcpy(wo->data[i].model.desc, mdesc->desc); + + wo->data[i].model.ucLangCode = mdesc->ucLangCode; + wo->data[i].model.ucSize = mdesc->ucSize; + if (wo->data[i].allocate(&wo->data[i]) != 0) /* Allocate space */ + error("allocate failed: %d, %s",wr_icc->errc,wr_icc->err); + memmove(wo->data[i].model.ucDesc, mdesc->ucDesc, 2 * mdesc->ucSize); + + wo->data[i].model.scCode = mdesc->scCode; + wo->data[i].model.scSize = mdesc->scSize; + strcpy((char *)wo->data[i].model.scDesc, (char *)mdesc->scDesc); + } + } + } + /* ColorantTable: */ + { + int i; + unsigned int j; + int repclip = 0; + + /* For the first and last profile in the chain */ + /* (Note that we're assuming that the link output PCS is always Lab) */ + for (i = 0; i < 2; i++) { + icc *iccs; + icmHeader *sh; + icmColorantTable *ro; + icmColorantTable *wo; + icTagSignature cts; + + if (i == 0) { + iccs = li.in.c; /* Input profile */ + sh = li.in.h; /* Input profile header */ + cts = icSigColorantTableTag; + } else { + iccs = li.out.c; /* Output profile */ + sh = li.out.h; /* Output profile header */ + cts = icSigColorantTableOutTag; + } + + /* Try and read the input ColorantTable */ + if ((ro = (icmColorantTable *)iccs->read_tag( + iccs, icSigColorantTableTag)) != NULL) { + + /* Create a ColorantTable in the output device link */ + if ((wo = (icmColorantTable *)wr_icc->add_tag( + wr_icc, cts, icSigColorantTableType)) == NULL) + error("add_tag failed: %d, %s",wr_icc->errc,wr_icc->err); + + /* Copy everything across */ + wo->count = ro->count; + if (wo->allocate((icmBase *)wo) != 0) + error("allocate failed: %d, %s",wr_icc->errc,wr_icc->err); + + for (j = 0; j < wo->count; j++) { + strcpy(wo->data[j].name, ro->data[j].name); + if (sh->pcs != icSigLabData) { + icmXYZ2Lab(&icmD50, wo->data[j].pcsCoords, ro->data[j].pcsCoords); + /* For device links the colorant table must be Lab PCS, */ + /* but embarassingly, XYZ profiles can have colorant values */ + /* not representable in the Lab PCS range. */ + if (icmClipLab(wo->data[j].pcsCoords, wo->data[j].pcsCoords)) { + if (repclip) + warning("Colorant Tag Lab value was clipped"); + repclip = 1; + } + } else { + icmAry2Ary(wo->data[j].pcsCoords, ro->data[j].pcsCoords); + } + } + + } else { /* Do this the hard way */ + icmLuBase *luo; + unsigned int count; + double dv[MAX_CHAN]; + double cvals[MAX_CHAN][3]; + inkmask imask; + + /* Get a lookup to read colorant values */ + if ((luo = iccs->get_luobj(iccs, icmFwd, icRelativeColorimetric, + icSigLabData, icmLuOrdNorm)) == NULL) + goto skip_coloranttable; + + count = icmCSSig2nchan(sh->colorSpace); + for (j = 0; j < count; j++) + dv[j] = 0.0; + + /* Lookup the colorant Lab values the recommended ICC way */ + for (j = 0; j < count; j++) { + dv[j] = 1.0; + luo->lookup(luo, cvals[j], dv); + /* For device links the colorant table must be Lab PCS, */ + /* but embarassingly, XYZ profiles can have colorant values */ + /* not representable in the Lab PCS range. */ + if (icmClipLab(cvals[j], cvals[j])) { + if (repclip) + warning("Colorant Tag Lab value was clipped"); + repclip = 1; + } + dv[j] = 0.0; + } + luo->del(luo); + + /* Lookup colorant names */ + if ((imask = icx_icc_cv_to_colorant_comb(sh->colorSpace, iccs->header->deviceClass, cvals)) == 0) + goto skip_coloranttable; + + /* Create a ColorantTable in the output device link */ + if ((wo = (icmColorantTable *)wr_icc->add_tag( + wr_icc, cts, icSigColorantTableType)) == NULL) + error("add_tag failed: %d, %s",wr_icc->errc,wr_icc->err); + + wo->count = count; + if (wo->allocate((icmBase *)wo) != 0) + error("allocate failed: %d, %s",wr_icc->errc,wr_icc->err); + + for (j = 0; j < count; j++) { + inkmask iimask; /* Individual ink mask */ + char *name; + + iimask = icx_index2ink(imask, j); + name = icx_ink2string(iimask); + if (strlen(name) > 31) + error("Internal: colorant name exceeds 31 characters"); + strcpy(wo->data[j].name, name); + wo->data[j].pcsCoords[0] = cvals[j][0]; + wo->data[j].pcsCoords[1] = cvals[j][1]; + wo->data[j].pcsCoords[2] = cvals[j][2]; + } + } + /* Jump to here if we can't figure out what to put in ColorantTag */ + skip_coloranttable:; + } + } + /* 16 bit input device -> output device lut: */ + { + int inputEnt, outputEnt, clutPoints; + icmLut *wo; + + /* Setup the cLUT resolutions */ + if (li.quality >= 3) + inputEnt = 4096; + else if (li.quality == 2) + inputEnt = 2048; + else + inputEnt = 256; + + /* Make sure that we have at least the number of input entries as the */ + /* input profile. */ + if (in_curve_res > inputEnt) + inputEnt = in_curve_res; + + /* See discussion in imdi/imdi_gen.c for ideal numbers */ + switch (li.in.chan) { + case 0: + error ("Illegal number of input chanels"); + case 1: + if (li.quality >= 3) + clutPoints = 255; + else if (li.quality == 2) + clutPoints = 255; + else + clutPoints = 255; + break; + + case 2: + if (li.quality >= 2) + clutPoints = 255; + else + clutPoints = 86; + break; + case 3: + if (li.quality >= 3) + clutPoints = 52; + else if (li.quality == 2) + clutPoints = 33; + else if (li.quality == 1) + clutPoints = 17; + else + clutPoints = 9; + break; + case 4: + if (li.quality >= 3) + clutPoints = 33; + else if (li.quality == 2) + clutPoints = 18; + else if (li.quality == 1) + clutPoints = 9; + else + clutPoints = 6; + break; + case 5: + if (li.quality >= 3) + clutPoints = 18; + else if (li.quality == 2) + clutPoints = 16; + else + clutPoints = 9; + break; + case 6: + if (li.quality >= 3) + clutPoints = 12; + else if (li.quality == 2) + clutPoints = 9; + else + clutPoints = 6; + break; + case 7: + if (li.quality >= 3) + clutPoints = 8; + else if (li.quality == 2) + clutPoints = 7; + else + clutPoints = 6; + break; + case 8: + if (li.quality >= 3) + clutPoints = 7; + else if (li.quality == 2) + clutPoints = 6; + else + clutPoints = 5; + break; + default: /* > 8 chan */ + clutPoints = 3; + break; + } + + if (li.clutres > 0) /* clut resolution override */ + clutPoints = li.clutres; + li.clutres = clutPoints; /* Actual resolution */ + + if (li.quality >= 3) + outputEnt = 4096; + else if (li.quality == 2) + outputEnt = 2048; + else + outputEnt = 256; + + /* Make sure that we have at least the number of input entries as the */ + /* output profile. */ + if (out_curve_res > outputEnt) + outputEnt = out_curve_res; + + + /* Link Lut = AToB0 */ + if ((wo = (icmLut *)wr_icc->add_tag( + wr_icc, icSigAToB0Tag, icSigLut16Type)) == NULL) + error("add_tag failed: %d, %s",wr_icc->errc,wr_icc->err); + + wo->inputChan = li.in.chan; + wo->outputChan = li.out.chan; + + /* Setup the tables resolutions */ + wo->inputEnt = inputEnt; + wo->clutPoints = clutPoints; + wo->outputEnt = outputEnt; + + if (wo->allocate((icmBase *)wo) != 0) /* Allocate space */ + error("allocate failed: %d, %s",wr_icc->errc,wr_icc->err); + + /* Special case if input profile is Lut with matrix */ + /* (Does this do anything since input is not XYZ ?) */ + if (li.in.alg == icmLutType && li.in.nocurve == 0) { + icxLuLut *lu = (icxLuLut *)li.in.luo; + lu->get_matrix(lu, wo->e); /* Copy it across */ + } + + + + if (li.verb) + printf("Filling in Lut table\n"); +#ifdef DEBUG_ONE +#define DBGNO 1 /* Up to 10 */ + +#ifndef NEVER + /* Test a single given rgb/cmyk -> cmyk value */ + { + double in[10][MAX_CHAN]; + double out[MAX_CHAN]; + in[0][0] = 1.0; + in[0][1] = 1.0; + in[0][2] = 1.0; + in[0][3] = 0.0; + + in[1][0] = 1.0; + in[1][1] = 1.0; + in[1][2] = 1.0; + in[1][3] = 0.0; + + for (i = 0; i < DBGNO; i++) { + printf("Input %f %f %f %f\n",in[i][0], in[i][1], in[i][2], in[i][3]); + devi_devip((void *)&li, out, in[i]); + devip_devop((void *)&li, out, out); + devop_devo((void *)&li, out, out); + printf("Output %f %f %f %f\n\n",out[0], out[1], out[2], out[3]); + } + } +#endif /* NEVER */ + +#else /* !DEBUG_ONE */ + /* Use helper function to do the hard work. */ + if (li.verb) { + unsigned int ui; + int itotal; + for (itotal = 1, ui = 0; ui < li.in.chan; ui++, itotal *= clutPoints) + ; + li.total = itotal; + /* Allow for extra lookups due to ICM_CLUT_SET_APXLS */ + for (itotal = 1, ui = 0; ui < li.in.chan; ui++, itotal *= (clutPoints-1)) + ; + li.total += itotal; + li.count = 0; + printf(" 0%%"); fflush(stdout); + } + if (icmSetMultiLutTables( + 1, + &wo, + ICM_CLUT_SET_APXLS, /* Use aproximate least squares */ + &li, /* Context */ + li.in.h->colorSpace, /* Input color space */ + li.out.h->colorSpace, /* Output color space */ + devi_devip, /* Input transfer tables devi->devi' */ + NULL, NULL, /* Use default input colorspace range */ + devip_devop, /* devi' -> devo' transfer function */ + NULL, NULL, /* Default output colorspace range */ + devop_devo /* Output transfer tables, devo'->devo */ + ) != 0) { + error("Setting 16 bit Lut failed: %d, %s",wr_icc->errc,wr_icc->err); + } + if (li.verb) { + printf("\n"); + } +#ifdef WARN_CLUT_CLIPPING + if (wr_icc->warnc) + warning("Values clipped in setting device link LUT"); +#endif /* WARN_CLUT_CLIPPING */ + +#endif /* !DEBUG_ONE */ + + } + + if (li.verb && li.wphack && li.wphacked == 0) + printf("Warning :- white point hack didn't trigger!\n"); + if (li.verb && li.wphack && li.wphacked > 1) + printf("Warning :- white point hack trigger more than once! (%d)\n",li.wphacked); + + if (li.verb) + printf("Writing out file\n"); + + /* Write the file out */ + if ((rv = wr_icc->write(wr_icc,wr_fp,0)) != 0) + error ("Write file: %d, %s",rv,wr_icc->err); + + wr_icc->del(wr_icc); + wr_fp->del(wr_fp); + + /* - - - - - - - - - - - - - - - - - - - */ + /* Verify the given link, assuming all the options are the same */ + } else { + icmFile *rd_fp; + icc *rd_icc; + icmLuBase *luo; + + icColorSpaceSignature ins, outs; /* Type of input and output spaces */ + int inn, outn; /* Number of components */ + icmLuAlgType alg; /* Type of lookup algorithm */ + + /* Lookup parameters */ + icmLookupFunc func = icmFwd; /* Default */ + icRenderingIntent intent = icmDefaultIntent; /* Default */ + icmLookupOrder order = icmLuOrdNorm; /* Default */ + + int gc[MAX_CHAN]; /* Grid counter */ + int vres = 8; //~~9 + double imin[MAX_CHAN], imax[MAX_CHAN]; /* Range of input values */ + double omin[MAX_CHAN], omax[MAX_CHAN]; /* Range of output values */ + double in[MAX_CHAN]; /* Input value */ + double ref[MAX_CHAN]; /* Reference output value */ + double out[MAX_CHAN]; /* Output value */ + double aerr, perr; /* Average, Peak error */ + double nerr; + int count, total; + int pc, lastpc; + double pin[MAX_CHAN], pref[MAX_CHAN], pout[MAX_CHAN]; /* Peak error values */ + + if (li.verb) + printf("Setting up to verify the link\n"); + + /* Open up the link file for reading */ + if ((rd_fp = new_icmFileStd_name(link_name,"r")) == NULL) + error ("Verify: Can't open file '%s'",link_name); + + if ((rd_icc = new_icc()) == NULL) + error ("Verify: Creation of ICC object failed"); + + if ((rv = rd_icc->read(rd_icc,rd_fp,0)) != 0) + error ("%d, %s",rv,rd_icc->err); + + /* Check that the profile is appropriate */ + if (rd_icc->header->deviceClass != icSigLinkClass) + error("Profile isn't a device link profile"); + + /* Get a conversion object */ + if ((luo = rd_icc->get_luobj(rd_icc, func, intent, icmSigDefaultData, order)) == NULL) + error ("%d, %s",rd_icc->errc, rd_icc->err); + + /* Get details of conversion (Arguments may be NULL if info not needed) */ + luo->spaces(luo, &ins, &inn, &outs, &outn, &alg, NULL, NULL, NULL, NULL); + + if (alg != icmLutType) + error ("DeviceLink profile doesn't have Lut !"); + + /* Get the icm value ranges */ + luo->get_ranges(luo, imin, imax, omin, omax); + + /* Init the grid counter */ + for (i = 0; i < inn; i++) + gc[i] = 0; + + for (total = 1, i = 0; i < inn; i++, total *= vres) + ; + + count = 0; + lastpc = 0; + nerr = aerr = perr = 0.0; + for(i = 0; i < inn; ) { + int j; + double err; + + /* Create and scale input */ + for (j = 0; j < inn; j++) { + in[j] = gc[j]/(vres-1.0); + in[j] = in[j] * (imax[j] - imin[j]) + imin[j]; + } + +// printf("Input %f %f %f %f\n",in[0], in[1], in[2], in[3]); + + /* Create the reference output value */ + devi_devip((void *)&li, ref, in); + devip_devop((void *)&li, ref, ref); + devop_devo((void *)&li, ref, ref); + + /* Lookup the icm output value */ + if ((rv = luo->lookup(luo, out, in)) > 1) + error ("%d, %s",rd_icc->errc,rd_icc->err); + +// printf("Output %f %f %f %f\n",out[0], out[1], out[2], out[3]); +// printf("Ref %f %f %f %f\n",ref[0], ref[1], ref[2], ref[3]); +// printf("\n"); + + /* Unscale output and compare the results */ + for (err = 0.0, j = 0; j < outn; j++) { + double o,r; + o = (out[j] - omin[j])/(omax[j] - omin[j]); + r = (ref[j] - omin[j])/(omax[j] - omin[j]); + err += (o - r) * (o - r); + } + err = sqrt(err); + + aerr += err; + nerr++; + if (err > perr) { + perr = err; + for (j = 0; j < outn; j++) { + pin[j] = in[j]; + pref[j] = ref[j]; + pout[j] = out[j]; + } + } + + count++; + pc = (int)(count * 100.0/total + 0.5); + if (pc != lastpc) { + printf("%c%2d%%",cr_char,pc); fflush(stdout); + lastpc = pc; + } + + /* Increment the grid counter */ + for (i = 0; i < inn; i++) { + if (++gc[i] < vres) + break; /* No carry */ + gc[i] = 0; /* Reset digit */ + } + } + + if (li.verb) + printf("Finished verfication\n"); + + printf("Average error = %f%%, peak error = %f%%\n",aerr * 100.0/nerr, perr * 100.0); + printf("Input %f %f %f %f\n",pin[0], pin[1], pin[2], pin[3]); + printf("Output %f %f %f %f\n",pout[0], pout[1], pout[2], pout[3]); + printf("Ref %f %f %f %f\n",pref[0], pref[1], pref[2], pref[3]); + + luo->del(luo); + rd_icc->del(rd_icc); + rd_fp->del(rd_fp); + } + + /* - - - - - - - - - - - - - - - - - - - */ + /* Cleanup source profiles and exit */ + + if (li.pcs2k != NULL) /* Free up PCS->K lookup for neutral hack */ + li.pcs2k->del(li.pcs2k); + + if (li.map != NULL) + li.map->del(li.map); + if (li.Kmap != NULL) + li.Kmap->del(li.Kmap); + + if (li.abs_luo != NULL) { /* Free up abstract transform */ + li.abs_luo->del(li.abs_luo); + li.abs_xicc->del(li.abs_xicc); + li.abs_icc->del(li.abs_icc); + li.abs_fp->del(li.abs_fp); + } + + li.in.luo->del(li.in.luo); + li.in.x->del(li.in.x); + li.in.c->del(li.in.c); + + if (li.out.b2aluo != NULL) + li.out.b2aluo->del(li.out.b2aluo); + li.out.luo->del(li.out.luo); + li.out.x->del(li.out.x); + li.out.c->del(li.out.c); + + return 0; +} + + + + + + + -- cgit v1.2.3