
/* 
 * Color Correct a TIFF file, using an ICC Device link profile.
 *
 * Author:  Graeme W. Gill
 * Date:    00/3/8
 * Version: 1.30
 *
 * Copyright 2000 - 2004 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.
 */

/*
 * Thanks to Neil Okamoto for the 16 bit TIFF mods.
 */

/* TTBD:
 */

/*
	This program is a framework that exercises the
	IMDI code, as well as a demonstration of simple
    profile linking.  It can also do the conversion using the
    floating point code in ICCLIB as a reference.

 */

#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 "tiffio.h"
#include "icc.h"
#include "imdi.h"

#undef TREAT_CMY_AS_RGB

void error(char *fmt, ...), warning(char *fmt, ...);

void usage(void) {
	fprintf(stderr,"Color Correct a TIFF file using an ICC device link profile, V%s\n",ARGYLL_VERSION_STR);
	fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n");
	fprintf(stderr,"usage: cctiff [-options] devlinkprofile.icm infile.tif outfile.tif\n");
	fprintf(stderr,"usage: cctiff [-options] -l inprofile.icm outprofile.icm infile.tif outfile.tif\n");
	fprintf(stderr," -v            Verbose\n");
	fprintf(stderr," -c            Combine linearisation curves into one transform\n");
	fprintf(stderr," -p            Use slow precise correction\n");
	fprintf(stderr," -k            Check fast result against precise, and report\n");
	fprintf(stderr," -l            Link input and output profiles\n"); 
	fprintf(stderr,"  -i in_intent  p = perceptual, r = relative colorimetric,\n");
	fprintf(stderr,"                s = saturation, a = absolute colorimetric\n");
	fprintf(stderr,"  -o out_intent p = perceptual, r = relative colorimetric,\n");
	fprintf(stderr,"                s = saturation, a = absolute colorimetric\n");
	exit(1);
}

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

/* Convert an ICC colorspace to the corresponding possible TIFF Photometric tags. */
/* Return the number of matching tags, and 0 if there is no corresponding tag. */
int
ColorSpaceSignature2TiffPhotometric(
uint16 tags[10],					/* Pointer to return array, up to 10 */
icColorSpaceSignature cspace	/* Input ICC colorspace */
) {
	switch(cspace) {
		case icSigGrayData:
			tags[0] = PHOTOMETRIC_MINISBLACK;
			return 1;
		case icSigRgbData:
#ifdef TREAT_CMY_AS_RGB
		case icSigCmyData:
#endif
			tags[0] = PHOTOMETRIC_RGB;
			return 1;
#ifndef TREAT_CMY_AS_RGB
		case icSigCmyData:
#endif
		case icSigCmykData:
			tags[0] = PHOTOMETRIC_SEPARATED;
			return 1;
		case icSigYCbCrData:
			tags[0] = PHOTOMETRIC_YCBCR;
			return 1;
		case icSigLabData:
			tags[0] = PHOTOMETRIC_CIELAB;
#ifdef PHOTOMETRIC_ICCLAB
			tags[1] = PHOTOMETRIC_ICCLAB;
			tags[2] = PHOTOMETRIC_ITULAB;
#endif
			return 3;

		case icSigXYZData:
		case icSigLuvData:
		case icSigYxyData:
		case icSigHsvData:
		case icSigHlsData:
			return 0;

		case icSig2colorData:
			tags[0] = PHOTOMETRIC_SEPARATED;
			tags[1] = 2;			/* Cheat */
			return 1;

		case icSig3colorData:
			tags[0] = PHOTOMETRIC_SEPARATED;
			tags[1] = 3;			/* Cheat */
			return 1;

		case icSig4colorData:
			tags[0] = PHOTOMETRIC_SEPARATED;
			tags[1] = 4;			/* Cheat */
			return 1;

		case icSig5colorData:
		case icSigMch5Data:
			tags[0] = PHOTOMETRIC_SEPARATED;
			tags[1] = 5;			/* Cheat */
			return 1;

		case icSig6colorData:
		case icSigMch6Data:
			tags[0] = PHOTOMETRIC_SEPARATED;
			tags[1] = 6;			/* Cheat */
			return 1;

		case icSig7colorData:
		case icSigMch7Data:
			tags[0] = PHOTOMETRIC_SEPARATED;
			tags[1] = 7;			/* Cheat */
			return 1;

		case icSig8colorData:
		case icSigMch8Data:
			tags[0] = PHOTOMETRIC_SEPARATED;
			tags[1] = 8;			/* Cheat */
			return 1;

		case icSig9colorData:
			tags[0] = PHOTOMETRIC_SEPARATED;
			tags[1] = 9;			/* Cheat */
			return 1;

		case icSig10colorData:
			tags[0] = PHOTOMETRIC_SEPARATED;
			tags[1] = 10;			/* Cheat */
			return 1;

		case icSig11colorData:
			tags[0] = PHOTOMETRIC_SEPARATED;
			tags[1] = 11;			/* Cheat */
			return 1;

		case icSig12colorData:
			tags[0] = PHOTOMETRIC_SEPARATED;
			tags[1] = 12;			/* Cheat */
			return 1;

		case icSig13colorData:
			tags[0] = PHOTOMETRIC_SEPARATED;
			tags[1] = 13;			/* Cheat */
			return 1;

		case icSig14colorData:
			tags[0] = PHOTOMETRIC_SEPARATED;
			tags[1] = 14;			/* Cheat */
			return 1;

		case icSig15colorData:
			tags[0] = PHOTOMETRIC_SEPARATED;
			tags[1] = 15;			/* Cheat */
			return 1;

		default:
			return 0;
	}
	return 0;
}


