
/* 
 * ICC reprocess to give true BtoA1 by inverting
 * the AtoB1 table, and also correct the neutral
 * axis of the BtoA0 and BtoA2 tables.
 *
 * This only works with profiles with icSigLut16Type & icSigLut8Type Luts
 *
 * Author:  Graeme W. Gill
 * Date:    9/7/00
 * Version: 1.00
 *
 * Copyright 2000 Graeme W. Gill
 * Please refer to License.txt file for details.
 */

/* TTBD:
 *
 * Add support for proper gamut mapping, just like profile,
 * or deprecate revfix by modifying profile to work from
 * an existing profile ?
 * 
 * Remove the auxiliary fixup stuff when we have implemented
 * optimised separations.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <fcntl.h>
#include <string.h>
#include <math.h>
#include "copyright.h"
#include "aconfig.h"
#include "numlib.h"
#include "xicc.h"

#define USE_CAM_CLIP_OPT		/* Clip in CAM Jab space rather than Lab */
#undef DEBUG		/* Print each value changed */


typedef icmLut1 icmLut;			/* Will only work for icSigLut16Type & icSigLut8Type */

void usage(void) {
	fprintf(stderr,"Invert AtoB1 to make BtoA1 for CMYK profiles, Version %s\n",ARGYLL_VERSION_STR);
	fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n");
	fprintf(stderr,"usage: revfix [-options] iccin iccout\n");
	fprintf(stderr," -v             Verbose\n");
	fprintf(stderr," -0             Process perceptual\n");
	fprintf(stderr," -1             Process absolute/relative colorimetric\n");
	fprintf(stderr," -2             Process saturation\n");
	fprintf(stderr," -r res         Override BtoA1 Clut res\n");
	fprintf(stderr," -k [ezhxr]     e = same K as existing BtoA table (def)\n");
	fprintf(stderr,"                z = zero, h = 0.5 K, x = max K, r = ramp K\n");
	fprintf(stderr," -k p stle stpo enle enpo shape\n");
	fprintf(stderr,"                p = curve parameters\n");
	fprintf(stderr,"                stle: K level at White 0.0 - 1.0\n");
	fprintf(stderr,"                stpo: start point of transition Wh 0.0 - Bk 1.0\n");
	fprintf(stderr,"                enpo: End point of transition Wh 0.0 - Bk 1.0\n");
	fprintf(stderr,"                enle: K level at Black 0.0 - 1.0\n");
	fprintf(stderr,"                shape: 1.0 = straight, 0.0-1.0 concave, 1.0-2.0 convex\n");
	fprintf(stderr," -K parameters  Same as -k, but target is K locus rather than K value itself\n");
	fprintf(stderr," -l tlimit      set total ink limit, 0 - 400%% (estimate by default)\n");
	fprintf(stderr," -L klimit      set black ink limit, 0 - 100%% (estimate by default)\n");
	fprintf(stderr," -p absprof     Include abstract profile in output tables\n");
//	fprintf(stderr," -s             Use internal optimized separation for CMYK\n");
	exit(1);
}

/* ------------------------------------------- */
/* structure to support icc Lut initialisation calbacks */

/* ~~~Note that  we're not coping with a matrix or XYZ PCS properly here. */

struct _callback {
	int verb;			/* Verbosity */
	int total, count, last;	/* Progress count information */
	icColorSpaceSignature pcsspace;
	int inking;			/* k inking algorithm */
	icxLuLut *BtoA;		/* BtoA of table being processed */
	icxLuLut *AtoB;		/* AtoB of table being processed */
	icxLuLut *AtoB1;	/* AtoB of colorimetric table */

