
/* 
 * International Color Consortium color transform expanded support
 *
 * Author:  Graeme W. Gill
 * Date:    2/7/00
 * Version: 1.00
 *
 * Copyright 2000, 2001, 2014 Graeme W. Gill
 * All rights reserved.
 * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
 * see the License.txt file for licencing details.
 *
 * Based on the old iccXfm class.
 */

/*
 * This module expands the basic icclib functionality,
 * providing more functionality in exercising.
 * The implementation for the three different types
 * of profile representation, are in their own source files.
 */

/*
 * TTBD:
 *       Some of the error handling is crude. Shouldn't use
 *       error(), should return status.
 *
 */

#include <sys/types.h>
#include <string.h>
#include <ctype.h>
#ifdef __sun
#include <unistd.h>
#endif
#include "aconfig.h"
#include "numlib.h"
#include "counters.h"
#include "plot.h"
#include "../h/sort.h"
#include "conv.h"
#include "xicc.h"		/* definitions for this library */

#define USE_CAM			/* Use CIECAM02 for clipping and gamut mapping, else use Lab */

#undef DEBUG			/* Plot 1d Luts */

#ifdef DEBUG
#include "plot.h"
#endif

#define MAX_INVSOLN 4

icxLuBase * xicc_get_luobj(xicc *p, int flags, icmLookupFunc func, icRenderingIntent intent,
                           icColorSpaceSignature pcsor, icmLookupOrder order,
                           icxViewCond *vc, icxInk *ink);
static void icxLutSpaces(icxLuBase *p, icColorSpaceSignature *ins, int *inn,
                         icColorSpaceSignature *outs, int *outn,
                         icColorSpaceSignature *pcs);
static void icxLuSpaces(icxLuBase *p, icColorSpaceSignature *ins, int *inn,
                        icColorSpaceSignature *outs, int *outn, 
                        icmLuAlgType *alg, icRenderingIntent *intt,
                        icmLookupFunc *fnc, icColorSpaceSignature *pcs);
static void icxLu_get_native_ranges (icxLuBase *p,
                              double *inmin, double *inmax, double *outmin, double *outmax);
static void icxLu_get_ranges (icxLuBase *p,
                              double *inmin, double *inmax, double *outmin, double *outmax);
static void icxLuEfv_wh_bk_points(icxLuBase *p, double *wht, double *blk, double *kblk);
//int xicc_get_viewcond(xicc *p, icxViewCond *vc);	/* Dev code */


/* Convert error from lu to xlu */
#define LUE2XLUE(PRV) ((PRV & icmPe_lurv_err) ? 2 : (PRV & icmPe_lurv_clip) ? 1 : 0)


/* The different profile types are in their own source files, */
/* and are included to keep their functions private. (static) */
#include "xmono.c"
#include "xmatrix.c"
#include "xlut.c"		/* New xfit3 in & out optimising based profiles */
//#include "xlut1.c"	/* Old xfit1 device curve based profiles */

#ifdef NT		/* You'd think there might be some standards.... */
# ifndef __BORLANDC__
#  define stricmp _stricmp
# endif
#else
# define stricmp strcasecmp
#endif
/* Utilities */

/* Return a string description of the given enumeration value */
const char *icx2str(icmEnumType etype, int enumval) {

	if (etype == icmColorSpaceSig) {
		if (((icColorSpaceSignature)enumval) == icxSigJabData)
			return "Jab";
		else if (((icColorSpaceSignature)enumval) == icxSigJChData)
			return "JCh";
		else if (((icColorSpaceSignature)enumval) == icxSigLChData)
			return "LCh";
	} else if (etype == icmRenderingIntent) {
		if (((icRenderingIntent)enumval) == icxAppearance)
			return "icxAppearance";
		else if (((icRenderingIntent)enumval) == icxAbsAppearance)
			return "icxAbsAppearance";
		else if (((icRenderingIntent)enumval) == icxPerceptualAppearance)
			return "icxPerceptualAppearance";
		else if (((icRenderingIntent)enumval) == icxAbsPerceptualAppearance)
			return "icxAbsPerceptualAppearance";
		else if (((icRenderingIntent)enumval) == icxSaturationAppearance)
			return "icxSaturationAppearance";
		else if (((icRenderingIntent)enumval) == icxAbsSaturationAppearance)
			return "icxAbsSaturationAppearance";
	}
	return icm2str(etype, enumval);
}

/* Common xicc stuff */

/* Return information about the native lut in/out colorspaces. */
/* Any pointer may be NULL if value is not to be returned */
static void
icxLutSpaces(
	icxLuBase *p,					/* This */
	icColorSpaceSignature *ins,		/* Return input color space */
	int *inn,						/* Return number of input components */
	icColorSpaceSignature *outs,	/* Return output color space */
	int *outn,						/* Return number of output components */
	icColorSpaceSignature *pcs		/* Return PCS color space */
) {
	icmCSInfo ini, outi, pcsi;

	p->plu->native_spaces(p->plu, &ini, &outi, &pcsi);
	if (ins != NULL)
		*ins = ini.sig;
	if (inn != NULL)
		*inn = ini.nch;
	if (outs != NULL)
		*outs = outi.sig;
	if (outn != NULL)
		*outn = outi.nch;
	if (pcs != NULL)
		*pcs = pcsi.sig;
}

/* Return information about the overall lookup in/out colorspaces, */
/* including allowance for any PCS override. */
/* Any pointer may be NULL if value is not to be returned */
static void
icxLuSpaces(
	icxLuBase *p,					/* This */
	icColorSpaceSignature *ins,		/* Return input color space */
	int *inn,						/* Return number of input components */
	icColorSpaceSignature *outs,	/* Return output color space */
	int *outn,						/* Return number of output components */
	icmLuAlgType *alg,				/* Return type of lookup algorithm used */
    icRenderingIntent *intt,		/* Return the intent implemented */
    icmLookupFunc *fnc,				/* Return the profile function being implemented */
	icColorSpaceSignature *pcs		/* Return the effective PCS */
) {
	icmCSInfo ini, outi;
	icmPeOp op;
	int can_bwd;

	p->plu->spaces(p->plu, &ini, &outi, NULL, NULL, NULL, fnc, NULL, &op, &can_bwd); 

	if (ins != NULL)
		*ins = p->ins;
	if (inn != NULL)
		*inn = ini.nch;

	if (outs != NULL)
		*outs = p->outs;
	if (outn != NULL)
		*outn = outi.nch;

	if (intt != NULL)
		*intt = p->intent;

	if (pcs != NULL)
		*pcs = p->pcs;

	/* Fake up alg from icmLuBase attributes */
	if (alg != NULL)
		*alg = icmSynthAlgType((icmLuBase *)p->plu);
}

/* Return the native (internaly visible) colorspace value ranges */
static void
icxLu_get_native_ranges (
icxLuBase *p,
double *inmin, double *inmax,		/* Return maximum range of inspace values */
double *outmin, double *outmax		/* Return maximum range of outspace values */
) {
	int i;
	if (inmin != NULL) {
		for (i = 0; i < p->inputChan; i++)
			inmin[i] = p->ninmin[i];
	}
	if (inmax != NULL) {
		for (i = 0; i < p->inputChan; i++)
			inmax[i] = p->ninmax[i];
	}
	if (outmin != NULL) {
		for (i = 0; i < p->outputChan; i++)
			outmin[i] = p->noutmin[i];
	}
	if (outmax != NULL) {
		for (i = 0; i < p->outputChan; i++)
			outmax[i] = p->noutmax[i];
	}
}