/* Compute the length of a double nul terminated string, including */
/* the nuls. */
static int zzstrlen(char *s) {
	int i;
	for (i = 0;; i++) {
		if (s[i] == '\000' && s[i+1] == '\000')
			return i+2;
	}
	return 0;
}

/* Convert an ICC colorspace to the corresponding TIFF Inkset tag */
/* return 0xffff if not possible or applicable. */

int
ColorSpaceSignature2TiffInkset(
icColorSpaceSignature cspace,
int *len,						/* Return length of ASCII inknames */
char **inknames					/* Return ASCII inknames if non NULL */
) {
	switch(cspace) {
		case icSigCmyData:
			return 0xffff;	// ~~9999
			if (inknames != NULL) {
				*inknames = "cyan\000magenta\000yellow\000\000";
				*len = zzstrlen(*inknames);
			}
			return 0;				/* Not CMYK */
		case icSigCmykData:
			if (inknames != NULL) {
				*inknames = NULL;	/* No inknames */
				*len = 0;
			}
			return INKSET_CMYK;

		case icSigGrayData:
		case icSigRgbData:
		case icSigYCbCrData:
		case icSigLabData:
		case icSigXYZData:
		case icSigLuvData:
		case icSigYxyData:
		case icSigHsvData:
		case icSigHlsData:
		case icSig2colorData:
		case icSig3colorData:
		case icSig4colorData:
		case icSig5colorData:
		case icSigMch5Data:
			return 0xffff;

		case icSig6colorData:
		case icSigMch6Data:
				/* This is a cheat and a hack. Should really make use of the */
				/* ColorantTable to determine the colorant names. */
				/* allowing cctiff to read it. */
			if (inknames != NULL) {
				*inknames = "cyan\000magenta\000yellow\000black\000orange\000green\000\000";
				*len = zzstrlen(*inknames);
			}
			return 0;				/* Not CMYK */

		case icSig7colorData:
		case icSigMch7Data:
			return 0xffff;

		case icSig8colorData:
		case icSigMch8Data:
				/* This is a cheat and a hack. Should really make use of the */
				/* ColorantTable to determine the colorant names. */
				/* allowing cctiff to read it. */
			if (inknames != NULL) {
				*inknames = "cyan\000magenta\000yellow\000black\000orange\000green\000lightcyan\000lightmagenta\000\000";
				*len = zzstrlen(*inknames);
			}
			return 0;				/* Not CMYK */
		case icSig9colorData:
		case icSig10colorData:
		case icSig11colorData:
		case icSig12colorData:
		case icSig13colorData:
		case icSig14colorData:
		case icSig15colorData:
		default:
			return 0xffff;
	}
	return 0xffff;
}

char *
Photometric2str(
int pmtc
) {
	static char buf[80];
	switch (pmtc) {
		case PHOTOMETRIC_MINISWHITE:
			return "Subtractive Gray";
		case PHOTOMETRIC_MINISBLACK:
			return "Additive Gray";
		case PHOTOMETRIC_RGB:
			return "RGB";
		case PHOTOMETRIC_PALETTE:
			return "Indexed";
		case PHOTOMETRIC_MASK:
			return "Transparency Mask";
		case PHOTOMETRIC_SEPARATED:
			return "Separated";
		case PHOTOMETRIC_YCBCR:
			return "YCbCr";
		case PHOTOMETRIC_CIELAB:
			return "CIELab";
#ifdef PHOTOMETRIC_ICCLAB
		case PHOTOMETRIC_ICCLAB:
			return "ICCLab";
		case PHOTOMETRIC_ITULAB:
			return "ITULab";
#endif
		case PHOTOMETRIC_LOGL:
			return "CIELog2L";
		case PHOTOMETRIC_LOGLUV:
			return "CIELog2Luv";
	}
	sprintf(buf,"Unknonw Tag %d",pmtc);
	return buf;
}

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

/* Callbacks used to initialise imdi */