	icRenderingIntent abs_intent;	/* Desired abstract profile rendering intent */
	icxLuBase *abs_luo;				/* abstract profile tranform in PCS, NULL if none */

}; typedef struct _callback 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(callback *p, double out[3], double in[3]) {
	out[0] = in[0];
	out[1] = in[1];
	out[2] = in[2];

//printf("~1 do_abstract got %f %f %f\n",in[0],in[1],in[2]);

	if (p->abs_intent == icAbsoluteColorimetric) {
		if (p->pcsspace == icSigLabData) {
			icmLab2XYZ(&icmD50, out, out);
//printf("~1 after Lab 2 XYZ got %f %f %f\n",out[0],out[1],out[2]);
		}
		p->AtoB1->plu->XYZ_Rel2Abs(p->AtoB1->plu, out, out);
//printf("~1 after rel to abs got %f %f %f\n",out[0],out[1],out[2]);
	}

	p->abs_luo->lookup(p->abs_luo, out, out);
//printf("~1 after abs_luo got %f %f %f\n",out[0],out[1],out[2]);

	if (p->abs_intent == icAbsoluteColorimetric) {
		p->AtoB1->plu->XYZ_Abs2Rel(p->AtoB1->plu, out, out);
//printf("~1 after abs2rel got %f %f %f\n",out[0],out[1],out[2]);
		if (p->pcsspace == icSigLabData) {
			icmXYZ2Lab(&icmD50, out, out);
//printf("~1 after XYZ to Lab got %f %f %f\n",out[0],out[1],out[2]);
		}
	}
//printf("~1 returning %f %f %f\n\n",out[0],out[1],out[2]);
}

/* - - - - - - - - - */
/* New input table */
void Lab_Labp(void *cntx, double out[3], double in[3]
, int tn
) {
	callback *p = (callback *)cntx;

#ifdef DEBUG
	printf("Got Lab %f %f %f\n",in[0],in[1],in[2]);
#endif
	if (p->AtoB != p->AtoB1) {
		/* Non-colorimetric, use existing input table */
		if (p->BtoA->input(p->BtoA, out, in) > 1)
			error ("%d, %s",p->BtoA->pp->e.c,p->BtoA->pp->e.m);
	} else {
		/* Colorimetric, use inverse AtoB output */
		if (p->AtoB1->inv_output(p->AtoB1, out, in) > 1)
			error ("%d, %s",p->AtoB1->pp->e.c,p->AtoB1->pp->e.m);
	}
#ifdef DEBUG
	printf("New Lab' %f %f %f\n",out[0],out[1],out[2]);
#endif
}

/* - - - - */
/*  clut  */