/* Return the effective (externaly visible) colorspace value ranges */
static void
icxLu_get_ranges (
icxLuBase *p,
double *inmin, double *inmax,		/* Return maximum range of inspace values */
double *outmin, double *outmax		/* Return maximum range of outspace values */
) {
	int i;
	if (inmin != NULL) {
		for (i = 0; i < p->inputChan; i++)
			inmin[i] = p->inmin[i];
	}
	if (inmax != NULL) {
		for (i = 0; i < p->inputChan; i++)
			inmax[i] = p->inmax[i];
	}
	if (outmin != NULL) {
		for (i = 0; i < p->outputChan; i++)
			outmin[i] = p->outmin[i];
	}
	if (outmax != NULL) {
		for (i = 0; i < p->outputChan; i++)
			outmax[i] = p->outmax[i];
	}
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* Routine to figure out a suitable black point for CMYK */

/* Structure to hold optimisation information */
typedef struct {
	icmLuSpace *p;
	int kch;				/* K channel, -1 if none */
	double tlimit, klimit;	/* Ink limit values */
	int inn;				/* Number of input channels */
	icColorSpaceSignature outs;		/* Output space */
	double p1[3];			/* white pivot point in abs Lab */
	double p2[3];			/* Point on vector towards black in abs Lab */
	double toll;			/* Tollerance of black direction */
} bpfind;

/* Optimise device values to minimise L, while remaining */
/* within the ink limit, and staying in line between p1 (white) and p2 (black dir) */
static double bpfindfunc(void *adata, double pv[]) {
	bpfind *b = (bpfind *)adata;
	double rv = 0.0;
	double Lab[3];
	double lr, ta, tb, terr;	/* L ratio, target a, target b, target error */
	double ovr = 0.0;
	int e;

	/* Compute amount outside total limit */
	if (b->tlimit >= 0.0) {
		double sum;
		for (sum = 0.0, e = 0; e < b->inn; e++)
			sum += pv[e];
		if (sum > b->tlimit) {
			ovr = sum - b->tlimit;
#ifdef DEBUG
	printf("~1 total ink ovr = %f\n",ovr);
#endif
		}
	}

	/* Compute amount outside black limit */
	if (b->klimit >= 0.0 && b->kch >= 0) {
		double kval = pv[b->kch] - b->klimit;
		if (kval > ovr) {
			ovr = kval;
#ifdef DEBUG
	printf("~1 black ink ovr = %f\n",ovr);
#endif
		}
	}
	/* Compute amount outside device value limits 0.0 - 1.0 */
	{
		double dval;
		for (dval = -1.0, e = 0; e < b->inn; e++) {
			if (pv[e] < 0.0) {
				if (-pv[e] > dval)
					dval = -pv[e];
			} else if (pv[e] > 1.0) {
				if ((pv[e] - 1.0) > dval)
					dval = pv[e] - 1.0;
			}
		}
		if (dval > ovr)
			ovr = dval;
	}

	/* Compute the Lab value: */
	b->p->lookup_fwd(b->p, Lab, pv);
	if (b->outs == icSigXYZData)
		icmXYZ2Lab(&icmD50, Lab, Lab);

#ifdef DEBUG
	printf("~1 p1 =  %f %f %f, p2 = %f %f %f\n",b->p1[0],b->p1[1],b->p1[2],b->p2[0],b->p2[1],b->p2[2]);
	printf("~1 device value %f %f %f %f, Lab = %f %f %f\n",pv[0],pv[1],pv[2],pv[3],Lab[0],Lab[1],Lab[2]);
#endif

	/* Primary aim is to minimise L value */
	rv = Lab[0];

	/* See how out of line from p1 to p2 we are */
	lr = (Lab[0] - b->p1[0])/(b->p2[0] - b->p1[0]);		/* Distance towards p2 from p1 */
	ta = lr * (b->p2[1] - b->p1[1]) + b->p1[1];			/* Target a value */
	tb = lr * (b->p2[2] - b->p1[2]) + b->p1[2];			/* Target b value */

	terr = (ta - Lab[1]) * (ta - Lab[1])
	     + (tb - Lab[2]) * (tb - Lab[2]);
	
	if (terr < b->toll)		/* Tollerance error doesn't count until it's over tollerance */
		terr = 0.0;
	
#ifdef DEBUG
	printf("~1 target error %f\n",terr);
#endif
	rv += XICC_BLACK_FIND_ABERR_WEIGHT * terr;	/* Make ab match more important than min. L */

#ifdef DEBUG
	printf("~1 out of range error %f\n",ovr);
#endif
	rv += 200 * ovr;

#ifdef DEBUG
	printf("~1 black find tc ret %f\n",rv);
#endif
	return rv;
}

/* Try and compute a real black point in XYZ given an iccLu, */
/* and also return the K only black or the normal black if the device doesn't have K */
/* black[] will be unchanged if black cannot be computed. */
/* Note that the black point will be in the space of the Lu */
/* converted to XYZ, so will have the Lu's intent etc. */
/* (Note that this is duplicated in xlut.c set_icxLuLut() !!!) */
static void icxLu_comp_bk_point(
icxLuBase *x,
int gblk,				/* If nz, compute black if possible. */
double *white,			/* XYZ Input, used for computing black */
double *black,			/* XYZ Input & Output. Set if gblk NZ and can be computed */
double *kblack			/* XYZ Output. Looked up if possible or set to black[] otherwise */
) {
	icmLuSpace *p = x->plu;
	icmLuSpace *op = p;			/* Original icmLu, in case we replace p */
	icc *icco = p->icp;
	icmHeader *h = icco->header;
	icColorSpaceSignature ins, outs;
	int inn, outn;
	icRenderingIntent intt;
	icmLookupFunc fnc;
	icmLookupOrder ord;
	int kch = -1;
	double dblack[MAX_CHAN];	/* device black value */
	int e;

#ifdef DEBUG
	printf("~1 icxLu_comp_bk_point() called, gblk %d, white = %s, black = %s\n",gblk,icmPdv(3, white),icmPdv(3,black));
#endif
	/* Default return incoming black as K only black */
	kblack[0] = black[0];
	kblack[1] = black[1];
	kblack[2] = black[2];

	/* Get the effective characteristics of the Lu */
	{
		icmCSInfo ini, outi, pcsi;

		p->spaces(p, &ini, &outi, NULL, NULL, &intt, &fnc, &ord, NULL, NULL);

		ins = ini.sig;
		inn = ini.nch;
		outs = outi.sig;
		outn = outi.nch;
	}

	if (fnc == icmBwd) { /* Hmm. We've got PCS to device, and we want device to PCS. */

		/* Strictly speaking this is a dubious approach, since for a cLut profile */
		/* the B2A table could make the effective white and black points */
		/* anything it likes, and they don't have to match what the corresponding */
		/* A2B table does. In our usage it's probably OK, since we tend */
		/* to use colorimetric B2A */ 
#ifdef DEBUG
		printf("~1 getting icmFwd\n");
#endif
		if ((p = (icmLuSpace *)icco->get_luobj(icco, icmFwd, intt, ins, ord)) == NULL)
			error("icxLu_comp_bk_point: assert: getting Fwd Lookup failed!");

		{
			icmCSInfo ini, outi, pcsi;
	
			p->spaces(p, &ini, &outi, NULL, NULL, &intt, &fnc, &ord, NULL, NULL);
	
			ins = ini.sig;
			inn = ini.nch;
			outs = outi.sig;
			outn = outi.nch;
		}
	}

	if (outs != icSigXYZData && outs != icSigLabData) {
		error("icxLu_comp_bk_point: assert: icc Lu output is not XYZ or Lab!, outs = 0x%x, ");
	}

#ifdef DEBUG
	printf("~1 icxLu_comp_bk_point called for inn = %d, ins = %s\n", inn, icx2str(icmColorSpaceSig,ins));
#endif

	switch (ins) {

    	case icSigXYZData:
    	case icSigLabData:
    	case icSigLuvData:
    	case icSigYxyData:
#ifdef DEBUG
			printf("~1 Assuming CIE colorspace black is 0.0\n");
#endif
			if (gblk) {
				for (e = 0; e < inn; e++)
					black[0] = 0.0;
			}
			kblack[0] = black[0];
			kblack[1] = black[1];
			kblack[2] = black[2];
			return;

		case icSigRgbData:
#ifdef DEBUG
			printf("~1 RGB:\n");
#endif
			for (e = 0; e < inn; e++)
				dblack[e] = 0.0;
			break;

		case icSigGrayData: {	/* Could be additive or subtractive */
			double dval[1];
			double minv[3], maxv[3];
#ifdef DEBUG
			printf("~1 Gray:\n");
#endif
			/* Check out 0 and 100% colorant */
			dval[0] = 0.0;
			p->lookup_fwd(p, minv, dval);
			if (outs == icSigXYZData)
				icmXYZ2Lab(&icmD50, minv, minv);
			dval[0] = 1.0;
			p->lookup_fwd(p, maxv, dval);
			if (outs == icSigXYZData)
				icmXYZ2Lab(&icmD50, maxv, maxv);

			if (minv[0] < maxv[0])
				dblack[0] = 0.0;
			else
				dblack[0] = 1.0;
			}
			break;

		case icSigCmyData:
			for (e = 0; e < inn; e++)
				dblack[e] = 1.0;
			break;

		case icSigCmykData:
#ifdef DEBUG
			printf("~1 CMYK:\n");
#endif
			kch = 3;
			dblack[0] = 0.0;
			dblack[1] = 0.0;
			dblack[2] = 0.0;
			dblack[3] = 1.0;
			if (x->lutype == icxLuLutType) {
				icxLuLut *pp = (icxLuLut *)x;
		
				if (pp->ink.tlimit >= 0.0)
					dblack[kch] = pp->ink.tlimit;
			};
			break;

			/* Use a heursistic. */
			/* This duplicates code in icxGuessBlackChan() :-( */
			/* Colorant guessing should go in icclib ? */
		case icSig2colorData:
    	case icSig3colorData:
    	case icSig4colorData:
    	case icSig5colorData:
    	case icSig6colorData:
    	case icSig7colorData:
    	case icSig8colorData:
    	case icSig9colorData:
    	case icSig10colorData:
    	case icSig11colorData:
    	case icSig12colorData:
    	case icSig13colorData:
    	case icSig14colorData:
    	case icSig15colorData:
    	case icSigMch5Data:
    	case icSigMch6Data:
    	case icSigMch7Data:
    	case icSigMch8Data: {
				double dval[MAX_CHAN];
				double ncval[3];
				double cvals[MAX_CHAN][3];
				int nlighter, ndarker;

				/* Decide if the colorspace is additive or subtractive */
#ifdef DEBUG
				printf("~1 N channel:\n");
#endif

				/* First the no colorant value */
				for (e = 0; e < inn; e++)
					dval[e] = 0.0;
				p->lookup_fwd(p, ncval, dval);
				if (outs == icSigXYZData)
					icmXYZ2Lab(&icmD50, ncval, ncval);

				/* Then all the colorants */
				nlighter = ndarker = 0;
				for (e = 0; e < inn; e++) {
					dval[e] = 1.0;
					p->lookup_fwd(p, cvals[e], dval);
					if (outs == icSigXYZData)
						icmXYZ2Lab(&icmD50, cvals[e], cvals[e]);
					dval[e] = 0.0;
					if (fabs(cvals[e][0] - ncval[0]) > 5.0) {
						if (cvals[e][0] > ncval[0])
							nlighter++;
						else
							ndarker++;
					}
				}
				if (ndarker == 0 && nlighter > 0) {		/* Assume additive */
					for (e = 0; e < inn; e++)
						dblack[e] = 0.0;
#ifdef DEBUG
					printf("~1 N channel is additive:\n");
#endif

				} else if (ndarker > 0 && nlighter == 0) {				/* Assume subtractive. */
					double pbk[3] = { 0.0,0.0,0.0 };	/* Perfect black */
					double smd = 1e10;			/* Smallest distance */

#ifdef DEBUG
					printf("~1 N channel is subtractive:\n");
#endif
					/* See if we can guess the black channel */
					for (e = 0; e < inn; e++) {
						double tt;
						tt = icmNorm33sq(pbk, cvals[e]);
						if (tt < smd) {
							smd = tt;	
							kch = e;
						}
					}
					/* See if the black seems sane */
					if (cvals[kch][0] > 40.0
					 || fabs(cvals[kch][1]) > 10.0
					 || fabs(cvals[kch][2]) > 10.0) {
						if (p != op)
							p->del(p);
#ifdef DEBUG
						printf("~1 black doesn't look sanem so assume nothing\n");
#endif
						return;		/* Assume nothing */
					}

					/* Chosen kch as black */
					for (e = 0; e < inn; e++)
						dblack[e] = 0.0;
					dblack[kch] = 1.0;
					if (x->lutype == icxLuLutType) {
						icxLuLut *pp = (icxLuLut *)x;
			
						if (pp->ink.tlimit >= 0.0)
							dblack[kch] = pp->ink.tlimit;
					};
#ifdef DEBUG
					printf("~1 N channel K = chan %d\n",kch);
#endif
				} else {
					if (p != op)
						p->del(p);
#ifdef DEBUG
					printf("~1 can't figure if additive or subtractive, so assume nothing\n");
#endif
					return;			/* Assume nothing */
				}
			}
			break;

		default:
#ifdef DEBUG
			printf("~1 unhandled colorspace, so assume nothing\n");
#endif
			if (p != op)
				p->del(p);
			return;				/* Don't do anything */
	}

	/* Lookup the K only value */
	if (kch >= 0) {
		p->lookup_fwd(p, kblack, dblack);
		
		/* We always return XYZ */
		if (outs == icSigLabData)
			icmLab2XYZ(&icmD50, kblack, kblack);
	}

	if (gblk == 0) {		/* That's all we have to do */
#ifdef DEBUG
		printf("~1 gblk == 0, so only return kblack\n");
#endif
		if (p != op)
			p->del(p);
		return;
	}

	/* Lookup the device black or K only value as a default */
	p->lookup_fwd(p, black, dblack);		/* May be XYZ or Lab */
#ifdef DEBUG
	printf("~1 Got default lu black %f %f %f, kch = %d\n", black[0],black[1],black[2],kch);
#endif

	/* !!! Hmm. For CMY and RGB we are simply using the device */
	/* combination values as the black point. In reality we might */
	/* want to have the option of using a neutral black point, */
	/* just like CMYK ?? */

	if (kch >= 0) {	/* The space is subtractive with a K channel. */
		/* If XICC_NEUTRAL_CMYK_BLACK then locate the darkest */
		/* CMYK within limits with the same chromaticity as the white point, */
		/* otherwise locate the device value within the ink limits that is */
		/* in the direction of the K channel */
		bpfind bfs;					/* Callback context */
		double sr[MXDO];			/* search radius */
		double tt[MXDO];			/* Temporary */
		double rs0[MXDO], rs1[MXDO];	/* Random start candidates */
		int trial;
		double brv;

		/* Setup callback function context */
		bfs.p = p;
		bfs.inn = inn;
		bfs.outs = outs;

		bfs.kch = kch;
		bfs.tlimit = -1.0;
		bfs.klimit = -1.0;
		bfs.toll = XICC_BLACK_POINT_TOLL;

		if (x->lutype == icxLuLutType) {
			icxLuLut *pp = (icxLuLut *)x;

			pp->kch = kch;
			bfs.tlimit = pp->ink.tlimit;
			bfs.klimit = pp->ink.klimit;
#ifdef DEBUG
			printf("~1 tlimit = %f, klimit = %f\n",bfs.tlimit,bfs.klimit);
#endif
		};
	
#ifdef XICC_NEUTRAL_CMYK_BLACK
#ifdef DEBUG
		printf("~1 Searching for neutral black\n");
#endif
		/* white has been given to us in XYZ */
		icmXYZ2Lab(&icmD50, bfs.p1, white);		/* pivot Lab */
		icmCpy3(bfs.p2, white);					/* temp white XYZ */
		icmScale3(bfs.p2, bfs.p2, 0.02);	 /* Scale white XYZ towards 0,0,0 */
		icmXYZ2Lab(&icmD50, bfs.p2, bfs.p2); /* Convert black direction to Lab */

#else /* Use K directin black */
#ifdef DEBUG
		printf("~1 Searching for K direction black\n");
#endif
		icmXYZ2Lab(&icmD50, bfs.p1, white);			/* Pivot */

		/* Now figure abs Lab value of K only, as the direction */
		/* to use for the rich black. */
		for (e = 0; e < inn; e++)
			dblack[e] = 0.0;
		if (bfs.klimit < 0.0)
			dblack[kch] = 1.0;
		else
			dblack[kch] = bfs.klimit;		/* K value */

		p->lookup(p, black, dblack);

		if (outs == icSigXYZData) {
			icmXYZ2Lab(&icmD50, bfs.p2, black);		/* K direction */
		} else {
			icmAry2Ary(bfs.p2, black);
		}
#endif

#ifdef DEBUG
		printf("~1 Lab pivot %f %f %f, Lab K direction %f %f %f\n",bfs.p1[0],bfs.p1[1],bfs.p1[2],bfs.p2[0],bfs.p2[1],bfs.p2[2]);
#endif
		/* Set the random start 0 location as 000K */
		/* and the random start 1 location as CMY0 */
		{
			double tt;

			for (e = 0; e < inn; e++)
				dblack[e] = rs0[e] = 0.0;
			if (bfs.klimit < 0.0)
				dblack[kch] = rs0[kch] = 1.0;
			else
				dblack[kch] = rs0[kch] = bfs.klimit;		/* K value */

			if (bfs.tlimit < 0.0)
				tt = 1.0;
			else
				tt = bfs.tlimit/(inn - 1.0);
			for (e = 0; e < inn; e++)
				rs1[e] = tt;
			rs1[kch] = 0.0;		/* K value */
		}

		/* Start with the K only as the current best value */
		brv = bpfindfunc((void *)&bfs, dblack);
#ifdef DEBUG
		printf("~1 initial brv for K only = %f\n",brv);
#endif

		/* Find the device black point using optimization */
		/* Do several trials to avoid local minima. */
		rand32(0x12345678);	/* Make trial values deterministic */
		for (trial = 0; trial < 200; trial++) {
			double rv;			/* Temporary */

			/* Start first trial at 000K */
			if (trial == 0) {
				for (e = 0; e < inn; e++) {
					tt[e] = rs0[e];
					sr[e] = 0.1;
				}

			} else {
				/* Base is random between 000K and CMY0: */
				if (trial < 100) {
					rv = d_rand(0.0, 1.0);
					for (e = 0; e < inn; e++) {
						tt[e] = rv * rs0[e] + (1.0 - rv) * rs1[e];
						sr[e] = 0.1;
					}
				/* Base on current best */
				} else {
					for (e = 0; e < inn; e++) {
						tt[e] = dblack[e];
						sr[e] = 0.1;
					}
				}
	
				/* Then add random start offset */
				for (rv = 0.0, e = 0; e < inn; e++) {
					tt[e] +=  d_rand(-0.5, 0.5);
					if (tt[e] < 0.0)
						tt[e] = 0.0;
					else if (tt[e] > 1.0)
						tt[e] = 1.0;
				}
			}

			/* Clip black */
			if (bfs.klimit >= 0.0 && tt[kch] > bfs.klimit)
				tt[kch] = bfs.klimit;	
			
			/* Compute amount outside total limit */
			if (bfs.tlimit >= 0.0) {
				for (rv = 0.0, e = 0; e < inn; e++)
					rv += tt[e];
		
				if (rv > bfs.tlimit) {
					rv /= (double)inn;
					for (e = 0; e < inn; e++)
						tt[e] -= rv;
				}
			}
	
			if (powell(&rv, inn, tt, sr, 0.000001, 1000, bpfindfunc,
			                      (void *)&bfs, NULL, NULL) == 0) {
#ifdef DEBUG
				printf("~1 trial %d, rv %f bp %f %f %f %f\n",trial,rv,tt[0],tt[1],tt[2],tt[3]);
#endif
				if (rv < brv) {
#ifdef DEBUG
					printf("~1   new best\n");
#endif
					brv = rv;
					for (e = 0; e < inn; e++)
						dblack[e] = tt[e];
				}
			}
		}
		if (brv > 1000.0)
			error("icxLu_comp_bk_point: Black point powell failed");

		for (e = 0; e < inn; e++) { /* Make sure device values are in range */
			if (dblack[e] < 0.0)
				dblack[e] = 0.0;
			else if (dblack[e] > 1.0)
				dblack[e] = 1.0;
		}
		/* Now have device black in dblack[] */
#ifdef DEBUG
		printf("~1 got device black %f %f %f %f\n",dblack[0], dblack[1], dblack[2], dblack[3]);
#endif

		p->lookup_fwd(p, black, dblack);		/* Convert to PCS */
	}

	if (p != op)
		p->del(p);

	/* We always return XYZ */
	if (outs == icSigLabData)
		icmLab2XYZ(&icmD50, black, black);

#ifdef DEBUG
	printf("~1 returning %f %f %f\n", black[0], black[1], black[2]);
#endif

	return;
}

/* - - - - - - - - - - - - - - - - - - - - - - - */

/* Return the media white and black points */
/* in the xlu effective PCS colorspace. Pointers may be NULL. */
/* (ie. these will be relative values for relative intent etc.) */
static void icxLuEfv_wh_bk_points(
icxLuBase *p,
double *wht,
double *blk,
double *kblk		/* K only black */
) {
	double white[3], black[3], kblack[3];

	/* Get the Lu PCS converted to XYZ icc black and white points in XYZ */
	if (p->plu->lu_wh_bk_points(p->plu, NULL, white, black))
	{
		/* Black point is assumed. We should determine one instead. */
		/* Lookup K only black too */
		icxLu_comp_bk_point(p, 1, white, black, kblack);
	} else {
		/* Lookup a possible K only black */
		icxLu_comp_bk_point(p, 0, white, black, kblack);
	}

	/* Convert to possibl xicc override PCS */
	switch ((int)p->pcs) {
		case icSigXYZData:
			break;								/* Don't have to do anyting */
		case icSigLabData:
			icmXYZ2Lab(&icmD50, white, white);	/* Convert from XYZ to Lab */
			icmXYZ2Lab(&icmD50, black, black);
			icmXYZ2Lab(&icmD50, kblack, kblack);
			break;
		case icxSigJabData:
			p->cam->XYZ_to_cam(p->cam, white, white);	/* Convert from XYZ to Jab */
			p->cam->XYZ_to_cam(p->cam, black, black);
			p->cam->XYZ_to_cam(p->cam, kblack, kblack);
			break;
		default:
			break;
	}

	if (wht != NULL) {
		wht[0] = white[0];
		wht[1] = white[1];
		wht[2] = white[2];
	}

	if (blk != NULL) {
		blk[0] = black[0];
		blk[1] = black[1];
		blk[2] = black[2];
	}

	if (kblk != NULL) {
		kblk[0] = kblack[0];
		kblk[1] = kblack[1];
		kblk[2] = kblack[2];
	}
}

/* Do away with the xicc (but not the icc!) */
static void xicc_del(
xicc *p
) {
	if (p->cal != NULL)
		p->cal->del(p->cal);
	free (p);
}

/* If given cal is not NULL, override or set the xicc cal. */
/* (This takes a reference) */
static void xicc_set_cal_curves(xicc *p,
	xcal *cal
) {
	if (cal != NULL) {
		if (p->cal != NULL)
			p->cal->del(p->cal);
		p->cal = cal->ref(cal);
	}
}

/* Return the currently set cal */
/* (It's up to the caller to take a reference if they need to) */
static void xicc_get_cal_curves(xicc *p,
	xcal **cal
) {
	if (cal != NULL)
		*cal = p->cal;
}

/* Create an instance of an xicc object */
xicc *new_xicc(
icc *picc		/* icc we are expanding */
) {
	xicc *p;
	if ((p = (xicc *) calloc(1,sizeof(xicc))) == NULL)
		return NULL;
	p->pp = picc;
	p->del            = xicc_del;
	p->set_cal_curves = xicc_set_cal_curves;
	p->get_cal_curves = xicc_get_cal_curves;
	p->get_luobj      = xicc_get_luobj;
	//p->get_viewcond  = xicc_get_viewcond;		/* Dev code */

	/* Create an xcal if it is in the profile 'targ' .ti3 data */
	p->cal = xiccReadCalTag(p->pp);


	return p;
}

/* return nz if the intent implies Jab space */
int xiccIsIntentJab(icRenderingIntent intent) {

	if (intent == icxAppearance
	 || intent == icxAbsAppearance
	 || intent == icxPerceptualAppearance
	 || intent == icxAbsPerceptualAppearance
	 || intent == icxSaturationAppearance
	 || intent == icxAbsSaturationAppearance)
		return 1;
	return 0;
}

/* Return an expanded lookup object, initialised */
/* from the icc. */
/* Return NULL on error, check e.c+e.m for reason. */
/* Set the pcsor & intent to consistent and values if */
/* Jab and/or icxAppearance has been requested. */
/* Create the underlying icm lookup object that is used */
/* to create and implement the icx one. The icm will be used */
/* to translate from native to effective PCS, unless the */
/* effective PCS is Jab, in which case the icm will be set to */
/* have an effective PCS of XYZ. Since native<->effecive PCS conversion */
/* is done at the to/from_abs() stage, none of this affects the individual */
/* conversion steps, which will all talk the native PCS (unless merged). */
icxLuBase *xicc_get_luobj(
xicc *p,					/* this */
int flags,					/* clip, merge flags */
icmLookupFunc func,			/* Functionality */
icRenderingIntent intent,	/* Intent */
icColorSpaceSignature pcsor,/* PCS override (0 = def) */
icmLookupOrder order,		/* Search Order */
icxViewCond *vc,			/* Viewing Condition (may be NULL if pcsor is not CIECAM) */
icxInk *ink					/* inking details (NULL for default) */
) {
	icmLuSpace *plu;
	icxLuBase *xplu;
	icmLuAlgType alg;
	icRenderingIntent n_intent = intent;			/* Native Intent to request */
	icColorSpaceSignature n_pcs = icmSigDefaultData;	/* Native PCS to request */

//printf("~1 xicc_get_luobj got intent '%s' and pcsor '%s'\n",icx2str(icmRenderingIntent,intent),icx2str(icmColorSpaceSig,pcsor));

	/* Ensure that appropriate PCS is slected for an appearance intent */
	if (xiccIsIntentJab(intent)) {		
		pcsor = icxSigJabData;
//printf("~1 pcsor = %s\n",tag2str(pcsor));

	/* Translate non-Jab intents to the equivalent appearance "intent" if pcsor == Jab. */
	/* This is how we get these when the UI's don't list all the apperances intents, */
	/* we select the analogous non-apperance intent with pcsor = Jab. */
	/* Note that Abs/non-abs selects between Apperance and AbsAppearance. */
	} else if (pcsor == icxSigJabData) {
		if (intent == icRelativeColorimetric)
			intent = icxAppearance;
		else if (intent == icAbsoluteColorimetric)
			intent = icxAbsAppearance;
		else if (intent == icPerceptual)
			intent = icxPerceptualAppearance;
		else if (intent == icmAbsolutePerceptual)
			intent = icxAbsPerceptualAppearance;
		else if (intent == icSaturation)
			intent = icxSaturationAppearance;
		else if (intent == icmAbsoluteSaturation)
			intent = icxAbsSaturationAppearance;
		else
			intent = icxAppearance;
	}
//printf("~1 intent = %s\n",tag2str(intent));

	/* Translate intent asked for into intent needed in icclib */
	if      (intent == icxAppearance
	      || intent == icxAbsAppearance)
		n_intent = icAbsoluteColorimetric;
	else if (intent == icxPerceptualAppearance
	      || intent == icxAbsPerceptualAppearance)
		n_intent = icmAbsolutePerceptual;
	else if (intent == icxSaturationAppearance
	      || intent == icxAbsSaturationAppearance)
		n_intent = icmAbsoluteSaturation;
//printf("~1 n_intent = %s\n",tag2str(n_intent));

	if (pcsor != icmSigDefaultData)
		n_pcs = pcsor;			/* There is an icclib override */

	if (pcsor == icxSigJabData)	/* xicc override */
		n_pcs = icSigXYZData;	/* Translate to XYZ */

#ifdef NEVER
	printf("xicc_get_luobj processed intent %s and pcsor %s\n",icx2str(icmRenderingIntent,intent),icx2str(icmColorSpaceSig,pcsor));
	printf("xicc_get_luobj icclib intent %s and pcsor %s\n",icx2str(icmRenderingIntent,n_intent),icx2str(icmColorSpaceSig,n_pcs));
#endif
	/* Get icclib lookup object */
	if ((plu = (icmLuSpace *)p->pp->get_luobj(p->pp, func, n_intent, n_pcs, order)) == NULL) {
		p->e.c = p->pp->e.c;		/* Copy error */
		strcpy(p->e.m, p->pp->e.m);
		return NULL;
	}

	/* Figure out what the algorithm is */
	{
		icmCSInfo pcsi;
		plu->spaces(plu, NULL, NULL, &pcsi, NULL, NULL, NULL, NULL, NULL, NULL);
		n_pcs = pcsi.sig;
		alg = icmSynthAlgType((icmLuBase *)plu);
	}

	/* make sure its "Abs CAM" */
	if (vc!= NULL
	 && (intent == icxAbsAppearance
	  || intent == icxAbsPerceptualAppearance
	  || intent == icxAbsSaturationAppearance)) {	/* make sure its "Abs CAM" */
//printf("~1 xicc_get_luobj using absolute apperance space with white = D50\n");
		/* Set white point and flare color to D50 */
		/* (Hmm. This doesn't match what happens within collink with absolute intent!!) */
		vc->Wxyz[0] = icmD50.X/icmD50.Y;
		vc->Wxyz[1] = icmD50.Y/icmD50.Y;	// Normalise white reference to Y = 1 ?
		vc->Wxyz[2] = icmD50.Z/icmD50.Y;
	
		vc->Gxyz[0] = icmD50.X;
		vc->Gxyz[1] = icmD50.Y;
		vc->Gxyz[2] = icmD50.Z;
	}

	/* Call xiccLu wrapper creation */
	switch (alg) {
		case icmMonoFwdType:
			xplu = new_icxLuMono(p, flags, plu, func, intent, pcsor, vc, 0);
			break;
		case icmMonoBwdType:
			xplu = new_icxLuMono(p, flags, plu, func, intent, pcsor, vc, 1);
			break;
    	case icmMatrixFwdType:
			xplu = new_icxLuMatrix(p, flags, plu, func, intent, pcsor, vc, 0);
			break;
    	case icmMatrixBwdType:
			xplu = new_icxLuMatrix(p, flags, plu, func, intent, pcsor, vc, 1);
			break;
    	case icmLutType:
			xplu = new_icxLuLut(p, flags, plu, func, intent, pcsor, vc, ink);
			break;
		default:
			xplu = NULL;
			break;
	}

	return xplu;
}



/* ------------------------------------------------------ */
/* Viewing Condition Parameter stuff */

#ifdef NEVER	/* Not currently used */

/* Guess viewing parameters from the technology signature */
static void guess_from_techsig(
icTechnologySignature tsig,
double *Ybp
) {
double Yb = -1.0;

	switch (tsig) {
		/* These are all inputing either a representation of */
		/* a natural scene captured on another medium, or are assuming */
		/* that the medium is the original. A _good_ system would */
		/* let the user indicate which is the case. */
		case icSigReflectiveScanner:
		case icSigFilmScanner:
			Yb = 0.2;
			break;

		/* Direct scene to value devices. */
		case icSigDigitalCamera:
		case icSigVideoCamera:
			Yb = 0.2;
			break;

		/* Emmisive displays. */
		/* We could try tweaking the white point on the assumption */
		/* that the viewer will be adapted to a combination of both */
		/* the CRT white point, and the ambient light. */
		case icSigVideoMonitor:
		case icSigCRTDisplay:
		case icSigPMDisplay:
		case icSigAMDisplay:
			Yb = 0.2;
			break;

		/* Photo CD has its own viewing definitions */
		/* (It represents original scene colors) */
		case icSigPhotoCD:
			Yb = 0.2;
			break;

		/* Projection devices, either direct, or */
		/* via another intermediate medium. */
		case icSigProjectionTelevision:
			Yb = 0.1;		/* Assume darkened room, little background */
			break;
		case icSigFilmWriter:
			Yb = 0.0;		/* Assume a dark room - no background */
			break;

		/* Printed media devices. */
		case icSigInkJetPrinter:
		case icSigThermalWaxPrinter:
		case icSigElectrophotographicPrinter:
		case icSigElectrostaticPrinter:
		case icSigDyeSublimationPrinter:
		case icSigPhotographicPaperPrinter:
		case icSigPhotoImageSetter:
		case icSigGravure:
		case icSigOffsetLithography:
		case icSigSilkscreen:
		case icSigFlexography:
			Yb = 0.2;
			break;

		default:
			Yb = 0.2;
	}

	if (Ybp != NULL)
		*Ybp = Yb;
}

#endif /* NEVER */

#ifdef NEVER		/* Development code */

/* See if we can read or guess the viewing conditions */
/* for an ICC profile. */
/* Return value 0 if it is well defined */
/* Return value 1 if it is a guess */
/* Return value 2 if it is not possible/appropriate */
int xicc_get_viewcond(
xicc *p,			/* Expanded profile we're working with */
icxViewCond *vc		/* Viewing parameters to return */
) {
	icc *pp = p->pp;	/* Base ICC */

	/* Numbers we're trying to find */
	ViewingCondition Ev = vc_none;
	double Wxyz[3] = {-1.0, -1.0, -1.0};	/* Adapting white color */
	double La = -1.0;						/* Adapting/Surround luminance */
	double Ixyz[3] = {-1.0, -1.0, -1.0};	/* Illuminant color */
	double Li = -1.0;						/* Illuminant luminance */
	double Lb = -1.0;		/* Backgrount luminance */
	double Yb = -1.0;		/* Background relative luminance to Lv */
	double Lve = -1.0;		/* Emissive device image luminance */
	double Lvr = -1.0;		/* Reflective device image luminance */
	double Lv = -1.0;		/* Device image luminance */
	double Yf = -1.0;		/* Flare relative luminance to Lv */
	double Yg = -1.0;		/* Glare relative luminance to La */
	double Gxyz[3] = {-1.0, -1.0, -1.0};	/* Glare color */
	int hk = XICC_USE_HK;
	double hkscale = 1.0;
    icTechnologySignature tsig = icMaxEnumTechnology; /* Technology Signature */
    icProfileClassSignature devc = icMaxEnumClass;
	int trans = -1;		/* Set to 0 if not transparency, 1 if it is */

	/* Collect all the information we can find */

	/* Emmisive devices image white luminance */
	{
		icmXYZArray *luminanceTag;

		if ((luminanceTag = (icmXYZArray *)pp->read_tag(pp, icSigLuminanceTag)) != NULL
         && luminanceTag->ttype == icSigXYZType && luminanceTag->count >= 1) {
			Lve = luminanceTag->data[0].Y;	/* Copy structure */
		} 
	}

	/* Flare: */
	{
		icmMeasurement *ro;

		if ((ro = (icmMeasurement *)pp->read_tag(pp, icSigMeasurementTag)) != NULL
		 && ro->ttype == icSigMeasurementType) {
	
    		Yf = 0.0 * ro->flare;		// ?????
    		Yg = 1.0 * ro->flare;		// ?????
   			/* ro->illuminant ie D50, D65, D93, A etc. */
		}
	}

	/* Media White Point */
	{
		icmXYZNumber wp;
		int wpassumed;

		if (pp->get_wb_points(pp, &wpassumed, &wp, NULL, NULL, NULL, NULL) == ICM_ERR_OK) {
			icmXYZ2Ary(Wxyz, wp);
		}  
	}

	/* ViewingConditions: */
	{
		icmViewingConditions *ro;

		if ((ro = (icmViewingConditions *)pp->read_tag(pp, icSigViewingConditionsTag)) != NULL
		 && ro->ttype == icSigViewingConditionsType) {
	
   			/* ro->illuminant.X */
   			/* ro->illuminant.Z */

    		Li = ro->illuminant.Y;

			/* Reflect illuminant off the media white */
    		Lvr = Li * Wxyz[1];

			/* Illuminant color */
			Ixyz[0] = ro->illuminant.X/ro->illuminant.Y;
			Ixyz[1] = 1.0;
			Ixyz[2] = ro->illuminant.Z/ro->illuminant.Y;
			
			/* Assume ICC surround is CICAM97 background */
    		/* ro->surround.X */
    		/* ro->surround.Z */
    		La = ro->surround.Y;

    		/* ro->stdIlluminant ie D50, D65, D93, A etc. */
		}
	}

	/* Stuff we might need */

	/* Technology: */
	{
		icmSignature *ro;

		/* Try and read the tag from the file */
		if ((ro = (icmSignature *)pp->read_tag(pp, icSigTechnologyTag)) != NULL
		 && ro->ttype != icSigSignatureType) {

		tsig = ro->sig;
		}
	}

    devc = pp->header->deviceClass;	/* Type of profile */
	if (devc == icSigLinkClass
	 || devc == icSigAbstractClass
	 || devc == icSigColorSpaceClass
	 || devc == icSigNamedColorClass)
		return 2;

	/*
    	icSigInputClass
    	icSigDisplayClass
    	icSigOutputClass
	*/

	if ((pp->header->flags & icTransparency) != 0)
		trans = 1;
	else
		trans = 0;


	/* figure Lv if we have the information */
	if (Lve >= 0.0)
		Lv = Lve;		/* Emmisive image white luminance */
	else
		Lv = Lvr;		/* Reflectance image white luminance */

	/* Fudge the technology signature */
	if (tsig == icMaxEnumTechnology) {
		if (devc == icSigDisplayClass)
			tsig = icSigCRTDisplay;
	}

#ifndef NEVER
	printf("Enumeration = %d\n", Ev);
	printf("Viewing Conditions:\n");
	printf("White adaptation color %f %f %f\n",Wxyz[0], Wxyz[1], Wxyz[2]);
	printf("Adapting Luminance La = %f\n",La);
	printf("Illuminant color %f %f %f\n",Ixyz[0], Ixyz[1], Ixyz[2]);
	printf("Illuminant Luminance Li = %f\n",Li);
	printf("Background Luminance Lb = %f\n",Lb);
	printf("Relative Background Yb = %f\n",Yb);
	printf("Emissive Image White Lve = %f\n",Lve);
	printf("Reflective Image White Lvr = %f\n",Lvr);
	printf("Device Image White Lv = %f\n",Lv);
	printf("Relative Flare Yf = %f\n",Yf);
	printf("Relative Glare Yg = %f\n",Yg);
	printf("Glare color %f %f %f\n",Gxyz[0], Gxyz[1], Gxyz[2]);
	printf("Technology = %s\n", icmtag2str(tsig));
	printf("deviceClass = %s\n",icmtag2str(devc));
	printf("Transparency = %d\n",trans);
	// hk ? hkscale ?
#endif
	
	/* See if the viewing conditions are completely defined as ICC can do it */
	if (Wxyz[0] >= 0.0 && Wxyz[1] >= 0.0 && Wxyz[2] >= 0.0
	 && La >= 0.0
	 && Yb >= 0.0
	 && Lv >= 0.0
	 && Yf >= 0.0
	 && Yg >= 0.0
	 && Gxyz[0] >= 0.0 && Gxyz[1] >= 0.0 && Gxyz[2] >= 0.0) {

		vc->Ev = vc_none;
		vc->Wxyz[0] = Wxyz[0];
		vc->Wxyz[1] = Wxyz[1];
		vc->Wxyz[2] = Wxyz[2];
		vc->La = La;
		vc->Yb = Yb;
		vc->Lv = Lv;
		vc->Yf = Yf;
		vc->Yg = Yg;
		vc->Gxyz[0] = Gxyz[0];	
		vc->Gxyz[1] = Gxyz[1];	
		vc->Gxyz[2] = Gxyz[2];	
		vc->hk = hk;
		vc->hkscale = hkscale;
		return 0;
	}

	/* Hmm. We didn't get all the info an ICC can contain. */
	/* We will try to guess some reasonable defaults */

	/* Have we at least got an adaptation white point ? */
	if (Wxyz[0] < 0.0 || Wxyz[1] < 0.0 || Wxyz[2] < 0.0)
		return 2;						/* No */

	/* Have we got the technology ? */
	if (tsig == icMaxEnumTechnology)
		return 2;						/* Hopeless */

	/* Guess from the technology */
	switch (tsig) {

		/* This is inputing either a representation of */
		/* a natural scene captured on another a print medium, or */
		/* are is assuming that the medium is the original. */
		/* We will assume that the print is the original. */
		case icSigReflectiveScanner:
			{
				if (La < 0.0)	/* No adapting luminance */
					La = 34.0;	/* Use a practical print evaluation number */
				if (Yb < 0.0)	/* No background relative luminance */
					Yb = 0.2;	/* Assume grey world */
				if (Lv < 0.0)	/* No device image luminance */
					Ev = vc_average;	/* Assume average viewing conditions */
				if (Yf < 0.0)	/* No flare figure */
					Yf = 0.0;	/* Assume 0% flare */
				if (Yg < 0.0)	/* No glare figure */
					Yg = 0.01;	/* Assume 1% glare */
				if (Gxyz[0] < 0.0 || Gxyz[1] < 0.0 || Gxyz[2] < 0.0)	/* No flare color */
					Gxyz[0] = Wxyz[0], Gxyz[1] = Wxyz[1], Gxyz[2] = Wxyz[2];
				break;
			}

		/* This is inputing either a representation of */
		/* a natural scene captured on another a photo medium, or */
		/* are is assuming that the medium is the original. */
		/* We will assume a compromise media original, natural scene */
		case icSigFilmScanner:
			{
				if (La < 0.0)	/* No adapting luminance */
					La = 50.0;	/* Use bright indoors, dull outdoors */
				if (Yb < 0.0)	/* No background relative luminance */
					Yb = 0.2;	/* Assume grey world */
				if (Lv < 0.0)	/* No device image luminance */
					Ev = vc_average;	/* Assume average viewing conditions */
				if (Yf < 0.0)	/* No flare figure */
					Yf = 0.0;	/* Assume 0% flare */
				if (Yg < 0.0)	/* No glare figure */
					Yg = 0.01;	/* Assume 1% glare */
				if (Gxyz[0] < 0.0 || Gxyz[1] < 0.0 || Gxyz[2] < 0.0)	/* No flare color */
					Gxyz[0] = Wxyz[0], Gxyz[1] = Wxyz[1], Gxyz[2] = Wxyz[2];
				break;
			}

		/* Direct scene to value devices. */
		case icSigDigitalCamera:
		case icSigVideoCamera:
			{
				if (La < 0.0)	/* No adapting luminance */
					La = 110.0;	/* Use very bright indoors, usual outdoors */
				if (Yb < 0.0)	/* No background relative luminance */
					Yb = 0.2;	/* Assume grey world */
				if (Lv < 0.0)	/* No device image luminance */
					Ev = vc_average;	/* Assume average viewing conditions */
				if (Yf < 0.0)	/* No flare figure */
					Yf = 0.0;	/* Assume 0% flare */
				if (Yg < 0.0)	/* No glare figure */
					Yg = 0.01;	/* Assume 1% glare */
				if (Gxyz[0] < 0.0 || Gxyz[1] < 0.0 || Gxyz[2] < 0.0)	/* No flare color */
					Gxyz[0] = Wxyz[0], Gxyz[1] = Wxyz[1], Gxyz[2] = Wxyz[2];
				if (Gxyz[0] < 0.0 || Gxyz[1] < 0.0 || Gxyz[2] < 0.0)	/* No flare color */
					Gxyz[0] = Wxyz[0], Gxyz[1] = Wxyz[1], Gxyz[2] = Wxyz[2];
				break;
			}

		/* Emmisive displays. */
		/* Assume a video monitor is in a darker environment than a CRT */
		case icSigVideoMonitor:
			{
				if (La < 0.0)	/* No adapting luminance */
					La = 4.0;	/* Darkened work environment */
				if (Yb < 0.0)	/* No background relative luminance */
					Yb = 0.2;	/* Assume grey world */
				if (Lv < 0.0)	/* No device image luminance */
					Ev = vc_dim;	/* Assume dim viewing conditions */
				if (Yf < 0.0)	/* No flare figure */
					Yf = 0.0;	/* Assume 0% flare */
				if (Yg < 0.0)	/* No glare figure */
					Yg = 0.01;	/* Assume 1% glare */
				if (Gxyz[0] < 0.0 || Gxyz[1] < 0.0 || Gxyz[2] < 0.0)	/* No flare color */
					Gxyz[0] = Wxyz[0], Gxyz[1] = Wxyz[1], Gxyz[2] = Wxyz[2];
				break;
			}


		/* Assume a typical work environment */
		case icSigCRTDisplay:
		case icSigPMDisplay:
		case icSigAMDisplay:
			{
				if (La < 0.0)	/* No adapting luminance */
					La = 33.0;	/* Typical work environment */
				if (Yb < 0.0)	/* No background relative luminance */
					Yb = 0.2;	/* Assume grey world */
				if (Lv < 0.0)	/* No device image luminance */
					Ev = vc_average;	/* Assume average viewing conditions */
				if (Yf < 0.0)	/* No flare figure */
					Yf = 0.0;	/* Assume 0% flare */
				if (Yg < 0.0)	/* No glare figure */
					Yg = 0.01;	/* Assume 1% glare */
				if (Gxyz[0] < 0.0 || Gxyz[1] < 0.0 || Gxyz[2] < 0.0)	/* No flare color */
					Gxyz[0] = Wxyz[0], Gxyz[1] = Wxyz[1], Gxyz[2] = Wxyz[2];
				break;
			}

		/* Photo CD has its own viewing definitions */
		/* (It represents original scene colors) */
		case icSigPhotoCD:
			{
				if (La < 0.0)	/* No adapting luminance */
					La = 320.0;	/* Bright outdoors */
				if (Yb < 0.0)	/* No background relative luminance */
					Yb = 0.2;	/* Assume grey world */
				if (Lv < 0.0)	/* No device image luminance */
					Ev = vc_average;	/* Assume average viewing conditions */
				if (Yf < 0.0)	/* No flare figure */
					Yf = 0.0;	/* Assume 0% flare */
				if (Yg < 0.0)	/* No glare figure */
					Yg = 0.0;	/* Assume 0% glare */
				if (Gxyz[0] < 0.0 || Gxyz[1] < 0.0 || Gxyz[2] < 0.0)	/* No flare color */
					Gxyz[0] = Wxyz[0], Gxyz[1] = Wxyz[1], Gxyz[2] = Wxyz[2];
				break;
			}

		/* Projection devices, either direct, or */
		/* via another intermediate medium. */
		/* Assume darkened room, little background */
		case icSigProjectionTelevision:
			{
				if (La < 0.0)	/* No adapting luminance */
					La = 7.0;	/* Dark environment */
				if (Yb < 0.0)	/* No background relative luminance */
					Yb = 0.1;	/* Assume little background */
				if (Lv < 0.0)	/* No device image luminance */
					Ev = vc_dim;	/* Dim environment */
				if (Yf < 0.0)	/* No flare figure */
					Yf = 0.0;	/* Assume 0% flare */
				if (Yg < 0.0)	/* No glare figure */
					Yg = 0.01;	/* Assume 1% glare */
				if (Gxyz[0] < 0.0 || Gxyz[1] < 0.0 || Gxyz[2] < 0.0)	/* No flare color */
					Gxyz[0] = Wxyz[0], Gxyz[1] = Wxyz[1], Gxyz[2] = Wxyz[2];
				break;
			}
		/* Assume very darkened room, no background */
		case icSigFilmWriter:
			{
				if (La < 0.0)	/* No adapting luminance */
					La = 7.0;	/* Dark environment */
				if (Yb < 0.0)	/* No background relative luminance */
					Yb = 0.0;	/* Assume no background */
				if (Lv < 0.0)	/* No device image luminance */
					Ev = vc_dark;	/* Dark environment */
				if (Yf < 0.0)	/* No flare figure */
					Yf = 0.0;	/* Assume 0% flare */
				if (Yg < 0.0)	/* No glare figure */
					Yg = 0.01;	/* Assume 1% glare */
				if (Gxyz[0] < 0.0 || Gxyz[1] < 0.0 || Gxyz[2] < 0.0)	/* No flare color */
					Gxyz[0] = Wxyz[0], Gxyz[1] = Wxyz[1], Gxyz[2] = Wxyz[2];
				break;
			}

		/* Printed media devices. */
		/* Assume a normal print viewing environment */
		case icSigInkJetPrinter:
		case icSigThermalWaxPrinter:
		case icSigElectrophotographicPrinter:
		case icSigElectrostaticPrinter:
		case icSigDyeSublimationPrinter:
		case icSigPhotographicPaperPrinter:
		case icSigPhotoImageSetter:
		case icSigGravure:
		case icSigOffsetLithography:
		case icSigSilkscreen:
		case icSigFlexography:
			{
				if (La < 0.0)	/* No adapting luminance */
					La = 40.0;	/* Use a practical print evaluation number */
				if (Yb < 0.0)	/* No background relative luminance */
					Yb = 0.2;	/* Assume grey world */
				if (Lv < 0.0)	/* No device image luminance */
					Ev = vc_average;	/* Assume average viewing conditions */
				if (Yf < 0.0)	/* No flare figure */
					Yf = 0.0;	/* Assume 0% flare */
				if (Yg < 0.0)	/* No glare figure */
					Yg = 0.01;	/* Assume 1% glare */
				if (Gxyz[0] < 0.0 || Gxyz[1] < 0.0 || Gxyz[2] < 0.0)	/* No flare color */
					Gxyz[0] = Wxyz[0], Gxyz[1] = Wxyz[1], Gxyz[2] = Wxyz[2];
				break;
			}

		default:
			{
			return 2;
			}
	}

	return 1;
}


/* Write our viewing conditions to the underlying ICC profile, */
/* using a private tag. */
void xicc_set_viewcond(
xicc *p,			/* Expanded profile we're working with */
icxViewCond *vc		/* Viewing parameters to return */
) {
	//icc *pp = p->pp;	/* Base ICC */

	// ~~1	Not implemented yet
}


#endif /* NEVER */

/* Return an enumerated viewing condition */
/* Return enumeration if OK, -999 if there is no such enumeration. */
/* xicc may be NULL if just the description is wanted, */
/* or an explicit white point is provided. */
int xicc_enum_viewcond(
xicc *p,			/* Expanded profile to get white point (May be NULL if desc NZ) */
icxViewCond *vc,	/* Viewing parameters to return, May be NULL if desc is nz */
int no,				/* Enumeration to return, -1 for default, -2 for none */
char *as,			/* String alias to number, NULL if none */
int desc,			/* NZ - Just return a description and index of this enumeration in vc */
double *wp			/* Provide XYZ white point if xicc is NULL or no White point tag */
) {

	if (desc == 0) {	/* We're setting the viewing condition */
		icc *pp;		/* Base ICC */
		icmXYZArray *whitePointTag;

		if (vc == NULL)
			return -999;

		if (p == NULL) {
			if (wp == NULL)
				return -999;
			vc->Wxyz[0] = wp[0];
			vc->Wxyz[1] = wp[1];
			vc->Wxyz[2] = wp[2];
		} else {
	
#ifdef NEVER
			// ~8 this doesn't allow for ICCV4 style media chromatic adapation
			pp = p->pp;
			if ((whitePointTag = (icmXYZArray *)pp->read_tag(pp, icSigMediaWhitePointTag)) != NULL
	   	      && whitePointTag->ttype == icSigXYZType && whitePointTag->count >= 1) {
				vc->Wxyz[0] = whitePointTag->data[0].X;
				vc->Wxyz[1] = whitePointTag->data[0].Y;
				vc->Wxyz[2] = whitePointTag->data[0].Z;
			} else {
				if (wp == NULL) { 
					sprintf(p->e.m,"Enum VC: Failed to read Media White point");
					p->e.c = 2;
					return -999;
				}
				vc->Wxyz[0] = wp[0];
				vc->Wxyz[1] = wp[1];
				vc->Wxyz[2] = wp[2];
			}
#else
			int wpassumed = 0;
			icmXYZNumber twp;

			pp = p->pp;
			if (pp->get_wb_points(pp, &wpassumed, &twp, NULL, NULL, NULL, NULL) != ICM_ERR_OK) {
				sprintf(p->e.m,"Enum VC: Failed to read Media White point");
				p->e.c = 2;
				return -999;
			}  
			if (!wpassumed || wp == NULL) {
				vc->Wxyz[0] = twp.X;
				vc->Wxyz[1] = twp.Y;
				vc->Wxyz[2] = twp.Z;
			} else {					/* Use provided wp */
				vc->Wxyz[0] = wp[0];
				vc->Wxyz[1] = wp[1];
				vc->Wxyz[2] = wp[2];
			}
#endif
		}

		/* Set a default Glare color */
		vc->Gxyz[0] = vc->Wxyz[0];
		vc->Gxyz[1] = vc->Wxyz[1];
		vc->Gxyz[2] = vc->Wxyz[2];

		/* Default HK scaling factor = full */
		vc->hk = XICC_USE_HK;
		vc->hkscale = 1.0;

		/* Default Mid tone partial adapation factor = none */
		vc->mtaf = 0.0;

		/* Default Mid tone partial adapation white point = D50 */
		vc->Wxyz2[0] = icmD50.X;
		vc->Wxyz2[1] = icmD50.Y;
		vc->Wxyz2[2] = icmD50.Z;
	}

	/*

	Typical adapting field luminances and white luminance in reflective setup:
	(Note that displays Lv is typically brighter under the same conditions)

	E = illuminance in Lux
	La = Adapting field luminance in cd/m^2, assuming 20% reflectance from surround
	Lv = White luminance assuming 100% reflectance
    
	    E     La     Lv   Condition 
	    11     0.7    4   Twilight
	    32     2     10   Subdued indoor lighting
	    64     4     20   Less than typical office light; sometimes recommended for
	                      display-only workplaces (sRGB)
	   350    22    111   Typical Office (sRGB annex D)
	   500    32    160   Practical print evaluationa (ISO-3664 P2)
	  1000    64    318   Good Print evaluation (CIE 116-1995)
	  1000    64    318   Television Studio lighting
	  1000    64    318   Overcast Outdoors
	  2000   127    637   Critical print evaluation (ISO-3664 P1)
	 10000   637   3183   Typical outdoors, full daylight 
	 50000  3185  15915   Bright summers day 

	Display numbers:

	SMPTE video standard white 100
	SMPTE cinema standard white 55

	Flare is image content dependent, and is typically 1% from factors
	including display self illumination and observer/camera internal
	stray light. Because image content is not static, using a 1% of white point
	flare results quite erronious appearance modelling for predominantly
	dark images. As a result, it is best to default to a Yf of 0%,
	and only introduce a higher number depending on the known image content.

	Glare is assumed to be from the ambient light reflecting from the display
	and also striking the observer directly, and is (typically) defaulted
	to 1% of ambient here. (too low ? Typical displays are 4-10%)
	
	*/

	if (no == -1
	 || (as != NULL && stricmp(as,"d") == 0)) {

		no = -1;
		if (vc != NULL) {
			vc->desc = "  d - Default Viewing Condition";
			vc->Ev = vc_average;	/* Average viewing conditions */
			vc->La = 50.0;			/* Practical to Good lighting */
			vc->Lv = 250.0;			/* Average viewing conditions ratio */
			vc->Yb = 0.2;			/* Grey world */
			vc->Yf = 0.0;			/* 0% flare */
			vc->Yg = 0.01 * XICC_DEFAULT_GLARE;	/* 5% glare */
		}
	}
	else if (no == 0
	 || (as != NULL && stricmp(as,"pc") == 0)) {

		no = 0;
		if (vc != NULL) {
			vc->desc = " pc - Critical print evaluation environment (ISO-3664 P1)";
			vc->Ev = vc_average;	/* Average viewing conditions */
			vc->La = 127.0;			/* 0.2 * Lv ? */
			vc->Lv = 2000.0/3.1415;	/* White of the image field */ 
			vc->Yb = 0.2;			/* Grey world */
			vc->Yf = 0.0;			/* 0% flare */
			vc->Yg = 0.01 * XICC_DEFAULT_GLARE;	/* 5% glare */
		}
	}
	else if (no == 1
	 || (as != NULL && stricmp(as,"pp") == 0)) {

		no = 1;
		if (vc != NULL) {
			vc->desc = " pp - Practical Reflection Print (ISO-3664 P2)";
			vc->Ev = vc_none;		/* Use explicit La/Lv */
			vc->La = 32.0;			/* 0.2 * Lv ? */
			vc->Lv = 500.0/3.1415;	/* White of the image field */ 
			vc->Yb = 0.2;			/* Grey world */
			vc->Yf = 0.0;			/* 0% flare */
			vc->Yg = 0.01 * XICC_DEFAULT_GLARE;	/* 5% glare */
		}
	}
	else if (no == 2
	 || (as != NULL && stricmp(as,"pe") == 0)) {

		no = 2;
		if (vc != NULL) {
			vc->desc = " pe - Print evaluation environment (CIE 116-1995)";
			vc->Ev = vc_none;		/* Use explicit La/Lv */
			vc->La = 30.0;			/* 0.2 * Lv ? */ 
			vc->Lv = 150.0;			/* White of the image field */ 
			vc->Yb = 0.2;			/* Grey world */
			vc->Yf = 0.0;			/* 0% flare */
			vc->Yg = 0.01 * XICC_DEFAULT_GLARE;	/* 5% glare */
		}
	}
	else if (no == 3
	 || (as != NULL && stricmp(as,"pm") == 0)) {

		no = 3;
		if (vc != NULL) {
			vc->desc = " pm - Print evaluation with partial Mid-tone adapation";
			vc->Ev = vc_none;		/* Use explicit La/Lv */
			vc->La = 30.0;			/* 0.2 * Lv ? */ 
			vc->Lv = 150.0;			/* White of the image field */ 
			vc->Yb = 0.2;			/* Grey world */
			vc->Yf = 0.0;			/* 0% flare */
			vc->Yg = 0.01 * XICC_DEFAULT_GLARE;	/* 5% glare */
			vc->mtaf = 0.7;			/* Mid-tone partial adapation to D50 */
		}
	}
	else if (no == 4
	 || (as != NULL && stricmp(as,"mb") == 0)) {

		no = 4;
		if (vc != NULL) {
			vc->desc = " mb - Bright monitor in bright work environment";
			vc->Ev = vc_none;		/* Use explicit La/Lv */
			vc->La = 42.0;			/* Bright work environment */
			vc->Lv = 150.0;			/* White of the image field */ 
			vc->Yb = 0.2;			/* Grey world */
			vc->Yf = 0.0;			/* 0% flare */
			vc->Yg = 0.01 * XICC_DEFAULT_GLARE;	/* 5% glare */
		}
	}
	else if (no == 5
	 || (as != NULL && stricmp(as,"mt") == 0)) {

		no = 5;
		if (vc != NULL) {
			vc->desc = " mt - Monitor in typical work environment";
			vc->Ev = vc_none;		/* Use explicit La/Lv */
			vc->La = 22.0;			/* Typical work environment */
			vc->Lv = 120.0;			/* White of the image field */ 
			vc->Yb = 0.2;			/* Grey world */
			vc->Yf = 0.0;			/* 0% flare */
			vc->Yg = 0.01 * XICC_DEFAULT_GLARE;	/* 5% glare */
		}
	}
	else if (no == 6
	 || (as != NULL && stricmp(as,"md") == 0)) {

		no = 6;
		if (vc != NULL) {
			vc->desc = " md - Monitor in darkened work environment";
			vc->Ev = vc_none;		/* Use explicit La/Lv */
			vc->La = 10.0;			/* Darkened work environment */
			vc->Lv = 100.0;			/* White of the image field */ 
			vc->Yb = 0.2;			/* Grey world */
			vc->Yf = 0.0;			/* 0% flare */
			vc->Yg = 0.01 * XICC_DEFAULT_GLARE;	/* 5% glare */
		}
	}
	else if (no == 7
	 || (as != NULL && stricmp(as,"jm") == 0)) {

		no = 7;
		if (vc != NULL) {
			vc->desc = " jm - Projector in dim environment";
			vc->Ev = vc_none;		/* Use explicit La/Lv */
			vc->La = 10.0;			/* Adaptation is from display */
			vc->Lv = 80.0;			/* White of the image field */ 
			vc->Yb = 0.2;			/* Grey world */
			vc->Yf = 0.0;			/* 0% flare */
			vc->Yg = 0.01 * XICC_DEFAULT_GLARE;	/* 5% glare */
		}
	}
	else if (no == 8
	 || (as != NULL && stricmp(as,"jd") == 0)) {

		no = 8;
		if (vc != NULL) {
			vc->desc = " jd - Projector in dark environment";
			vc->Ev = vc_none;		/* Use explicit La/Lv */
			vc->La = 8.0;			/* Adaptation is from display */
			vc->Lv = 80.0;			/* White of the image field */ 
			vc->Yb = 0.2;			/* Grey world */
			vc->Yf = 0.0;			/* 0% flare */
			vc->Yg = 0.01 * XICC_DEFAULT_GLARE;	/* 5% glare */
		}
	}
	else if (no == 9
	 || (as != NULL && stricmp(as,"tv") == 0)) {

		no = 9;
		if (vc != NULL) {
			vc->desc = " tv - Television/Film Studio";
			vc->Ev = vc_none;				/* Compute from La/Lv */
			vc->La = 0.2 * 1000.0/3.1415;	/* Adative/Surround */
			vc->Yb = 0.2;					/* Grey world */
			vc->Lv = 1000.0/3.1415;			/* White of the image field */ 
			vc->Yf = 0.0;			/* 0% flare */
			vc->Yg = 0.01 * XICC_DEFAULT_GLARE;	/* 5% glare */
		}
	}
	else if (no == 10
	 || (as != NULL && stricmp(as,"pcd") == 0)) {

		no = 10;
		if (vc != NULL) {
			vc->desc = "pcd - Photo CD - original scene outdoors";
			vc->Ev = vc_average;	/* Average viewing conditions */
			vc->La = 320.0;			/* Typical outdoors, 1600 cd/m^2 */
			vc->Yb = 0.2;			/* Grey world */
			vc->Yf = 0.0;			/* 0% flare */
			vc->Yg = 0.0;			/* 0% glare - assumed to be compensated ? */
		}
	}
	else if (no == 11
	 || (as != NULL && stricmp(as,"ob") == 0)) {

		no = 11;
		if (vc != NULL) {
			vc->desc = " ob - Original scene - Bright Outdoors";
			vc->Ev = vc_average;	/* Average viewing conditions */
			vc->La = 2000.0;		/* Bright Outdoors */
			vc->Yb = 0.2;			/* Grey world */
			vc->Yf = 0.0;			/* 0% flare */
			vc->Yg = 0.0;			/* 0% glare - assumed to be compensated ? */
		}
	}
	else if (no == 12
	 || (as != NULL && stricmp(as,"cx") == 0)) {

		no = 12;
		if (vc != NULL) {
			vc->desc = " cx - Cut Sheet Transparencies on a viewing box";
			vc->Ev = vc_cut_sheet;	/* Cut sheet viewing conditions */
			vc->La = 53.0;			/* Dim, adapted to slide ? */
			vc->Yb = 0.2;			/* Grey world */
			vc->Yf = 0.0;			/* 0% flare */
			vc->Yg = 0.01 * XICC_DEFAULT_GLARE;	/* 5% glare */
		}
	}
	else {
		if (p != NULL) {
			sprintf(p->e.m,"Enum VC: Unrecognised enumeration %d",no);
			p->e.c = 1;
		}
		return -999;
	}

	return no;
}

/* Debug: dump a Viewing Condition to standard out */
void xicc_dump_viewcond(
icxViewCond *vc
) {
	printf("Viewing Condition:\n");
	if (vc->Ev == vc_dark)
		printf("  Surround to Image: Dark\n");
	else if (vc->Ev == vc_dim)
		printf("  Surround to Image: Dim\n");
	else if (vc->Ev == vc_average)
		printf("  Surround to Image: Average\n");
	else if (vc->Ev == vc_cut_sheet)
		printf("  Transparency on Light box\n");
	printf("  Adapted white = %f %f %f\n",vc->Wxyz[0], vc->Wxyz[1], vc->Wxyz[2]);
	printf("  Adapted luminance = %f cd/m^2\n",vc->La);
	printf("  Background to image ratio = %f\n",vc->Yb);
	if (vc->Ev == vc_none)
		printf("  Image luminance = %f cd/m^2\n",vc->Lv);
	printf("  Flare to image ratio = %f\n",vc->Yf);
	printf("  Glare to adapting/surround ratio = %f\n",vc->Yg);
	printf("  Flare color = %f %f %f\n",vc->Gxyz[0], vc->Gxyz[1], vc->Gxyz[2]);
	printf("  HK enabled = %d\n",vc->hk);
	printf("  HK scaling = %f\n",vc->hkscale);
	printf("  Mid tone partial adapation factor = %f\n",vc->mtaf);
	if (vc->mtaf > 0.0)
		printf("  Mid tone adapted white = %f %f %f\n",vc->Wxyz2[0], vc->Wxyz2[1], vc->Wxyz2[2]);
}


/* Debug: dump an Inking setup to standard out */
void xicc_dump_inking(icxInk *ik) {
	printf("Inking settings:\n");
	if (ik->tlimit < 0.0)
		printf("No total limit\n");
	else
		printf("Total limit = %f%%\n",ik->tlimit * 100.0);

	if (ik->klimit < 0.0)
		printf("No black limit\n");
	else
		printf("Black limit = %f%%\n",ik->klimit * 100.0);

	if (ik->KonlyLmin)
		printf("K only black as locus Lmin\n");
	else
		printf("Normal black as locus Lmin\n");

	if (ik->k_rule == icxKvalue) {
		printf("Inking rule is a fixed K target\n");
	} if (ik->k_rule == icxKlocus) {
		printf("Inking rule is a fixed locus target\n");
	} if (ik->k_rule == icxKluma5 || ik->k_rule == icxKluma5k) {
		if (ik->k_rule == icxKluma5)
			printf("Inking rule is a 5 parameter locus function of L\n");
		else
			printf("Inking rule is a 5 parameter K function of L\n");
		printf("Ksmth = %f\n",ik->c.Ksmth);
		printf("Kskew = %f\n",ik->c.Kskew);
		printf("Kstle = %f\n",ik->c.Kstle);
		printf("Kstpo = %f\n",ik->c.Kstpo);
		printf("Kenpo = %f\n",ik->c.Kenpo);
		printf("Kenle = %f\n",ik->c.Kenle);
		printf("Kshap = %f\n",ik->c.Kshap);
	} if (ik->k_rule == icxKl5l || ik->k_rule == icxKl5lk) {
		if (ik->k_rule == icxKl5l)
			printf("Inking rule is a 2x5 parameter locus function of L and K aux\n");
		else
			printf("Inking rule is a 2x5 parameter K function of L and K aux\n");
		printf("Min Ksmth = %f\n",ik->c.Ksmth);
		printf("Min Kskew = %f\n",ik->c.Kskew);
		printf("Min Kstle = %f\n",ik->c.Kstle);
		printf("Min Kstpo = %f\n",ik->c.Kstpo);
		printf("Min Kenpo = %f\n",ik->c.Kenpo);
		printf("Min Kenle = %f\n",ik->c.Kenle);
		printf("Min Kshap = %f\n",ik->c.Kshap);
		printf("Max Ksmth = %f\n",ik->x.Ksmth);
		printf("Max Kskew = %f\n",ik->x.Kskew);
		printf("Max Kstle = %f\n",ik->x.Kstle);
		printf("Max Kstpo = %f\n",ik->x.Kstpo);
		printf("Max Kenpo = %f\n",ik->x.Kenpo);
		printf("Max Kenle = %f\n",ik->x.Kenle);
		printf("Max Kshap = %f\n",ik->x.Kshap);
	} 
}

/* ------------------------------------------------------ */
/* Gamut Mapping Intent stuff */

/* Return an enumerated gamut mapping intent */
/* Return enumeration if OK, icxIllegalGMIntent if there is no such enumeration. */
int xicc_enum_gmapintent(
icxGMappingIntent *gmi,	/* Gamut Mapping parameters to return */
int no,					/* Enumeration selected, icxNoGMIntent for none */
char *as				/* Alias string selector, NULL for none */
) {
#ifdef USE_CAM
	int colccas = 0x3;	/* Use abs. CAS for abs colorimetric intents */
	int perccas = 0x2;	/* Use CAS for other intents */
#else
	int colccas = 0x1;	/* Use abs. Lab for abs colorimetric intents */
	int perccas = 0x0;	/* Use Lab for other intents */
	fprintf(stderr,"!!!!!! Warning, USE_CAM is off in xicc.c !!!!!!\n");
#endif

	gmi->hkscale = -1.0;	/* Default is to not override viewing condition HK factor */

	/* Assert default if no guidance given */
	if (no == icxNoGMIntent && as == NULL)
		no = icxDefaultGMIntent;

	if (no == 0
	 || no == icxAbsoluteGMIntent
	 || (as != NULL && stricmp(as,"a") == 0)) {
		/* Map Absolute appearance space Jab to Jab and clip out of gamut */
		no = 0;
		gmi->as = "a";
		gmi->desc = " a - Absolute Colorimetric (in Jab) [ICC Absolute Colorimetric]";
		gmi->icci = icAbsoluteColorimetric;
		gmi->usecas  = colccas;		/* Use absolute appearance space */
		gmi->usemap  = 0;			/* Don't use gamut mapping */
		gmi->greymf  = 0.0;
		gmi->glumwcpf = 0.0;
		gmi->glumwexf = 0.0;
		gmi->glumbcpf = 0.0;
		gmi->glumbexf = 0.0;
		gmi->glumknf = 0.0;
		gmi->bph = gmm_noBPadpt;	/* No BP adapation */
		gmi->gamcpf  = 0.0;
		gmi->gamexf  = 0.0;
		gmi->gamcknf  = 0.0;
		gmi->gamxknf  = 0.0;
		gmi->gampwf  = 0.0;
		gmi->gamlpwf = 0.0;			/* No Linear Preserving Perceptual surface wghtg. factor */
		gmi->gamswf  = 0.0;
		gmi->satenh  = 0.0;			/* No saturation enhancement */
	}
	else if (no == 1
	 || (as != NULL && stricmp(as,"aw") == 0)) {

		/* I'm not sure how often this intent is useful. It's less likely than */
		/* I though that a printer white point won't fit within the gamut */
		/* of a display profile, since the display white always has Y = 1.0, */
		/* and no paper has better than about 95% reflectance. */
		/* Perhaps it may be more useful for targeting printer profiles ? */

		/* Map Absolute Jab to Jab and scale source to avoid clipping the white point */
		no = 1;
		gmi->as = "aw";
		gmi->desc = "aw - Absolute Colorimetric (in Jab) with scaling to fit white point";
		gmi->icci = icAbsoluteColorimetric;
		gmi->usecas = 0x100 | colccas;	/* Absolute Appearance space with scaling */
									/* to avoid clipping the source white point */
		gmi->usemap  = 0;			/* Don't use gamut mapping */
		gmi->greymf  = 0.0;
		gmi->glumwcpf = 0.0;
		gmi->glumwexf = 0.0;
		gmi->glumbcpf = 0.0;
		gmi->glumbexf = 0.0;
		gmi->glumknf = 0.0;
		gmi->bph = gmm_noBPadpt;	/* No BP adapation */
		gmi->gamcpf  = 0.0;
		gmi->gamexf  = 0.0;
		gmi->gamcknf  = 0.0;
		gmi->gamxknf  = 0.0;
		gmi->gampwf  = 0.0;
		gmi->gamlpwf = 0.0;			/* No Linear Preserving Perceptual surface wghtg. factor */
		gmi->gamswf  = 0.0;
		gmi->satenh  = 0.0;			/* No saturation enhancement */
	}
	else if (no == 2
	 || (as != NULL && stricmp(as,"aa") == 0)) {

		/* Map appearance space Jab to Jab and clip out of gamut */
		no = 2;
		gmi->as = "aa";
		gmi->desc = "aa - Absolute Appearance";
		gmi->icci = icRelativeColorimetric;
		gmi->usecas  = perccas;		/* Appearance space */
		gmi->usemap  = 0;			/* Don't use gamut mapping */
		gmi->greymf  = 0.0;
		gmi->glumwcpf = 0.0;
		gmi->glumwexf = 0.0;
		gmi->glumbcpf = 0.0;
		gmi->glumbexf = 0.0;
		gmi->glumknf = 0.0;
		gmi->bph = gmm_noBPadpt;	/* No BP adapation */
		gmi->gamcpf  = 0.0;
		gmi->gamexf  = 0.0;
		gmi->gamcknf  = 0.0;
		gmi->gamxknf  = 0.0;
		gmi->gampwf  = 0.0;
		gmi->gamlpwf = 0.0;			/* No Linear Preserving Perceptual surface wghtg. factor */
		gmi->gamswf  = 0.0;
		gmi->satenh  = 0.0;			/* No saturation enhancement */
	}
	else if (no == 3
	 || no == icxRelativeGMIntent
	 || (as != NULL && stricmp(as,"r") == 0)) {

		/* Align neutral axes and linearly map white point, then */
		/* map appearance space Jab to Jab and clip out of gamut */
		no = 3;
		gmi->as = "r";
		gmi->desc = " r - White Point Matched Appearance [ICC Relative Colorimetric]";
		gmi->icci = icRelativeColorimetric;
		gmi->usecas  = perccas;		/* Appearance space */
		gmi->usemap  = 1;			/* Use gamut mapping */
		gmi->greymf  = 1.0;			/* Fully align grey axis */
		gmi->glumwcpf = 1.0;		/* Fully compress grey axis at white end */
		gmi->glumwexf = 1.0;		/* Fully expand grey axis at white end */
		gmi->glumbcpf = 0.0;		/* No compression at black end */
		gmi->glumbexf = 0.0;		/* No expansion at black end */
		gmi->glumknf = 0.0;
		gmi->bph = gmm_noBPadpt;	/* No BP adapation */
		gmi->gamcpf  = 0.0;
		gmi->gamexf  = 0.0;
		gmi->gamcknf  = 0.0;
		gmi->gamxknf  = 0.0;
		gmi->gampwf  = 0.0;
		gmi->gamlpwf = 0.0;			/* No Linear Preserving Perceptual surface wghtg. factor */
		gmi->gamswf  = 0.0;
		gmi->satenh  = 0.0;			/* No saturation enhancement */
	}
	else if (no == 4
	 || (as != NULL && stricmp(as,"la") == 0)) {

		/* Align neutral axes and linearly map white and black points, then */
		/* map appearance space Jab to Jab and clip out of gamut */
		no = 4;
		gmi->as = "la";
		gmi->desc = "la - Luminance axis matched Appearance";
		gmi->icci = icRelativeColorimetric;
		gmi->usecas  = perccas;		/* Appearance space */
		gmi->usemap  = 1;			/* Use gamut mapping */
		gmi->greymf  = 1.0;			/* Fully align grey axis */
		gmi->glumwcpf = 1.0;		/* Fully compress grey axis at white end */
		gmi->glumwexf = 1.0;		/* Fully expand grey axis at white end */
		gmi->glumbcpf = 1.0;		/* Fully compress grey axis at black end */
		gmi->glumbexf = 1.0;		/* Fully expand grey axis at black end */
		gmi->glumknf = 0.0;			/* No knee on grey mapping */
		gmi->bph = gmm_bendBP;		/* extent and bend */
		gmi->gamcpf  = 0.0;			/* No gamut compression */
		gmi->gamexf  = 0.0;			/* No gamut expansion */
		gmi->gamcknf  = 0.0;		/* No knee in gamut compress */
		gmi->gamxknf  = 0.0;		/* No knee in gamut expand */
		gmi->gampwf  = 0.0;			/* No Perceptual surface weighting factor */
		gmi->gamlpwf = 0.0;			/* No Linear Preserving Perceptual surface wghtg. factor */
		gmi->gamswf  = 0.0;			/* No Saturation surface weighting factor */
		gmi->satenh  = 0.0;			/* No saturation enhancement */
	}
	else if (no == 5
	 || no == icxDefaultGMIntent
	 || no == icxPerceptualGMIntent
	 || (as != NULL && stricmp(as,"p") == 0)) {

		/* Align neutral axes and perceptually map white and black points, */ 
		/* perceptually compress out of gamut and map appearance space Jab to Jab. */  
		no = 5;
		gmi->as = "p";
		gmi->desc = " p - Perceptual (Preferred) (Default) [ICC Perceptual]";
		gmi->icci = icPerceptual;
		gmi->usecas  = perccas;		/* Appearance space */
		gmi->usemap  = 1;			/* Use gamut mapping */
		gmi->greymf  = 1.0;			/* Fully align grey axis */
		gmi->glumwcpf = 1.0;		/* Fully compress grey axis at white end */
		gmi->glumwexf = 1.0;		/* Fully expand grey axis at white end */
		gmi->glumbcpf = 1.0;		/* Fully compress grey axis at black end */
		gmi->glumbexf = 1.0;		/* Fully expand grey axis at black end */
		gmi->glumknf = 1.0;			/* Sigma knee in grey compress/expand */
		gmi->bph = gmm_bendBP;		/* extent and bend */
		gmi->gamcpf  = 1.0;			/* Full gamut compression */
		gmi->gamexf  = 0.0;			/* No gamut expansion */
		gmi->gamcknf  = 1.0;		/* Full Sigma knee in gamut compress */
		gmi->gamxknf  = 0.0;		/* No knee in gamut expand */
		gmi->gampwf  = 1.0;			/* Full Perceptual surface weighting factor */
		gmi->gamlpwf = 0.0;			/* No Linear Preserving Perceptual surface wghtg. factor */
		gmi->gamswf  = 0.0;			/* No Saturation surface weighting factor */
		gmi->satenh  = 0.0;			/* No saturation enhancement */
	}
	else if (no == 6
	 || (as != NULL && stricmp(as,"pa") == 0)) {

		/* Don't align neutral axes, but perceptually compress out of gamut */
		/* and map appearance space Jab to Jab. */  
		no = 6;
		gmi->as = "pa";
		gmi->desc = "pa - Perceptual Apperance ";
		gmi->icci = icPerceptual;
		gmi->usecas  = perccas;		/* Appearance space */
		gmi->usemap  = 1;			/* Use gamut mapping */
		gmi->greymf  = 0.0;			/* Don't align grey axis */
		gmi->glumwcpf = 1.0;		/* Fully compress grey axis at white end */
		gmi->glumwexf = 1.0;		/* Fully expand grey axis at white end */
		gmi->glumbcpf = 1.0;		/* Fully compress grey axis at black end */
		gmi->glumbexf = 1.0;		/* Fully expand grey axis at black end */
		gmi->glumknf = 1.0;			/* Sigma knee in grey compress/expand */
		gmi->bph = gmm_bendBP;		/* extent and bend */
		gmi->gamcpf  = 1.0;			/* Full gamut compression */
		gmi->gamexf  = 0.0;			/* No gamut expansion */
		gmi->gamcknf  = 1.0;		/* Full Sigma knee in gamut compress */
		gmi->gamxknf  = 0.0;		/* No knee in gamut expand */
		gmi->gampwf  = 1.0;			/* Full Perceptual surface weighting factor */
		gmi->gamlpwf = 0.0;			/* No Linear Preserving Perceptual surface wghtg. factor */
		gmi->gamswf  = 0.0;			/* No Saturation surface weighting factor */
		gmi->satenh  = 0.0;			/* No saturation enhancement */
	}
	else if (no == 7
	 || (as != NULL && stricmp(as,"lp") == 0)) {

		/* Align neutral axes and perceptually map white and black points, */ 
		/* perceptually compress out of gamut and map appearance space Jab to Jab, */  
		/* and heavily weight preserving the luminance over saturation. */ 
		/* No neutral axis sigma enhancement. */
		no = 7;
		gmi->as = "lp";
		gmi->desc = "lp - Luminance Preserving Perceptual";
		gmi->icci = icPerceptual;
		gmi->usecas  = perccas;		/* Appearance space */
		gmi->usemap  = 1;			/* Use gamut mapping */
		gmi->greymf  = 1.0;			/* Fully align grey axis */
		gmi->glumwcpf = 1.0;		/* Fully compress grey axis at white end */
		gmi->glumwexf = 1.0;		/* Fully expand grey axis at white end */
		gmi->glumbcpf = 1.0;		/* Fully compress grey axis at black end */
		gmi->glumbexf = 1.0;		/* Fully expand grey axis at black end */
		gmi->glumknf = 0.3;			/* Low Sigma knee in grey compress/expand */
		gmi->bph = gmm_bendBP;		/* extent and bend */
		gmi->gamcpf  = 1.0;			/* Full gamut compression */
		gmi->gamexf  = 0.0;			/* No gamut expansion */
		gmi->gamcknf  = 1.3;		/* [1.3] High Sigma knee in gamut compress */
		gmi->gamxknf  = 0.0;		/* No knee in gamut expand */
		gmi->gampwf  = 0.0;			/* No Perceptual weighting factor */
		gmi->gamlpwf = 1.0;			/* Full Linear Preserving Perceptual wghtg. factor */
		gmi->gamswf  = 0.0;			/* No Saturation weighting factor */
		gmi->satenh  = 0.0;			/* No saturation enhancement */
		gmi->hkscale = 0.2;			/* Mostly disable HK appearance modeling */
	}
	else if (no == 8
	 || (as != NULL && stricmp(as,"ms") == 0)) {

		/* Align neutral axes and perceptually map white and black points, */ 
		/* perceptually compress and expand to match gamuts and map Jab to Jab. */  
		no = 8;
		gmi->as = "ms";
		gmi->desc = "ms - Saturation";
		gmi->icci = icSaturation;
		gmi->usecas  = perccas;		/* Appearance space */
		gmi->usemap  = 1;			/* Use gamut mapping */
		gmi->greymf  = 1.0;			/* Fully align grey axis */
		gmi->glumwcpf = 1.0;		/* Fully compress grey axis at white end */
		gmi->glumwexf = 1.0;		/* Fully expand grey axis at white end */
		gmi->glumbcpf = 1.0;		/* Fully compress grey axis at black end */
		gmi->glumbexf = 1.0;		/* Fully expand grey axis at black end */
		gmi->glumknf = 1.0;			/* Sigma knee in grey compress/expand */
		gmi->bph = gmm_bendBP;		/* extent and bend */
		gmi->gamcpf  = 1.0;			/* Full gamut compression */
		gmi->gamexf  = 1.0;			/* Full gamut expansion  */
		gmi->gamcknf = 1.1;			/* Sigma knee in gamut compress */
		gmi->gamxknf = 0.4;			/* Moderate Sigma knee in gamut expand */
		gmi->gampwf  = 0.2;			/* Slight perceptual surface weighting factor */
		gmi->gamlpwf = 0.0;			/* No Linear Preserving Perceptual surface wghtg. factor */
		gmi->gamswf  = 0.8;			/* Most saturation surface weighting factor */
		gmi->satenh  = 0.0;			/* No saturation enhancement */
	}
	else if (no == 9
	 || no == icxSaturationGMIntent
	 || (as != NULL && stricmp(as,"s") == 0)) {

		/* Same as "ms" but enhance saturation */
		no = 9;
		gmi->as = "s";
		gmi->desc = " s - Enhanced Saturation [ICC Saturation]";
		gmi->icci = icSaturation;
		gmi->usecas  = perccas;		/* Appearance space */
		gmi->usemap  = 1;			/* Use gamut mapping */
		gmi->greymf  = 1.0;			/* Fully align grey axis */
		gmi->glumwcpf = 1.0;		/* Fully compress grey axis at white end */
		gmi->glumwexf = 1.0;		/* Fully expand grey axis at white end */
		gmi->glumbcpf = 1.0;		/* Fully compress grey axis at black end */
		gmi->glumbexf = 1.0;		/* Fully expand grey axis at black end */
		gmi->glumknf = 1.0;			/* Sigma knee in grey compress/expand */
		gmi->bph = gmm_bendBP;		/* extent and bend */
		gmi->gamcpf  = 1.0;			/* Full gamut compression */
		gmi->gamexf  = 1.0;			/* Full gamut expansion */
		gmi->gamcknf = 1.1;			/* High sigma knee in gamut compress */
		gmi->gamxknf = 0.5;			/* Moderate sigma knee in gamut expand */
		gmi->gampwf  = 0.0;			/* No Perceptual surface weighting factor */
		gmi->gamlpwf = 0.0;			/* No Linear Preserving Perceptual surface wghtg. factor */
		gmi->gamswf  = 1.0;			/* Full Saturation surface weighting factor */
		gmi->satenh  = 0.9;			/* Medium saturation enhancement */
	}
	else if (no == 10
	 || (as != NULL && stricmp(as,"al") == 0)) {

		/* Map absolute L*a*b* to L*a*b* and clip out of gamut */
		no = 10;
		gmi->as = "al";
		gmi->desc = "al - Absolute Colorimetric (Lab)";
		gmi->icci = icAbsoluteColorimetric;
		gmi->usecas  = 0x1;			/* Don't use appearance space, use abs. L*a*b* */
		gmi->usemap  = 0;			/* Don't use gamut mapping */
		gmi->greymf  = 0.0;
		gmi->glumwcpf = 0.0;
		gmi->glumwexf = 0.0;
		gmi->glumbcpf = 0.0;
		gmi->glumbexf = 0.0;
		gmi->glumknf = 0.0;
		gmi->bph = gmm_noBPadpt;	/* No BP adapation */
		gmi->gamcpf  = 0.0;
		gmi->gamexf  = 0.0;
		gmi->gamcknf = 0.0;
		gmi->gamxknf = 0.0;
		gmi->gampwf  = 0.0;
		gmi->gamlpwf = 0.0;			/* No Linear Preserving Perceptual surface wghtg. factor */
		gmi->gamswf  = 0.0;
		gmi->satenh  = 0.0;			/* No saturation enhancement */
	}
	else if (no == 11
	 || (as != NULL && stricmp(as,"rl") == 0)) {

		/* Align neutral axes and linearly map white point, then */
		/* map L*a*b* to L*a*b* and clip out of gamut */
		no = 11;
		gmi->as = "rl";
		gmi->desc = "rl - White Point Matched Colorimetric (Lab)";
		gmi->icci = icRelativeColorimetric;
		gmi->usecas  = 0x0;			/* Don't use appearance space, use relative L*a*b* */
		gmi->usemap  = 1;			/* Use gamut mapping */
		gmi->greymf  = 1.0;			/* And linearly map white point */
		gmi->glumwcpf = 1.0;
		gmi->glumwexf = 1.0;
		gmi->glumbcpf = 0.0;
		gmi->glumbexf = 0.0;
		gmi->glumknf = 0.0;
		gmi->bph = gmm_noBPadpt;	/* No BP adapation */
		gmi->gamcpf  = 0.0;
		gmi->gamexf  = 0.0;
		gmi->gamcknf = 0.0;
		gmi->gamxknf = 0.0;
		gmi->gampwf  = 0.0;
		gmi->gamlpwf = 0.0;			/* No Linear Preserving Perceptual surface wghtg. factor */
		gmi->gamswf  = 0.0;
		gmi->satenh  = 0.0;			/* No saturation enhancement */
	}
	else {	/* icxIllegalGMIntent */
		return icxIllegalGMIntent;
	}

	return no;
}


/* Debug: dump a Gamut Mapping specification */
void xicc_dump_gmi(
icxGMappingIntent *gmi	/* Gamut Mapping parameters to return */
) {
	printf(" Gamut Mapping Specification:\n");
	if (gmi->desc != NULL)
		printf("  Description = '%s'\n",gmi->desc);
	printf("  Closest ICC intent = '%s'\n",icm2str(icmRenderingIntent,gmi->icci));

	if ((gmi->usecas & 0xff) == 0)
		printf("  Not using Color Apperance Space - using L*a*b*\n");
	else if ((gmi->usecas & 0xff) == 1)
		printf("  Not using Color Apperance Space - using Absoute L*a*b*\n");
	else if ((gmi->usecas & 0xff) == 2)
		printf("  Using Color Apperance Space\n");
	else if ((gmi->usecas & 0xff) == 3)
		printf("  Using Absolute Color Apperance Space\n");

	if ((gmi->usecas & 0x100) != 0)
		printf("  Scaling source to avoid white point clipping\n");

	if (gmi->usemap == 0)
		printf("  Not using Mapping\n");
	else {
		printf("  Using Mapping with parameters:\n");
		printf("  Grey axis alignment   factor %f\n", gmi->greymf);
		printf("  Grey axis white compression factor %f\n", gmi->glumwcpf);
		printf("  Grey axis white expansion   factor %f\n", gmi->glumwexf);
		printf("  Grey axis black compression factor %f\n", gmi->glumbcpf);
		printf("  Grey axis black expansion   factor %f\n", gmi->glumbexf);
		printf("  Grey axis knee        factor %f\n", gmi->glumknf);
		printf("  Black point algorithm: ");
		switch(gmi->bph) {
			case gmm_clipBP:	printf("Neutral axis no-adapt extend and clip\n"); break;
			case gmm_BPadpt:	printf("Neutral axis fully adapt\n"); break;
			case gmm_bendBP:	printf("Neutral axis no-adapt extend and bend\n"); break;
			case gmm_noBPadpt:	printf("Neutral axis no-adapt\n"); break;
		}
		printf("  Gamut compression factor %f\n", gmi->gamcpf);
		printf("  Gamut expansion   factor %f\n", gmi->gamexf);
		printf("  Gamut compression knee factor %f\n", gmi->gamcknf);
		printf("  Gamut expansion   knee factor %f\n", gmi->gamxknf);
		printf("  Gamut Perceptual mapping weighting factor %f\n", gmi->gampwf);
		printf("  Gamut Lightness Preserving Perceptual mapping weighting %f\n", gmi->gamlpwf);
		printf("  Gamut Saturation mapping weighting factor %f\n", gmi->gamswf);
		printf("  Saturation enhancement factor %f\n", gmi->satenh);
	}
	if (gmi->hkscale >= 0.0)
		printf("  HK scale override %f\n", gmi->hkscale);
}

/* ------------------------------------------------------ */
/* Turn xicc xcal into limit calibration callback */

/* Given an icc profile, try and create an xcal from 'targ' tag .ti3 data. */
/* Return NULL on error or no cal */
xcal *xiccReadCalTag(icc *p) {
	xcal *cal = NULL;
	icTagSignature sig = icSigCharTargetTag;
	icmText *ro;
	int oi, tab;

	if ((ro = (icmText *)p->read_tag(p, sig)) != NULL) { 
		cgatsFile *cgf;
		cgats *icg;

		if (ro->ttype != icSigTextType)
			return NULL;
	
		if ((icg = new_cgats()) == NULL) {
			return NULL;
		}
		if ((cgf = new_cgatsFileMem(ro->desc, ro->count)) != NULL) {
			icg->add_other(icg, "CTI3");
			oi = icg->add_other(icg, "CAL");

			if (icg->read(icg, cgf) == 0) {

				for (tab = 0; tab < icg->ntables; tab++) {
					if (icg->t[tab].tt == tt_other && icg->t[tab].oi == oi) {
						break;
					}
				}
				if (tab < icg->ntables) {
		
					if ((cal = new_xcal()) == NULL) {
						icg->del(icg);
						cgf->del(cgf);
						return NULL;
					}
					if (cal->read_cgats(cal, icg, tab, "'targ' tag") != 0)  {
#ifdef DEBUG
						printf("read_cgats on cal tag failed\n");
#endif
						cal->del(cal);
						cal = NULL;
					}
				}
			}
			cgf->del(cgf);
		}
		icg->del(icg);
	}
	return cal;
}

/* A callback that uses an xcal, that can be used with icc get_tac */
/* This is only called if there is a cal */
void xiccCalCallback(icxCalCBctx *cntx, double *out, double *in) {
	if (cntx->cal != NULL) {
		cntx->cal->interp(cntx->cal, out, in);
	}
}

/* ---------------------------------------------- */

/* Utility function - given an open icc profile, */
/* guess which channel is the black. */
/* Return -1 if there is no black channel or it can't be guessed */
int icxGuessBlackChan(icc *p) {
	int kch = -1;

	switch (p->header->colorSpace) {
   	 	case icSigCmykData:
			kch = 3;
			break;

		/* Use a heuristic to detect the black channel. */
		/* This duplicates code in icxLu_comp_bk_point() :-( */ 
		/* Colorant guessing should go in icclib ? */
		case icSig2colorData:
		case icSig3colorData:
		case icSig4colorData:
		case icSig5colorData:
		case icSig6colorData:
		case icSig7colorData:
		case icSig8colorData:
		case icSig9colorData:
		case icSig10colorData:
		case icSig11colorData:
		case icSig12colorData:
		case icSig13colorData:
		case icSig14colorData:
		case icSig15colorData:
		case icSigMch5Data:
		case icSigMch6Data:
		case icSigMch7Data:
		case icSigMch8Data: {
				icmLuSpace *lu;
				double dval[MAX_CHAN];
				double ncval[3];
				double cvals[MAX_CHAN][3];
				int inn, e, nlighter, ndarker;

				/* Grab a lookup object */
				if ((lu = (icmLuSpace *)p->get_luobj(p, icmFwd, icRelativeColorimetric, icSigLabData, icmLuOrdNorm)) == NULL)
					error("icxGuessBlackChan: assert: getting Fwd Lookup failed!");

				{
					icmCSInfo ini;
					lu->spaces(lu, NULL, &ini, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
					inn = ini.nch;
				}

				/* Decide if the colorspace is aditive or subtractive */

				/* First the no colorant value */
				for (e = 0; e < inn; e++)
					dval[e] = 0.0;
				lu->lookup_fwd(lu, ncval, dval);

				/* Then all the colorants */
				nlighter = ndarker = 0;
				for (e = 0; e < inn; e++) {
					dval[e] = 1.0;
					lu->lookup_fwd(lu, cvals[e], dval);
					dval[e] = 0.0;
					if (fabs(cvals[e][0] - ncval[0]) > 5.0) {
						if (cvals[e][0] > ncval[0])
							nlighter++;
						else
							ndarker++;
					}
				}

				if (ndarker > 0 && nlighter == 0) {		/* Assume subtractive. */
					double pbk[3] = { 0.0,0.0,0.0 };	/* Perfect black */
					double smd = 1e10;			/* Smallest distance */

					/* Guess the black channel */
					for (e = 0; e < inn; e++) {
						double tt;
						tt = icmNorm33sq(pbk, cvals[e]);
						if (tt < smd) {
							smd = tt;	
							kch = e;
						}
					}
					/* See if the black seems sane */
					if (cvals[kch][0] > 40.0
					 || fabs(cvals[kch][1]) > 10.0
					 || fabs(cvals[kch][2]) > 10.0) {
						kch = -1;
					}
				}
				lu->del(lu);
			}
			break;

		default:
			break;
	}

	return kch;
}

/* Utility function - given an open icc profile, */
/* estmate the total ink limit and black ink limit. */
/* Note that this is rather rough, because ICC profiles */
/* don't have a tag for this information, and ICC profiles */
/* don't have any straightforward way of identifying particular */
/* color channels for > 4 color. */
/* If there are no limits, or they are not discoverable or */
/* applicable, return values of -1.0 */
/* final raw limits are returned if cal tables are present in the 'targ' .ti3 */
void icxGetLimits(
xicc *xp,
double *tlimit,
double *klimit 
) {
	icc *p = xp->pp;
	int nch;
	double max[MAX_CHAN];		/* Max of each channel */
	double total;
	icxCalCBctx ctx;

	ctx.cal = xp->cal;

	if (xp->cal != NULL
	)
		total = p->get_tac(p, max, xiccCalCallback_void, (void *)&ctx);
	else
		total = p->get_tac(p, max, NULL, NULL);

	if (total < 0.0) {	/* Not valid */
		if (tlimit != NULL)
			*tlimit = -1.0;
		if (klimit != NULL)
			*klimit = -1.0;
		return;
	}

	nch = icmCSSig2nchan(p->header->colorSpace);

	/* No effective limit */
	if (tlimit != NULL) {
		if (total >= (double)nch) {
			*tlimit = -1.0;
		} else {
			*tlimit = total;
		}
	}

	if (klimit != NULL) {
		int kch;

		kch = icxGuessBlackChan(p);

		if (kch < 0 || max[kch] >= 1.0) {
			*klimit = -1.0;
		} else {
			*klimit = max[kch];
		}
	}
}

/* Replace a non-set limit (ie. < 0.0) with the heuristic from */
/* the given profile. */
void icxDefaultLimits(
xicc *xp,
double *tlout,
double tlin,
double *klout,
double klin
) {
	if (tlin < 0.0 || klin < 0.0) { 
		double tl, kl;

		icxGetLimits(xp, &tl, &kl);

		if (tlin < 0.0)
			tlin = tl;

		if (klin < 0.0)
			klin = kl;
	}

	if (tlout != NULL)
		*tlout = tlin;

	if (klout != NULL)
		*klout = klin;
}

/* Structure to hold optimisation information */
typedef struct {
	xcal *cal;
	double ilimit;		/* raw final device space limit */
	double uilimit;		/* linearized device space limit */
} ulimctx;

/* Callback to find maximum linearized (underlying) total limit */
/* that has the target final raw limit. */
/* We do this by searching in the linearized device space */
/* for a value that maximimzes the linearized total, while */
/* being at the raw final total. */
static double ulimitfunc(void *cntx, double pv[]) {
	ulimctx *cx = (ulimctx *)cntx;
	xcal *cal = cx->cal;
	int devchan;
	int i;
	double dv, odv;
	double og = 0.0, rv = 0.0;

	double usum = 0.0, sum = 0.0;

	if (cal != NULL)
		devchan = cal->devchan;

	/* Compute final raw sum of channels except last */
	for (i = 0; i < (devchan-1); i++) {
		double dv = pv[i];		/* Underlying (linearized) device value */
		usum += dv;				/* Underlying (linearized) sum */
		if (dv < 0.0) {
			og += -dv;
			dv = 0.0;
		} else if (dv > 1.0) {
			og += dv - 1.0;
			dv = 1.0;
		}
		if (cal != NULL)
			dv = cal->interp_ch(cal, i, dv);	/* final raw device value */
		sum += dv;							/* final raw device sum */
	}
	/* Compute the omitted channel value that makes the total */
	/* equal the final raw device value limit. */
	dv = cx->ilimit - sum;				/* Omitted final raw device value */

	if (dv < 0.0) {
		og += -dv;
		dv = 0.0;
	} else if (dv > 1.0) {
		og += dv - 1.0;
		dv = 1.0;
	}
	if (cal != NULL)
		dv = cal->inv_interp_ch(cal, i, dv); 	/* Omitted linerizd (underlying) device value */
	usum += dv;								/* Linearized (underlying) sum */

	cx->uilimit = usum; 			/* Result */
	
	rv = 10000.0 * og - usum;		/* Penalize out of gamut, maximize linearized sum */

//printf("~1 returning %f from %f %f %f %f\n",rv,pv[0],pv[1],pv[2],dv);
	return rv;
}

/* Given a final raw total ink limit and an xcal, return the */
/* equivalent linearized (underlying) total ink limit. */
/* This is the maximum equivalent, that makes sure that */
/* the final raw limit is met or exceeded. */
double icxMaxUnderlyingLimit(xcal *cal, double ilimit) {
	ulimctx cx;
	int i, devchan;
	double dv[MAX_CHAN];
	double sr[MAX_CHAN];
	double rv;				/* Residual value */

	if (cal != NULL && cal->devchan <= 1) {
		return cal->inv_interp_ch(cal, 0, ilimit);
	}

	cx.cal = cal;
	cx.ilimit = ilimit;

	if (cal != NULL)
		devchan = cal->devchan;

	for (i = 0; i < (devchan-1); i++) {
		sr[i] = 0.05;
		dv[i] = 0.1;
	}
	if (powell(&rv, devchan-1, dv, sr, 0.000001, 1000, ulimitfunc,
			                      (void *)&cx, NULL, NULL) != 0) {
		warning("icxUnderlyingLimit() failed for chan %d, ilimit %f\n",devchan-1,ilimit);
		return ilimit;
	}
	ulimitfunc((void *)&cx, dv);	/* Compute actual result */

	return cx.uilimit;
}

/* ------------------------------------------------------ */
/* Conversion and deltaE formular that include partial */
/* derivatives, for use within fit parameter optimisations. */

/* CIE XYZ to perceptual Lab with partial derivatives. */
void icxdXYZ2Lab(icmXYZNumber *w, double *out, double dout[3][3], double *in) {
	double wp[3], tin[3], dtin[3];
	int i;

	wp[0] = w->X, wp[1] = w->Y, wp[2] = w->Z;

	for (i = 0; i < 3; i++) {
		tin[i] = in[i]/wp[i];
		dtin[i] = 1.0/wp[i];

		if (tin[i] > 0.008856451586) {
			dtin[i] *= pow(tin[i], -2.0/3.0) / 3.0;
			tin[i] = pow(tin[i],1.0/3.0);
		} else {
			dtin[i] *= 7.787036979;
			tin[i] = 7.787036979 * tin[i] + 16.0/116.0;
		}
	}

	out[0] = 116.0 * tin[1] - 16.0;
	dout[0][0] = 0.0;
	dout[0][1] = 116.0 * dtin[1];
	dout[0][2] = 0.0;

	out[1] = 500.0 * (tin[0] - tin[1]);
	dout[1][0] = 500.0 * dtin[0];
	dout[1][1] = 500.0 * -dtin[1];
	dout[1][2] = 0.0;

	out[2] = 200.0 * (tin[1] - tin[2]);
	dout[2][0] = 0.0;
	dout[2][1] = 200.0 * dtin[1];
	dout[2][2] = 200.0 * -dtin[2];
}


/* Return the normal Delta E squared, given two Lab values, */
/* including partial derivatives. */
double icxdLabDEsq(double dout[2][3], double *Lab0, double *Lab1) {
	double rv = 0.0, tt;

	tt = Lab0[0] - Lab1[0];
	dout[0][0] =  2.0 * tt;
	dout[1][0] = -2.0 * tt;
	rv += tt * tt;
	tt = Lab0[1] - Lab1[1];
	dout[0][1] =  2.0 * tt;
	dout[1][1] = -2.0 * tt;
	rv += tt * tt;
	tt = Lab0[2] - Lab1[2];
	dout[0][2] =  2.0 * tt;
	dout[1][2] = -2.0 * tt;
	rv += tt * tt;
	return rv;
}

/* Return the CIE94 Delta E color difference measure, squared */
/* including partial derivatives. */
double icxdCIE94sq(double dout[2][3], double Lab0[3], double Lab1[3]) {
	double desq, _desq[2][3];
	double dlsq;
	double dcsq, _dcsq[2][2];	/* == [x][1,2] */
	double c12,   _c12[2][2];	/* == [x][1,2] */
	double dhsq, _dhsq[2][2];	/* == [x][1,2] */
	double rv;

	{
		double dl, da, db;

		dl = Lab0[0] - Lab1[0];
		dlsq = dl * dl;		/* dl squared */
		da = Lab0[1] - Lab1[1];
		db = Lab0[2] - Lab1[2];


		/* Compute normal Lab delta E squared */
		desq = dlsq + da * da + db * db;
		_desq[0][0] =  2.0 * dl;
		_desq[1][0] = -2.0 * dl;
		_desq[0][1] =  2.0 * da;
		_desq[1][1] = -2.0 * da;
		_desq[0][2] =  2.0 * db;
		_desq[1][2] = -2.0 * db;
	}

	{
		double c1, c2, dc, tt;

		/* Compute chromanance for the two colors */
		c1 = sqrt(Lab0[1] * Lab0[1] + Lab0[2] * Lab0[2]);
		c2 = sqrt(Lab1[1] * Lab1[1] + Lab1[2] * Lab1[2]);
		c12 = sqrt(c1 * c2);	/* Symetric chromanance */

		tt = 0.5 * (pow(c2, 0.5) + 1e-12)/(pow(c1, 1.5) + 1e-12);
		_c12[0][0] = Lab0[1] * tt;
		_c12[0][1] = Lab0[2] * tt;
		tt = 0.5 * (pow(c1, 0.5) + 1e-12)/(pow(c2, 1.5) + 1e-12);
		_c12[1][0] = Lab1[1] * tt;
		_c12[1][1] = Lab1[2] * tt;

		/* delta chromanance squared */
		dc = c2 - c1;
		dcsq = dc * dc;
		if (c1 < 1e-12 || c2 < 1e-12) {
			c1 += 1e-12;
			c2 += 1e-12;
		}
		_dcsq[0][0] = -2.0 * Lab0[1] * (c2 - c1)/c1;
		_dcsq[0][1] = -2.0 * Lab0[2] * (c2 - c1)/c1;
		_dcsq[1][0] =  2.0 * Lab1[1] * (c2 - c1)/c2;
		_dcsq[1][1] =  2.0 * Lab1[2] * (c2 - c1)/c2;
	}

	/* Compute delta hue squared */
	dhsq = desq - dlsq - dcsq;
	if (dhsq >= 0.0) {
		_dhsq[0][0] = _desq[0][1] - _dcsq[0][0];
		_dhsq[0][1] = _desq[0][2] - _dcsq[0][1];
		_dhsq[1][0] = _desq[1][1] - _dcsq[1][0];
		_dhsq[1][1] = _desq[1][2] - _dcsq[1][1];
	} else {
		dhsq = 0.0;
		_dhsq[0][0] = 0.0;
		_dhsq[0][1] = 0.0;
		_dhsq[1][0] = 0.0;
		_dhsq[1][1] = 0.0;
	}

	{
		double sc, scsq, scf;
		double sh, shsq, shf;

		/* Weighting factors for delta chromanance & delta hue */
		sc = 1.0 + 0.048 * c12;
		scsq = sc * sc;
		
		sh = 1.0 + 0.014 * c12;
		shsq = sh * sh;
	
		rv = dlsq + dcsq/scsq + dhsq/shsq;

		scf = 0.048 * -2.0 * dcsq/(scsq * sc);
		shf = 0.014 * -2.0 * dhsq/(shsq * sh);
		dout[0][0] = _desq[0][0];
		dout[0][1] = _dcsq[0][0]/scsq + _c12[0][0] * scf
		           + _dhsq[0][0]/shsq + _c12[0][0] * shf;
		dout[0][2] = _dcsq[0][1]/scsq + _c12[0][1] * scf
		           + _dhsq[0][1]/shsq + _c12[0][1] * shf;
		dout[1][0] = _desq[1][0];
		dout[1][1] = _dcsq[1][0]/scsq + _c12[1][0] * scf
		           + _dhsq[1][0]/shsq + _c12[1][0] * shf;
		dout[1][2] = _dcsq[1][1]/scsq + _c12[1][1] * scf
		           + _dhsq[1][1]/shsq + _c12[1][1] * shf;
		return rv;
	}
}

// ~~99 not sure if these are correct:

/* Return the normal Delta E given two Lab values, */
/* including partial derivatives. */
double icxdLabDE(double dout[2][3], double *Lab0, double *Lab1) {
	double rv = 0.0, tt;

	tt = Lab0[0] - Lab1[0];
	dout[0][0] =  1.0 * tt;
	dout[1][0] = -1.0 * tt;
	rv += tt * tt;
	tt = Lab0[1] - Lab1[1];
	dout[0][1] =  1.0 * tt;
	dout[1][1] = -1.0 * tt;
	rv += tt * tt;
	tt = Lab0[2] - Lab1[2];
	dout[0][2] =  1.0 * tt;
	dout[1][2] = -1.0 * tt;
	rv += tt * tt;
	return sqrt(rv);
}

/* Return the CIE94 Delta E color difference measure */
/* including partial derivatives. */
double icxdCIE94(double dout[2][3], double Lab0[3], double Lab1[3]) {
	double desq, _desq[2][3];
	double dlsq;
	double dcsq, _dcsq[2][2];	/* == [x][1,2] */
	double c12,   _c12[2][2];	/* == [x][1,2] */
	double dhsq, _dhsq[2][2];	/* == [x][1,2] */
	double rv;

	{
		double dl, da, db;

		dl = Lab0[0] - Lab1[0];
		dlsq = dl * dl;		/* dl squared */
		da = Lab0[1] - Lab1[1];
		db = Lab0[2] - Lab1[2];


		/* Compute normal Lab delta E squared */
		desq = dlsq + da * da + db * db;
		_desq[0][0] =  1.0 * dl;
		_desq[1][0] = -1.0 * dl;
		_desq[0][1] =  1.0 * da;
		_desq[1][1] = -1.0 * da;
		_desq[0][2] =  1.0 * db;
		_desq[1][2] = -1.0 * db;
	}

	{
		double c1, c2, dc, tt;

		/* Compute chromanance for the two colors */
		c1 = sqrt(Lab0[1] * Lab0[1] + Lab0[2] * Lab0[2]);
		c2 = sqrt(Lab1[1] * Lab1[1] + Lab1[2] * Lab1[2]);
		c12 = sqrt(c1 * c2);	/* Symetric chromanance */

		tt = 0.5 * (pow(c2, 0.5) + 1e-12)/(pow(c1, 1.5) + 1e-12);
		_c12[0][0] = Lab0[1] * tt;
		_c12[0][1] = Lab0[2] * tt;
		tt = 0.5 * (pow(c1, 0.5) + 1e-12)/(pow(c2, 1.5) + 1e-12);
		_c12[1][0] = Lab1[1] * tt;
		_c12[1][1] = Lab1[2] * tt;

		/* delta chromanance squared */
		dc = c2 - c1;
		dcsq = dc * dc;
		if (c1 < 1e-12 || c2 < 1e-12) {
			c1 += 1e-12;
			c2 += 1e-12;
		}
		_dcsq[0][0] = -1.0 * Lab0[1] * (c2 - c1)/c1;
		_dcsq[0][1] = -1.0 * Lab0[2] * (c2 - c1)/c1;
		_dcsq[1][0] =  1.0 * Lab1[1] * (c2 - c1)/c2;
		_dcsq[1][1] =  1.0 * Lab1[2] * (c2 - c1)/c2;
	}

	/* Compute delta hue squared */
	dhsq = desq - dlsq - dcsq;
	if (dhsq >= 0.0) {
		_dhsq[0][0] = _desq[0][1] - _dcsq[0][0];
		_dhsq[0][1] = _desq[0][2] - _dcsq[0][1];
		_dhsq[1][0] = _desq[1][1] - _dcsq[1][0];
		_dhsq[1][1] = _desq[1][2] - _dcsq[1][1];
	} else {
		dhsq = 0.0;
		_dhsq[0][0] = 0.0;
		_dhsq[0][1] = 0.0;
		_dhsq[1][0] = 0.0;
		_dhsq[1][1] = 0.0;
	}

	{
		double sc, scsq, scf;
		double sh, shsq, shf;

		/* Weighting factors for delta chromanance & delta hue */
		sc = 1.0 + 0.048 * c12;
		scsq = sc * sc;
		
		sh = 1.0 + 0.014 * c12;
		shsq = sh * sh;
	
		rv = dlsq + dcsq/scsq + dhsq/shsq;

		scf = 0.048 * -1.0 * dcsq/(scsq * sc);
		shf = 0.014 * -1.0 * dhsq/(shsq * sh);
		dout[0][0] = _desq[0][0];
		dout[0][1] = _dcsq[0][0]/scsq + _c12[0][0] * scf
		           + _dhsq[0][0]/shsq + _c12[0][0] * shf;
		dout[0][2] = _dcsq[0][1]/scsq + _c12[0][1] * scf
		           + _dhsq[0][1]/shsq + _c12[0][1] * shf;
		dout[1][0] = _desq[1][0];
		dout[1][1] = _dcsq[1][0]/scsq + _c12[1][0] * scf
		           + _dhsq[1][0]/shsq + _c12[1][0] * shf;
		dout[1][2] = _dcsq[1][1]/scsq + _c12[1][1] * scf
		           + _dhsq[1][1]/shsq + _c12[1][1] * shf;
		return sqrt(rv);
	}
}

/* ------------------------------------------------------ */
/* A power-like function, based on Graphics Gems adjustment curve. */
/* Avoids "toe" problem of pure power. */
/* Adjusted so that "power" 2 and 0.5 agree with real power at 0.5 */

double icx_powlike(double vv, double pp) {
	double tt, g;

	if (pp >= 1.0) {
		g = 2.0 * (pp - 1.0);
		vv = vv/(g - g * vv + 1.0);
	} else {
		g = 2.0 - 2.0/pp;
		vv = (vv - g * vv)/(1.0 - g * vv);
	}

	return vv;
}

/* Compute the necessary aproximate power, to transform */
/* the given value from src to dst. They are assumed to be */
/* in the range 0.0 .. 1.0 */
double icx_powlike_needed(double src, double dst) {
	double pp, g;

//printf("~1 icx_powlike_needed got src %f dst %f\n",src,dst);
	if (dst <= src) {
		g = -((src - dst)/(dst * src - dst));
		pp = (0.5 * g) + 1.0;
	} else {
		g = -((src - dst)/((dst - 1.0) * src));
		pp = 1.0/(1.0 - 0.5 * g);
	}
//printf("~1 icx_powlike_needed returning %f\n",pp);

	return pp;
}

/* ------------------------------------------------------ */
/* Parameterized transfer/dot gain function. */
/* Used for device modelling. Including partial */
/* derivative for input and parameters. */

/* NOTE that clamping the input values seems to cause */
/* conjgrad() problems. */

/* Transfer function */
double icxTransFunc(
double *v,			/* Pointer to first parameter */
int    luord,		/* Number of parameters */
double vv			/* Source of value */
) {
	double g;
	int ord;

	/* Process all the shaper orders from low to high. */
	/* [These shapers were inspired by a Graphics Gem idea */
	/* (Gems IV, VI.3, "Fast Alternatives to Perlin's Bias and */
	/*  Gain Functions, pp 401). */
	/*  They have the nice properties that they are smooth, and */
	/*  are monotonic. The control parameter has been */
	/*  altered to have a range from -oo to +oo rather than 0.0 to 1.0 */
	/*  so that the search space is less non-linear. */
	for (ord = 0; ord < luord; ord++) {
		int nsec;			/* Number of sections */
		double sec;			/* Section */

		g = v[ord];			/* Parameter */

		nsec = ord + 1;		/* Increase sections for each order */

		vv *= (double)nsec;

		sec = floor(vv);
		if (((int)sec) & 1)
			g = -g;				/* Alternate action in each section */
		vv -= sec;
		if (g >= 0.0) {
			vv = vv/(g - g * vv + 1.0);
		} else {
			vv = (vv - g * vv)/(1.0 - g * vv);
		}
		vv += sec;
		vv /= (double)nsec;
	}

	return vv;
}

/* Inverse transfer function */
double icxInvTransFunc(
double *v,			/* Pointer to first parameter */
int    luord,		/* Number of parameters */
double vv			/* Source of value */
) {
	double g;
	int ord;

	/* Process the shaper orders in reverse from high to low. */
	/* [These shapers were inspired by a Graphics Gem idea */
	/* (Gems IV, VI.3, "Fast Alternatives to Perlin's Bias and */
	/*  Gain Functions, pp 401). */
	/*  They have the nice properties that they are smooth, and */
	/*  are monotonic. The control parameter has been */
	/*  altered to have a range from -oo to +oo rather than 0.0 to 1.0 */
	/*  so that the search space is less non-linear. */
	for (ord = luord-1; ord >= 0; ord--) {
		int nsec;			/* Number of sections */
		double sec;			/* Section */

		g = -v[ord];		/* Inverse parameter */

		nsec = ord + 1;		/* Increase sections for each order */

		vv *= (double)nsec;

		sec = floor(vv);
		if (((int)sec) & 1)
			g = -g;				/* Alternate action in each section */
		vv -= sec;
		if (g >= 0.0) {
			vv = vv/(g - g * vv + 1.0);
		} else {
			vv = (vv - g * vv)/(1.0 - g * vv);
		}
		vv += sec;
		vv /= (double)nsec;
	}

	return vv;
}

/* Transfer function with offset and scale */
double icxSTransFunc(
double *v,			/* Pointer to first parameter */
int    luord,		/* Number of parameters */
double vv,			/* Source of value */
double min,			/* Scale values */
double max
) {
	max -= min;

	vv = (vv - min)/max;
	vv = icxTransFunc(v, luord, vv);
	vv = (vv * max) + min;
	return vv;
}

/* Inverse Transfer function with offset and scale */
double icxInvSTransFunc(
double *v,			/* Pointer to first parameter */
int    luord,		/* Number of parameters */
double vv,			/* Source of value */
double min,			/* Scale values */
double max
) {
	max -= min;

	vv = (vv - min)/max;
	vv = icxInvTransFunc(v, luord, vv);
	vv = (vv * max) + min;
	return vv;
}

/* Transfer function with partial derivative */
/* with respect to the parameters. */
double icxdpTransFunc(
double *v,			/* Pointer to first parameter */
double *dv,			/* Return derivative wrt each parameter */
int    luord,		/* Number of parameters */
double vv			/* Source of value */
) {
	double g;
	int i, ord;

	/* Process all the shaper orders from high to low. */
	for (ord = 0; ord < luord; ord++) {
		double dsv;		/* del for del in g */
		double ddv;		/* del for del in vv */
		int nsec;		/* Number of sections */
		double sec;		/* Section */

		g = v[ord];			/* Parameter */

		nsec = ord + 1;		/* Increase sections for each order */

		vv *= (double)nsec;

		sec = floor(vv);
		if (((int)sec) & 1) {
			g = -g;				/* Alternate action in each section */
		}
		vv -= sec;
		if (g >= 0.0) {
			double tt = g - g * vv + 1.0;
			dsv = (vv * vv - vv)/(tt * tt);
			ddv = (g + 1.0)/(tt * tt);
			vv = vv/tt;
		} else {
			double tt = 1.0 - g * vv;
			dsv = (vv * vv - vv)/(tt * tt);
			ddv = (1.0 - g)/(tt * tt);
			vv = (vv - g * vv)/tt;
		}

		vv += sec;
		vv /= (double)nsec;
		dsv /= (double)nsec;
		if (((int)sec) & 1)
			dsv = -dsv;

		dv[ord] = dsv;
		for (i = ord - 1; i >= 0; i--)
			dv[i] *= ddv;
	}

	return vv;
}

/* Transfer function with offset and scale, and */
/* partial derivative with respect to the parameters. */
double icxdpSTransFunc(
double *v,			/* Pointer to first parameter */
double *dv,			/* Return derivative wrt each parameter */
int    luord,		/* Number of parameters */
double vv,			/* Source of value */
double min,			/* Scale values */
double max
) {
	int i;
	max -= min;

	vv = (vv - min)/max;
	vv = icxdpTransFunc(v, dv, luord, vv);
	vv = (vv * max) + min;
	for (i = 0; i < luord; i++)
		dv[i] *= max;
	return vv;
}


/* Transfer function with partial derivative */
/* with respect to the input value. */
double icxdiTransFunc(
double *v,			/* Pointer to first parameter */
double *pdin,		/* Return derivative wrt source value */
int    luord,		/* Number of parameters */
double vv			/* Source of value */
) {
	double g, din;
	int ord;

#ifdef NEVER
	if (vv < 0.0 || vv > 1.0) {
		if (vv < 0.0)
			vv = 0.0;
	 	else
			vv = 1.0;

		*pdin = 0.0;
		return vv;
	}
#endif
	din = 1.0;

	/* Process all the shaper orders from high to low. */
	for (ord = 0; ord < luord; ord++) {
		double ddv;		/* del for del in vv */
		int nsec;		/* Number of sections */
		double sec;		/* Section */

		g = v[ord];			/* Parameter */

		nsec = ord + 1;		/* Increase sections for each order */

		vv *= (double)nsec;

		sec = floor(vv);
		if (((int)sec) & 1) {
			g = -g;				/* Alternate action in each section */
		}
		vv -= sec;
		if (g >= 0.0) {
			double tt = g - g * vv + 1.0;
			ddv = (g + 1.0)/(tt * tt);
			vv = vv/tt;
		} else {
			double tt = 1.0 - g * vv;
			ddv = (1.0 - g)/(tt * tt);
			vv = (vv - g * vv)/tt;
		}

		vv += sec;
		vv /= (double)nsec;
		din *= ddv;
	}

	*pdin = din;
	return vv;
}

/* Transfer function with offset and scale, and */
/* partial derivative with respect to the input value. */
double icxdiSTransFunc(
double *v,			/* Pointer to first parameter */
double *pdv,		/* Return derivative wrt source value */
int    luord,		/* Number of parameters */
double vv,			/* Source of value */
double min,			/* Scale values */
double max
) {
	max -= min;

	vv = (vv - min)/max;
	vv = icxdiTransFunc(v, pdv, luord, vv);
	vv = (vv * max) + min;
	return vv;
}


/* Transfer function with partial derivative */
/* with respect to the parameters and the input value. */
double icxdpdiTransFunc(
double *v,			/* Pointer to first parameter */
double *dv,			/* Return derivative wrt each parameter */
double *pdin,		/* Return derivative wrt source value */
int    luord,		/* Number of parameters */
double vv			/* Source of value */
) {
	double g, din;
	int i, ord;

#ifdef NEVER
	if (vv < 0.0 || vv > 1.0) {
		if (vv < 0.0)
			vv = 0.0;
	 	else
			vv = 1.0;

		for (ord = 0; ord < luord; ord++)
			dv[ord] = 0.0;
		*pdin = 0.0;
		return vv;
	}
#endif
	din = 1.0;

	/* Process all the shaper orders from high to low. */
	for (ord = 0; ord < luord; ord++) {
		double dsv;		/* del for del in g */
		double ddv;		/* del for del in vv */
		int nsec;		/* Number of sections */
		double sec;		/* Section */

		g = v[ord];			/* Parameter */

		nsec = ord + 1;		/* Increase sections for each order */

		vv *= (double)nsec;

		sec = floor(vv);
		if (((int)sec) & 1) {
			g = -g;				/* Alternate action in each section */
		}
		vv -= sec;
		if (g >= 0.0) {
			double tt = g - g * vv + 1.0;
			dsv = (vv * vv - vv)/(tt * tt);
			ddv = (g + 1.0)/(tt * tt);
			vv = vv/tt;
		} else {
			double tt = 1.0 - g * vv;
			dsv = (vv * vv - vv)/(tt * tt);
			ddv = (1.0 - g)/(tt * tt);
			vv = (vv - g * vv)/tt;
		}

		vv += sec;
		vv /= (double)nsec;
		dsv /= (double)nsec;
		if (((int)sec) & 1)
			dsv = -dsv;

		dv[ord] = dsv;
		for (i = ord - 1; i >= 0; i--)
			dv[i] *= ddv;
		din *= ddv;
	}

	*pdin = din;
	return vv;
}

/* Transfer function with offset and scale, and */
/* partial derivative with respect to the */
/* parameters and the input value. */
double icxdpdiSTransFunc(
double *v,			/* Pointer to first parameter */
double *dv,			/* Return derivative wrt each parameter */
double *pdin,		/* Return derivative wrt source value */
int    luord,		/* Number of parameters */
double vv,			/* Source of value */
double min,			/* Scale values */
double max
) {
	int i;
	max -= min;

	vv = (vv - min)/max;
	vv = icxdpdiTransFunc(v, dv, pdin, luord, vv);
	vv = (vv * max) + min;
	for (i = 0; i < luord; i++)
		dv[i] *= max;
	return vv;
}

/* ------------------------------------------------------ */
/* Multi-plane interpolation, used for device modelling. */
/* Including partial derivative for input and parameters. */
/* A simple flat plane is used for each output. */

/* Multi-plane interpolation - uses base + di slope values.  */
/* Parameters are assumed to be fdi groups of di + 1 parameters. */
void icxPlaneInterp(
double *v,			/* Pointer to first parameter [fdi * (di + 1)] */
int    fdi,			/* Number of output channels */
int    di,			/* Number of input channels */
double *out,		/* Resulting fdi values */
double *in			/* Input di values */
) {
	int e, f;

	for (f = 0; f < fdi; f++) {
		for (out[f] = 0.0, e = 0; e < di; e++, v++) {
			out[f] += in[e] * *v;
		}
		out[f] += *v;
	}
}


/* Multii-plane interpolation with partial derivative */
/* with respect to the input and parameters. */
void icxdpdiPlaneInterp(
double *v,			/* Pointer to first parameter value [fdi * (di + 1)] */
double *dv,			/* Return [1 + di] deriv. wrt each parameter v */
double *din,		/* Return [fdi * di] deriv. wrt each input value */
int    fdi,			/* Number of output channels */
int    di,			/* Number of input channels */
double *out,		/* Resulting fdi values */
double *in			/* Input di values */
) {
	int e, ee, f, g;
	int dip2 = (di + 1);	/* Output dim increment through parameters */

	/* Compute the output values */
	for (f = 0; f < fdi; f++) {
		for (out[f] = 0.0, e = 0; e < di; e++)
			out[f] += in[e] * v[f * dip2 + e];
		out[f] += v[f * dip2 + e];
	}

	/* Since interpolation is verys simple, derivative are also simple */

	/* Copy del for parameter to return array */
	for (e = 0; e < di; e++)
		dv[e] = in[e];
	dv[e] = 1.0;

	/* Compute del of out[] from in[] */
	for (f = 0; f < fdi; f++) {
		for (e = 0; e < di; e++) {
			din[f * di + e] = v[f * dip2 + e];
		}
	}
}

/* ------------------------------------------------------ */
/* Matrix cube interpolation, used for device modelling. */
/* Including partial derivative for input and parameters. */

/* Matrix cube interpolation - interpolate between 2^di output corner values. */
/* Parameters are assumed to be fdi groups of 2^di parameters. */
void icxCubeInterp(
double *v,			/* Pointer to first parameter */
int    fdi,			/* Number of output channels */
int    di,			/* Number of input channels */
double *out,		/* Resulting fdi values */
double *in			/* Input di values */
) {
	int e, f, g;
	double gw[1 << MXDI];		/* weight for each matrix grid cube corner */

	/* Compute corner weights needed for interpolation */
	gw[0] = 1.0;
	for (e = 0, g = 1; e < di; e++, g *= 2) {
		int i; 
		for (i = 0; i < g; i++) {
			gw[g+i] = gw[i] * in[e];
			gw[i] *= (1.0 - in[e]);
		}
	}

	/* Now compute the output values */
	for (f = 0; f < fdi; f++) {
		out[f] = 0.0;						/* For each output value */
		for (e = 0; e < (1 << di); e++) {	/* For all corners of cube */
			out[f] += gw[e] * *v;
			v++;
		}
	}
}


/* Matrix cube interpolation. with partial derivative */
/* with respect to the input and parameters. */
void icxdpdiCubeInterp(
double *v,			/* Pointer to first parameter value [fdi * 2^di] */
double *dv,			/* Return [2^di] deriv. wrt each parameter v */
double *din,		/* Return [fdi * di] deriv. wrt each input value */
int    fdi,			/* Number of output channels */
int    di,			/* Number of input channels */
double *out,		/* Resulting fdi values */
double *in			/* Input di values */
) {
	int e, ee, f, g;
	int dip2 = (1 << di);
	double gw[1 << MXDI];			/* weight for each matrix grid cube corner */

	/* Compute corner weights needed for interpolation */
	gw[0] = 1.0;
	for (e = 0, g = 1; e < di; e++, g *= 2) {
		int i; 
		for (i = 0; i < g; i++) {
			gw[g+i] = gw[i] * in[e];
			gw[i] *= (1.0 - in[e]);
		}
	}

	/* Now compute the output values */
	for (f = 0; f < fdi; f++) {
		out[f] = 0.0;						/* For each output value */
		for (ee = 0; ee < dip2; ee++) {	/* For all corners of cube */
			out[f] += gw[ee] * v[f * dip2 + ee];
		}
	}

	/* Copy del for parameter to return array */
	for (ee = 0; ee < dip2; ee++) {	/* For all other corners of cube */
		dv[ee] = gw[ee];					/* del from parameter */
	}

	/* Compute del from in[] value we want */
	for (e = 0; e < di; e++) {				/* For input we want del wrt */

		for (f = 0; f < fdi; f++)
			din[f * di + e] = 0.0;			/* Zero del ready of accumulation */

		for (ee = 0; ee < dip2; ee++) {		/* For all corners of cube weights, */
			int e2;							/* accumulate del from in[] we want. */
			double vv = 1.0;

			/* Compute in[] weighted cube corners for all except del of in[] we want */
			for (e2 = 0; e2 < di; e2++) {			/* const from non del inputs */
				if (e2 == e)
					continue;
				if (ee & (1 << e2))
					vv *= in[e2];
				else
					vv *= (1.0 - in[e2]);
			}

			/* Accumulate contribution of in[] we want for corner to out[] we want */
			if (ee & (1 << e)) {
				for (f = 0; f < fdi; f++)
					din[f * di + e] += v[f * dip2 + ee] * vv;
			} else {
				for (f = 0; f < fdi; f++)
					din[f * di + e] -= v[f * dip2 + ee] * vv;
			}
		}
	}
}

/* ------------------------------------------------------ */
/* Matrix cube simplex interpolation, used for device modelling. */

/* Matrix cube simplex interpolation - interpolate between 2^di output corner values. */
/* Parameters are assumed to be fdi groups of 2^di parameters. */
void icxCubeSxInterp(
double *v,			/* Pointer to first parameter */
int    fdi,			/* Number of output channels */
int    di,			/* Number of input channels */
double *out,		/* Resulting fdi values */
double *in			/* Input di values */
) {
	int    si[MAX_CHAN];		/* in[] Sort index, [0] = smalest */

//{
//	double tout[MXDO];
//
//	icxCubeInterp(v, fdi, di, tout, in);
//printf("\n~1 Cube interp result = %f\n",tout[0]);
//}

//printf("~1 icxCubeSxInterp: %f %f %f\n", in[0], in[1], in[2]);
	/* Do insertion sort on coordinates, smallest to largest. */
	{
		int ff, vf;
		unsigned int e;
		double v;
		for (e = 0; e < di; e++)
			si[e] = e;						/* Initial unsorted indexes */

		for (e = 1; e < di; e++) {
			ff = e;
			v = in[si[ff]];
			vf = ff;
			while (ff > 0 && in[si[ff-1]] > v) {
				si[ff] = si[ff-1];
				ff--;
			}
			si[ff] = vf;
		}
	}
//printf("~1 sort order %d %d %d\n", si[0], si[1], si[2]);
//printf("         from %f %f %f\n", in[si[0]], in[si[1]], in[si[2]]);

	/* Now compute the weightings, simplex vertices and output values */
	{
		unsigned int e, f;
		double w;		/* Current vertex weight */

		w = 1.0 - in[si[di-1]];		/* Vertex at base of cell */
		for (f = 0; f < fdi; f++) {
			out[f] = w * v[f * (1 << di)];
//printf("~1 out[%d] = %f = %f * %f\n",f,out[f],w,v[f * (1 << di)]);
		}

		for (e = di-1; e > 0; e--) {	/* Middle vertices */
			w = in[si[e]] - in[si[e-1]];
			v += (1 << si[e]);				/* Move to top of cell in next largest dimension */
			for (f = 0; f < fdi; f++) {
				out[f] += w * v[f * (1 << di)];
//printf("~1 out[%d] = %f += %f * %f\n",f,out[f],w,v[f * (1 << di)]);
			}
		}

		w = in[si[0]];
		v += (1 << si[0]);		/* Far corner from base of cell */
		for (f = 0; f < fdi; f++) {
			out[f] += w * v[f * (1 << di)];
//printf("~1 out[%d] = %f += %f * %f\n",f,out[f],w,v[f * (1 << di)]);
		}
	}
}

/* ------------------------------------------------------ */
/* Matrix multiplication, used for device modelling. */
/* Including partial derivative for input and parameters. */


/* 3x3 matrix in 1D array multiplication */
void icxMulBy3x3Parm(
	double out[3],			/* Return input multiplied by matrix */
	double mat[9],			/* Matrix organised in [slow][fast] order */
	double in[3]			/* Input values */
) {
	double *v = mat, ov[3];
	int e, f;

	/* Compute the output values */
	for (f = 0; f < 3; f++) {
		ov[f] = 0.0;						/* For each output value */
		for (e = 0; e < 3; e++) {
			ov[f] += *v++ * in[e];
		}
	}
	out[0] = ov[0];
	out[1] = ov[1];
	out[2] = ov[2];
}


/* 3x3 matrix in 1D array multiplication, with partial derivatives */
/* with respect to just the input. */
void icxdpdiiMulBy3x3Parm(
	double out[3],			/* Return input multiplied by matrix */
	double din[3][3],		/* Return deriv for each [output] with respect to [input] */
	double mat[9],			/* Matrix organised in [slow][fast] order */
	double in[3]			/* Input values */
) {
	double *v, ov[3];
	int e, f;

	/* Compute the output values */
	v = mat;
	for (f = 0; f < 3; f++) {
		ov[f] = 0.0;						/* For each output value */
		for (e = 0; e < 3; e++) {
			ov[f] += *v++ * in[e];
		}
	}

	/* Compute deriv. with respect to the input values */
	/* This is pretty simple for a matrix ... */
	v = mat;
	for (f = 0; f < 3; f++)
		for (e = 0; e < 3; e++)
			din[f][e] = *v++;

	out[0] = ov[0];
	out[1] = ov[1];
	out[2] = ov[2];
}

/* 3x3 matrix in 1D array multiplication, with partial derivatives */
/* with respect to the input and parameters. */
void icxdpdiMulBy3x3Parm(
	double out[3],			/* Return input multiplied by matrix */
	double dv[3][9],		/* Return deriv for each [output] with respect to [param] */
	double din[3][3],		/* Return deriv for each [output] with respect to [input] */
	double mat[9],			/* Matrix organised in [slow][fast] order */
	double in[3]			/* Input values */
) {
	double *v, ov[3];
	int e, f;

	/* Compute the output values */
	v = mat;
	for (f = 0; f < 3; f++) {
		ov[f] = 0.0;						/* For each output value */
		for (e = 0; e < 3; e++) {
			ov[f] += *v++ * in[e];
		}
	}

	/* Compute deriv. with respect to the matrix parameter % 3 */
	/* This is pretty simple for a matrix ... */
	for (f = 0; f < 3; f++) {
		for (e = 0; e < 9; e++) {
			if (e/3 ==  f)
				dv[f][e] = in[e % 3];
			else
				dv[f][e] = 0.0;
		}
	}
	
	/* Compute deriv. with respect to the input values */
	/* This is pretty simple for a matrix ... */
	v = mat;
	for (f = 0; f < 3; f++)
		for (e = 0; e < 3; e++)
			din[f][e] = *v++;

	out[0] = ov[0];
	out[1] = ov[1];
	out[2] = ov[2];
}

/* - - - - - - - - - - */
#undef stricmp





