/* Information needed from a profile */
struct _profinfo {
	char name[100];
	icmFile *fp;
	icc *c;
	icmHeader *h;
	icRenderingIntent intent;
	icmLuBase *luo;				/* Base Lookup type object */
	icmLuAlgType alg;			/* Type of lookup algorithm */
	int chan;					/* Device channels */
}; typedef struct _profinfo profinfo;

/* Context for imdi setup callbacks */
typedef struct {
	/* Overall parameters */
	int verb;			/* Non-zero if verbose */
	icColorSpaceSignature ins, outs;	/* Input/Output spaces */
	int id, od;			/* Input/Output dimensions */
	int icombine;		/* Non-zero if input curves are to be combined */
	int ocombine;		/* Non-zero if output curves are to be combined */
	int dolink;			/* Non-zero if input and output profiles are to be linked */

	profinfo dev;		/* Device link profile */
	profinfo in;		/* Device to PCS profile */
	profinfo out;		/* PCS to Device profile */
} sucntx;

/* Input curve function */
static void input_curves(
	void *cntx,
	double *out_vals,
	double *in_vals
) {
	sucntx *rx = (sucntx *)cntx;

//printf("~1 incurve in %f %f %f %f\n",in_vals[0],in_vals[1],in_vals[2],in_vals[3]);
	if (rx->icombine) {
		int i;
		for (i = 0; i < rx->id; i++)
			out_vals[i] = in_vals[i];
	} else {
		if (rx->dolink) {		/* Two ICC profiles */
			rx->in.luo->lookup_in(rx->in.luo, out_vals, in_vals);
		} else {			/* Device link */
			rx->dev.luo->lookup_in(rx->dev.luo, out_vals, in_vals);
		}
	}
//printf("~1 incurve out %f %f %f %f\n",out_vals[0],out_vals[1],out_vals[2],out_vals[3]);
}

/* Multi-dim table function */
static void md_table(
void *cntx,
double *out_vals,
double *in_vals
) {
	sucntx *rx = (sucntx *)cntx;
	double vals[MAX_CHAN];
	
//printf("~1 md_table in %f %f %f %f\n",in_vals[0],in_vals[1],in_vals[2]);
	if (rx->dolink) {		/* Two ICC profiles */

		if (rx->icombine) {
			rx->in.luo->lookup_in(rx->in.luo, vals, in_vals);
			rx->in.luo->lookup_core(rx->in.luo, vals, vals);
		} else {
			rx->in.luo->lookup_core(rx->in.luo, vals, in_vals);
		}
		rx->in.luo->lookup_out(rx->in.luo, vals, vals);
		rx->out.luo->lookup_in(rx->out.luo, vals, vals);
		rx->out.luo->lookup_core(rx->out.luo, out_vals, vals);
		if (rx->ocombine)
			rx->out.luo->lookup_out(rx->out.luo, out_vals, out_vals);

	} else {			/* Device link */

		if (rx->icombine) {
			rx->dev.luo->lookup_in(rx->dev.luo, vals, in_vals);
			rx->dev.luo->lookup_core(rx->dev.luo, out_vals, vals);
		} else {
			rx->dev.luo->lookup_core(rx->dev.luo, out_vals, in_vals);
		}
		if (rx->ocombine)
			rx->dev.luo->lookup_out(rx->dev.luo, out_vals, out_vals);
	}
//printf("~1 md_table returns %f %f %f %f\n",out_vals[0],out_vals[1],out_vals[2],out_vals[3]);
}

/* Output curve function */
static void output_curves(
	void *cntx,
	double *out_vals,
	double *in_vals
) {
	sucntx *rx = (sucntx *)cntx;
	
//printf("~1 outurve in %f %f %f %f\n",in_vals[0],in_vals[1],in_vals[2],in_vals[3]);
	if (rx->ocombine) {
		int i;
		for (i = 0; i < rx->od; i++)
			out_vals[i] = in_vals[i];
	} else {
		if (rx->dolink) {	/* Two ICC profiles */
			rx->out.luo->lookup_out(rx->out.luo, out_vals, in_vals);
		} else {	/* Device link */
			rx->dev.luo->lookup_out(rx->dev.luo, out_vals, in_vals);
		}
	}
//printf("~1 outurve out %f %f %f %f\n",out_vals[0],out_vals[1],out_vals[2],out_vals[3]);
}

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


