
/* 
 * Argyll Color Correction System
 * Output device profile creator.
 *
 * Author: Graeme W. Gill
 * Date:   11/10/00
 *
 * Copyright 2000-2011 Graeme W. Gill
 * All rights reserved.
 *
 * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
 * see the License.txt file for licencing details.
 */

/*
 * This program takes in the scattered test chart
 * points, and interpolates them into a gridded
 * forward ICC device profile for an output or display device,
 * using a clut based profle.
 * It also creates backward tables based on the forward grid.
 *
 * Preview profiles are not currently generated.
 * 
 * The gamut clut should be implemented with xicc/rspl
 */

/*
 * TTBD:
 *
 *	For flexibility, it might be nice to allow the user to override the
 *  default intent for all 3 output tables, allowing fully custom usage.
 *  ie. Would be nice to offer various relative colorimetric intents,
 *  so that more image friendly versions could be chosen. 
 *  (Need to break dependence between intents CAM to do this with full flexibility).
 *
 *  Need to make this more of a library:
 *  Fix error handling
 *  fix verbose output
 *  hand icc object back rather than writing file ?
 */

/*
	Outline of code flow:

	profout:
		Create ICC profile and all the tags. Table tags are initialy not set.

		Read in the CGTATS data and convert spectral to PCS if needed.

		Wrap the icc in an xicc, and then create an xicc lookup object
		for the device->PCS tables by calling xicc->set_luobj().

		For a CLUT type profile, create a gamut mapping object and
		setup all the other bits and peices needed to convert a color
		from PCS to device, then use (icc) icmSetMultiLutTables() which will
		call back the out_b2a_input(), out_b2a_clut() and out_b2a_output()
		functions.
 */

#undef DEBUG				/* Print B2A processing information */
#undef DEBUG_ONE			/* Test a particular value rather than process whole grid */

#define verbo stdout

#undef IMP_MONO					/* [Undef] Turn on development code */

#define NO_B2A_PCS_CURVES		/* [Define] PCS curves seem to make B2A less accurate. Why ? */
#define USE_CAM_CLIP_OPT		/* [Define] Clip out of gamut in CAM space rather than PCS */
#define USE_LEASTSQUARES_APROX	/* [Define] Use least squares fitting approximation in B2A */
//#undef USE_EXTRA_FITTING		/* [Undef] Turn on data point error compensation in A2B */
//#undef USE_2PASSSMTH			/* [Undef] Turn on Gaussian smoothing in A2B */
#undef DISABLE_GAMUT_TAG		/* [Undef] To disable gamut tag */
#undef WARN_CLUT_CLIPPING		/* [Undef] Print warning if setting clut clips */
#undef COMPARE_INV_CLUT			/* [Undef] Compare result of inv_clut with clut to diag inv probs */
#define FILTER_B2ACLIP			/* [Define] Filter clip area of B2A */
#define FILTER_THR_DE 3.0		/* [5.0] Filtering threshold DE */
#define FILTER_MAX_DE 5.0		/* [10.0] Filtering DE to gamut surface at whit MAX_RAD starts */
#define FILTER_MAX_RAD 0.1		/* [0.1] Filtering maximum radius in grid */

#include <stdio.h>
#include "numlib.h"
#include "icc.h"
#include "cgats.h"
#include "xicc.h"
#include "counters.h"
#include "rspl.h"
#include "insttypes.h"
#include "prof.h"
#include "gamut.h"
#include "gammap.h"

#ifndef MAX_CAL_ENT
#define MAX_CAL_ENT 4096
#endif

/*
   Basic algorithm outline:

 Printer:
   Figure out the input curves to give
   the flattest grid.

   Figure out the grid values.

   Use them to generate all the A2B tables.

   Use the inverse rspl code to compute
   all the B2A profiles.

*/

/*
	Notes:

	The shared gamma/shaper support is for silly applications
	like which can't handle display profiles that have per
	chanel gamma's, per chanel curves or clut based display profiles.

*/

/* NOTE:-
	It's interesting that the white and black points recorded in the tags,
	generally won't quite match the white and black points returned by
	looking up the profile in absolute mode.

	For a Matrix profile, in the case of the white point this is
	because we're not using the ICC 16 bit quantized value to
	create the relative transform matrix, and in the case of
	the black point, it can never be a perfect match because the black
	point returned by a profile lookup will be the quantized black
	point of the matrix, transformed by the rel->abs matrix, which
	generally won't be equal to an ICC quantized value.
	It might help the latter case if we were at least able to convert
	the profile quantized black point into the absolute black point
	via the rel->abs transform, and then quantize it.

	Of course all of this will be worse in the Lut type profile,
	due to the white and black points being stored in a different
	quantized space (XYZ vs. Lab) than the Lut grid point values!
 */


#ifdef DEBUG
#undef DBG
#define DBG(xxx) printf xxx ;
#else
#undef DBG
#define DBG(xxx) 
#endif

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

/* structure to support output icc B2A Lut initialisation calbacks. */
/* Note that we don't cope with a LUT matrix - assume it's unity. */

typedef struct {
	int verb;
	int total, count, last;	/* Progress count information */
	int noPCScurves;		/* Flag set if we don't want PCS curves */
	int filter;				/* Filter clipped values */
	double filter_thr;		/* Clip DE threshold */
	double filter_ratio;	/* Clip DE to radius factor */
	double filter_maxrad;	/* Clip maximum filter radius */
	icColorSpaceSignature pcsspace;	/* The PCS colorspace */
	icColorSpaceSignature devspace;	/* The device colorspace */
	icxLuLut *x;			/* A2B icxLuLut we are inverting in std PCS */

	int ntables;			/* Number of tables being set. 1 = colorimetric */
							/* 2 = colorimetric + saturation, 3 = all intents */
	int ochan;				/* Number of output channels for B2A */
	gammap *pmap;			/* Perceptual CAM to CAM Gamut mapping, NULL if no mapping */
	gammap *smap;			/* Saturation CAM to CAM Gamut mapping, NULL if no mapping */
	icxLuBase *ixp;			/* Source profile perceptual PCS to CAM conversion */
	icxLuBase *ox;			/* Destination profile CAM to std PCS conversion */
							/* (This is NOT used for the colorimetric B2A table creation!) */

							/* Abstract transform for each table. These may be */
							/* duplicates. */
	icRenderingIntent abs_intent[3];	/* Desired abstract profile rendering intent */
	icxLuBase *abs_luo[3];		/* abstract profile transform in PCS, NULL if none */

	double xyzscale[2];		/* < 1.0 if XYZ is to be scaled in destination space */
							/* for perceptual [0], and saturation [1] */
	double swxyz[3];		/* Source white point in XYZ */

    gamut *gam;				/* Output gamut object for setting gamut Lut */
	int wantLab;			/* 0 if want is XYZ PCS, 1 want is Lab PCS */
} out_b2a_callback;

/* Utility to handle abstract profile application to PCS. */
/* PCS in creating output table is always XYZ or Lab relative colorimetric, */
/* and abstract profile is absolute or relative, and will be */
/* XYZ if absolute, and PCS if relative. */
static void do_abstract(out_b2a_callback *p, int tn, double out[3], double in[3]) {
	out[0] = in[0];
	out[1] = in[1];
	out[2] = in[2];

	if (p->abs_intent[tn] == icAbsoluteColorimetric) {
		if (p->pcsspace == icSigLabData) {
			icmLab2XYZ(&icmD50, out, out);
		}
		p->x->plu->XYZ_Rel2Abs(p->x->plu, out, out);
	}

	p->abs_luo[tn]->lookup(p->abs_luo[tn], out, out);

	if (p->abs_intent[tn] == icAbsoluteColorimetric) {
		p->x->plu->XYZ_Abs2Rel(p->x->plu, out, out);
		if (p->pcsspace == icSigLabData) {
			icmXYZ2Lab(&icmD50, out, out);
		}
	}
}

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

/* Extra non-linearity applied to BtoA XYZ PCS. */
/* This distributes the LUT indexes more evenly in */
/* perceptual space, greatly improving the B2A accuracy of XYZ LUT */
/* To better use the full range of the grid, and also to make sure */
/* that the white point gets mapped accurately, scale the XYZ to put */
/* the D50 white at the top corner of the grid. */

/* Y to L* */
static void y2l_curve(double *out, double *in) {
	int i;
	double sc = 65535.0/32768.0;	/* Scale from 0.0 .. 1.999969 to 0.0 .. 1.0 and back */
	double val;

	for (i = 0; i < 3; i++) {
		val = in[i];
		val /= icmD50_ary3[i];		/* Put white at top of grid and scale */
		if (val > 0.008856451586)
			val = 1.16 * pow(val,1.0/3.0) - 0.16;
		else
			val = 9.032962896 * val;
		if (val > 1.0)
			val = 1.0;
		val *= sc;					/* Unscale */
		out[i] = val;
	}
}

/* L* to Y */
static void l2y_curve(double *out, double *in) {
	int i;
	double sc = 65535.0/32768.0;	/* Scale from 0.0 .. 1.999969 to 0.0 .. 1.0 and back */
	double val;

	/* Use an L* like curve, scaled to the maximum XYZ value */
	for (i = 0; i < 3; i++) {
		val = in[i];
		val /= sc;					/* Scale */
		if (val > 0.08)
			val = pow((val + 0.16)/1.16, 3.0);
		else
			val = val/9.032962896;
		val *= icmD50_ary3[i];		/* Unscale and put white at top of grid */
		out[i] = val;
	}
}

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

/* sRGB device gamma encoded value to linear value 0.0 .. 1.0 */
static double gdv2dv(double iv) {
	double ov;

	if (iv < 0.04045)
		ov = iv/12.92;
	else
		ov = pow((iv + 0.055)/1.055, 2.4);
	return ov;
}


/* --------------------------------------------------------- */
/* NOTE :- the assumption that each stage of the BtoA is a mirror */
/* of the AtoB makes for inflexibility. */
/* Perhaps it would be better to remove this asumption from the */
/* out_b2a_clut processing ? */
/* To do this we then need inv_out_b2a_input(), and */
/* inv_out_b2a_output(), and we need to clearly distinguish between */
/* AtoB PCS' & DEV', and BtoA PCS' & DEV', since they are not */
/* necessarily the same... */


/* B2A Input table is the inverse of the AtoB output table */
/* Input PCS output PCS'' */
void out_b2a_input(void *cntx, double out[3], double in[3]) {
	out_b2a_callback *p = (out_b2a_callback *)cntx;

	DBG(("out_b2a_input got         PCS %f %f %f\n",in[0],in[1],in[2]))

	/* PCS to PCS' */
	if (p->noPCScurves) {
		out[0] = in[0];
		out[1] = in[1];
		out[2] = in[2];
	} else {
		if (p->x->inv_output(p->x, out, in) > 1)
			error("%d, %s",p->x->pp->errc,p->x->pp->err);
	}
	/* PCS' to PCS'' */
	if (p->pcsspace == icSigXYZData)	/* Apply XYZ non-linearity curve */
		y2l_curve(out, out);

	DBG(("out_b2a_input returning PCS'' %f %f %f\n",out[0],out[1],out[2]))
}