/* Normal CLUT routine */
void Labp_CMYKp(void *cntx, double out[4], double in[3]
, int tn
) {
	double temp[4], targetk = 0.0;
	int rv;
	callback *p = (callback *)cntx;

#ifdef DEBUG
	printf("Got Lab' %f %f %f\n",in[0],in[1],in[2]);
#endif

	if (p->inking == 0) {	/* If we are copying existing K value */
		
		/* Figure out what K value was previously here */
		if (p->AtoB != p->AtoB1) {
			/* Simple because BtoA input & output tables don't change */
			/* Figure out what DEV' K value the BtoA table currently has for this PCS' */
			if (p->BtoA->clut(p->BtoA, temp, in) > 1)
				error ("%d, %s",p->BtoA->pp->e.c,p->BtoA->pp->e.m);

			/* Convert DEV' to DEV */
			if (p->BtoA->output(p->BtoA, temp, temp) > 1)
				error ("%d, %s",p->BtoA->pp->e.c,p->AtoB->pp->e.m);
		} else {
			/* More complicated because old BtoA in/out tables are different */
			/* from the new ones. */
			/* We know that new BtoA in/out tables are inverse of AtoB in/out, */
			/* so we don't have to use BtoA1->inv_input, & BtoA1->inv_output */
			/* Convert PCS' to PCS */
			if (p->AtoB->output(p->AtoB, temp, in) > 1)
				error ("%d, %s",p->AtoB->pp->e.c,p->AtoB->pp->e.m);
	
			/* Figure out what DEV K value the BtoA table currently has for this PCS */
			if (((icxLuBase *)p->BtoA)->lookup((icxLuBase *)p->BtoA, temp, temp) > 1)
				error ("%d, %s",p->BtoA->pp->e.c,p->BtoA->pp->e.m);
		}
		targetk = temp[3];
#ifdef DEBUG
		printf("Got existing CMYK %f %f %f %f\n",temp[0],temp[1],temp[2],temp[3]);
#endif
	}

	/* Copy the Lab in */
	temp[0] = in[0];
	temp[1] = in[1];
	temp[2] = in[2];

	if (p->AtoB != p->AtoB1) {
		double tt[4];

		/* Can't assume B2A in/out tables are inverses of AtoB */
		/* Convert PCS' -> PCS for this table */
		if (p->BtoA->inv_input(p->BtoA, temp, temp) > 1)
			error ("%d, %s",p->BtoA->pp->e.c,p->BtoA->pp->e.m);
		/* Convert PCS -> PCS' for colorimetric */
		if (p->AtoB1->inv_output(p->AtoB1, temp, temp) > 1)
			error ("%d, %s",p->AtoB->pp->e.c,p->AtoB->pp->e.m);
	}

	/* Abstract profile applied before inversion */
	if (p->abs_luo != NULL) {
		do_abstract(p, temp, temp);
	}

	/* Invert AtoB1 clut, using set inking policy */
	out[3] = targetk;

	/* PCS' -> DEV' colorimetric (aux target is DEV space) */
	if ((rv = p->AtoB1->inv_clut(p->AtoB1, out, temp)) > 1)
		error ("%d, %s",p->AtoB1->pp->e.c,p->AtoB1->pp->e.m);

	/* ~~~ Note that the ink limit will be wrong for non-colorimetric, */
	/* since AtoB1->inv_clut will be assuming A->toB1->inv_input as the output table, */
	/* while we will actually be using BtoA->output ~~~~ */
	/* Need to override ink limit computation function in icx for non-colorimetric. */

//#ifdef DEBUG
//		if (rv != 0) {
//			printf("Inversion clipped\n");
//		}
//#endif
	if (p->AtoB != p->AtoB1) {
		/* Can't assume B2A in/out tables are inverses of AtoB */
		/* Converts DEV' -> DEV colorimetric */
		if (p->AtoB1->inv_input(p->AtoB1, out, out) > 1)
			error ("%d, %s",p->AtoB->pp->e.c,p->AtoB->pp->e.m);
		/* Convert DEV -> DEV' for this table */
		if (p->BtoA->inv_output(p->BtoA, out, out) > 1)
			error ("%d, %s",p->BtoA->pp->e.c,p->BtoA->pp->e.m);
	}
#ifdef DEBUG
	printf("New CMYK' %f %f %f %f\n",out[0],out[1],out[2],out[3]);
	printf("\n");
#endif

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

/* - - - - - - - - - */
/* New output table */
void CMYKp_CMYK(void *cntx, double out[4], double in[4]
, int tn
) {
	callback *p = (callback *)cntx;

#ifdef DEBUG
	printf("Got CMYK' %f %f %f %f\n",in[0],in[1],in[2],in[3]);
#endif
	if (p->AtoB != p->AtoB1) {
		/* Non-colorimetric, use existing output table */
		if (p->BtoA->output(p->BtoA, out, in) > 1)
			error ("%d, %s",p->BtoA->pp->e.c,p->BtoA->pp->e.m);
	} else {
		/* Colorimetric, use inverse AtoB input */
		if (p->AtoB1->inv_input(p->AtoB1, out, in) > 1)
			error ("%d, %s",p->AtoB1->pp->e.c,p->AtoB1->pp->e.m);
	}
#ifdef DEBUG
	printf("New CMYK %f %f %f %f\n",out[0],out[1],out[2],out[3]);
#endif
}


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

int
main(int argc, char *argv[]) {
	int fa,nfa;				/* argument we're looking at */
	char in_name[MAXNAMEL+1];
	char out_name[MAXNAMEL+1];
	char abs_name[MAXNAMEL+1] = "\000";	/* Abstract profile name */
	icmFile *rd_fp, *wr_fp;
	icc *icco;
	icmErr err = { 0, { '\000'} };
	int verb = 0;
	int clutres = 0;
	int do0 = 0;
	int do1 = 0;
	int do2 = 0;
	int inking = 0;			/* Default copy from existing */
	int locus = 0;			/* Default K value target */
	double Kstle = 0.0, Kstpo = 0.0, Kenle = 0.0, Kenpo = 0.0, Kshap = 0.0;
	double tlimit = -1.0;	/* Total ink limit */
	double klimit = -1.0;	/* Black ink limit */
	int intsep = 0;			/* Not implemented in xicc yet ??? */
	int rv = 0;

	error_program = argv[0];
	check_if_not_interactive();

	if (argc < 2)
		usage();

	/* Process the arguments */
	for(fa = 1;fa < argc;fa++) {
		nfa = fa;					/* skip to nfa if next argument is used */
		if (argv[fa][0] == '-')	{	/* Look for any flags */
			char *na = NULL;		/* next argument after flag, null if none */

			if (argv[fa][2] != '\000')
				na = &argv[fa][2];		/* next is directly after flag */
			else {
				if ((fa+1) < argc) {
					if (argv[fa+1][0] != '-') {
						nfa = fa + 1;
						na = argv[nfa];		/* next is seperate non-flag argument */
					}
				}
			}

			if (argv[fa][1] == '?')
				usage();

			/* Verbosity */
			else if (argv[fa][1] == 'v' || argv[fa][1] == 'V') {
				verb = 1;
			}
			else if (argv[fa][1] == '0') {
				do0 = 1;
			}
			else if (argv[fa][1] == '1') {
				do1 = 1;
			}
			else if (argv[fa][1] == '2') {
				do2 = 1;
			}
			else if (argv[fa][1] == 'r' || argv[fa][1] == 'R') {
				fa = nfa;
				if (na == NULL) usage();
				clutres = atoi(na);
			}

			/* Inking rule */
			else if (argv[fa][1] == 'k' || argv[fa][1] == 'K') {
				fa = nfa;
				if (na == NULL) usage();
				if (argv[fa][1] == 'k')
					locus = 0;			/* K value target */
				else
					locus = 1;			/* K locus target */
    			switch (na[0]) {
					case 'e':
					case 'E':
						inking = 0;		/* Use existing table as guide */
						break;
					case 'z':
					case 'Z':
						inking = 1;		/* Use minimum k */
						break;
					case 'h':
					case 'H':
						inking = 2;		/* Use 0.5 k */
						break;
					case 'x':
					case 'X':
						inking = 3;		/* Use maximum k */
						break;
					case 'r':
					case 'R':
						inking = 4;		/* Use ramp */
						break;
					case 'p':
					case 'P':
						inking = 5;		/* Use curve parameter */
						++fa;
						if (fa >= argc) usage();
						Kstle = atof(argv[fa]);

						++fa;
						if (fa >= argc) usage();
						Kstpo = atof(argv[fa]);

						++fa;
						if (fa >= argc || argv[fa][0] == '-') usage();
						Kenpo = atof(argv[fa]);

						++fa;
						if (fa >= argc) usage();
						Kenle = atof(argv[fa]);

						++fa;
						if (fa >= argc || argv[fa][0] == '-') usage();
						Kshap = atof(argv[fa]);
						break;
					default:
						usage();
				}
			}
			else if (argv[fa][1] == 'l') {
				fa = nfa;
				if (na == NULL) usage();
				tlimit = atoi(na)/100.0;
			}
			else if (argv[fa][1] == 'L') {
				fa = nfa;
				if (na == NULL) usage();
				klimit = atoi(na)/100.0;
			}

			/* Use internal separation */
			else if (argv[fa][1] == 's' || argv[fa][1] == 'S') {
				intsep = 1;
			}

			/* Abstract profile */
			else if (argv[fa][1] == 'p' || argv[fa][1] == 'P') {
				if (na == NULL) usage();
				fa = nfa;
				strncpy(abs_name,na,MAXNAMEL); abs_name[MAXNAMEL] = '\000';
			}

			else 
				usage();
		} else
			break;
	}

	if (fa >= argc || argv[fa][0] == '-') usage();
	strncpy(in_name,argv[fa++],MAXNAMEL); in_name[MAXNAMEL] = '\000';

	if (fa >= argc || argv[fa][0] == '-') usage();
	strncpy(out_name,argv[fa++],MAXNAMEL); out_name[MAXNAMEL] = '\000';

	/* Open up the profile for reading */
	if ((rd_fp = new_icmFileStd_name(&err, in_name,"r")) == NULL)
		error ("Can't open file '%s' (0x%x, '%s')",in_name,err.c,err.m);

	if ((icco = new_icc(&err)) == NULL)
		error ("Creation of ICC object failed (0x%x, '%s')",err.c,err.m);

	/* Read header etc. */
	if ((rv = icco->read(icco,rd_fp,0)) != 0)
		error ("%d, %s",rv,icco->e.m);

	/* Read every tag */
	if (icco->read_all_tags(icco) != 0) {
		error("Unable to read all tags: %d, %s",icco->e.c,icco->e.m);
	}

	rd_fp->del(rd_fp);

	/* ======================= */
	/* Check that it is a suitable icc */
	{
		icmHeader *rh = icco->header;

		if (rh->deviceClass != icSigOutputClass)
			error("Profile isn't an output device profile");

    	if (rh->colorSpace != icSigCmykData)
			error("Profile isn't for a CMYK device");

    	if (rh->pcs != icSigLabData)
			error("Profile is not using a PCS of Lab - can't cope with this yet");
	}

	if (verb && inking == 5) {
		double tL;
		printf("K parameters are are %f %f %f %f %f\n",Kstle, Kstpo, Kenpo, Kenle, Kshap);
		for (tL = 100.0; tL >= 0.0; tL -= 10.0) {
			double rv, L;

			L = 0.01 * tL;

			/* Code from xlut.c: */
			/* Invert sense of L, so that 0.0 = white, 1.0 = black */
			L = 1.0 - L;

			if (L <= Kstpo && L <= Kenpo) {
				/* We are at white level */
				rv = Kstle;
			} else if (L >= Kstpo && L >= Kenpo) {
				/* We are at black level */
				rv = Kenle;
			} else {
				double g;
				/* We must be on the curve from start to end levels */

				if (Kstpo > Kenpo) {
					rv = (L - Kenpo)/(Kstpo - Kenpo);
				} else {
					rv = (L - Kstpo)/(Kenpo - Kstpo);
				}

				g = Kshap/2.0;

				/* A value of 0.5 will be tranlated to g */
				rv = rv/((1.0/g - 2.0) * (1.0 - rv) + 1.0);

				/* Transition between start end end levels */
				rv = rv * (Kenle - Kstle) + Kstle;
			}

			/* To be safe */
			if (rv < 0.0)
				rv = 0.0;
			else if (rv > 1.0)
				rv = 1.0;

			printf("L = %f, K %s = %f\n",tL, locus ? "locus" : "value", rv);
		}
	}

	/* ======================= */
	{
		int ii;						/* Current intent */
		icmLut *done[3];			/* record pointers to done Luts here */
		icRenderingIntent intent = 0;
		icTagSignature sig = 0;
		xicc *xicco;
		callback cb;					/* Callback support stucture */
		icxInk ink;						/* Ink parameters */
		icmLuAlgType alg;				/* Type of lookup algorithm */
		icmFile *abs_fp = NULL;			/* Abstract profile transform: */
		icc *abs_icc = NULL;
		xicc *abs_xicc = NULL;
		
		/* Wrap with an expanded icc */
		if ((xicco = new_xicc(icco)) == NULL)
			error ("Creation of xicc failed");

		/* Setup CMYK -> Lab conversion (Fwd) object */

		/* Set the default ink limits if not set on command line */
		icxDefaultLimits(xicco, &ink.tlimit, tlimit, &ink.klimit, klimit);

		if (verb) {
			if (!do0 && !do1 && !do2)
				printf("WARNING: nothing to do!\n");
			if (ink.tlimit >= 0.0)
				printf("Total ink limit assumed is %3.0f%%\n",100.0 * ink.tlimit);
			if (ink.klimit >= 0.0)
				printf("Black ink limit assumed is %3.0f%%\n",100.0 * ink.klimit);
		}

		ink.KonlyLmin = 0;		/* Use normal black Lmin for locus */

		ink.c.Ksmth = ICXINKDEFSMTH;	/* Default curve smoothing */
		ink.c.Kskew = ICXINKDEFSKEW;	/* default curve skew */
		ink.x.Ksmth = ICXINKDEFSMTH;
		ink.x.Kskew = ICXINKDEFSKEW;

		if (inking == 0) {
			ink.k_rule = icxKvalue;		/* K is auxiliary target */

		} else if (inking == 1) {		/* Use minimum */
			ink.k_rule = locus ? icxKluma5 : icxKluma5k;
			ink.c.Kstle = 0.0;
			ink.c.Kstpo = 0.0;
			ink.c.Kenpo = 1.0;
			ink.c.Kenle = 0.0;
			ink.c.Kshap = 1.0;
		} else if (inking == 2) {		/* Use 0.5  */
			ink.k_rule = locus ? icxKluma5 : icxKluma5k;
			ink.c.Kstle = 0.5;
			ink.c.Kstpo = 0.0;
			ink.c.Kenpo = 1.0;
			ink.c.Kenle = 0.5;
			ink.c.Kshap = 1.0;
		} else if (inking == 3) {		/* Use maximum  */
			ink.k_rule = locus ? icxKluma5 : icxKluma5k;
			ink.c.Kstle = 1.0;
			ink.c.Kstpo = 0.0;
			ink.c.Kenpo = 1.0;
			ink.c.Kenle = 1.0;
			ink.c.Kshap = 1.0;
		} else if (inking == 4) {		/* Use ramp  */
			ink.k_rule = locus ? icxKluma5 : icxKluma5k;
			ink.c.Kstle = 0.0;
			ink.c.Kstpo = 0.0;
			ink.c.Kenpo = 1.0;
			ink.c.Kenle = 1.0;
			ink.c.Kshap = 1.0;
		} else {						/* Use specified curve */
			ink.k_rule = locus ? icxKluma5 : icxKluma5k;
			ink.c.Kstle = Kstle;
			ink.c.Kstpo = Kstpo;
			ink.c.Kenpo = Kenpo;
			ink.c.Kenle = Kenle;
			ink.c.Kshap = Kshap;
		}

		cb.verb = verb;
		cb.count = 0;
		cb.last = -1;
		cb.inking = inking;

		/* Setup our access to the device characteristic */
		if ((cb.AtoB1 = (icxLuLut *)xicco->get_luobj(xicco,
		                            ICX_CLIP_NEAREST
#ifdef USE_CAM_CLIP_OPT
									| ICX_CAM_CLIP
#endif
		                            | (intsep ? ICX_INT_SEPARATE : 0),
		                            icmFwd, icRelativeColorimetric,
		                            icmSigDefaultData, icmLuOrdNorm, NULL, &ink)) == NULL)
			error ("%d, %s",xicco->e.c, xicco->e.m);

		cb.AtoB1->spaces((icxLuBase *)cb.AtoB1, NULL, NULL, NULL, NULL, &alg,
		                                              NULL, NULL, &cb.pcsspace);
		if (alg != icmLutType)
			error("Forward conversion is not a Lut");

		/* Open up the abstract profile if supplied, and setup luo */
		if (abs_name[0] != '\000') {
			if ((abs_fp = new_icmFileStd_name(&err, abs_name,"r")) == NULL)
				error ("Can't open abstract profile file '%s' (0x%x, '%s')",abs_name,err.c,err.m);
			
			if ((abs_icc = new_icc(&err)) == NULL)
				error ("Creation of Abstract profile ICC object failed (0x%x, '%s')",err.c,err.m);
	
			/* Read header etc. */
			if ((rv = abs_icc->read(abs_icc,abs_fp,0)) != 0)
				error ("%d, %s",rv,abs_icc->e.m);
	
			if (abs_icc->header->deviceClass != icSigAbstractClass)
				error("Abstract profile isn't an abstract profile");
	
			/* Take intended abstract intent from profile itself */
			if ((cb.abs_intent = abs_icc->header->renderingIntent) != icAbsoluteColorimetric)
				cb.abs_intent = icRelativeColorimetric;
	
			/* Wrap with an expanded icc */
			if ((abs_xicc = new_xicc(abs_icc)) == 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 ((cb.abs_luo = abs_xicc->get_luobj(abs_xicc, ICX_CLIP_NEAREST, icmFwd, cb.abs_intent,
		        (cb.pcsspace == icSigLabData && cb.abs_intent == icRelativeColorimetric)
				             ? icSigLabData : icSigXYZData,
				icmLuOrdNorm, NULL, NULL)) == NULL)
				error ("%d, %s",abs_icc->e.c, abs_icc->e.m);
		} else {
			cb.abs_luo = NULL;
		}
			
		/* for all intents, and not already processed */
		for (ii = 0; ii <= 2; ii++) {
			int i;
			icmLut *wo;

			switch(ii) {
				case 0:
					intent = icRelativeColorimetric;
					sig    = icSigBToA1Tag;
					if (do1 == 0)
						continue;
					break;
				case 1:
					intent = icPerceptual;
					sig    = icSigBToA0Tag;
					if (do0 == 0)
						continue;
					break;
				case 2:
					intent = icSaturation;
					sig    = icSigBToA2Tag;
					if (do2 == 0)
						continue;
					break;
			}

			if (intent == icRelativeColorimetric) {
				cb.AtoB = cb.AtoB1;
			} else {
				/* Setup CMYK -> Lab conversion (Fwd) object */
				if ((cb.AtoB = (icxLuLut *)xicco->get_luobj(xicco, ICX_CLIP_NEAREST, icmFwd, intent,
				                         icmSigDefaultData, icmLuOrdNorm, NULL, NULL)) == NULL)
					error ("%d, %s",xicco->e.c, xicco->e.m);
	
				cb.AtoB->spaces((icxLuBase *)cb.AtoB, NULL, NULL, NULL, NULL, &alg, NULL, NULL, NULL);
				if (alg != icmLutType)
					error("Forwards conversion is not a Lut");
			}

			/* Setup Lab -> CMYK conversion (Bwd) object */
			if ((cb.BtoA = (icxLuLut *)xicco->get_luobj(xicco, ICX_CLIP_NEAREST, icmBwd, intent,
			                           icmSigDefaultData, icmLuOrdNorm, NULL, NULL)) == NULL)
				error ("%d, %s",xicco->e.c, xicco->e.m);

			cb.BtoA->spaces((icxLuBase *)cb.BtoA, NULL, NULL, NULL, NULL, &alg, NULL, NULL, NULL);
			if (alg != icmLutType)
				error("Backwards conversion is not a Lut");

			/* Try and read the tag from the file */
			wo = (icmLut *)icco->read_tag(icco, sig);
			if (wo == NULL) 
				error("Can't find %s", icm2str(icmRenderingIntent, intent));

			/* Need to check that the cast is appropriate. */
			if (wo->ttype != icSigLut16Type && wo->ttype != icSigLut8Type)
				error("Lut table isn't Lut8 or Lut16 Type");


			/* Make sure that we don't process a table twice */
			done[ii] = wo;
			for (i = ii-1; i >= 0; i--) {
				if (done[i] == wo) {
					break;
				}
			}
			if (i >= 0) {
				if (verb)
					printf("Skipping %s table - already done\n", icm2str(icmRenderingIntent, intent));

			} else {
				icmXformSigs sigs[1];
				unsigned int inputEnt;
				unsigned int cres, clutPoints[MAX_CHAN];
				unsigned int outputEnt;

				sigs[0].sig = sig;
				sigs[0].ttype = icSigLut16Type;

				if ((inputEnt = cb.AtoB->plu->max_out_res(cb.AtoB->plu, NULL)) == 0)
					inputEnt = 1024;

				if ((cres = cb.AtoB->plu->max_clut_res(cb.AtoB->plu, NULL)) == 0)
					cres = 17;

				/* Let user override for BtoA1 */
				if (sig == icSigBToA1Tag && clutres > 0) {
					if (verb)
						printf("Overriding existing clut resolution %d with %d\n",cres,clutres);
			    	cres = clutres;
				}
				for (i = 0; i < 3; i++)
					clutPoints[i] = cres; 
				if ((outputEnt = cb.AtoB->plu->max_in_res(cb.AtoB->plu, NULL)) == 0)
					outputEnt = 1024;
		
				if (verb)
					printf("About to start processing %s\n", icm2str(icmRenderingIntent, intent));
	
				if (cb.verb) {
					unsigned int ui;
					int extra;
					for (cb.total = 1, ui = 0; ui < 3; cb.total *= clutPoints[ui], ui++)
						; 
					/* Add in cell center points */
					for (extra = 1, ui = 0; ui < 3; extra *= clutPoints[ui]-1, ui++)
						;
					cb.total += extra;
					printf(" 0%%"), fflush(stdout);
				}

				if (icco->create_lut_xforms(
					icco,
					ICM_CLUT_SET_APXLS,
					&cb,				/* Context */
					1,					/* One table */
					sigs,				/* signatures and tag types for each table */
					2,					/* Bytes per value of AToB or BToA CLUT, 1 or 2 */
					inputEnt, clutPoints, outputEnt,	/* Table resolutions */
					icSigLabData, 		/* Input color space */
					icSigCmykData, 		/* Output color space */
					NULL, NULL,			/* Use default input range */
					Lab_Labp,			/* Linear input transform Lab->Lab' */
					NULL, NULL,			/* Use default Lab' range */
					Labp_CMYKp,			/* Lab' -> CMYK' transfer function */
					NULL, NULL,			/* Use default CMYK' range */
					CMYKp_CMYK,		 	/* Output transfer function, CMYK'->CMYK */
					NULL, NULL			/* default APXLS range */
				) != ICM_ERR_OK)
					error("Setting 16 bit Lab->CMYK Lut failed: %d, %s",icco->e.c,icco->e.m);

	
				if (verb)
					printf("\nDone processing %s\n", icm2str(icmRenderingIntent, intent));
	
			}

			/* Done with this intents lookup object */
			cb.BtoA->del((icxLuBase *)cb.BtoA);
			if (cb.AtoB != cb.AtoB1)
				cb.AtoB->del((icxLuBase *)cb.AtoB);
		}
		/* Done with colorimetric intents AtoB1 lookup objects, and xicc */
		cb.AtoB1->del((icxLuBase *)cb.AtoB1);
		xicco->del(xicco);

		if (cb.abs_luo != NULL) {		/* Free up abstract transform */
			cb.abs_luo->del(cb.abs_luo);
			abs_xicc->del(abs_xicc);
			abs_icc->del(abs_icc);
			abs_fp->del(abs_fp);
		}

	}
	/* ======================================= */
	
	/* Open up the other profile for writing */
	if ((wr_fp = new_icmFileStd_name(&err, out_name,"w")) == NULL)
		error ("Can't open file '%s' (0x%x, '%s')",out_name,err.c,err.m);

	/* Relax format so we don't barf on input profile contents */
	icco->set_cflag(icco, icmCFlagWrFormatWarn);
	icco->set_cflag(icco, icmCFlagWrVersionWarn);

	if ((rv = icco->write(icco,wr_fp,0)) != 0)
		error ("Write file: %d, %s",rv,icco->e.m);

	wr_fp->del(wr_fp);
	icco->del(icco);

	return 0;
}