int
main(int argc, char *argv[]) {
	int fa,nfa;				/* argument we're looking at */
	char in_name[100];		/* Raster file name */
	char out_name[100];		/* Raster file name */
	int slow = 0;
	int check = 0;
	int i, rv = 0;

	TIFF *rh = NULL, *wh = NULL;
	int x, y, width, height;					/* Size of image */
	uint16 samplesperpixel, bitspersample;
	int no_pmtc;								/* Number of input photometrics */
	uint16 photometric, pmtc[10];				/* Photometrics of input file, and input profile */
	uint16 pconfig;								/* Planar configuration */
	uint16 resunits;
	float resx, resy;
	tdata_t *inbuf, *outbuf, *checkbuf = NULL;

	/* IMDI */
	imdi *s = NULL;
	sucntx su;		/* Setup context */
	unsigned char *inp[MAX_CHAN];
	unsigned char *outp[MAX_CHAN];
	int clutres = 33;

	/* Error check */
	int mxerr = 0;
	double avgerr = 0.0;
	double avgcount = 0.0;

	if (argc < 2)
		usage();

	su.verb = 0;
	su.icombine = 0;
	su.ocombine = 0;
	su.dolink = 0;
	su.in.intent = icmDefaultIntent;
	su.out.intent = icmDefaultIntent;

	/* 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();

			/* Slow, Precise */
			else if (argv[fa][1] == 'p' || argv[fa][1] == 'P') {
				slow = 1;
			}

			/* Combine per channel curves */
			else if (argv[fa][1] == 'c' || argv[fa][1] == 'C') {
				su.icombine = 1;
				su.ocombine = 1;
			}

			/* Check curves */
			else if (argv[fa][1] == 'k' || argv[fa][1] == 'K') {
				check = 1;
			}

			/* Link profiles */
			else if (argv[fa][1] == 'l' || argv[fa][1] == 'L') {
				su.dolink = 1;
			}

			/* Input profile Intent */
			else if (argv[fa][1] == 'i' || argv[fa][1] == 'I') {
				fa = nfa;
				if (na == NULL) usage();
    			switch (na[0]) {
					case 'p':
					case 'P':
						su.in.intent = icPerceptual;
						break;
					case 'r':
					case 'R':
						su.in.intent = icRelativeColorimetric;
						break;
					case 's':
					case 'S':
						su.in.intent = icSaturation;
						break;
					case 'a':
					case 'A':
						su.in.intent = icAbsoluteColorimetric;
						break;
					default:
						usage();
				}
			}

			/* Output profile Intent */
			else if (argv[fa][1] == 'o' || argv[fa][1] == 'O') {
				fa = nfa;
				if (na == NULL) usage();
    			switch (na[0]) {
					case 'p':
					case 'P':
						su.out.intent = icPerceptual;
						break;
					case 'r':
					case 'R':
						su.out.intent = icRelativeColorimetric;
						break;
					case 's':
					case 'S':
						su.out.intent = icSaturation;
						break;
					case 'a':
					case 'A':
						su.out.intent = icAbsoluteColorimetric;
						break;
					default:
						usage();
				}
			}

			/* Verbosity */
			else if (argv[fa][1] == 'v' || argv[fa][1] == 'V') {
				su.verb = 1;
			}

			else 
				usage();
		} else
			break;
	}

	if (su.dolink) {
		if (fa >= argc || argv[fa][0] == '-') usage();
		strcpy(su.in.name,argv[fa++]);

		if (fa >= argc || argv[fa][0] == '-') usage();
		strcpy(su.out.name,argv[fa++]);
	} else {
		if (fa >= argc || argv[fa][0] == '-') usage();
		strcpy(su.dev.name,argv[fa++]);
	}

	if (fa >= argc || argv[fa][0] == '-') usage();
	strcpy(in_name,argv[fa++]);

	if (fa >= argc || argv[fa][0] == '-') usage();
	strcpy(out_name,argv[fa++]);

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

	if (su.dolink) {
		icColorSpaceSignature natpcs;

		/* Open up the input device profile for reading */
		if ((su.in.fp = new_icmFileStd_name(su.in.name,"r")) == NULL)
			error ("Can't open file '%s'",su.in.name);
		
		if ((su.in.c = new_icc()) == NULL)
			error ("Creation of Input profile ICC object failed");
	
		/* Read header etc. */
		if ((rv = su.in.c->read(su.in.c,su.in.fp,0)) != 0)
			error ("%d, %s on file '%s'",rv,su.in.c->err,su.in.name);
		su.in.h = su.in.c->header;
	
		/* Check that it is a suitable device input icc */
		if (su.in.h->deviceClass != icSigInputClass
		 && su.in.h->deviceClass != icSigDisplayClass
		 && su.in.h->deviceClass != icSigOutputClass
		 && su.in.h->deviceClass != icSigColorSpaceClass)	/* For sRGB etc. */
			error("Input profile isn't a device profile");
	
		/* Get a conversion object */
		if ((su.in.luo = su.in.c->get_luobj(su.in.c, icmFwd, su.in.intent,
		                                             icSigLabData, icmLuOrdNorm)) == NULL)
			error ("%d, %s for profile '%s'",su.in.c->errc, su.in.c->err, su.in.name);
	
		/* Get details of conversion (Arguments may be NULL if info not needed) */
		su.in.luo->spaces(su.in.luo, &su.ins, &su.id, NULL, NULL, &su.in.alg, NULL, NULL, NULL);

		/* Get native PCS space */
		su.in.luo->lutspaces(su.in.luo, NULL, NULL, NULL, NULL, &natpcs);
	
		if (natpcs == icSigXYZData) {
			su.icombine = 1;			/* XYZ is to non-linear to be a benefit */
		}

		/* Open up the output device profile for reading */
		if ((su.out.fp = new_icmFileStd_name(su.out.name,"r")) == NULL)
			error ("Can't open file '%s'",su.out.name);
		
		if ((su.out.c = new_icc()) == NULL)
			error ("Creation of Output profile ICC object failed");
	
		/* Read header etc. */
		if ((rv = su.out.c->read(su.out.c,su.out.fp,0)) != 0)
			error ("%d, %s on file '%s'",rv,su.out.c->err,su.out.name);
		su.out.h = su.out.c->header;
	
		/* Check that it is a suitable device output icc */
		if (su.out.h->deviceClass != icSigInputClass
		 && su.out.h->deviceClass != icSigDisplayClass
		 && su.out.h->deviceClass != icSigOutputClass
		 && su.out.h->deviceClass != icSigColorSpaceClass)	/* For sRGB etc. */
			error("Output profile isn't a device profile");
	
		/* Get a conversion object */
		if ((su.out.luo = su.out.c->get_luobj(su.out.c, icmBwd, su.out.intent,
		                                             icSigLabData, icmLuOrdNorm)) == NULL)
			error ("%d, %s for profile '%s'",su.out.c->errc, su.out.c->err, su.out.name);
	
		/* Get details of conversion (Arguments may be NULL if info not needed) */
		su.out.luo->spaces(su.out.luo, NULL, NULL, &su.outs, &su.od, &su.out.alg, NULL, NULL, NULL);

		/* Get native PCS space */
		su.out.luo->lutspaces(su.out.luo, NULL, NULL, NULL, NULL, &natpcs);
	
		if (natpcs == icSigXYZData) {
			su.ocombine = 1;			/* XYZ is to non-linear to be a benefit */
		}

		/* See discussion in imdi/imdi_gen.c for ideal numbers */
		/* Use "high quality" resolution numbers */
		switch (su.id) {
			case 0:
				error ("Illegal number of input chanels");
			case 1:
	  		  	clutres = 256;
				break;
			case 2:
	  		  	clutres = 256;
				break;
			case 3:
	  		  	clutres = 33;
				break;
			case 4:
  		  		clutres = 18;
				break;
			case 5:
  		  		clutres = 16;
				break;
			case 6:
  		  		clutres = 9;
				break;
			case 7:
  		  		clutres = 7;
				break;
			case 8:
  		  		clutres = 6;
				break;
			default: /* > 8 chan */
				clutres = 3;
				break;
		}	

	} else {
		icmLut *lut;						/* ICC LUT table */
		icmLuLut *luluo;					/* LUT lookup object */

		/* Open up the device link profile for reading */
		if ((su.dev.fp = new_icmFileStd_name(su.dev.name,"r")) == NULL)
			error ("Can't open file '%s'",su.dev.name);
	
		if ((su.dev.c = new_icc()) == NULL)
			error ("Creation of ICC object failed");
	
		if ((rv = su.dev.c->read(su.dev.c, su.dev.fp, 0)) != 0)
			error ("%d, %s",rv,su.dev.c->err);
		su.dev.h = su.dev.c->header;

		if (su.verb) {
			icmFile *op;
			if ((op = new_icmFileStd_fp(stdout)) == NULL)
				error ("Can't open stdout");
			su.dev.h->dump(su.dev.h, op, 1);
			op->del(op);
		}

		/* Check that the profile is appropriate */
		if (su.dev.h->deviceClass != icSigLinkClass)
			error("Profile isn't a device link profile");

		/* Get a conversion object */
		if ((su.dev.luo = su.dev.c->get_luobj(su.dev.c, icmFwd, icmDefaultIntent,
		                                             icmSigDefaultData, icmLuOrdNorm)) == NULL)
			error ("%d, %s",su.dev.c->errc, su.dev.c->err);
	
		/* Get details of conversion (Arguments may be NULL if info not needed) */
		su.dev.luo->spaces(su.dev.luo, &su.ins, &su.id, &su.outs, &su.od, &su.dev.alg, NULL, NULL, NULL);

		if (su.dev.alg != icmLutType)
			error ("DeviceLink profile doesn't have Lut !");

		luluo = (icmLuLut *)su.dev.luo;		/* Safe to coerce */
		luluo->get_info(luluo, &lut, NULL, NULL, NULL);	/* Get some details */
		clutres = lut->clutPoints;			/* Desired table resolution */
	}

	/* - - - - - - - - - - - - - - - */
	/* Open up input tiff file ready for reading */
	/* Got arguments, so setup to process the file */
	if ((rh = TIFFOpen(in_name, "r")) == NULL)
		error("error opening read file '%s'",in_name);

	TIFFGetField(rh, TIFFTAG_IMAGEWIDTH,  &width);
	TIFFGetField(rh, TIFFTAG_IMAGELENGTH, &height);

	TIFFGetField(rh, TIFFTAG_BITSPERSAMPLE, &bitspersample);
	if (bitspersample != 8 && bitspersample != 16) {
		error("TIFF Input file must be 8 or 16 bit/channel");
	}

	TIFFGetField(rh, TIFFTAG_PHOTOMETRIC, &photometric);
	if  ((no_pmtc = ColorSpaceSignature2TiffPhotometric(pmtc, su.ins)) == 0)
		error("ICC  input colorspace '%s' can't be handled by a TIFF file!",
		      icm2str(icmColorSpaceSignature, su.ins));
	for (i = 0; i < no_pmtc; i++) {
		if (pmtc[i] == photometric)
			break;				/* Matches */
	}
	if (i >= no_pmtc) {
		/* These error reports are a bit sloppy */
		switch (no_pmtc) {
			case 1:
				error("TIFF colorspace '%s' doesn't match ICC colorspace '%s'!",
			      Photometric2str(photometric), Photometric2str(pmtc[0]));
			case 2:
				error("TIFF colorspace '%s' doesn't match ICC colorspace '%s' or '%s'!",
			      Photometric2str(photometric), Photometric2str(pmtc[0]),
			      Photometric2str(pmtc[1]));
			default:
				error("TIFF colorspace '%s' doesn't match ICC colorspace '%s', '%s' or '%s'!",
			      Photometric2str(photometric), Photometric2str(pmtc[0]),
			      Photometric2str(pmtc[1]), Photometric2str(pmtc[2]));
		}
	}

	TIFFGetField(rh, TIFFTAG_SAMPLESPERPIXEL, &samplesperpixel);
	if (su.id != samplesperpixel)
		error ("TIFF Input file has %d input channels mismatched to colorspace '%s'",
		       samplesperpixel, icm2str(icmColorSpaceSignature, su.ins));

	TIFFGetField(rh, TIFFTAG_PLANARCONFIG, &pconfig);
	if (pconfig != PLANARCONFIG_CONTIG)
		error ("TIFF Input file must be planar");

	TIFFGetField(rh, TIFFTAG_RESOLUTIONUNIT, &resunits);
	TIFFGetField(rh, TIFFTAG_XRESOLUTION, &resx);
	TIFFGetField(rh, TIFFTAG_YRESOLUTION, &resy);

	/* - - - - - - - - - - - - - - - */
	if ((wh = TIFFOpen(out_name, "w")) == NULL)
		error("Can\'t create TIFF file '%s'!",out_name);
	
	TIFFSetField(wh, TIFFTAG_IMAGEWIDTH,  width);
	TIFFSetField(wh, TIFFTAG_IMAGELENGTH, height);
	TIFFSetField(wh, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT);
	TIFFSetField(wh, TIFFTAG_SAMPLESPERPIXEL, su.od);
	TIFFSetField(wh, TIFFTAG_BITSPERSAMPLE, bitspersample);
	TIFFSetField(wh, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
	if  ((no_pmtc = ColorSpaceSignature2TiffPhotometric(pmtc, su.outs)) == 0)
		error("TIFF file can't handle output colorspace '%s'!",
		      icm2str(icmColorSpaceSignature, su.outs));
	TIFFSetField(wh, TIFFTAG_PHOTOMETRIC, pmtc[0]);	/* Use first returned */
	if (pmtc[0] == PHOTOMETRIC_SEPARATED) {
		int iset;
		int inlen;
		char *inames;
		iset = ColorSpaceSignature2TiffInkset(su.outs, &inlen, &inames);
		if (iset != 0xffff && inlen > 0 && inames != NULL) {
			TIFFSetField(wh, TIFFTAG_INKSET, iset);
			if (inames != NULL) {
				TIFFSetField(wh, TIFFTAG_INKNAMES, inlen, inames);
			}
		}
	}
	TIFFSetField(wh, TIFFTAG_COMPRESSION, COMPRESSION_NONE);
	if (resunits) {
		TIFFSetField(wh, TIFFTAG_RESOLUTIONUNIT, resunits);
		TIFFSetField(wh, TIFFTAG_XRESOLUTION, resx);
		TIFFSetField(wh, TIFFTAG_YRESOLUTION, resy);
	}
	TIFFSetField(wh, TIFFTAG_IMAGEDESCRIPTION, "Color corrected by Argyll");

	/* - - - - - - - - - - - - - - - */
	/* Setup the imdi */

	if (!slow) {
		s = new_imdi(
			su.id,			/* Number of input dimensions */
			su.od,			/* Number of output dimensions */
							/* Input pixel representation */
			bitspersample == 8 ? pixint8 : pixint16,
							/* Output pixel representation */
			0x0,			/* Treat every channel as unsigned */
			NULL,			/* No raster to callback mapping */
			prec_min,		/* Minimum of input and output precision */
			bitspersample == 8 ? pixint8 : pixint16,
			0x0,			/* Treat every channel as unsigned */
			NULL,			/* No raster to callback mapping */
			clutres,		/* Desired table resolution */
			oopts_none,		/* Desired per channel output options */
			NULL,			/* Output channel check values */
			opts_none,		/* Desired processing direction and stride support */
			input_curves,	/* Callback functions */
			md_table,
			output_curves,
			(void *)&su		/* Context to callbacks */
		);
	
		if (s == NULL)
			error("new_imdi failed");
	}

	/* - - - - - - - - - - - - - - - */
	/* Process colors to translate */
	/* (Should fix this to process a group of lines at a time ?) */

	inbuf  = _TIFFmalloc(TIFFScanlineSize(rh));
	outbuf = _TIFFmalloc(TIFFScanlineSize(wh));
	if (check)
		checkbuf = _TIFFmalloc(TIFFScanlineSize(wh));

	inp[0] = (unsigned char *)inbuf;
	outp[0] = (unsigned char *)outbuf;

	if (!slow) {		/* Fast */
		for (y = 0; y < height; y++) {

			/* Read in the next line */
			if (TIFFReadScanline(rh, inbuf, y, 0) < 0)
				error ("Failed to read TIFF line %d",y);

			/* Do fast conversion */
			s->interp(s, (void **)outp, 0, (void **)inp, 0, width);
			
			if (check) {
				/* Do floating point conversion */
				for (x = 0; x < width; x++) {
					int i;
					double in[MAX_CHAN], out[MAX_CHAN];
					
					if (bitspersample == 8)
						for (i = 0; i < su.id; i++)
							in[i] = ((unsigned char *)inbuf)[x * su.id + i]/255.0;
					else
						for (i = 0; i < su.id; i++)
							in[i] = ((unsigned short *)inbuf)[x * su.id + i]/65535.0;
					
#ifdef NEVER
					if (su.dolink) {
						if ((rv = su.in.luo->lookup(su.in.luo, out, in)) > 1)
							error ("%d, %s",su.dev.c->errc,su.dev.c->err);
						if ((rv = su.out.luo->lookup(su.out.luo, out, out)) > 1)
							error ("%d, %s",su.dev.c->errc,su.dev.c->err);
					} else {
						if ((rv = su.dev.luo->lookup(su.dev.luo, out, in)) > 1)
							error ("%d, %s",su.dev.c->errc,su.dev.c->err);
					}
#else
					/* Apply the reference conversion */
					input_curves((void *)&su, out, in);
					md_table((void *)&su, out, out);
					output_curves((void *)&su, out, out);
#endif
					if (bitspersample == 8)
						for (i = 0; i < su.od; i++)
							((unsigned char *)checkbuf)[x * su.od + i] = (int)(out[i] * 255.0 + 0.5);
					else
						for (i = 0; i < su.od; i++)
							((unsigned short *)checkbuf)[x * su.od + i] = (int)(out[i] * 65535.0 + 0.5);
				}
				/* Compute the errors */
				for (x = 0; x < (width * su.od); x++) {
					int err;
					if (bitspersample == 8)
						err = ((unsigned char *)outbuf)[x] - ((unsigned char *)checkbuf)[x];
					else
						err = ((unsigned short *)outbuf)[x] - ((unsigned short *)checkbuf)[x];
					if (err < 0)
						err = -err;
					if (err > mxerr)
						mxerr = err;
					avgerr += (double)err;
					avgcount++;
				}
			}
				
			if (TIFFWriteScanline(wh, outbuf, y, 0) < 0)
				error ("Failed to write TIFF line %d",y);

		}

	} else {	/* Slow but precise */
		if (bitspersample == 8) {
			for (y = 0; y < height; y++) {

				/* Read in the next line */
				if (TIFFReadScanline(rh, inbuf, y, 0) < 0)
					error ("Failed to read TIFF line %d",y);

				/* Do floating point conversion */
				for (x = 0; x < width; x++) {
					int i;
					double in[MAX_CHAN], out[MAX_CHAN];
					
					for (i = 0; i < su.id; i++) {
						in[i] = ((unsigned char *)inbuf)[x * su.id + i]/255.0;
					}
					
#ifdef NEVER
					if (su.dolink) {
						if ((rv = su.in.luo->lookup(su.in.luo, out, in)) > 1)
							error ("%d, %s",su.dev.c->errc,su.dev.c->err);
						if ((rv = su.out.luo->lookup(su.out.luo, out, out)) > 1)
							error ("%d, %s",su.dev.c->errc,su.dev.c->err);
					} else {
						if ((rv = su.dev.luo->lookup(su.dev.luo, out, in)) > 1)
							error ("%d, %s",su.dev.c->errc,su.dev.c->err);
					}
#else
					/* Apply the reference conversion */
					input_curves((void *)&su, out, in);
					md_table((void *)&su, out, out);
					output_curves((void *)&su, out, out);
#endif

					for (i = 0; i < su.od; i++) {
						double outi = out[i];
						if (outi < 0.0)			/* Protect against sillies */
							outi = 0.0;
						else if (outi > 1.0)
							outi = 1.0;
						((unsigned char *)outbuf)[x * su.od + i] = (int)(outi * 255.0 + 0.5);
					}
				}
				if (TIFFWriteScanline(wh, outbuf, y, 0) < 0)
					error ("Failed to write TIFF line %d",y);
			}
		} else if (bitspersample == 16) {
			for (y = 0; y < height; y++) {

				/* Read in the next line */
				if (TIFFReadScanline(rh, inbuf, y, 0) < 0)
					error ("Failed to read TIFF line %d",y);

				/* Do floating point conversion */
				for (x = 0; x < width; x++) {
					int i;
					double in[MAX_CHAN], out[MAX_CHAN];
					
					for (i = 0; i < su.id; i++) {
						in[i] = ((unsigned short *)inbuf)[x * su.id + i]/65535.0;
					}
					
#ifdef NEVER
					if (su.dolink) {
						if ((rv = su.in.luo->lookup(su.in.luo, out, in)) > 1)
							error ("%d, %s",su.dev.c->errc,su.dev.c->err);
						if ((rv = su.out.luo->lookup(su.out.luo, out, out)) > 1)
							error ("%d, %s",su.dev.c->errc,su.dev.c->err);
					} else {
						if ((rv = su.dev.luo->lookup(su.dev.luo, out, in)) > 1)
							error ("%d, %s",su.dev.c->errc,su.dev.c->err);
					}
#else
					/* Apply the reference conversion */
					input_curves((void *)&su, out, in);
					md_table((void *)&su, out, out);
					output_curves((void *)&su, out, out);
#endif

					for (i = 0; i < su.od; i++) {
						double outi = out[i];
						if (outi < 0.0)			/* Protect against sillies */
							outi = 0.0;
						else if (outi > 1.0)
							outi = 1.0;
						((unsigned short *)outbuf)[x * su.od + i] = (int)(outi * 65535.0 + 0.5);
					}
				}
				if (TIFFWriteScanline(wh, outbuf, y, 0) < 0)
					error ("Failed to write TIFF line %d",y);
			}
		}
	}

	if (check) {
		printf("Worst error = %d bits, average error = %f bits\n", mxerr, avgerr/avgcount);
		if (bitspersample == 8)
			printf("Worst error = %f%%, average error = %f%%\n",
			       mxerr/2.55, avgerr/(2.55 * avgcount));
		else
			printf("Worst error = %f%%, average error = %f%%\n",
			       mxerr/655.35, avgerr/(655.35 * avgcount));
	}

	/* Done with lookup object */
	if (s != NULL)
		s->del(s);

	if (su.dolink) {
		su.in.luo->del(su.in.luo);
		su.in.c->del(su.in.c);
		su.in.fp->del(su.in.fp);
		su.out.luo->del(su.out.luo);
		su.out.c->del(su.out.c);
		su.out.fp->del(su.out.fp);
	} else {
		su.dev.luo->del(su.dev.luo);
		su.dev.c->del(su.dev.c);
		su.dev.fp->del(su.dev.fp);
	}

	_TIFFfree(inbuf);
	_TIFFfree(outbuf);
	if (check)
		_TIFFfree(checkbuf);

	TIFFClose(rh);		/* Close Input file */
	TIFFClose(wh);		/* Close Output file */

	return 0;
}


/* Basic printf type error() and warning() routines */

void
error(char *fmt, ...)
{
	va_list args;

	fprintf(stderr,"cctiff: Error - ");
	va_start(args, fmt);
	vfprintf(stderr, fmt, args);
	va_end(args);
	fprintf(stderr, "\n");
	exit (-1);
}

void
warning(char *fmt, ...)
{
	va_list args;

	fprintf(stderr,"cctiff: Warning - ");
	va_start(args, fmt);
	vfprintf(stderr, fmt, args);
	va_end(args);
	fprintf(stderr, "\n");
}