/* clut - multitable */
/* Input PCS' output Dev' */
/* We're applying any abstract profile after gamut mapping, */
/* on the assumption is is primarily being used to "correct" the */
/* output device. Ideally the gamut mapping should take the change */
/* the abstract profile has on the output device into account, but */
/* currently we're not doing this.. */
/* If ICM_CLUT_SET_FILTER is being used, then */
/* we need to set a filter radius value at out[-1-table] */
void out_b2a_clut(void *cntx, double *out, double in[3]) {
	out_b2a_callback *p = (out_b2a_callback *)cntx;
	double inn[3], in1[3];
	double cdist = 0.0;		/* Clipping DE */
	int tn;

	DBG(("\nout_b2a_clut got      PCS'' %f %f %f\n",in[0],in[1],in[2]))

	in1[0] = in[0];		/* in[] may be aliased with out[] */
	in1[1] = in[1];		/* so take a copy.  */
	in1[2] = in[2];		/* (If we were using "index-under", we should copy it too) */

	DBG(("out_b2a_clut got       PCS' %f %f %f\n",in[0],in[1],in[2]))

	if (p->pcsspace == icSigXYZData)		/* Undo effects of extra XYZ non-linearity curve */
		l2y_curve(in1, in1);

	inn[0] = in1[0];	/* Copy of PCS' for 2nd and 3rd tables */
	inn[1] = in1[1];
	inn[2] = in1[2];

	if (p->abs_luo[0] != NULL) {	/* Abstract profile to apply to first table only. */

		if (!p->noPCScurves) {	/* Convert from PCS' to PCS so we can apply abstract */
			if (p->x->output(p->x, in1, in1) > 1)
				error("%d, %s",p->x->pp->errc,p->x->pp->err);
		}
		do_abstract(p, 0, in1, in1);	/* Abstract profile to apply to first table */
		DBG(("through abstract prof   PCS %f %f %f\n",in1[0],in1[1],in1[2]))
		/* We now have PCS */
	}

	if (p->noPCScurves || p->abs_luo[0] != NULL) {	/* We were given PCS or have converted to PCS */

		/* PCS to PCS' */
		if (p->x->inv_output(p->x, in1, in1) > 1)
			error("%d, %s",p->x->pp->errc,p->x->pp->err);

		DBG(("noPCScurves = %d, abs_luo[0] = 0x%x\n",p->noPCScurves,p->abs_luo[0]))
		DBG(("convert PCS to PCS' got     %f %f %f\n",in1[0],in1[1],in1[2]))
	}

	/* Invert AtoB clut (PCS' to Dev') Colorimetric */
	/* to producte the colorimetric tables output. */
	/* (Note that any aux target if we were using one, would be in Dev space) */
	if (p->x->inv_clut_aux(p->x, out, NULL, NULL, NULL, &cdist, in1) > 1)
		error("%d, %s",p->x->pp->errc,p->x->pp->err);
	if (p->filter) {
		cdist -= p->filter_thr;
		if (cdist < 0.0)
			cdist = 0.0; 
		cdist *= p->filter_ratio;
		if (cdist > p->filter_maxrad)
			cdist = p->filter_maxrad;
		out[-1-0] = cdist;
	}

	DBG(("convert PCS' to DEV' got    %s\n",icmPdv(p->ochan, out)))

#ifdef COMPARE_INV_CLUT		/* Compare the inversion result with the fwd lookup */
	{
		double chk[3];

		if (p->x->clut(p->x, chk, out) > 1)
			error("%d, %s",p->x->pp->errc,p->x->pp->err);

		DBG(("check DEV' to PCS' got      %f %f %f\n",chk[0],chk[1],chk[2]))
	}
#endif
	if (p->ntables > 1) {		/* Do first part once for both intents */
		double *tnout = out;	/* This tables output values */

		DBG(("\n"))

		/* Starting with original input inn[] PCS' (no abstract profile applied to other tables) */
		in1[0] = inn[0];
		in1[1] = inn[1];
		in1[2] = inn[2];

		if (!p->noPCScurves) {					/* Convert from PCS' to PCS */
			if (p->x->output(p->x, in1, in1) > 1)
				error("%d, %s",p->x->pp->errc,p->x->pp->err);
		}
		DBG(("convert PCS' to PCS got %f %f %f\n",in1[0],in1[1],in1[2]))

		/* Convert from PCS to CAM/Gamut mapping space */
		p->ixp->fwd_relpcs_outpcs(p->ixp, p->pcsspace, in1, in1);

		DBG(("convert PCS to CAM got %f %f %f\n",in1[0],in1[1],in1[2]))

		/* Apply gamut mapping in CAM space for remaining tables */
		/* and create the output values */
		for (tn = 1; tn < p->ntables; tn++) {
			double in2[3];

			tnout += p->ochan;	/* next table/intent */
			in2[0] = in1[0];	/* Copy in1[] so it can be used for both tables */
			in2[1] = in1[1];
			in2[2] = in1[2];

			/* Do luminence scaling if requested */
			if (p->xyzscale[tn-1] < 1.0) {
				double xyz[3];

				DBG(("got xyzscale = %f\n",p->xyzscale[tn-1]))
				DBG(("PCS %f %f %f\n",in2[0], in2[1], in2[2]))
				/* Convert our CAM to XYZ */
				/* We're being bad in delving inside the xluo, but we'll fix it latter */
				p->ox->cam->cam_to_XYZ(p->ox->cam, xyz, in2);

				DBG(("XYZ %f %f %f\n",xyz[0], xyz[1], xyz[2]))
				/* Scale it */
				xyz[0] *= p->xyzscale[tn-1];
				xyz[1] *= p->xyzscale[tn-1];
				xyz[2] *= p->xyzscale[tn-1];

				DBG(("scaled XYZ %f %f %f\n",xyz[0], xyz[1], xyz[2]))
				/* Convert back to CAM */
				/* We're being bad in delving inside the xluo, but we'll fix it latter */
				p->ox->cam->XYZ_to_cam(p->ox->cam, in2, xyz);

				DBG(("scaled PCS %f %f %f\n",in2[0], in2[1], in2[2]))
			}

			if (tn == 1) {
				DBG(("percep gamut map in %f %f %f\n",in2[0],in2[1],in2[2]))
				p->pmap->domap(p->pmap, in2, in2);	/* Perceptual mapping */
				DBG(("percep gamut map out %f %f %f\n",in2[0],in2[1],in2[2]))
			} else {
				DBG(("sat gamut map in %f %f %f\n",in2[0],in2[1],in2[2]))
				p->smap->domap(p->smap, in2, in2);	/* Saturation mapping */
				DBG(("sat gamut map got %f %f %f\n",in2[0],in2[1],in2[2]))
			}

			/* Convert from Gamut maping/CAM space to PCS */
			p->ox->bwd_outpcs_relpcs(p->ox, p->pcsspace, in2, in2);
			DBG(("convert CAM to PCS got %f %f %f\n",in2[0],in2[1],in2[2]))

			if (p->abs_luo[tn] != NULL)		/* Abstract profile to other tables after gamut map */
				do_abstract(p, tn, in2, in2);

			/* Convert from PCS to PCS' */
			if (p->x->inv_output(p->x, in2, in2) > 1)
				error("%d, %s",p->x->pp->errc,p->x->pp->err);
				DBG(("convert PCS to PCS' got %f %f %f\n",in2[0],in2[1],in2[2]))

			/* Invert AtoB clut (PCS' to Dev') */
			/* to producte the perceptual or saturation tables output. */
			/* (Note that any aux target if we were using one, would be in Dev space) */
			if (p->x->inv_clut_aux(p->x, tnout, NULL, NULL, NULL, &cdist, in2) > 1)
				error("%d, %s",p->x->pp->errc,p->x->pp->err);
			if (p->filter) {
				cdist -= p->filter_thr;
				if (cdist < 0.0)
					cdist = 0.0; 
				cdist *= p->filter_ratio;
				if (cdist > p->filter_maxrad)
					cdist = p->filter_maxrad;
				out[-1-tn] = cdist;
			}
			DBG(("convert PCS' to DEV' got %s\n",icmPdv(p->ochan, tnout)))
		}
	}

	DBG(("out_b2a_clut returning DEV' %s\n",icmPdv(p->ochan, out)))

	if (p->verb) {		/* Output percent intervals */
		int pc;
		p->count++;
		pc = (int)(p->count * 100.0/p->total + 0.5);
		if (pc != p->last) {
			printf("%c%2d%%",cr_char,pc); fflush(stdout);
			p->last = pc;
		}
	}
}

/* Output table is the inverse of the AtoB input table */
/* Input Dev' output Dev */
void out_b2a_output(void *cntx, double out[4], double in[4]) {
	out_b2a_callback *p = (out_b2a_callback *)cntx;

	DBG(("out_b2a_output got      DEV' %s\n",icmPdv(p->ochan,in)))

	if (p->x->inv_input(p->x, out, in) > 1)
		error("%d, %s",p->x->pp->errc,p->x->pp->err);

	DBG(("out_b2a_output returning DEV %s\n",icmPdv(p->ochan,out)))
}

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

/* PCS' -> distance to gamut boundary */
static void PCSp_bdist(void *cntx, double out[1], double in[3]) {
	out_b2a_callback *p = (out_b2a_callback *)cntx;
	double pcs[3];				/* PCS value of input */
	double nrad;				/* normalised radial value of point */
	double np[3];				/* Nearest point */
	double gdist;				/* Out of gamut distance */
	
//printf("~1 bdist got PCS %f %f %f\n",in[0],in[1],in[2]);
	/* Do PCS' -> PCS */
	if (p->x->inv_output(p->x, pcs, in) > 1)
		error("%d, %s",p->x->pp->errc,p->x->pp->err);

	/* If PCS is XYZ, convert to Lab */
	if (p->wantLab == 0) {
		icmXYZ2Lab(&icmD50, pcs, pcs);
//printf("~1 bdist converted to Lab %f %f %f\n",pcs[0],pcs[1],pcs[2]);
	}

	/* Check if this point is in or out of gamut */
	nrad = p->gam->nradial(p->gam, np, pcs);

//printf("~1 nrad = %f\n",nrad);

	/* Radial rather than nearest distance seems the best overall. */

	gdist = icmNorm33(np, pcs);
	if (nrad <= 1.0)
		gdist = -gdist;		/* -ve delta E if within gamut */
	
//printf("~1 gdist %f\n",gdist);

	/* Distance in PCS space will be roughly -128 -> 128 */
	/* Clip to range -20 - +20, then scale to 0.0 - 1.0 */
	if (gdist < -20.0)
		gdist = -20.0;
	else if (gdist > 20.0)
		gdist = 20.0;

	out[0] = (gdist + 20.0)/40.0;
//printf("~1 bdist returning %f\n",out[0]);

	if (p->verb) {		/* Output percent intervals */
		int pc;
		p->count++;
		pc = (int)(p->count * 100.0/p->total + 0.5);
		if (pc != p->last) {
			printf("%c%2d%%",cr_char,pc); fflush(stdout);
			p->last = pc;
		}
	}
}

/* The output table for a Gamut lut is usually a special function, returning */
/* a value of 0 for all inputs <= 0.5, and then outputing between */
/* 0.0 and 1.0 for the input range 0.5 to 1.0. This is so a graduated */
/* "gamut boundary distance" number from the multi-d lut can be */
/* translated into the ICC "0.0 if in gamut, > 0.0 if not" number. */
static void gamut_output(void *cntx, double out[1], double in[1]) {
	double iv, ov;
	iv = in[0];
	if (iv <= 0.5)
		ov = 0.0;
	else
		ov = (iv - 0.5) * 2.0;
	out[0] = ov;
}

/* -------------------------------------------------------------- */
/* powell() callback to set XYZ white scaling factor for */
/* Absolute Appearance mode with scaling intent */

static double xyzoptfunc(void *cntx, double *v) {
	out_b2a_callback *p = (out_b2a_callback *)cntx;
	double swxyz[3], jab[3], dev[MAX_CHAN];
	double rv;
	int rc;

	rv = 2.0 - v[0];	/* Make Y as large as possible */

	/* If we wanted to use this function to maximise the brightness */
	/* we would not limit the scale to 1.0 */
	if (v[0] > 1.0) {
		rv += 1000.0;
		return rv;
	}
	if (v[0] < 0.0) {
		rv += 100.0;
		return rv;
	}
	swxyz[0] = v[0] * p->swxyz[0];
	swxyz[1] = v[0] * p->swxyz[1];
	swxyz[2] = v[0] * p->swxyz[2];

//printf("~1 scaled white XYZ = %f %f %f\n", swxyz[0], swxyz[1], swxyz[2]);

	/* We're being bad in delving inside the xluo, but we'll fix it latter */
	p->ox->cam->XYZ_to_cam(p->ox->cam, jab, swxyz);

//printf("~1 scaled white Jab = %f %f %f\n", jab[0], jab[1], jab[2]);

	/* Run the target PCS backwards through the output space to see if it clips */
	rc = p->ox->inv_lookup(p->ox, dev, jab);
//printf("~1 device = %f %f %f, rc = %d\n", dev[0], dev[1], dev[2],rc);
	if (rc != 0)
		rv += 500.0;

//printf("~1 xyzoptfunc rv %f from xyzscale %f\n\n",rv,v[0]);
	return rv;
}

/* -------------------------------------------------------------- */
/* Make an output device profile, where a forward mapping is from */
/* RGB/CMYK to XYZ/Lab space */
void
make_output_icc(
	prof_atype ptype,		/* Profile algorithm type */
	int mtxtoo,				/* NZ if matrix tags should be created for Display XYZ cLUT */
	icmICCVersion iccver,	/* ICC profile version to create */
	int verb,				/* Vebosity level, 0 = none */
	int iquality,			/* A2B table quality, 0..3 */
	int oquality,			/* B2A table quality, 0..3 */
	int noisluts,			/* nz to supress creation of input (Device) shaper luts */
	int noipluts,			/* nz to supress creation of input (Device) position luts */
	int nooluts,			/* nz to supress creation of output (PCS) shaper luts */
	int nocied,				/* nz to supress inclusion of .ti3 data in profile */
	int noptop,				/* nz to use colorimetic source gamut to make perceptual table */ 
	int nostos,				/* nz to use colorimetic source gamut to make saturation table */
	int gamdiag,			/* Make gamut mapping diagnostic wrl plots */
	int verify,				/* nz to print verification */
	int clipprims,			/* Clip white, black and primaries */
	icxInk *oink,			/* Ink limit/black generation setup (NULL if n/a) */
	char *in_name,			/* input .ti3 file name */
	char *file_name,		/* output icc name */
	cgats *icg,				/* input cgats structure */
	int spec,				/* Use spectral data flag */
	icxIllumeType tillum,	/* Target/simulated instrument illuminant */
	xspect *cust_tillum,	/* Possible custom target/simulated instrument illumination */
	icxIllumeType illum,	/* Spectral illuminant */
	xspect *cust_illum,		/* Possible custom illumination */
	icxObserverType observ,	/* Spectral observer */
	int fwacomp,			/* FWA compensation requested */
	double smooth,			/* RSPL smoothing factor, -ve if raw */
	double avgdev,			/* reading Average Deviation as a proportion of the input range */
	char *ipname,			/* input icc profile - enables gamut map, NULL if none */
	char *sgname,			/* source image gamut - NULL if none */
	char *absname[3],		/* abstract profile name for each table */
	int sepsat,				/* Create separate Saturation B2A */
	icxViewCond *ivc_p,		/* Input Viewing Parameters for CAM */
	icxViewCond *ovc_p,		/* Output Viewing Parameters for CAM */
	int ivc_e,				/* Input Enumerated viewing condition */
	int ovc_e,				/* Output Enumerated viewing condition */
	icxGMappingIntent *pgmi,/* Perceptual gamut mapping intent */
	icxGMappingIntent *sgmi,/* Saturation gamut mapping intent */
	profxinf *xpi			/* Optional Profile creation extra data */
) {
	int isdisp;				/* nz if this is a display device, 0 if output */
	double dispLuminance = 0.0;	/* Display luminance. 0 if not known */
	int isdnormed = 0;		/* Has display data been normalised to 100 ? */
	int allintents;			/* nz if all intents should possibly be created */
	icmFile *wr_fp;
	icc *wr_icco;
	int npat;				/* Number of patches */
	cow *tpat;				/* Patch input values */
	int i, j, rv = 0;
	icColorSpaceSignature devspace = icmSigDefaultData;	/* The device colorspace */
	inkmask imask = 0;		/* Device inkmask */ 
	int isAdditive = 0;		/* 0 if subtractive, 1 if additive colorspace */
	int isLab = 0;			/* 0 if input is XYZ, 1 if input is Lab */
	int wantLab = 0;		/* 0 if want XYZ, 1 want Lab */
	int isLut = 0;			/* 0 if shaper+ matrix, 1 if lut type */
	int isShTRC = 0;		/* 0 if separate gamma/shaper TRC, 1 if shared */
	int devchan = 0;		/* Number of device chanels */
	int a2binres = 0;		/* A2B input (device) table resolution */
	int a2bres = 0;			/* A2B clut resolution */
	int a2boutres = 0;		/* A2B output (PCS) table resolution */
	int b2ainres = 0;		/* B2A input (PCS) table resolution */
	int b2ares = 0;			/* B2A clut resolution */
	int b2aoutres = 0;		/* B2A output (device) table resolution */
	xcal *cal = NULL;		/* Calibration if present, NULL if none */
	icxInk iink;			/* Source profile ink limit values */

	memset((void *)&iink, 0, sizeof(icxInk));
	iink.tlimit = -1.0;		/* default to unknown */
	iink.klimit = -1.0;

	if (ptype == prof_clutLab) {		/* Lab lut */
		wantLab = 1;
		isLut = 1;
	} else if (ptype == prof_clutXYZ) {	/* XYZ lut */
		wantLab = 0;
		isLut = 1;
	} else {
		wantLab = 0;			/* gamma/shaper + matrix profile must be XYZ */
		isLut = 0;

		if (ptype == prof_gam1mat	
		 || ptype == prof_sha1mat
		 || ptype == prof_matonly) {
			isShTRC = 1;		/* Single curve */
		}
	}

	{
		int ti;

		if ((ti = icg->find_kword(icg, 0, "DEVICE_CLASS")) < 0)
			error ("Input file doesn't contain keyword DEVICE_CLASS");

		if (strcmp(icg->t[0].kdata[ti],"DISPLAY") == 0) {
			isdisp = 1;
			if (isLut && ipname != NULL)
				allintents = 1;
			else
				allintents = 0;		/* Only the default intent */
		} else {
			isdisp = 0;
			allintents = 1;
		}
	}

	/* See if display luminance data is present */
	if (isdisp) {
		int ti;

		if ((ti = icg->find_kword(icg, 0, "LUMINANCE_XYZ_CDM2")) >= 0) {
			if (sscanf(icg->t[0].kdata[ti], " %*lf %lf %*lf ",&dispLuminance) != 1)
				dispLuminance = 0.0;
		}

		/* See if the CIE data has been normalised to Y = 100 */
		if ((ti = icg->find_kword(icg, 0, "NORMALIZED_TO_Y_100")) < 0
		 || strcmp(icg->t[0].kdata[ti],"NO") == 0) {
			isdnormed = 0;
		} else {
			isdnormed = 1;
		}
	}

	/* Figure out what sort of device colorspace it is */
	{
		int ti;

		if ((ti = icg->find_kword(icg, 0, "COLOR_REP")) < 0)
			error("Input file doesn't contain keyword COLOR_REPS");

		if (strcmp(icg->t[0].kdata[ti],"CMYK_XYZ") == 0) {
			imask = ICX_CMYK;
			devspace = icSigCmykData;
			devchan = 4;
			isLab = 0;
			isAdditive = 0;
		} else if (strcmp(icg->t[0].kdata[ti],"CMYK_LAB") == 0) {
			imask = ICX_CMYK;
			devspace = icSigCmykData;
			devchan = 4;
			isLab = 1;
			isAdditive = 0;
		} else if (strcmp(icg->t[0].kdata[ti],"CMY_XYZ") == 0) {
			imask = ICX_CMY;
			devspace = icSigCmyData;
			devchan = 3;
			isLab = 0;
			isAdditive = 0;
		} else if (strcmp(icg->t[0].kdata[ti],"CMY_LAB") == 0) {
			imask = ICX_CMY;
			devspace = icSigCmyData;
			devchan = 3;
			isLab = 1;
			isAdditive = 0;
		} else if (strcmp(icg->t[0].kdata[ti],"RGB_XYZ") == 0) {
			imask = ICX_RGB;
			devspace = icSigRgbData;
			devchan = 3;
			isLab = 0;
			isAdditive = 1;
		} else if (strcmp(icg->t[0].kdata[ti],"RGB_LAB") == 0) {
			imask = ICX_RGB;
			devspace = icSigRgbData;
			devchan = 3;
			isLab = 1;
			isAdditive = 1;
		} else if (   strcmp(icg->t[0].kdata[ti],"iRGB_XYZ") == 0) {
			imask = ICX_IRGB;
			devspace = icSigRgbData;
			devchan = 3;
			isLab = 0;
			isAdditive = 1;
		} else if (strcmp(icg->t[0].kdata[ti],"iRGB_LAB") == 0) {
			imask = ICX_IRGB;
			devspace = icSigRgbData;
			devchan = 3;
			isLab = 1;
			isAdditive = 1;
#ifdef IMP_MONO
		} else if (strcmp(icg->t[0].kdata[ti],"K_XYZ") == 0) {
			imask = ICX_K;
			devspace = icSigGrayData;
			devchan = 1;
			isLab = 0;
			isAdditive = 0;
		} else if (strcmp(icg->t[0].kdata[ti],"K_LAB") == 0) {
			imask = ICX_K;
			devspace = icSigGrayData;
			devchan = 1;
			isLab = 1;
			isAdditive = 0;
		} else if (strcmp(icg->t[0].kdata[ti],"W_XYZ") == 0) {
			imask = ICX_W;
			devspace = icSigGrayData;
			devchan = 1;
			isLab = 0;
			isAdditive = 1;
		} else if (strcmp(icg->t[0].kdata[ti],"W_LAB") == 0) {
			imask = ICX_W;
			devspace = icSigGrayData;
			devchan = 1;
			isLab = 1;
			isAdditive = 1;
#endif /* IMP_MONO */
		} else 
			error("Output device input file has unhandled color representation '%s'",
			                                                     icg->t[0].kdata[ti]);
		/* Figure out some suitable table sizes */
		if (devchan >= 4) {			/* devchan == 4 or greater */
			if (iquality >= 3) {
		    	a2binres = 2048;
		    	a2bres = 23;
		    	a2boutres = 2048;
			} else if (iquality == 2) {
		    	a2binres = 2048;
		    	a2bres = 17;
		    	a2boutres = 2048;
			} else if (iquality == 1) {
		    	a2binres = 1024;
		    	a2bres = 9;
		    	a2boutres = 1024;
			} else {
		    	a2binres = 512;
		    	a2bres = 5;
		    	a2boutres = 512;
			}
		} else if (devchan >= 2) {		/* devchan == 2 or 3 */
			if (iquality >= 3) {
		    	a2binres = 2048;
		    	a2bres = 45;
		    	a2boutres = 2048;
			} else if (iquality == 2) {
		    	a2binres = 2048;
		    	a2bres = 33;
		    	a2boutres = 2048;
			} else if (iquality == 1) {
		    	a2binres = 1024;
		    	a2bres = 17;
		    	a2boutres = 1024;
			} else {
		    	a2binres = 512;
		    	a2bres = 9;
		    	a2boutres = 512;
			}
		} else {	/* devchan == 1 */
			if (iquality >= 3) {
		    	a2binres = 256;
		    	a2bres = 2048;
		    	a2boutres = 256;
			} else if (iquality == 2) {
		    	a2binres = 256;
		    	a2bres = 1024;
		    	a2boutres = 256;
			} else if (iquality == 1) {
		    	a2binres = 256;
		    	a2bres = 512;
		    	a2boutres = 256;
			} else {
		    	a2binres = 256;
		    	a2bres = 256;
		    	a2boutres = 256;
			}
		}

		if (devchan >= 2) {
			if (oquality >= 3) {	/* Ultra High */
		    	b2ainres = 2048;
		    	b2ares = 45;
		    	b2aoutres = 2048;
			} else if (oquality == 2) {
		    	b2ainres = 2048;
		    	b2ares = 33;		/* High */
		    	b2aoutres = 2048;
			} else if (oquality == 1) {
		    	b2ainres = 1024;
		    	b2ares = 17;		/* Medium */
		    	b2aoutres = 1024;
			} else if (oquality >= 0) {
		    	b2ainres = 512;
		    	b2ares = 9;			/* Low */
		    	b2aoutres = 512;
			} else {				/* Special, Extremely low quality */
		    	b2ainres = 64;
		    	b2ares = 3;
		    	b2aoutres = 64;
			}
		} else {	/* devchan == 1 */
			if (oquality >= 3) {	/* Ultra High */
		    	b2ainres = 256;
		    	b2ares = 3;
		    	b2aoutres = 4096;
			} else if (oquality == 2) {
		    	b2ainres = 256;
		    	b2ares = 3;		/* High */
		    	b2aoutres = 2048;
			} else if (oquality == 1) {
		    	b2ainres = 256;
		    	b2ares = 3;		/* Medium */
		    	b2aoutres = 1024;
			} else if (oquality >= 0) {
		    	b2ainres = 256;
		    	b2ares = 3;			/* Low */
		    	b2aoutres = 512;
			} else {				/* Special, Extremely low quality */
		    	b2ainres = 64;
		    	b2ares = 3;
		    	b2aoutres = 64;
			}
		}

	}

	/* See if there is a calibration in the .ti3, and read it if there is */
	{
		int oi, tab;

		if ((oi = icg->get_oi(icg, "CAL")) < 0)
			error("Expect CAL type to be registered");

		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) {
				error("new_xcal failed");
			}
			if (cal->read_cgats(cal, icg, tab, in_name) != 0)  {
				error("%s",cal->err);
			}

			if (cal->devmask != imask)
				error("Calibrate colorspace %s doesn't match .ti3 %s",
				       icx_inkmask2char(cal->devmask, 1), 
				       icx_inkmask2char(imask, 1));

			if ((isdisp && cal->devclass != icSigDisplayClass)
			 || (!isdisp && cal->devclass != icSigOutputClass)) 
				error("Calibration class %s doesn't match .ti3 %s",
				       icm2str(icmProfileClassSignature,cal->devclass),
				       isdisp ? "Display" : "Output");
		}
	}

	/* Open up the file for writing */
	if ((wr_fp = new_icmFileStd_name(file_name,"w")) == NULL)
		error("Write: Can't open file '%s'",file_name);

	if ((wr_icco = new_icc()) == NULL)
		error("Write: Creation of ICC object failed");

	/* Add all the tags required */

	/* The header: */
	{
		icmHeader *wh = wr_icco->header;

		/* Values that must be set before writing */
		if (isdisp)
			wh->deviceClass     = icSigDisplayClass;
		else
			wh->deviceClass     = icSigOutputClass;
    	wh->colorSpace      = devspace;					/* Device space it is */
		if (wantLab) 
	    	wh->pcs         = icSigLabData;
		else
    		wh->pcs         = icSigXYZData;				/* Must be XYZ for matrix based profile */

		if (xpi->default_ri != icMaxEnumIntent)
	    	wh->renderingIntent = xpi->default_ri;
		else
	    	wh->renderingIntent = icRelativeColorimetric;

		/* Values that should be set before writing */
		if (xpi != NULL && xpi->manufacturer != 0L)
			wh->manufacturer = xpi->manufacturer;
		else
			wh->manufacturer = icmSigUnknownType;

		if (xpi != NULL && xpi->model != 0L)
			wh->model        = xpi->model;
		else
	    	wh->model        = icmSigUnknownType;

		/* Values that may be set before writing */
		if (xpi != NULL && xpi->creator != 0L)
			wh->creator = xpi->creator;

#ifdef NT
		wh->platform = icSigMicrosoft;
#endif
#ifdef __APPLE__
		wh->platform = icSigMacintosh;
#endif
#if defined(UNIX) && !defined(__APPLE__)
		wh->platform = icmSig_nix;
#endif

		if (xpi != NULL && xpi->transparency)
			wh->attributes.l |= icTransparency;
		if (xpi != NULL && xpi->matte)
			wh->attributes.l |= icMatte;
		if (xpi != NULL && xpi->negative)
			wh->attributes.l |= icNegative;
		if (xpi != NULL && xpi->blackandwhite)
			wh->attributes.l |= icBlackAndWhite;
	}

	/* mtxtoo only applies to Display cLUT profiles */
	if (!isLut
		|| wr_icco->header->deviceClass != icSigDisplayClass
	    || wr_icco->header->pcs != icSigXYZData) {
		mtxtoo = 0;
	}

	/* Set the version of ICC profile we want */
	if (isdisp && allintents) {
		if (iccver < icmVersion2_4) {
			iccver = icmVersion2_4;		/* Need 2.4.0 for Display intents */
			if (verb)
				fprintf(verbo,"Bumped ICC version to 2.4.0 to accomodate multiple Display intents\n");
		}
	}
	if (wr_icco->set_version(wr_icco, iccver) != 0)
		error("set_version failed: %d, %s",wr_icco->errc,wr_icco->err);

	/* Profile Description Tag: */
	{
		icmTextDescription *wo;
		char *dst, dstm[200];			/* description */

		if (xpi != NULL && xpi->profDesc != NULL)
			dst = xpi->profDesc;
		else {
			sprintf(dstm, "This is a Lut style %s - %s Output Profile",
			devspace == icSigCmykData ? "CMYK" :
			            devspace == icSigCmyData ? "CMY" : "RGB",
			wantLab ? "Lab" : "XYZ");
			dst = dstm;
		}

		if ((wo = (icmTextDescription *)wr_icco->add_tag(
		           wr_icco, icSigProfileDescriptionTag,	icSigTextDescriptionType)) == NULL) 
			error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);

		wo->size = strlen(dst)+1; 	/* Allocated and used size of desc, inc null */
		wo->allocate((icmBase *)wo);/* Allocate space */
		strcpy(wo->desc, dst);		/* Copy the string in */
	}
	/* Copyright Tag: */
	{
		icmText *wo;
		char *crt;

		if (xpi != NULL && xpi->copyright != NULL)
			crt = xpi->copyright;
		else
			crt = "Copyright, the creator of this profile";

		if ((wo = (icmText *)wr_icco->add_tag(
		           wr_icco, icSigCopyrightTag,	icSigTextType)) == NULL) 
			error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);

		wo->size = strlen(crt)+1; 	/* Allocated and used size of text, inc null */
		wo->allocate((icmBase *)wo);/* Allocate space */
		strcpy(wo->data, crt);		/* Copy the text in */
	}
	/* Device Manufacturers Description Tag: */
	if (xpi != NULL && xpi->deviceMfgDesc != NULL) {
		icmTextDescription *wo;
		char *dst = xpi->deviceMfgDesc;

		if ((wo = (icmTextDescription *)wr_icco->add_tag(
		           wr_icco, icSigDeviceMfgDescTag,	icSigTextDescriptionType)) == NULL) 
			error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);

		wo->size = strlen(dst)+1; 	/* Allocated and used size of desc, inc null */
		wo->allocate((icmBase *)wo);/* Allocate space */
		strcpy(wo->desc, dst);		/* Copy the string in */
	}
	/* Model Description Tag: */
	if (xpi != NULL && xpi->modelDesc != NULL) {
		icmTextDescription *wo;
		char *dst = xpi->modelDesc;

		if ((wo = (icmTextDescription *)wr_icco->add_tag(
		           wr_icco, icSigDeviceModelDescTag,	icSigTextDescriptionType)) == NULL) 
			error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);

		wo->size = strlen(dst)+1; 	/* Allocated and used size of desc, inc null */
		wo->allocate((icmBase *)wo);/* Allocate space */
		strcpy(wo->desc, dst);		/* Copy the string in */
	}
	/* Display Luminance tag */
	if (isdisp && dispLuminance > 0.0) {
		{
			icmXYZArray *wo;;
	
			if ((wo = (icmXYZArray *)wr_icco->add_tag(
			           wr_icco, icSigLuminanceTag, icSigXYZArrayType)) == NULL) 
				error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);

			wo->size = 1;
			wo->allocate((icmBase *)wo);	/* Allocate space */
			wo->data[0].X = 0.0;			/* Set a default value */
			wo->data[0].Y = dispLuminance;	/* Set a default value */
			wo->data[0].Z = 0.0;			/* Set a default value */
		}
	}
	/* White Point Tag: */
	{
		icmXYZArray *wo;
		/* Note that tag types icSigXYZType and icSigXYZArrayType are identical */
		if ((wo = (icmXYZArray *)wr_icco->add_tag(
		           wr_icco, icSigMediaWhitePointTag, icSigXYZArrayType)) == NULL) 
			error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);

		wo->size = 1;
		wo->allocate((icmBase *)wo);	/* Allocate space */
		wo->data[0] = icmD50;			/* Set a default value - D50 */
	}
	/* Black Point Tag: */
	{
		icmXYZArray *wo;
		if ((wo = (icmXYZArray *)wr_icco->add_tag(
		           wr_icco, icSigMediaBlackPointTag, icSigXYZArrayType)) == NULL) 
			error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);

		wo->size = 1;
		wo->allocate((icmBase *)wo);	/* Allocate space */
		wo->data[0] = icmBlack;			/* Set a default value - absolute black */
	}

	/* Colorant Table Tag: */
	{
		unsigned int i;
		icmColorantTable *wo;
		if ((wo = (icmColorantTable *)wr_icco->add_tag(
		           wr_icco, icSigColorantTableTag, icSigColorantTableType)) == NULL) 
			error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);

   		wo->count = icmCSSig2nchan(devspace);
   		if (wo->count != (unsigned long)icx_noofinks(imask))
			error("Interna: device colorspace and inkmask conflict!");

		wo->allocate((icmBase *)wo);	/* Allocate ColorantTable structures */

		for (i = 0; i < wo->count; i++) {
			inkmask iimask;			/* Individual ink mask */
			char *name;
			
			iimask = icx_index2ink(imask, i);
			name = icx_ink2string(iimask); 
			if (strlen(name) > 31)
				error("Internal: colorant name exceeds 31 characters");
			strcpy(wo->data[i].name, name);
		}
		/* Fill in the colorant PCS values when we've got something to lookup */
	}

	/* vcgt tag */
	if (verb & isdisp && cal != NULL && cal->noramdac) {	/* We've been given vcgt information */
		fprintf(verbo,"Not writing calibration to 'vcgt' because there is no VideoLUT access\n");
	}
	if (isdisp && cal != NULL && !cal->noramdac) {	/* We've been given vcgt information */
		int j, i;
		int ncal = 256;					/* This is safe with other s/w */
		icmVideoCardGamma *wo;
		wo = (icmVideoCardGamma *)wr_icco->add_tag(wr_icco,
		                           icSigVideoCardGammaTag, icSigVideoCardGammaType);
		if (wo == NULL)
			error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);

		wo->tagType = icmVideoCardGammaTableType;
		wo->u.table.channels = 3;			/* rgb */
		wo->u.table.entryCount = ncal;		/* full lut */
		wo->u.table.entrySize = 2;			/* 16 bits */
		wo->allocate((icmBase*)wo);
		for (j = 0; j < 3; j++) {
			for (i = 0; i < ncal; i++) {
				double cc, vv = i/(ncal - 1.0);
				cc = cal->interp_ch(cal, j, vv);
				if (cc < 0.0)
					cc = 0.0;
				else if (cc > 1.0)
					cc = 1.0;
				((unsigned short*)wo->u.table.data)[ncal * j + i] = (int)(cc * 65535.0 + 0.5);
			}
		}
	}

	if (isLut) {		/* Lut type profile */

		/* Up to and including ICC Version 2.3, Display LUT profiles were assumed */
		/* to have AtoB0 with no interpretation of the intent, which */
		/* implies Relative Colorimetric. */

		/* 16 bit dev -> pcs lut: (A2B) */
		{
			icmLut *wo;
	
			if (!allintents) {	/* Only A2B0, no intent */
				if ((wo = (icmLut *)wr_icco->add_tag(
				           wr_icco, icSigAToB0Tag,	icSigLut16Type)) == NULL) 
					error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
			} else {
				/* Intent 1 = relative colorimetric */
				if ((wo = (icmLut *)wr_icco->add_tag(
				           wr_icco, icSigAToB1Tag,	icSigLut16Type)) == NULL) 
					error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
			}
		
			wo->inputChan = devchan;
			wo->outputChan = 3;
	    	wo->clutPoints = a2bres;
	    	wo->inputEnt   = a2binres;
	    	wo->outputEnt  = a2boutres;
			wo->allocate((icmBase *)wo);/* Allocate space */
	
			/* icxLuLut will set tables values */
		}

		if (allintents) {	/* All the intents may be needed */

			/* 16 bit dev -> pcs lut - link intent 0 to intent 1 */
			{
				icmLut *wo;
				/* Intent 0 = perceptual */
				if ((wo = (icmLut *)wr_icco->link_tag(
				           wr_icco, icSigAToB0Tag,	icSigAToB1Tag)) == NULL) 
					error("link_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
			}
	
			/* 16 dev -> pcs bit lut - link intent 2 to intent 1 */
			{
				icmLut *wo;
				/* Intent 2 = saturation */
				if ((wo = (icmLut *)wr_icco->link_tag(
				           wr_icco, icSigAToB2Tag,	icSigAToB1Tag)) == NULL) 
					error("link_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
			}
		}

		/* 16 bit pcs -> dev lut: (B2A) */
		{
			icmLut *wo;

			if (!allintents) {	/* Only B2A0, no intent */
				if ((wo = (icmLut *)wr_icco->add_tag(
				           wr_icco, icSigBToA0Tag,	icSigLut16Type)) == NULL) 
					error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);

			} else {

				/* Intent 1 = relative colorimetric */
				if ((wo = (icmLut *)wr_icco->add_tag(
				           wr_icco, icSigBToA1Tag,	icSigLut16Type)) == NULL) 
					error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
			}

			wo->inputChan = 3;
			wo->outputChan = devchan;
		    wo->clutPoints = b2ares;
		    wo->inputEnt   = b2ainres;
		    wo->outputEnt  = b2aoutres;
			wo->allocate((icmBase *)wo);/* Allocate space */
		}

		if (allintents) {	/* All the intents may be needed */

			if (ipname == NULL) {		/* No gamut mapping */
				icmLut *wo;

				/* link intent 0 = perceptual to intent 1 = colorimetric */
				if ((wo = (icmLut *)wr_icco->link_tag(
				           wr_icco, icSigBToA0Tag,	icSigBToA1Tag)) == NULL) 
					error("link_tag failed: %d, %s",wr_icco->errc,wr_icco->err);

				/* Intent 2 = saturation */
				/* link intent 2 = saturation to intent 1 = colorimetric */
				if ((wo = (icmLut *)wr_icco->link_tag(
				           wr_icco, icSigBToA2Tag,	icSigBToA1Tag)) == NULL) 
					error("link_tag failed: %d, %s",wr_icco->errc,wr_icco->err);

			} else {				/* We have gamut mapping */
				icmLut *wo;

				/* Intent 0 = perceptual */
				if ((wo = (icmLut *)wr_icco->add_tag(
				           wr_icco, icSigBToA0Tag,	icSigLut16Type)) == NULL) 
					error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);

				wo->inputChan = 3;
				wo->outputChan = devchan;
			    wo->inputEnt   = b2ainres;
			    wo->clutPoints = b2ares;
			    wo->outputEnt  = b2aoutres;
				wo->allocate((icmBase *)wo);/* Allocate space */

				if (sepsat == 0) {		/* No separate gamut mapping for saturation */
					/* link intent 2 = saturation to intent 0 = perceptual */
					if ((wo = (icmLut *)wr_icco->link_tag(
					           wr_icco, icSigBToA2Tag,	icSigBToA0Tag)) == NULL) 
						error("link_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
				} else {
					/* Intent 2 = saturation */
					if ((wo = (icmLut *)wr_icco->add_tag(
					           wr_icco, icSigBToA2Tag,	icSigLut16Type)) == NULL) 
						error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);

					wo->inputChan = 3;
					wo->outputChan = devchan;
				    wo->inputEnt   = b2ainres;
				    wo->clutPoints = b2ares;
				    wo->outputEnt  = b2aoutres;
					wo->allocate((icmBase *)wo);/* Allocate space */
				}
			}

			/* 16 bit pcs -> gamut lut: */
			{
				icmLut *wo;

				if ((wo = (icmLut *)wr_icco->add_tag(
				           wr_icco, icSigGamutTag,	icSigLut16Type)) == NULL) 
					error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);

				wo->inputChan = 3;
				wo->outputChan = 1;
				wo->inputEnt = 256;
				wo->clutPoints = b2ares;
				wo->outputEnt = 256;
				wo->allocate((icmBase *)wo);/* Allocate space */
			}
		}
	}

	/* shaper + matrix type tags */
	if (!isLut
	|| (   wr_icco->header->deviceClass == icSigDisplayClass
	    && wr_icco->header->pcs == icSigXYZData)) {

		/* Red, Green and Blue Colorant Tags: */
		{
			icmXYZArray *wor, *wog, *wob;
			if ((wor = (icmXYZArray *)wr_icco->add_tag(
			           wr_icco, icSigRedColorantTag, icSigXYZArrayType)) == NULL) 
				error("add_tag failed: %d, %s",rv,wr_icco->err);
			if ((wog = (icmXYZArray *)wr_icco->add_tag(
			           wr_icco, icSigGreenColorantTag, icSigXYZArrayType)) == NULL) 
				error("add_tag failed: %d, %s",rv,wr_icco->err);
			if ((wob = (icmXYZArray *)wr_icco->add_tag(
			           wr_icco, icSigBlueColorantTag, icSigXYZArrayType)) == NULL) 
				error("add_tag failed: %d, %s",rv,wr_icco->err);

			wor->size = wog->size = wob->size = 1;
			wor->allocate((icmBase *)wor);	/* Allocate space */
			wog->allocate((icmBase *)wog);
			wob->allocate((icmBase *)wob);

			wor->data[0].X = 1.0; wor->data[0].Y = 0.0; wor->data[0].Z = 0.0;
			wog->data[0].X = 0.0; wog->data[0].Y = 1.0; wog->data[0].Z = 0.0;
			wob->data[0].X = 0.0; wob->data[0].Y = 0.0; wob->data[0].Z = 1.0;

			/* Setup deliberately wrong dummy values (channels rotated). */
			/* icxMatrix may override override these later */
			wor->data[0].X = 0.143066; wor->data[0].Y = 0.060608; wor->data[0].Z = 0.714096;
			wog->data[0].X = 0.436066; wog->data[0].Y = 0.222488; wog->data[0].Z = 0.013916;
			wob->data[0].X = 0.385147; wob->data[0].Y = 0.716873; wob->data[0].Z = 0.097076;
		}

		/* Red, Green and Blue Tone Reproduction Curve Tags: */
		{
			icmCurve *wor, *wog, *wob;
			if ((wor = (icmCurve *)wr_icco->add_tag(
			           wr_icco, icSigRedTRCTag, icSigCurveType)) == NULL) 
				error("add_tag failed: %d, %s",rv,wr_icco->err);

			if (isShTRC) {	/* Make all TRCs shared */
				if ((wog = (icmCurve *)wr_icco->link_tag(
				           wr_icco, icSigGreenTRCTag, icSigRedTRCTag)) == NULL) 
					error("link_tag failed: %d, %s",rv,wr_icco->err);
				if ((wob = (icmCurve *)wr_icco->link_tag(
				           wr_icco, icSigBlueTRCTag, icSigRedTRCTag)) == NULL) 
					error("link_tag failed: %d, %s",rv,wr_icco->err);

			} else {		/* Else individual */
				if ((wog = (icmCurve *)wr_icco->add_tag(
				           wr_icco, icSigGreenTRCTag, icSigCurveType)) == NULL) 
					error("add_tag failed: %d, %s",rv,wr_icco->err);
				if ((wob = (icmCurve *)wr_icco->add_tag(
				           wr_icco, icSigBlueTRCTag, icSigCurveType)) == NULL) 
					error("add_tag failed: %d, %s",rv,wr_icco->err);
			}
	
			/* Shaper */
			if (ptype == prof_shamat || ptype == prof_sha1mat || ptype == prof_clutXYZ) {
				wor->flag = wog->flag = wob->flag = icmCurveSpec; 
				wor->size = wog->size = wob->size = 256;			/* Number of entries */
			} else {						/* Gamma */
				wor->flag = wog->flag = wob->flag = icmCurveGamma;
				wor->size = wog->size = wob->size = 1;				/* Must be 1 for gamma */
			}
			wor->allocate((icmBase *)wor);	/* Allocate space */
			wog->allocate((icmBase *)wog);
			wob->allocate((icmBase *)wob);

			/* Setup a default sRGB like curve. */
			/* icxMatrix will may override curve values later */
			for (i = 0; i < wor->size; i++) {
				wor->data[i] = 
				wog->data[i] = 
				wob->data[i] = gdv2dv(i/(wor->size-1.0));
			}

		}
	}
	/* .ti3 Sample data use to create profile, plus any calibration curves: */
	if (nocied == 0) {
		icmText *wo;
		char *crt;
		FILE *fp;

		if ((wo = (icmText *)wr_icco->add_tag(
		           wr_icco, icmMakeTag('t','a','r','g'), icSigTextType)) == NULL) 
			error("add_tag failed: %d, %s",wr_icco->errc,wr_icco->err);

#if defined(O_BINARY) || defined(_O_BINARY)
	    if ((fp = fopen(in_name, "rb")) == NULL)
#else
	    if ((fp = fopen(in_name, "r")) == NULL)
#endif
			error("Unable to open input file '%s' for reading",in_name);

		if (fseek(fp, 0, SEEK_END))
			error("Unable to seek to end of file '%s'",in_name);
		wo->size = ftell(fp) + 1;		/* Size needed + null */
		wo->allocate((icmBase *)wo);/* Allocate space */

		if (fseek(fp, 0, SEEK_SET))
			error("Unable to seek to end of file '%s'",in_name);

		if (fread(wo->data, 1, wo->size-1, fp) != wo->size-1)
			error("Failed to read file '%s'",in_name);
		wo->data[wo->size-1] = '\000';
		fclose(fp);

		/* Duplicate for compatibility */
		if (wr_icco->link_tag(
		         wr_icco, icmMakeTag('D','e','v','D'), icmMakeTag('t','a','r','g')) == NULL) 
			error("link_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
		if (wr_icco->link_tag(
		         wr_icco, icmMakeTag('C','I','E','D'), icmMakeTag('t','a','r','g')) == NULL) 
			error("link_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
	}

	if ((npat = icg->t[0].nsets) <= 0)
		error("No sets of data");

	if (verb)
		fprintf(verbo,"No of test patches = %d\n",npat);

	/* Allocate arrays to hold test patch input and output values */
	if ((tpat = (cow *)malloc(sizeof(cow) * npat)) == NULL)
		error("Malloc failed - tpat[]");

	/* Read in the CGATs fields */
	{
		int ti, ii, ci, mi, yi, ki;
		int Xi, Yi, Zi;

		/* Read the ink limit */
		if (oink != NULL && (ii = icg->find_kword(icg, 0, "TOTAL_INK_LIMIT")) >= 0) {
			double ilimit = -1;
			ilimit = atof(icg->t[0].kdata[ii]);
			if (ilimit > 1e-4 && ilimit <= 400.0 && oink->tlimit < 0.0) {
				oink->tlimit = ilimit/100.0;	/* Set requested ink limit */
			}
		}

		/* A sanity check */
		if (isAdditive && oink != NULL && oink->tlimit > 0 && oink->tlimit < (double)devchan) {
			warning("\n!!!!!!! Additive space has ink limit of %.0f%% set !!!!!!!\n"
			        ">> You probably don't want to do this, as it will limit the white point <<",oink->tlimit * 100.0);
		}

		/* Should targen/.ti3 file allow for BLACK_INK_LIMIT ?? */
		
		/* A problem here is that if the .ti3 is corrupted, then */
		/* often this results in the field type being "wrong", */
		/* rather than a more inteligable message. */

		if (devspace == icSigGrayData) {

			if (isAdditive) {
				if ((ci = icg->find_field(icg, 0, "GRAY_W")) < 0)
					error("Input file doesn't contain field GRAY_W");
				if (icg->t[0].ftype[ci] != r_t)
					error("Field GRAY_W is wrong type - corrupted file ?");
			} else {
				if ((ci = icg->find_field(icg, 0, "GRAY_K")) < 0)
					error("Input file doesn't contain field GRAY_K");
				if (icg->t[0].ftype[ci] != r_t)
					error("Field GRAY_K is wrong type - corrupted file ?");
			}
			mi = yi = ki = ci;

		} else if (devspace == icSigRgbData) {

			if ((ci = icg->find_field(icg, 0, "RGB_R")) < 0)
				error("Input file doesn't contain field RGB_R");
			if (icg->t[0].ftype[ci] != r_t)
				error("Field RGB_R is wrong type - corrupted file ?");
			if ((mi = icg->find_field(icg, 0, "RGB_G")) < 0)
				error("Input file doesn't contain field RGB_G");
			if (icg->t[0].ftype[mi] != r_t)
				error("Field RGB_G is wrong type - corrupted file ?");
			if ((yi = icg->find_field(icg, 0, "RGB_B")) < 0)
				error("Input file doesn't contain field RGB_B");
			if (icg->t[0].ftype[yi] != r_t)
				error("Field RGB_B is wrong type - corrupted file ?");
			ki = yi;

		} else if (devspace == icSigCmyData) {

			if ((ci = icg->find_field(icg, 0, "CMY_C")) < 0)
				error("Input file doesn't contain field CMY_C");
			if (icg->t[0].ftype[ci] != r_t)
				error("Field CMY_C is wrong type - corrupted file ?");
			if ((mi = icg->find_field(icg, 0, "CMY_M")) < 0)
				error("Input file doesn't contain field CMY_M");
			if (icg->t[0].ftype[mi] != r_t)
				error("Field CMY_M is wrong type - corrupted file ?");
			if ((yi = icg->find_field(icg, 0, "CMY_Y")) < 0)
				error("Input file doesn't contain field CMY_Y");
			if (icg->t[0].ftype[yi] != r_t)
				error("Field CMY_Y is wrong type - corrupted file ?");
			ki = yi;

		} else {	/* Assume CMYK */

			if ((ci = icg->find_field(icg, 0, "CMYK_C")) < 0)
				error("Input file doesn't contain field CMYK_C");
			if (icg->t[0].ftype[ci] != r_t)
				error("Field CMYK_C is wrong type - corrupted file ?",icg->t[0].ftype[ci],r_t);
			if ((mi = icg->find_field(icg, 0, "CMYK_M")) < 0)
				error("Input file doesn't contain field CMYK_M");
			if (icg->t[0].ftype[mi] != r_t)
				error("Field CMYK_M is wrong type - corrupted file ?");
			if ((yi = icg->find_field(icg, 0, "CMYK_Y")) < 0)
				error("Input file doesn't contain field CMYK_Y");
			if (icg->t[0].ftype[yi] != r_t)
				error("Field CMYK_Y is wrong type - corrupted file ?");
			if ((ki = icg->find_field(icg, 0, "CMYK_K")) < 0)
				error("Input file doesn't contain field CMYK_K");
			if (icg->t[0].ftype[ki] != r_t)
				error("Field CMYK_K is wrong type - corrupted file ?");
		}

		if (spec == 0) { 		/* Using instrument tristimulous value */

			if (isLab) {		/* Expect Lab */
				if ((Xi = icg->find_field(icg, 0, "LAB_L")) < 0)
					error("Input file doesn't contain field LAB_L");
				if (icg->t[0].ftype[Xi] != r_t)
					error("Field LAB_L is wrong type - corrupted file ?");
				if ((Yi = icg->find_field(icg, 0, "LAB_A")) < 0)
					error("Input file doesn't contain field LAB_A");
				if (icg->t[0].ftype[Yi] != r_t)
					error("Field LAB_A is wrong type - corrupted file ?");
				if ((Zi = icg->find_field(icg, 0, "LAB_B")) < 0)
					error("Input file doesn't contain field LAB_B");
				if (icg->t[0].ftype[Zi] != r_t)
					error("Field LAB_B is wrong type - corrupted file ?");

			} else { 		/* Expect XYZ */
				if ((Xi = icg->find_field(icg, 0, "XYZ_X")) < 0)
					error("Input file doesn't contain field XYZ_X");
				if (icg->t[0].ftype[Xi] != r_t)
					error("Field XYZ_X is wrong type - corrupted file ?");
				if ((Yi = icg->find_field(icg, 0, "XYZ_Y")) < 0)
					error("Input file doesn't contain field XYZ_Y");
				if (icg->t[0].ftype[Yi] != r_t)
					error("Field XYZ_Y is wrong type - corrupted file ?");
				if ((Zi = icg->find_field(icg, 0, "XYZ_Z")) < 0)
					error("Input file doesn't contain field XYZ_Z");
				if (icg->t[0].ftype[Zi] != r_t)
					error("Field XYZ_Z is wrong type - corrupted file ?");
			}

			for (i = 0; i < npat; i++) {
				tpat[i].w = 1.0;
				tpat[i].p[0] = *((double *)icg->t[0].fdata[i][ci]) / 100.0;
				tpat[i].p[1] = *((double *)icg->t[0].fdata[i][mi]) / 100.0;
				tpat[i].p[2] = *((double *)icg->t[0].fdata[i][yi]) / 100.0;
				tpat[i].p[3] = *((double *)icg->t[0].fdata[i][ki]) / 100.0;
				if (tpat[i].p[0] > 1.0
				 || tpat[i].p[1] > 1.0
				 || tpat[i].p[2] > 1.0
				 || tpat[i].p[3] > 1.0) {
					double bgst = 0.0;
					int j, bj = 0;
					for (j = 0; j < 4; j++) {
						if (tpat[i].p[j] > bgst) {
							bgst = tpat[i].p[j];
							bj = j;
						}
					}
					error("Device value field value exceeds 100.0 (%d:%d:%f) !",i,bj,bgst * 100.0);
				}
				tpat[i].v[0] = *((double *)icg->t[0].fdata[i][Xi]);
				tpat[i].v[1] = *((double *)icg->t[0].fdata[i][Yi]);
				tpat[i].v[2] = *((double *)icg->t[0].fdata[i][Zi]);
				if (!isLab && (!isdisp || isdnormed != 0)) {
					tpat[i].v[0] /= 100.0;		/* Normalise XYZ to range 0.0 - 1.0 */
					tpat[i].v[1] /= 100.0;
					tpat[i].v[2] /= 100.0;
				}
				if (!isLab && wantLab) { /* Convert test patch result XYZ to PCS (D50 Lab) */
					icmXYZ2Lab(&icmD50, tpat[i].v, tpat[i].v);
				} else if (isLab && !wantLab) {
					icmLab2XYZ(&icmD50, tpat[i].v, tpat[i].v);
				}
			}

		} else { 		/* Using spectral data */
			int ii;
			xspect sp;
			char buf[100];
			int  spi[XSPECT_MAX_BANDS];	/* CGATS indexes for each wavelength */
			xsp2cie *sp2cie;	/* Spectral conversion object */

			if ((ii = icg->find_kword(icg, 0, "SPECTRAL_BANDS")) < 0)
				error ("Input file doesn't contain keyword SPECTRAL_BANDS");
			sp.spec_n = atoi(icg->t[0].kdata[ii]);
			if ((ii = icg->find_kword(icg, 0, "SPECTRAL_START_NM")) < 0)
				error ("Input file doesn't contain keyword SPECTRAL_START_NM");
			sp.spec_wl_short = atof(icg->t[0].kdata[ii]);
			if ((ii = icg->find_kword(icg, 0, "SPECTRAL_END_NM")) < 0)
				error ("Input file doesn't contain keyword SPECTRAL_END_NM");
			sp.spec_wl_long = atof(icg->t[0].kdata[ii]);
			if (!isdisp || isdnormed != 0)
				sp.norm = 100.0;
			else
				sp.norm = 1.0;

			/* Find the fields for spectral values */
			for (j = 0; j < sp.spec_n; j++) {
				int nm;
		
				/* Compute nearest integer wavelength */
				nm = (int)(sp.spec_wl_short + ((double)j/(sp.spec_n-1.0))
				            * (sp.spec_wl_long - sp.spec_wl_short) + 0.5);
				
				sprintf(buf,"SPEC_%03d",nm);

				if ((spi[j] = icg->find_field(icg, 0, buf)) < 0)
					error("Input file doesn't contain field %s",buf);
			}

			if (isdisp) {
				illum = icxIT_none;		/* Displays are assumed to be self luminous */
				cust_illum = NULL;
			}

			/* Create a spectral conversion object */
			if ((sp2cie = new_xsp2cie(illum, cust_illum, observ, NULL,
			                          wantLab ? icSigLabData : icSigXYZData, icxClamp)) == NULL)
				error("Creation of spectral conversion object failed");

			/* If Fluorescent Whitening Agent compensation is enabled */
			if (!isdisp && fwacomp) {
				double nw = 0.0;		/* Number of media white patches */
				xspect mwsp;			/* Medium spectrum */
				instType itype;			/* Spectral instrument type */
				xspect insp;			/* Instrument illuminant */
				xspect tinsp, *tinspp = NULL;	/* Target/simulated instrument illuminant */

				mwsp = sp;		/* Struct copy */

	 			if ((ti = icg->find_kword(icg, 0, "TARGET_INSTRUMENT")) < 0)
					error ("Can't find target instrument needed for FWA compensation");

				if ((itype = inst_enum(icg->t[0].kdata[ti])) == instUnknown)
					error ("Unrecognised target instrument '%s'", icg->t[0].kdata[ti]);

				if (inst_illuminant(&insp, itype) != 0)
					error ("Instrument doesn't have an FWA illuminent");

				/* Find the media white spectral reflectance */
				for (j = 0; j < mwsp.spec_n; j++)
					mwsp.spec[j] = 0.0;

				/* Compute the mean of all the media white patches */
				for (i = 0; i < npat; i++) {
					int use = 0;

					if (devspace == icSigGrayData) {
						if (isAdditive) {
							if (*((double *)icg->t[0].fdata[i][ci]) > (100.0 - 0.1))
								use = 1;
						} else {
							if (*((double *)icg->t[0].fdata[i][ci]) < 0.1)
								use = 1;
						}
					} else if (devspace == icSigRgbData) {
						if (*((double *)icg->t[0].fdata[i][ci]) > (100.0 - 0.1)
						 && *((double *)icg->t[0].fdata[i][mi]) > (100.0 - 0.1)
						 && *((double *)icg->t[0].fdata[i][yi]) > (100.0 - 0.1))
							use = 1;
					} else if (devspace == icSigCmyData) {
						if (*((double *)icg->t[0].fdata[i][ci]) < 0.1 
						 && *((double *)icg->t[0].fdata[i][mi]) < 0.1 
						 && *((double *)icg->t[0].fdata[i][yi]) < 0.1)
							use = 1;
					} else {	/* Assume CMYK */

						if (*((double *)icg->t[0].fdata[i][ci]) < 0.1
						 && *((double *)icg->t[0].fdata[i][mi]) < 0.1
						 && *((double *)icg->t[0].fdata[i][yi]) < 0.1
						 && *((double *)icg->t[0].fdata[i][ki]) < 0.1) {
							use = 1;
						}
					}

					if (use) {
						/* Read the spectral values for this patch */
						for (j = 0; j < mwsp.spec_n; j++) {
							mwsp.spec[j] += *((double *)icg->t[0].fdata[i][spi[j]]);
						}
						nw++;
					}
				}

				if (nw == 0.0) {
					warning("Can't find a media white patch to init FWA");

					/* Track the maximum reflectance for any band to determine white. */
					/* This might give bogus results if there is no white patch... */
					for (i = 0; i < npat; i++) {
						for (j = 0; j < mwsp.spec_n; j++) {
							double rv = *((double *)icg->t[0].fdata[i][spi[j]]);
							if (rv > mwsp.spec[j])
								mwsp.spec[j] = rv;
						}
					}
					nw++;
				}
				for (j = 0; j < mwsp.spec_n; j++) {
					mwsp.spec[j] /= nw;	/* Compute average */
				}

				/* If the simulated instrument illumination is */
				/* not the observer/final illuminant */
				if (tillum != icxIT_none) {
					if (tillum == icxIT_custom)
						tinspp = cust_tillum;
					else {
						tinspp = &tinsp;
						if (standardIlluminant(tinspp, tillum, 0.0)) {
							error("simulated inst. illum. not recognised");
						}
					}
				}

				/* (Note that sp and mwsp.norm is set to 100.0) */
				if (sp2cie->set_fwa(sp2cie, &insp, tinspp, &mwsp)) 
					error ("Set FWA on sp2cie failed");

				if (verb) {
					double FWAc;
					sp2cie->get_fwa_info(sp2cie, &FWAc);
					fprintf(verbo,"FWA content = %f\n",FWAc);
				}
			}

			for (i = 0; i < npat; i++) {

				tpat[i].w = 1.0;
				tpat[i].p[0] = *((double *)icg->t[0].fdata[i][ci]) / 100.0;
				tpat[i].p[1] = *((double *)icg->t[0].fdata[i][mi]) / 100.0;
				tpat[i].p[2] = *((double *)icg->t[0].fdata[i][yi]) / 100.0;
				tpat[i].p[3] = *((double *)icg->t[0].fdata[i][ki]) / 100.0;

				if (tpat[i].p[0] > 1.0
				 || tpat[i].p[1] > 1.0
				 || tpat[i].p[2] > 1.0
				 || tpat[i].p[3] > 1.0) {
					error("Device value field value exceeds 100.0 !");
				}

				/* Read the spectral values for this patch */
				for (j = 0; j < sp.spec_n; j++) {
					sp.spec[j] = *((double *)icg->t[0].fdata[i][spi[j]]);
				}

				/* Convert it to CIE space */
				sp2cie->convert(sp2cie, tpat[i].v, &sp);

			}

			sp2cie->del(sp2cie);		/* Done with this */

		}

		/* Normalize display values to Y = 1.0 if needed */
		/* (re-norm spec derived, since observer may be different) */
		if (isdisp && (isdnormed == 0 || spec != 0)) {
			double scale = -1e6;

			if (wantLab) {
				double bxyz[3];

				/* Locate max Y */
				for (i = 0; i < npat; i++) {
					icmLab2XYZ(&icmD50, bxyz,  tpat[i].v);
					if (bxyz[1] > scale)
						scale = bxyz[1];
				}
				
				scale = 1.0/scale;

				/* Scale max Y to 1.0 */
				for (i = 0; i < npat; i++) {
					icmLab2XYZ(&icmD50, tpat[i].v, tpat[i].v);
					tpat[i].v[0] *= scale;
					tpat[i].v[1] *= scale;
					tpat[i].v[2] *= scale;
					icmXYZ2Lab(&icmD50, tpat[i].v, tpat[i].v);
				}
			} else {

				/* Locate max Y */
				for (i = 0; i < npat; i++) {
					if (tpat[i].v[1] > scale)
						scale = tpat[i].v[1];
				}
				
				scale = 1.0/scale;

				for (i = 0; i < npat; i++) {
					tpat[i].v[0] *= scale;
					tpat[i].v[1] *= scale;
					tpat[i].v[2] *= scale;
				}
			}
		}
	}	/* End of reading in CGATs file */

	if (isLut) {
		xicc *wr_xicc;			/* extention object */
		icxLuBase *AtoB;		/* AtoB ixcLu */

		/* Create A2B clut */
		{
			int flags = 0;

			/* Wrap with an expanded icc */
			if ((wr_xicc = new_xicc(wr_icco)) == NULL)
				error("Creation of xicc failed");
		
			flags |= ICX_CLIP_NEAREST;		/* This will avoid clip caused rev setup */

			if (noisluts)
				flags |= ICX_NO_IN_SHP_LUTS;

			if (noipluts)
				flags |= ICX_NO_IN_POS_LUTS;

			if (nooluts)
				flags |= ICX_NO_OUT_LUTS;

			if (clipprims)
				flags |= ICX_CLIP_WB;
			
			if (verb)
				flags |= ICX_VERBOSE;

			flags |= ICX_SET_WHITE | ICX_SET_BLACK; 		/* Compute & use white & black */

			/* Setup Device -> PCS conversion (Fwd) object from scattered data. */
			if ((AtoB = wr_xicc->set_luobj(
			               wr_xicc, icmFwd, !allintents ? icmDefaultIntent : icRelativeColorimetric,
			               icmLuOrdNorm,
#ifdef USE_EXTRA_FITTING
			               ICX_EXTRA_FIT |
#endif
#ifdef USE_2PASSSMTH
			               ICX_2PASSSMTH |
#endif
			               flags,
			               npat, npat, tpat, NULL, dispLuminance, -1.0, smooth, avgdev,
			               NULL, oink, cal, iquality)) == NULL)
				error("%d, %s",wr_xicc->errc, wr_xicc->err);

			AtoB->del(AtoB);		/* Done with lookup */
		}

		/* Create B2A clut */
		{
			icc *src_icco = NULL;
			xicc *src_xicc = NULL;	/* Source profile */
			icxViewCond ivc;		/* Input Viewing Condition for CAM */
			icxViewCond ovc;		/* Output Viewing Condition for CAM */
			icmFile *abs_fp[3] = { NULL, NULL, NULL };	/* Abstract profile transform: */
			icc *abs_icc[3] = { NULL, NULL, NULL };
			xicc *abs_xicc[3] = { NULL, NULL, NULL };
			icmLut *wo[3];

			out_b2a_callback cx;

			if (verb)
				printf("Setting up B to A table lookup\n");

#ifdef FILTER_B2ACLIP
			cx.filter = 1;
			cx.filter_thr = FILTER_THR_DE;
			cx.filter_ratio = FILTER_MAX_RAD/FILTER_MAX_DE;
			cx.filter_maxrad = FILTER_MAX_RAD;
#else
			cx.filter = 0;
			cx.filter_thr = 100.0;
			cx.filter_ratio = 0.0;
			cx.filter_maxrad = 0.0;
#endif

			if (ipname != NULL) {		/* There is a source profile to determine gamut mapping */

				/* Open up the profile for reading */
				if ((src_icco = read_embedded_icc(ipname)) == NULL)
					error ("Can't open file '%s'",ipname);

				/* Wrap with an expanded icc */
				if ((src_xicc = new_xicc(src_icco)) == NULL)
					error ("Creation of src_xicc failed");
			}

			/* Figure out the final src & dst viewing conditions */
			for (i = 0; i < 2; i++) {
				xicc *x;
				icxViewCond *v, *vc;
				int es;
		
				if (i == 0) {			/* Input */
					v = ivc_p;			/* Override parameters */
					es = ivc_e;
					vc = &ivc;			/* Target parameters */
					if (src_xicc == NULL)
						continue;		/* Source viewing conditions won't be used */
					x = src_xicc;
				} else {				/* Output */
					v = ovc_p;			/* Override parameters */
					es = ovc_e;
					vc = &ovc;			/* Target parameters */
					x = wr_xicc;
				}
				
				/* Set the default */
				xicc_enum_viewcond(x, vc, -1, NULL, 0, NULL);
		
				/* Override the viewing conditions */
				if (es >= 0)
					if (xicc_enum_viewcond(x, vc, es, NULL, 0, NULL) == -2)
						error ("%d, %s",x->errc, x->err);
				if (v->Ev >= 0)
					vc->Ev = v->Ev;
				if (v->Wxyz[0] >= 0.0 && v->Wxyz[1] > 0.0 && v->Wxyz[2] >= 0.0) {
					/* Normalise XYZ to current media white */
					vc->Wxyz[0] = v->Wxyz[0]/v->Wxyz[1] * vc->Wxyz[1];
					vc->Wxyz[2] = v->Wxyz[2]/v->Wxyz[1] * vc->Wxyz[1];
				} 
				if (v->Wxyz[0] >= 0.0 && v->Wxyz[1] >= 0.0 && v->Wxyz[2] < 0.0) {
					/* Convert Yxy to XYZ */
					double x = v->Wxyz[0];
					double y = v->Wxyz[1];	/* If Y == 1.0, then X+Y+Z = 1/y */
					double z = 1.0 - x - y;
					vc->Wxyz[0] = x/y * vc->Wxyz[1];
					vc->Wxyz[2] = z/y * vc->Wxyz[1];
				}
				if (v->La >= 0.0)
					vc->La = v->La;
				if (v->Yb >= 0.0)
					vc->Yb = v->Yb;
				if (v->Lv >= 0.0)
					vc->Lv = v->Lv;
				if (v->Yf >= 0.0)
					vc->Yf = v->Yf;
				if (v->Fxyz[0] >= 0.0 && v->Fxyz[1] > 0.0 && v->Fxyz[2] >= 0.0) {
					/* Normalise XYZ to current media white */
					vc->Fxyz[0] = v->Fxyz[0]/v->Fxyz[1] * vc->Fxyz[1];
					vc->Fxyz[2] = v->Fxyz[2]/v->Fxyz[1] * vc->Fxyz[1];
				}
				if (v->Fxyz[0] >= 0.0 && v->Fxyz[1] >= 0.0 && v->Fxyz[2] < 0.0) {
					/* Convert Yxy to XYZ */
					double x = v->Fxyz[0];
					double y = v->Fxyz[1];	/* If Y == 1.0, then X+Y+Z = 1/y */
					double z = 1.0 - x - y;
					vc->Fxyz[0] = x/y * vc->Fxyz[1];
					vc->Fxyz[2] = z/y * vc->Fxyz[1];
				}
			}

			/* Get a suitable forward conversion object to invert. */
			/* By creating a separate one to the one created using scattered data, */
			/* we get the chance to set ICX_CAM_CLIP. */
			{
				int flags = 0;
	
				if (verb)
					flags |= ICX_VERBOSE;
	
				flags |= ICX_CLIP_NEAREST;		/* Not vector clip */
#ifdef USE_CAM_CLIP_OPT
				flags |= ICX_CAM_CLIP;			/* Clip in CAM Jab space rather than Lab */
#else
				warning("!!!! USE_CAM_CLIP_OPT in profout.c is off !!!!");
#endif

				if ((AtoB = wr_xicc->get_luobj(wr_xicc, flags, icmFwd,
				                  !allintents ? icmDefaultIntent : icRelativeColorimetric,
				                  wantLab ? icSigLabData : icSigXYZData,
                                  icmLuOrdNorm, &ovc, oink)) == NULL)
					error ("%d, %s",wr_xicc->errc, wr_xicc->err);
			}

			/* setup context ready for B2A table setting */
			cx.verb = verb;
			cx.pcsspace = wantLab ? icSigLabData : icSigXYZData;
#ifdef NO_B2A_PCS_CURVES
			cx.noPCScurves = 1;		/* Don't use PCS curves */
#else
			cx.noPCScurves = 0;
#endif
			cx.devspace = devspace;
			cx.x = (icxLuLut *)AtoB;		/* A2B icxLuLut created from scattered data */

			cx.ixp = NULL;		/* Perceptual PCS to CAM conversion */
			cx.ox = NULL;		/* CAM to PCS conversion */
			cx.pmap = NULL;		/* perceptual gamut map */
			cx.smap = NULL;		/* Saturation gamut map */

			cx.abs_luo[0] = cx.abs_luo[1] = cx.abs_luo[2] = NULL;
			cx.xyzscale[0] = 1.0;
			cx.xyzscale[1] = 1.0;
			cx.gam = NULL;
			cx.wantLab = wantLab;			/* Copy PCS flag over */

			/* Determine the number of tables */
			cx.ntables = 1;
			if (src_xicc) {		/* Creating separate perceptual and Saturation tables */
				cx.ntables = 2;
				if (sepsat)
					cx.ntables = 3;
			}

			/* Open up the abstract profile if supplied, and setup luo */
			for (i = 0; i < cx.ntables; i++) {
				if (absname[i] != NULL && cx.abs_luo[i] == NULL) {

					if ((abs_fp[i] = new_icmFileStd_name(absname[i],"r")) == NULL)
						error ("Can't open abstract profile file '%s'",absname[i]);
					
					if ((abs_icc[i] = new_icc()) == NULL)
						error ("Creation of Abstract profile ICC object failed");
			
					/* Read header etc. */
					if ((rv = abs_icc[i]->read(abs_icc[i],abs_fp[i],0)) != 0)
						error ("%d, %s",rv,abs_icc[i]->err);
			
					if (abs_icc[i]->header->deviceClass != icSigAbstractClass)
						error("Abstract profile isn't an abstract profile");
			
					/* Take intended abstract intent from profile itself */
					if ((cx.abs_intent[i] = abs_icc[i]->header->renderingIntent) != icAbsoluteColorimetric)
						cx.abs_intent[i] = icRelativeColorimetric;
			
					/* Wrap with an expanded icc */
					if ((abs_xicc[i] = new_xicc(abs_icc[i])) == NULL)
						error ("Creation of abstract profile xicc failed");
	
					/* The abstract profile intent is assumed to determine how it gets applied. */
					/* Make abstract PCS XYZ if icAbsoluteColorimetric is needed. */
					if ((cx.abs_luo[i] = abs_xicc[i]->get_luobj(abs_xicc[i], ICX_CLIP_NEAREST, icmFwd,
					                  cx.abs_intent[i],
				        (cx.pcsspace == icSigLabData && cx.abs_intent[i] == icRelativeColorimetric)
						             ? icSigLabData : icSigXYZData,
						icmLuOrdNorm, NULL, NULL)) == NULL)
						error ("%d, %s",abs_icc[i]->errc, abs_icc[i]->err);

					/* If the same abstract profile is used in the other tables, */
					/* duplicate the transform pointer */
					for (j = i; j < cx.ntables; j++) {
						if (absname[i] == absname[j]) {
							cx.abs_intent[j] = cx.abs_intent[i];
							cx.abs_luo[j] = cx.abs_luo[i];
							if (verb)
								printf("Applying %s abstract profile '%s' to %s table\n",
								i == 0 ? "first" : i == 1 ? "second" : "third",
								absname[i], 
								j == 0 ? "colorimetric" : j == 1 ? "perceptual" : "saturation");
						}
					}
				}
			}
			
			if (!allintents) {	/* Only B2A0, no intent */
				if ((wo[0] = (icmLut *)wr_icco->read_tag(
				           wr_icco, icSigBToA0Tag)) == NULL) 
					error("read_tag failed: %d, %s",wr_icco->errc,wr_icco->err);

			} else {		/* All 3 intent tables */
				/* Intent 1 = relative colorimetric */
				if ((wo[0] = (icmLut *)wr_icco->read_tag(
				           wr_icco, icSigBToA1Tag)) == NULL) 
					error("read_tag failed: %d, %s",wr_icco->errc,wr_icco->err);

				if (src_xicc) {		/* Creating separate perceptual and Saturation tables */
					icRenderingIntent intentp;	/* Gamut mapping space perceptual selection */
					icRenderingIntent intents;	/* Gamut mapping space saturation selection */
					icRenderingIntent intento;	/* Gamut mapping space output selection */
					gamut *csgamp;			/* Incoming colorspace perceptual gamut */
					gamut *csgams;			/* Incoming colorspace saturation gamut */
					gamut *igam = NULL;		/* Incoming image gamut */
					gamut *ogam;			/* Destination colorspace gamut */
					double gres;			/* Gamut surface feature resolution */
					int    mapres;			/* Mapping rspl resolution */
			
					if (verb)
						printf("Creating Gamut Mapping\n");
			
					/* Gamut mapping will extend given grid res to encompas */
					/* source gamut by a margin. */
					if (oquality == 3) {	/* Ultra High */
			  	 		gres = 8.0;
			  	 		mapres = 41;
					} else if (oquality == 2) {	/* High */
			  	 		gres = 8.0;
			  	 		mapres = 33;
					} else if (oquality == 1) {	/* Medium */
			  	 		gres = 10.0;
			  	 		mapres = 25;
					} else if (oquality == 0) {	/* Low quality */
			  	 		gres = 12.0;
			  	 		mapres = 17;
					} else {					/* Extremely low */
			  	 		gres = 14.0;
			  	 		mapres = 9;
					}

					/* We could lift this restriction by allowing for separate */
					/* cx.ix and cx.ox for each intent, but that would be expensive!. */
					if (sepsat && (pgmi->usecas & 0xff) != (sgmi->usecas & 0xff))
						error("Can't handle percept and sat table intents with different CAM spaces");
					/* Default perceptual input gamut mapping space is absolute perceptual */
					intentp = noptop ? icAbsoluteColorimetric : icmAbsolutePerceptual;

					/* But override this for apperance space gamut mapping */
					if ((pgmi->usecas & 0xff) != 0x0) {
						intentp = noptop ? icxAppearance : icxPerceptualAppearance;
					}
					if (sepsat) {
						/* Default saturation gamut mapping space is absolute saturation */
						intents = nostos ? icAbsoluteColorimetric : icmAbsoluteSaturation;

						/* But override this for apperance space gamut mapping */
						if ((sgmi->usecas & 0xff) != 0x0) {
							intents = nostos ? icxAppearance : icxSaturationAppearance;
						}
					}
					/* Default output gamut mapping space is absolute colorimetric */
					intento = icAbsoluteColorimetric;

					/* But override this for apperance space gamut mapping */
					if ((pgmi->usecas & 0xff) != 0x0) {
						intento = icxAppearance;
					}

					if ((pgmi->usecas & 0xff) == 0x2) {
						double mxw;
						intentp = intents = intento = icxAbsAppearance;

						/* Make absolute common white point average between the two */
						ivc.Wxyz[0] = 0.5 * (ivc.Wxyz[0] + ovc.Wxyz[0]);
						ivc.Wxyz[1] = 0.5 * (ivc.Wxyz[1] + ovc.Wxyz[1]);
						ivc.Wxyz[2] = 0.5 * (ivc.Wxyz[2] + ovc.Wxyz[2]);
		
						/* And scale it's Y to be equal to 1.0 */
						mxw = 1.0/ivc.Wxyz[1];
						ivc.Wxyz[0] *= mxw;
						ivc.Wxyz[1] *= mxw;
						ivc.Wxyz[2] *= mxw;
		
						/* set output view conditions the same as the input */
						ovc = ivc;		/* Structure copy */
					}

					/* Unlike icclink, we've not provided a way for the user */
					/* to set the source profile ink limit, so estimate it */
					/* from the profile */
					icxDefaultLimits(src_xicc, &iink.tlimit, iink.tlimit,
					                           &iink.klimit, iink.klimit);

					/* Get lookup object simply for fwd_relpcs_outpcs() */
					/* and perceptual input gamut shell creation. */
					/* Note that the intent=Appearance will trigger Jab CAM, */
					/* overriding icSigLabData.. */
#ifdef NEVER
					printf("~1 input space flags = 0x%x\n",ICX_CLIP_NEAREST);
					printf("~1 input space intent = %s\n",icx2str(icmRenderingIntent,intentp));
					printf("~1 input space pcs = %s\n",icx2str(icmColorSpaceSignature,icSigLabData));
					printf("~1 input space viewing conditions =\n"); xicc_dump_viewcond(&ivc);
					printf("~1 input space inking =\n"); xicc_dump_inking(&iink);
#endif
					if ((cx.ixp = src_xicc->get_luobj(src_xicc,ICX_CLIP_NEAREST
					       , icmFwd, intentp, icSigLabData, icmLuOrdNorm, &ivc, &iink)) == NULL)
						error ("%d, %s",src_xicc->errc, src_xicc->err);

					/* Create the source colorspace gamut surface */
					if (verb)
						printf(" Finding Source Colorspace Perceptual Gamut\n");
			
					if ((csgamp = cx.ixp->get_gamut(cx.ixp, gres)) == NULL)
						error ("%d, %s",src_xicc->errc, src_xicc->err);
			
					if (sepsat) {
						icxLuBase *ixs = NULL;	/* Source profile saturation lookup for gamut */
						/* Get lookup object for saturation input gamut shell creation */
						/* Note that the intent=Appearance will trigger Jab CAM, */
						/* overriding icSigLabData.. */
						if ((ixs = src_xicc->get_luobj(src_xicc, ICX_CLIP_NEAREST
						   , icmFwd, intents, icSigLabData, icmLuOrdNorm, &ivc, &iink)) == NULL)
						error ("%d, %s",src_xicc->errc, src_xicc->err);

						if (verb)
							printf(" Finding Source Colorspace Saturation Gamut\n");

						if ((csgams = ixs->get_gamut(ixs, gres)) == NULL)
							error ("%d, %s",src_xicc->errc, src_xicc->err);
						ixs->del(ixs);
					}

					/* Read image source gamut if provided */
					if (sgname != NULL) {	/* Optional source gamut - ie. from an images */
						int isJab = 0;
			
						if ((pgmi->usecas & 0xff) != 0)
							isJab = 1;

						if (verb)
							printf(" Loading Image Source Gamut '%s'\n",sgname);
			
						igam = new_gamut(gres, 0, 0);
			
						if (igam->read_gam(igam, sgname))
							error("Reading source gamut '%s' failed",sgname);

						if (igam->getisjab(igam) != isJab) {
							/* Should really convert to/from Jab here! */
							warning("Image gamut is wrong colorspace for gamut mapping (Lab != Jab)");
							/* This will actually error in the gamut mapping code */
							/* Note that we're not checking relative/absolute colorspace here. */
							/* At the moment it's up to the user to get this right. */
						}
					}

					/* Get lookup object for bwd_outpcs_relpcs(), */
					/* and output gamut shell creation */
					/* Note that the intent=Appearance will trigger Jab CAM, */
					/* overriding icSigLabData.. */
					if ((cx.ox = wr_xicc->get_luobj(wr_xicc, ICX_CLIP_NEAREST
						   , icmFwd, intento,
					                  icSigLabData, icmLuOrdNorm, &ovc, oink)) == NULL)
						error ("%d, %s",wr_xicc->errc, wr_xicc->err);

					/* Creat the destination gamut surface */
					if (verb)
						printf(" Finding Destination Gamut\n");
			
					if ((ogam = cx.ox->get_gamut(cx.ox, gres)) == NULL)
						error ("%d, %s",wr_xicc->errc, wr_xicc->err);
			
					if (verb)
						printf(" Creating Gamut match\n");

					/* The real range of Lab 0..100,-128..128,1-28..128 cube */
					/* when mapped to CAM is ridiculously large (ie. */
					/* 0..100, -288..265, -112..533), so we don't attempt to */
					/* set a gamut mapping grid range based on this. Instead */
					/* rely on the gamut map code to set a reasonable grid range */
					/* around the source gamut, and to cope reasonably with */
					/* values outside the grid range. */

					/* setup perceptual gamut mapping */
					cx.pmap = new_gammap(verb, csgamp, igam, ogam, pgmi, 0, 0, 0, 0, mapres,
					                     NULL, NULL, gamdiag ? "gammap_p.wrl" : NULL
					);
					if (cx.pmap == NULL)
						error ("Failed to make perceptual gamut map transform");

					/* Intent 0 = perceptual */
					if ((wo[1] = (icmLut *)wr_icco->read_tag(
					           wr_icco, icSigBToA0Tag)) == NULL) 
						error("read_tag failed: %d, %s",wr_icco->errc,wr_icco->err);

					if (sepsat) {
						/* setup saturation gamut mapping */
						cx.smap = new_gammap(verb, csgams, igam, ogam, sgmi, 0, 0, 0, 0, mapres,
						                     NULL, NULL, gamdiag ? "gammap_s.wrl" : NULL
						);
						if (cx.smap == NULL)
							error ("Failed to make saturation gamut map transform");

						/* Intent 2 = saturation */
						if ((wo[2] = (icmLut *)wr_icco->read_tag(
						           wr_icco, icSigBToA2Tag)) == NULL) 
							error("read_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
					}
					csgamp->del(csgamp);
					csgamp = NULL;
					if (sepsat) {
						csgams->del(csgams);
						csgams = NULL;
					}
					if (igam != NULL) {
						igam->del(igam);
						igam = NULL;
					}
					ogam->del(ogam);
					ogam = NULL;
				}
			}
			cx.ochan = wo[0]->outputChan;

			/* If we've got a request for Absolute Appearance mode with scaling */
			/* to avoid clipping the source white point, compute the needed XYZ scaling factor. */
			if (src_xicc != NULL && allintents
			  && ((pgmi->usecas & 0x100) || (sgmi->usecas & 0x100))) {
				double swcam[3];
				double xyzscale[1], sa[1];

				/* Grab the source white point in CAM space */
				cx.ixp->efv_wh_bk_points(cx.ixp, swcam, NULL, NULL);

				/* Convert it to destination XYZ */
				/* We're being bad in delving inside the xluo, but we'll fix it latter */
				cx.ox->cam->cam_to_XYZ(cx.ox->cam, cx.swxyz, swcam);

//printf("~1 Source white Jab = %f %f %f\n", swcam[0], swcam[1], swcam[2]);
//printf("~1 Source white XYZ = %f %f %f\n", cx.swxyz[0], cx.swxyz[1], cx.swxyz[2]);

				/* Compute the bigest scale factor less than or equal to 1.0, */
				/* that doesn't clip the cx.swxyz[] on the destination gamut */
				sa[0] = 0.1;
				xyzscale[0] = 0.5;
				if (powell(NULL, 1, xyzscale, sa, 1e-6, 2000,
				                             xyzoptfunc, (void *)&cx, NULL, NULL) != 0) {
					warning("make_output_icc: XYZ scale powell failed to converge - set scale to 1.0");
				} else {
					if (pgmi->usecas & 0x100) {
						cx.xyzscale[0] = xyzscale[0];
						if (cx.verb) printf("Set Perceptual XYZ scale factor to %f\n",xyzscale[0]);
					}
					if (sgmi->usecas & 0x100) {
						cx.xyzscale[1] = xyzscale[0];
						if (cx.verb) printf("Set Saturation XYZ scale factor to %f\n",xyzscale[0]);
					}
				}
			}

// ====================================================================
#ifdef NEVER
// ~~99
			/* DEVELOPMENT CODE - not complete */
			/* Setup optimised B2A per channel curves */
			{
				xfit *xf;				/* Curve fitting class instance */
				int xfflags = 0;		/* xfit flags */
				double in_min[MXDI];	/* Input value scaling minimum */
				double in_max[MXDI];	/* Input value scaling maximum */
				double out_min[MXDO];	/* Output value scaling minimum */
				double out_max[MXDO];	/* Output value scaling maximum */
				int iluord, oluord;
				int iord[MXDI];			/* Input curve orders */
				int oord[MXDO];			/* Output curve orders */
				int nodp;				/* Number of inverse data points */
				co *points;				/* List of inverse points as PCS->dev */

				optcomb tcomb = oc_imo;	/* Create all by default */

				if ((xf = new_xfit()) == NULL) {
					error("profout: Creation of xfit object failed");
				}
					
				/* Setup for optimising run */
				if (nooluts)				/* Use option flags - swap sense for B2A */
					tcomb &= ~oc_i;

				if (noiluts)
					tcomb &= ~oc_o;

				if (verb)
					xfflags |= XFIT_VERB;

				xfflags |= XFIT_OUT_DEV;			/* Outpupt is device */
													/* (Switch to XFIT_OUT_LU latter ?) */
				if (cx.pcsspace == icSigLabData)
					xfflags |= XFIT_IN_ZERO;		/* Adjust a & b to zero */

~~~~~~~~~~~~~~~~~~~
				/* Set the curve order for input (PCS) */
				if (oquality >= 3) {				/* Ultra high */
					iluord = 25;			
					nodp = 2000;
				} else if (oquality == 2) {		/* High */
					iluord = 20;			
					nodp = 1000;
				} else if (oquality == 1) {		/* Medium */
					iluord = 17;			
					nodp = 750;
				} else {						/* Low */
					iluord = 10;			
					nodp = 500;
				}
				for (e = 0; e < p->inputChan; e++) {
					iord[e] = iluord;
					in_min[e] = p->inmin[e];
					in_max[e] = p->inmax[e];

					/* Hack to prevent a convex L curve pushing */
					/* the clut L values above the maximum value */
					/* that can be represented, causing clipping. */
					/* Do this by making sure that the L curve pivots */
					/* through 100.0 to 100.0 */
					if (e == 0 && cx.pcsspace == icSigLabData) {
						if (in_min[e] < 0.0001 && in_max[e] > 100.0) {
							in_max[e] = 100.0;	
						}
					}
				}

				/* Set curve order for output (Device) */
				if (oquality >= 3) {			/* Ultra high */
					oluord = 25;			
				} else if (oquality == 2) {		/* High */
					oluord = 20;			
				} else if (oquality == 1) {		/* Medium */
					oluord = 17;			
				} else {						/* Low */
					oluord = 10;			
				}
				for (f = 0; f < p->outputChan; f++) {
					oord[f] = oluord;
					out_min[f] = p->outmin[f];
					out_max[f] = p->outmax[f];

				}

				/* Create the sample points */
				~~~~~~~~~~~
				malloc

				for (i = 0; i < nodp;) {
					generate random pcs value

					if not within gamut
						continue;

					lookup through overall conversion
					i++;
				}

				/* Fit input and output curves to our data points */
				if (xf->fit(xf, xfflags, p->inputChan, p->outputChan, nodp, points,
				            in_min, in_max, out_min, out_max, iord, oord, tcomb, NULL) != 0) {
					p->pp->errc = 2;
					sprintf(p->pp->err,"xfit fitting failed");
					xf->del(xf);
					p->del((icxLuBase *)p);
					return NULL;
					
				}  

				/* - - - - - - - - - - - - - - - */
				/* Set the xicc input curve rspl */
				for (e = 0; e < p->inputChan; e++) {
					curvectx cx;
			
					cx.xf = xf;
					cx.oix = -1;
					cx.iix = e;

					if ((p->inputTable[e] = new_rspl(1, 1)) == NULL) {
						p->pp->errc = 2;
						sprintf(p->pp->err,"Creation of input table rspl failed");
						p->del((icxLuBase *)p);
						return NULL;
					}

					p->inputTable[e]->set_rspl(p->inputTable[e], RSPL_NOFLAGS,
					           (void *)&cx, set_linfunc,
    			       &p->ninmin[e], &p->ninmax[e],
					           &p->lut->inputEnt,
					           &p->ninmin[e], &p->ninmax[e]);
				}

				/* - - - - - - - - - - - - - - - */
				/* Set the xicc output curve rspl */

				/* Allow for a bigger than normal input and output range, to */
				/* give some leaway in accounting for approximate white point shifted */
				/* profile creation. */
				for (f = 0; f < p->outputChan; f++) {
					double min[1], max[1], exval;
					int entries;
					curvectx cx;

					cx.xf = xf;
					cx.iix = -1;
					cx.oix = f;

					/* Expand in and out range by 1.05 */
					exval = (p->noutmax[f] - p->noutmin[f]);
					min[0] = p->noutmin[f] - exval * 0.05 * 0.5;
					max[0] = p->noutmax[f] + exval * 0.05 * 0.5;
					entries = (int)(1.05 * (double)p->lut->outputEnt + 0.5);

					if ((p->outputTable[f] = new_rspl(1, 1)) == NULL) {
						p->pp->errc = 2;
						sprintf(p->pp->err,"Creation of output table rspl failed");
						p->del((icxLuBase *)p);
						return NULL;
					}

					p->outputTable[f]->set_rspl(p->outputTable[f], RSPL_NOFLAGS,
				           (void *)&cx, set_linfunc,
							min, max, &entries, min, max);

				}

				xf->del(xf);
			}
#endif /* NEVER (Setup optimised B2A per channel curves) */
// ====================================================================

			/* We now setup an exact inverse, colorimetric style, plus gamut mapping */
			/* for perceptual and saturation intents */
			/* Use helper function to do the hard work. */

			if (cx.verb) {
				unsigned int ui;
				int extra;
				cx.count = 0;
				cx.last = -1;
				for (cx.total = 1, ui = 0; ui < wo[0]->inputChan; ui++, cx.total *= wo[0]->clutPoints)
					; 
				/* Add in cell center points */
				for (extra = 1, ui = 0; ui < wo[0]->inputChan; ui++, extra *= (wo[0]->clutPoints-1))
					;
				cx.total += extra;
				printf("Creating B to A tables\n");
				printf(" 0%%"); fflush(stdout);
			}

#ifdef DEBUG_ONE
#define DBGNO 1		/* Up to 10 */

			/* Test a single given PCS (Rel D50 Lab) -> cmyk value */
			{
				double in[10][MAX_CHAN];
				double out[MAX_CHAN];
				in[0][0] = 100.0;			/* White point */
				in[0][1] = 0.001;
				in[0][2] = 0.001;

				for (i = 0; i < DBGNO; i++) {
					printf("Input %s\n",icmPdv(3,in[i]));
					out_b2a_input((void *)&cx, out, in[i]);
					printf("Input' %s\n",icmPdv(3,out));
					out_b2a_clut((void *)&cx, out, out);
					printf("Output' %s\n\n",icmPdv(4,out));
					out_b2a_output((void *)&cx, out, out);
					printf("Output %s\n\n",icmPdv(4,out));
				}
			}
#else /* !DEBUG_ONE */

#ifndef USE_LEASTSQUARES_APROX
			fprintf(stderr,"!!!!! profile/profout: USE_LEASTSQUARES_APROX undef !!!!!\n");
#endif
			if (icmSetMultiLutTables(
			        cx.ntables,
			        wo,
#ifdef USE_LEASTSQUARES_APROX
					ICM_CLUT_SET_APXLS | 
#endif
#ifdef FILTER_B2ACLIP
					ICM_CLUT_SET_FILTER | 
#endif
					0,
					&cx,						/* Context */
					cx.pcsspace,				/* Input color space */
					devspace, 					/* Output color space */
					out_b2a_input,				/* Input transform PCS->PCS' */
					NULL, NULL,					/* Use default PCS range */
					out_b2a_clut,				/* Lab' -> Device' transfer function */
					NULL, NULL,					/* Use default Device' range */
					out_b2a_output) != 0)		/* Output transfer function, Device'->Device */
				error("Setting 16 bit PCS->Device Lut failed: %d, %s",wr_icco->errc,wr_icco->err);
			if (cx.verb) {
				printf("\n");
			}

#ifdef WARN_CLUT_CLIPPING	/* Print warning if setting clut clips */
			/* Ignore clipping of the input table, because this happens */
			/* anyway due to Lab symetry adjustment. */
			if (wr_icco->warnc != 0 && wr_icco->warnc != 1) {
				warning("Values clipped in setting B2A LUT!");
			}
#endif /* WARN_CLUT_CLIPPING */
#endif /* !DEBUG_ONE */

			/* Free up abstract transform */
			for (i = 0; i < cx.ntables; i++) {
				if (cx.abs_luo[i] != NULL) {
					for (j = cx.ntables-1; j >= i; j--) {	/* Free all duplicates */
						if (cx.abs_luo[j] == cx.abs_luo[i]) {
							cx.abs_luo[j]->del(cx.abs_luo[j]);
							abs_xicc[j]->del(abs_xicc[j]);
							abs_icc[j]->del(abs_icc[j]);
							abs_fp[j]->del(abs_fp[j]);
							cx.abs_luo[j] = NULL;
						}
					}
				}
			}

			if (cx.pmap != NULL)
				cx.pmap->del(cx.pmap), cx.pmap = NULL;
			if (cx.smap != NULL)
				cx.smap->del(cx.smap), cx.smap = NULL;
			if (cx.ixp != NULL)
				cx.ixp->del(cx.ixp), cx.ixp = NULL;
			if (cx.ox != NULL)
				cx.ox->del(cx.ox), cx.ox = NULL;

			if (src_xicc != NULL)
				src_xicc->del(src_xicc), src_xicc = NULL;
			if (src_icco != NULL)
				src_icco->del(src_icco), src_icco = NULL;

			if (verb)
				printf("Done B to A tables\n");
		}

		/* Set the ColorantTable PCS values */
		{
			unsigned int i;
			icmColorantTable *wo;
			double dv[MAX_CHAN];

			if ((wo = (icmColorantTable *)wr_icco->read_tag(
			           wr_icco, icSigColorantTableTag)) == NULL) 
				error("read_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
	
			for (i = 0; i < wo->count; i++)
				dv[i] = 0.0;

			/* Lookup the colorant PCS values the recommended ICC way */
			for (i = 0; i < wo->count; i++) {
				dv[i] = 1.0;
				AtoB->lookup(AtoB, wo->data[i].pcsCoords, dv);
				dv[i] = 0.0;
			}
		}

#ifndef DISABLE_GAMUT_TAG
		/* Create Gamut clut for output type */
		/* This is not mandated for V2.4.0 Display profiles, but add it anyway */
		if (allintents) {	
			icmLut *wo;
			out_b2a_callback cx;
			double gres = 0.0;

			cx.verb = verb;
			cx.pcsspace = wantLab ? icSigLabData : icSigXYZData;
			cx.devspace = devspace;
			cx.x = (icxLuLut *)AtoB;		/* A2B icxLuLut */

			if (verb)
				printf("Creating gamut boundary table\n");

			/* Need to switch AtoB to be override Lab PCS */
			/* Do this the dirty way, by delving into xicclu and icclu. Alternatively */
			/* we could create an xlut set method, delete AtoB and recreate it, */
			/* or fix get_gamut to independently override convert to icmSigLabData */
			/* ~~~~~~~~999 should fix this !!! */
			cx.x->outs = icSigLabData;
			cx.x->pcs = icSigLabData;
			cx.x->plu->e_outSpace = icSigLabData;
			cx.x->plu->e_pcs = icSigLabData;
			cx.wantLab = wantLab;			/* Copy PCS flag over */

			if (oquality == 3) {	/* Ultra High */
	  	 		gres = 8.0;
			} else if (oquality == 2) {	/* High */
	  	 		gres = 9.0;
			} else if (oquality == 1) {	/* Medium */
	  	 		gres = 10.0;
			} else if (oquality == 0) {	/* Low quality */
	  	 		gres = 12.0;
			} else {					/* Extremely low */
	  	 		gres = 15.0;
			}

			/* Creat a gamut surface */
			if ((cx.gam = AtoB->get_gamut(AtoB, gres)) == NULL)
				error("Get_gamut failed: %d, %s",AtoB->pp->errc,AtoB->pp->err);
			
			if ((wo = (icmLut *)wr_icco->read_tag(
			           wr_icco, icSigGamutTag)) == NULL) 
				error("read_tag failed: %d, %s",wr_icco->errc,wr_icco->err);

			if (cx.verb) {
				unsigned int ui;
				cx.count = 0;
				cx.last = -1;
				for (cx.total = 1, ui = 0; ui < wo->inputChan; ui++, cx.total *= wo->clutPoints)
					; 
				printf(" 0%%"); fflush(stdout);
			}
#ifndef DEBUG_ONE	/* Skip this when debugging */
			if (wo->set_tables(wo,
					ICM_CLUT_SET_EXACT,
					&cx,				/* Context */
					cx.pcsspace,		/* Input color space */
					icSigGrayData,		/* Output color space */
					out_b2a_input,		/* Input transform PCS->PCS' */
					NULL, NULL,			/* Use default Lab' range */
					PCSp_bdist,			/* Lab' -> Boundary distance */
					NULL, NULL,			/* Use default Device' range */
					gamut_output) != 0)	/* Boundary distance to out of gamut value */
				error("Setting 16 bit PCS->Device Gamut Lut failed: %d, %s",wr_icco->errc,wr_icco->err);
#endif /* !DEBUG_ONE */
			if (cx.verb) {
				printf("\n");
			}
#ifdef WARN_CLUT_CLIPPING	/* Print warning if setting clut clips */
			if (wr_icco->warnc)
				warning("Values clipped in setting Gamut LUT");
#endif /* WARN_CLUT_CLIPPING */

			cx.gam->del(cx.gam);		/* Done with gamut object */
			cx.gam = NULL;

			if (verb)
				printf("Done gamut boundary table\n");
		}
#endif /* !DISABLE_GAMUT_TAG */

		/* Free up xicc stuff */
		AtoB->del(AtoB); /* Done with device to PCS lookup */
		wr_xicc->del(wr_xicc);

	}
	/* Gamma/Shaper + matrix profile */
	/* or XYZ cLUT with matrix as well. */
	if (!isLut || mtxtoo) {
		xicc *wr_xicc;			/* extention object */
		icxLuBase *xluo;		/* Forward ixcLu */
		int flags = 0;

		/* Wrap with an expanded icc */
		if ((wr_xicc = new_xicc(wr_icco)) == NULL)
			error("Creation of xicc failed");
		
		if (verb)
			flags |= ICX_VERBOSE;

		if (ptype == prof_matonly)
			flags |= ICX_NO_IN_SHP_LUTS;	/* Make it linear */

		if (clipprims)
			flags |= ICX_CLIP_WB | ICX_CLIP_PRIMS;
				
		flags |= ICX_SET_WHITE | ICX_SET_BLACK; 		/* Compute & use white & black */

		if (!mtxtoo)	/* Write matrix white/black/Luminance if no cLUT */
			flags |= ICX_WRITE_WBL;

		/* Setup Device -> XYZ conversion (Fwd) object from scattered data. */
		if ((xluo = wr_xicc->set_luobj(
		               wr_xicc, icmFwd, isdisp ? icmDefaultIntent : icRelativeColorimetric,
		               icmLuOrdRev,
			           flags,		 		/* Compute white & black */
		               npat, npat, tpat, NULL, dispLuminance, -1.0, smooth, avgdev,
		               NULL, oink, cal, iquality)) == NULL)
			error("%d, %s",wr_xicc->errc, wr_xicc->err);

		/* Free up xicc stuff */
		xluo->del(xluo);

		/* Set the ColorantTable PCS values */
		if (!mtxtoo) {
			unsigned int i;
			icmColorantTable *wo;
			double dv[MAX_CHAN];

			/* Get lookup object simply for fwd_relpcs_outpcs() */
			if ((xluo = wr_xicc->get_luobj(wr_xicc, ICX_CLIP_NEAREST, icmFwd,
			                     icRelativeColorimetric, icmSigDefaultData,
			                     icmLuOrdNorm, NULL, NULL)) == NULL)
				error ("%d, %s",wr_xicc->errc, wr_xicc->err);

			if ((wo = (icmColorantTable *)wr_icco->read_tag(
			           wr_icco, icSigColorantTableTag)) == NULL) 
				error("read_tag failed: %d, %s",wr_icco->errc,wr_icco->err);
	
			for (i = 0; i < wo->count; i++)
				dv[i] = 0.0;

			/* Lookup the colorant PCS values the recommended ICC way */
			for (i = 0; i < wo->count; i++) {
				dv[i] = 1.0;
				xluo->lookup(xluo, wo->data[i].pcsCoords, dv);
				/* Matrix profile can produce -ve values not representable by 16 bit XYZ */
				icmClipXYZ(wo->data[i].pcsCoords,wo->data[i].pcsCoords);
				dv[i] = 0.0;
			}
			xluo->del(xluo);
		}

		wr_xicc->del(wr_xicc);
	}

	/* We're done with any cal now */
	if (cal != NULL)
		cal->del(cal);

	/* Write the file (including all tags) out */
	if ((rv = wr_icco->write(wr_icco,wr_fp,0)) != 0) {
		error("Write file: %d, %s",rv,wr_icco->err);
	}

	/* Close the file */
	wr_icco->del(wr_icco);
	wr_fp->del(wr_fp);

	/* Check the forward profile accuracy against the data points */
	if (verb || verify) {
		icmFile *rd_fp;
		icc *rd_icco;
		icmLuBase *luo;
		double merr = 0.0;
		double rerr = 0.0;
		double aerr = 0.0;
		double nsamps = 0.0;

		/* Open up the file for reading */
		if ((rd_fp = new_icmFileStd_name(file_name,"r")) == NULL)
			error("Write: Can't open file '%s'",file_name);

		if ((rd_icco = new_icc()) == NULL)
			error("Write: Creation of ICC object failed");

		/* Read the header and tag list */
		if ((rv = rd_icco->read(rd_icco,rd_fp,0)) != 0)
			error("Read: %d, %s",rv,rd_icco->err);

		/* Get the Fwd table */
		if ((luo = rd_icco->get_luobj(rd_icco, icmFwd, icAbsoluteColorimetric,
		                              icSigLabData, icmLuOrdNorm)) == NULL) {
			error("%d, %s",rd_icco->errc, rd_icco->err);
		}

		for (i = 0; i < npat; i++) {
			double out[3], ref[3];
			double mxd;

			/* Lookup the profiles PCS for out test patch point */
			if (luo->lookup(luo, out, tpat[i].p) > 1)
				error("%d, %s",rd_icco->errc,rd_icco->err);
		
			/* Our tpat data might be in XYZ, so generate an Lab ref value */
			if (!wantLab) { /* Convert test patch result XYZ to PCS (D50 Lab) */
				icmXYZ2Lab(&icmD50, ref, tpat[i].v);

			} else {
				ref[0] = tpat[i].v[0];
				ref[1] = tpat[i].v[1];
				ref[2] = tpat[i].v[2];
			}

			if (verify && verb) {
				if (devspace == icSigCmykData) {
					printf("[%f] %f %f %f %f -> %f %f %f should be %f %f %f\n",
					       icmLabDE(ref, out),
					       tpat[i].p[0],tpat[i].p[1],tpat[i].p[2],tpat[i].p[3],
					       out[0],out[1],out[2],
					       ref[0],ref[1],ref[2]);
				} else {
					printf("[%f] %f %f %f -> %f %f %f should be %f %f %f\n",
					       icmLabDE(ref, out),
					       tpat[i].p[0],tpat[i].p[1],tpat[i].p[2],
					       out[0],out[1],out[2],
					       ref[0],ref[1],ref[2]);
				}
			}

			/* Check the result */
			mxd = icmLabDE(ref, out);
			if (mxd > merr)
				merr = mxd;

			rerr += mxd * mxd;
			aerr += mxd;
			nsamps++;
		}
		rerr = sqrt(rerr/nsamps);
		aerr /= nsamps;
		printf("Profile check complete, peak err = %f, avg err = %f, RMS = %f\n",merr,aerr,rerr);

		/* Done with lookup object */
		luo->del(luo);

		/* Close the file */
		rd_icco->del(rd_icco);
		rd_fp->del(rd_fp);
	}

	free(tpat);
}

