
/* 
 * International Color Consortium Format Library (icclib)
 *
 * Author:  Graeme W. Gill
 * Date:    2002/04/22
 * Version: 3.0.0
 *
 * Copyright 1997 - 2022 Graeme W. Gill
 *
 * This material is licensed with an "MIT" free use license:-
 * see the License4.txt file in this directory for licensing details.
 */

/*
 * TTBD:
 *
 *      Need to fix all legacy icm_err() functions to have new error codes.
 *
 *		Should fix all write_number failure errors to indicate failed value.
 *		(Partially implemented - need to check all write_number functions)
 *
 *		Make write fail error messages be specific on which element failed.
 *
 *		Complete adding check() methods for TagTypes & improve existing ones.
 *
 *		Should add named color space lookup function support (see icmLuNamed type).
 *
 *		Should add dict color space lookup function support.
 *
 *      Would be nice to add generic ability to add new tag type handling,
 *      so that the base library doesn't need to be modified ?
 *
 */

// ~~88 Check all _FMT_ warnings and see if any more need converting to _FMTF_.
//		Make tag read code mark tag as not accessible if a _FMTF_ is returned from read.

// ~~88 Add support for ZXML type ??

#define _ICC_C_				/* Turn on implimentation code */

#undef BREAK_ON_ICMERR		/* [Und] Break on an icmErr() */
#undef DEBUG_BOUNDARY_EXCEPTION /* [Und] Show details of boundary exception & Break */
#undef DEBUG_SETLUT			/* [Und] Show each value being set in setting lut contents */
#undef DEBUG_SETLUT_CLIP	/* [Und] Show clipped values when setting LUT */
#undef DEBUG_LULUT			/* [Und] Show each value being looked up from lut contents */
#undef DEBUG_LLULUT			/* [Und] Debug individual lookup steps (not fully implemented) */
#define ENABLE_DEDUP		/* [def] Enable automatic de-duplication of tag elements */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <ctype.h>
#include <math.h>
#include <time.h>
#ifdef __sun
#include <unistd.h>
#endif
#include "icc.h"
#include "icc_util.h"

/*  Make the default grid points of the Lab clut be symetrical about */
/* a/b 0.0, and also make L = 100.0 fall on a grid point. */
#define PUT_PCS_WHITE_ON_GRIDPOINT		/* [def] In B2A creation map PCS white to cLUT grid point */


#ifdef BREAK_ON_ICMERR
# pragma message("######### BREAK_ON_ICMERR enabled ########")
#endif

#ifdef DEBUG_BOUNDARY_EXCEPTION
# pragma message("######### DEBUG_BOUNDARY_EXCEPTION enabled ########")
#endif


#ifndef ENABLE_DEDUP
# pragma message("######### ENABLE_DEDUP disabled ########")
#endif

#if defined(_MSC_VER) && !defined(vsnprintf)
#define vsnprintf _vsnprintf
#define snprintf _snprintf
#endif

/* ========================================================== */
/* Default system interface object implementations */

#ifndef SEPARATE_STD
#define COMBINED_STD

#include "iccstd.c"

#undef COMBINED_STD
#endif /* SEPARATE_STD */

/* =========================================================== */

#ifdef DEBUG_SETLUT
#undef DBGSL
#define DBGSL(xxx) printf xxx ;
#else
#undef DBGSL
#define DBGSL(xxx) 
#endif

#if defined(DEBUG_SETLUT) || defined(DEBUG_SETLUT_CLIP)
#undef DBGSLC
#define DBGSLC(xxx) printf xxx ;
#else
#undef DBGSLC
#define DBGSLC(xxx) 
#endif

#ifdef DEBUG_LULUT
#undef DBGLL
#define DBGLL(xxx) printf xxx ;
#else
#undef DBGLL
#define DBGLL(xxx) 
#endif

#ifdef DEBUG_LLULUT
#undef DBLLL
#define DBLLL(xxx) printf xxx ;
#else
#undef DBLLL
#define DBLLL(xxx) 
#endif

/* ========================================================== */
/* Set the error code and error message in an icmErr context. */
/* This makes sure that the error buffer can't overflow. */
/* Do nothing if e == NULL or there is aready an error present. */
/* Return err */
int icm_verr_e(icmErr *e, int err, const char *format, va_list vp) {
	if (e != NULL && e->c == ICM_ERR_OK) {
		int nn;
	
		e->c = err;
	
		nn = vsnprintf(e->m, ICM_ERRM_SIZE, format, vp);
	
#ifdef BREAK_ON_ICMERR
		fprintf(stderr,"Error - 0x%x, '%s'\n",e->c, e->m);
		DEBUG_BREAK;
#endif

		if (nn > -1 && nn < ICM_ERRM_SIZE)	/* Fitted in buffer */
			return err;
	
		/* We assume ICM_ERRM_SIZE isn't stupidly small.. */
		strcpy(e->m, "(Error message exceeded buffer size)");
	}
	return err;
}

/* Same as above using variable arguments */
/* Return err */
int icm_err_e(icmErr *e, int err, const char *format, ...) {
	if (e != NULL && e->c == ICM_ERR_OK) {
		va_list vp;
		va_start(vp, format);
		icm_verr_e(e, err, format, vp);
		va_end(vp);
	}
	return err;
}

/* Clear any error and message */
/* Ignore if e == NULL */
void icm_err_clear_e(icmErr *e) {
	if (e != NULL) {
		e->c = ICM_ERR_OK;
		e->m[0] = '\000';
	}
}

/* Version  of above that default to using icc icmErr */
int icm_verr(icc *icp, int err, const char *format, va_list vp) {
	icm_verr_e(&icp->e, err, format, vp);
	return err;
}

int icm_err(icc *icp, int err, const char *format, ...) {
	va_list vp;
	va_start(vp, format);
	icm_verr_e(&icp->e, err, format, vp);
	va_end(vp);
	return err;
}

/* Should use icc->clear_err() */
void icm_err_clear(icc *icp) {
	icm_err_clear_e(&icp->e);
}


/* =========================================================== */
/* Overflow protected unsigned int arithmatic functions. */
/* These functions saturate rather than wrapping around. */
/* (Divide doesn't need protection) */
/* They return UINT_MAX if there was an overflow */

#ifndef SIZE_T_MAX
# define SIZE_T_MAX ((size_t)(-1))
#endif

/* a + b unsigned int with indicator */
unsigned int sati_add(int *ind, unsigned int a, unsigned int b) {
	if (b > (UINT_MAX - a)) {
		if (ind != NULL)
			*ind = 1;
		return UINT_MAX;
	}
	return a + b;
}

/* a + b unsigned int */
unsigned int sat_add(unsigned int a, unsigned int b) {
	if (b > (UINT_MAX - a))
		return UINT_MAX;
	return a + b;
}

/* a + b size_t */
static size_t ssat_add(size_t a, size_t b) {
	if (b > (SIZE_T_MAX - a))
		return SIZE_T_MAX;
	return a + b;
}

/* a - b unsigned int with indicator */
unsigned int sati_sub(int *ind, unsigned int a, unsigned int b) {
	if (a >= b)
		return a - b;
	if (ind != NULL)
		*ind = 1;
	return 0;
}

/* a - b unsigned int */
unsigned int sat_sub(unsigned int a, unsigned int b) {
	if (a >= b)
		return a - b;
	return 0;
}

/* a - b */
static size_t ssat_sub(size_t a, size_t b) {
	if (a >= b)
		return a - b;
	return 0;
}

/* a * b unsigned int with indicator */
unsigned int sati_mul(int *ind, unsigned int a, unsigned int b) {
	unsigned int c;

	if (a == 0 || b == 0) {
		return 0;
	}

	if (a < (UINT_MAX/b)) {
		c = a * b;
	} else {
		c = UINT_MAX;
		if (ind != NULL)
			*ind = 1;
	}
	return c;
}

/* a * b unsigned int */
unsigned int sat_mul(unsigned int a, unsigned int b) {
	unsigned int c;

	if (a == 0 || b == 0)
		return 0;

	if (a < (UINT_MAX/b))
		c = a * b;
	else
		c = UINT_MAX;
	return c;
}

/* a * b size_t */
static size_t ssat_mul(size_t a, size_t b) {
	size_t c;

	if (a == 0 || b == 0)
		return 0;

	if (a > (SIZE_T_MAX/b))
		return SIZE_T_MAX;
	else
		return a * b;
}

/* a ^ b unsigned int with indicator */
unsigned int sati_pow(int *ind, unsigned int a, unsigned int b) {
	unsigned int c = 1;
	for (; b > 0; b--) {
		c = sati_mul(ind, c, a);
		if (c == UINT_MAX)
			break;
	}
	return c;
}

/* a ^ b unsigned int */
unsigned int sat_pow(unsigned int a, unsigned int b) {
	unsigned int c = 1;
	for (; b > 0; b--) {
		c = sat_mul(c, a);
		if (c == UINT_MAX)
			break;
	}
	return c;
}

/* unsigned + signed */
unsigned int sat_add_us(unsigned int a, int b) {
	if (b > 0)
		return sat_add(a, (unsigned int)b);
	else
		return sat_sub(a, (unsigned int)-b);
}


/* A + B + C */
#define sat_addadd(A, B, C) sat_add(A, sat_add(B, C))

/* A + B * C */
#define sat_addmul(A, B, C) sat_add(A, sat_mul(B, C))

/* A + B + C * D */
#define sat_addaddmul(A, B, C, D) sat_add(A, sat_add(B, sat_mul(C, D)))

/* A * B * C */
#define sat_mul3(A, B, C) sat_mul(A, sat_mul(B, C))


/* Alignment */
static unsigned int sat_align(unsigned int align_size, unsigned int a) {
	if (align_size == 0)
		return a;

	align_size--;
	
	if (align_size > (UINT_MAX - a))
		return UINT_MAX;
	
	return (a + align_size) & ~align_size;
}

/* These test functions detect whether an overflow would occur */

/* Return nz if add would overflow */
static int ovr_add(unsigned int a, unsigned int b) {

	if (b > (UINT_MAX - a))
		return 1;
	return 0;
}

/* Return nz if sub would overflow */
static int ovr_sub(unsigned int a, unsigned int b) {
	if (a < b)
		return 1;
	return 0;
}

/* Return nz if mult would overflow */
static int ovr_mul(unsigned int a, unsigned int b) {
	if (a > (UINT_MAX/b))
		return 1;
	return 0;
}

/* Float comparisons to certain precision */

/* u16f16 precision */
int diff_u16f16(double a, double b) {
	if (fabs(a - b) > 0.5/65536.0)
		return 1;
	return 0;
}

/* ------------------------------------------------- */
/* Memory image icmFile compatible class */
/* Buffer is assumed to have been allocated by the given allocator, */
/* and will be expanded on write. */

/* Get the size of the file */
static size_t icmFileMem_get_size(icmFile *pp) {
	icmFileMem *p = (icmFileMem *)pp;

	return p->end - p->start;
}

/* Set current position to offset. Return 0 on success, nz on failure. */
static int icmFileMem_seek(
icmFile *pp,
unsigned int offset
) {
	icmFileMem *p = (icmFileMem *)pp;
	unsigned char *np;

	np = p->start + offset;
	if (np < p->start || np > p->end)
		return 1;
	p->cur = np;
	return 0;
}

/* Read count items of size length. Return number of items successfully read. */
static size_t icmFileMem_read(
icmFile *pp,
void *buffer,
size_t size,
size_t count
) {
	icmFileMem *p = (icmFileMem *)pp;
	size_t len;

	len = ssat_mul(size, count);
	if (len > (p->end - p->cur)) {		/* Too much */
		if (size > 0)
			count = (p->end - p->cur)/size;
		else
			count = 0;
	}
	len = size * count;
	if (len > 0)
		memmove(buffer, p->cur, len);
	p->cur += len;
	return count;
}

/* Expand the memory buffer file to hold up to pointer ep */
/* Don't expand if realloc fails */
static void icmFileMem_filemem_resize(icmFileMem *p, unsigned char *ep) {
	size_t na, co, ce;
	unsigned char *nstart;
	
	/* No need to realloc */
	if (ep <= p->aend) {
		return;
	}

	co = p->cur - p->start;		/* Current offset */
	ce = p->end - p->start;     /* Current end */
	na = ep - p->start;			/* new allocated size */

	/* Round new allocation up */
	if (na <= 1024)
		na += 1024;
	else
		na += 4096;
	if ((nstart = p->al->realloc(p->al, p->start, na)) != NULL) {
		p->start = nstart;
		p->cur = nstart + co;
		p->end = nstart + ce;
		p->aend = nstart + na;
	}
}

/* write count items of size length. Return number of items successfully written. */
static size_t icmFileMem_write(
icmFile *pp,
void *buffer,
size_t size,
size_t count
) {
	icmFileMem *p = (icmFileMem *)pp;
	size_t len;

	len = ssat_mul(size, count);
	if (len > (size_t)(p->aend - p->cur))	/* Try and expand buffer */
		icmFileMem_filemem_resize(p, p->cur + len);

	if (len > (size_t)(p->aend - p->cur)) {
		if (size > 0)
			count = (p->aend - p->cur)/size;
		else
			count = 0;
	}
	len = size * count;
	if (len > 0)
		memmove(p->cur, buffer, len);
	p->cur += len;
	if (p->end < p->cur)
		p->end = p->cur;
	return count;
}

/* do a printf */
static int icmFileMem_printf(
icmFile *pp,
const char *format,
...
) {
	int rv;
	va_list args;
	icmFileMem *p = (icmFileMem *)pp;
	int alen, len;

	va_start(args, format);

	rv = 1;
	alen = 100;					/* Initial allocation for printf */
	icmFileMem_filemem_resize(p, p->cur + alen);

	/* We have to use the available printf functions to resize the buffer if needed. */
	for (;rv != 0;) {
		/* vsnprintf() either returns -1 if it doesn't fit, or */
		/* returns the size-1 needed in order to fit. */
		len = vsnprintf((char *)p->cur, (p->aend - p->cur), format, args);

		if (len > -1 && ((p->cur + len +1) <= p->aend))	/* Fitted in current allocation */
			break;

		if (len > -1)				/* vsnprintf returned needed size-1 */
			alen = len+2;			/* (In case vsnprintf returned 1 less than it needs) */
		else
			alen *= 2;				/* We just have to guess */

		/* Attempt to resize */
		icmFileMem_filemem_resize(p, p->cur + alen);

		/* If resize failed */
		if ((p->aend - p->cur) < alen) {
			rv = 0;
			break;			
		}
	}
	if (rv != 0) {
		/* Figure out where end of printf is */
		len = strlen((char *)p->cur);	/* Length excluding nul */
		p->cur += len;
		if (p->cur > p->end)
			p->end = p->cur;
		rv = len;
	}
	va_end(args);
	return rv;
}

/* flush all write data out to secondary storage. Return nz on failure. */
static int icmFileMem_flush(
icmFile *pp
) {
	return 0;
}

/* Return the memory buffer. Error if not icmFileMem */
static int icmFileMem_get_buf(
icmFile *pp,
unsigned char **buf,
size_t *len
) {
	icmFileMem *p = (icmFileMem *)pp;
	if (buf != NULL)
		*buf = p->start;
	if (len != NULL)
		*len = p->end - p->start;
	return 0;
}

/* Take a reference to the icmFile */
static icmFile *icmFileMem_reference(
icmFile *pp
) {
	pp->refcount++;
	return pp;
}

/* we're done with the file object, return nz on failure */
static int icmFileMem_delete(
icmFile *pp
) {
	if (pp == NULL || --pp->refcount > 0)
		return 0;
	{
		icmFileMem *p = (icmFileMem *)pp;
		icmAlloc *al = p->al;
	
		if (p->del_buf)     /* Free the memory buffer */
		    al->free(al, p->start);
		al->free(al, p);	/* Free object */
		al->del(al);		/* Delete allocator if this is the last reference */
		return 0;
	}
}

/* Create a memory image file access class with allocator */
/* Note that this will fail if e has an error already set */
icmFile *new_icmFileMem_a(
icmErr *e,			/* Error return - may be NULL */
void *base,			/* Pointer to base of memory buffer */
size_t length,		/* Number of bytes in buffer */
icmAlloc *al		/* heap allocator to reference */
) {
	icmFileMem *p;

	if (e != NULL && e->c != ICM_ERR_OK)
		return NULL;		/* Pre-existing error */

	if ((p = (icmFileMem *) al->calloc(al, 1, sizeof(icmFileMem))) == NULL) {
		icm_err_e(e, ICM_ERR_MALLOC, "Allocating a memory image file object failed");
		return NULL;
	}
	p->refcount  = 1;
	p->al        = al->reference(al);
	p->get_size  = icmFileMem_get_size;
	p->seek      = icmFileMem_seek;
	p->read      = icmFileMem_read;
	p->write     = icmFileMem_write;
	p->printf    = icmFileMem_printf;
	p->flush     = icmFileMem_flush;
	p->get_buf   = icmFileMem_get_buf;
	p->reference = icmFileMem_reference;
	p->del       = icmFileMem_delete;

	p->start = (unsigned char *)base;
	p->cur = p->start;
	p->aend = p->end = p->start + length;

	return (icmFile *)p;
}

/* Create a memory image file access class and take allocator reference */
/* and delete buffer when icmFile is deleted. */
/* Note that this will fail if e has an error already set */
icmFile *new_icmFileMem_ad(icmErr *e, void *base, size_t length, icmAlloc *al) {
	icmFile *fp;

	if ((fp = new_icmFileMem_a(e, base, length, al)) != NULL) {	
		((icmFileMem *)fp)->del_buf = 1;
	}

	return fp;
}

/* ======================================================================== */

/* Return the number of channels for the given color space. Return 0 if unknown. */
unsigned int icmCSSig2nchan(icColorSpaceSignature sig) {
	switch ((int)sig) {
		case icSigXYZData:
			return 3;
		case icSigLabData:
			return 3;
		case icSigLuvData:
			return 3;
		case icSigYCbCrData:
			return 3;
		case icSigYxyData:
			return 3;
		case icSigRgbData:
			return 3;
		case icSigGrayData:
			return 1;
		case icSigHsvData:
			return 3;
		case icSigHlsData:
			return 3;
		case icSigCmykData:
			return 4;
		case icSigCmyData:
			return 3;
		case icSig2colorData:
			return 2;
		case icSig3colorData:
			return 3;
		case icSig4colorData:
			return 4;
		case icSig5colorData:
		case icSigMch5Data:
			return 5;
		case icSig6colorData:
		case icSigMch6Data:
			return 6;
		case icSig7colorData:
		case icSigMch7Data:
			return 7;
		case icSig8colorData:
		case icSigMch8Data:
			return 8;
		case icSig9colorData:
		case icSigMch9Data:
			return 9;
		case icSig10colorData:
		case icSigMchAData:
			return 10;
		case icSig11colorData:
		case icSigMchBData:
			return 11;
		case icSig12colorData:
		case icSigMchCData:
			return 12;
		case icSig13colorData:
		case icSigMchDData:
			return 13;
		case icSig14colorData:
		case icSigMchEData:
			return 14;
		case icSig15colorData:
		case icSigMchFData:
			return 15;

		/* Non-standard and Pseudo spaces */
		case icSig1colorData:
		case icSigMch1Data:
			return 1;

		case icmSigYuvData:
		case icmSigLptData:
			return 3;

#ifdef NEVER
		case icmSigYData:
		case icmSigY8Data:
		case icmSigY16Data:
		case icmSigLData:
		case icmSigL8Data:
		case icmSigLV2Data:
#endif
			return 1;

		case icmSigXYZ8Data:
		case icmSigXYZ16Data:
		case icmSigLab8Data:
		case icmSigLabV2Data:
		case icmSigLuv16Data:
		case icmSigYxy16Data:
		case icmSigYCbCr16Data:
			return 3;

		default:
			break;
	}
	return 0;
}

/* Given a number of channels, return a matching device color space. */
/* Return 0 if unknown. */
static icColorSpaceSignature icmNchan2CSSig(unsigned int nchan) {
	switch (nchan) {
		case 1:
			return icSigGrayData;
		case 2:
			return icSig2colorData;
		case 3:
			return icSig3colorData;
		case 4:
			return icSig4colorData;
		case 5:
			return icSig5colorData;
		case 6:
			return icSig6colorData;
		case 7:
			return icSig7colorData;
		case 8:
			return icSig8colorData;
		case 9:
			return icSig9colorData;
		case 10:
			return icSig10colorData;
		case 11:
			return icSig11colorData;
		case 12:
			return icSig12colorData;
		case 13:
			return icSig13colorData;
		case 14:
			return icSig14colorData;
		case 15:
			return icSig15colorData;

		default:
			break;
	}
	return 0;
}

/* Return a mask classifying the color space */
unsigned int icmCSSig2type(icColorSpaceSignature sig) {
	switch ((int)sig) {
		case icSigXYZData:
			return CSSigType_PCS | CSSigType_NDEV | CSSigType_gPCS | CSSigType_gXYZ;
		case icSigLabData:
			return CSSigType_PCS | CSSigType_NDEV | CSSigType_gPCS | CSSigType_gLab;

		case icSigLuvData:
		case icSigYxyData:
			return CSSigType_NDEV;

		case icSigYCbCrData:
		case icSigRgbData:
		case icSigGrayData:
		case icSigHsvData:
		case icSigHlsData:
		case icSigCmykData:
		case icSigCmyData:
			return CSSigType_DEV;

		case icmSigYCbCr16Data:
			return CSSigType_DEV | CSSigType_EXT | CSSigType_NORM;

		case icSig2colorData:
		case icSig3colorData:
		case icSig4colorData:
		case icSig5colorData:
		case icSig6colorData:
		case icSig7colorData:
		case icSig8colorData:
		case icSig9colorData:
		case icSig10colorData:
		case icSig11colorData:
		case icSig12colorData:
		case icSig13colorData:
		case icSig14colorData:
		case icSig15colorData:
			return CSSigType_DEV | CSSigType_NCOL;

		case icSig1colorData:
		case icSigMch1Data:
    	case icSigMch2Data:
    	case icSigMch3Data:
    	case icSigMch4Data:
    	case icSigMch5Data:
    	case icSigMch6Data:
    	case icSigMch7Data:
    	case icSigMch8Data:
    	case icSigMch9Data:
    	case icSigMchAData:
    	case icSigMchBData:
    	case icSigMchCData:
    	case icSigMchDData:
    	case icSigMchEData:
    	case icSigMchFData:
			return CSSigType_DEV | CSSigType_NCOL | CSSigType_EXT;

//		case icmSigLData:
//		case icmSigYData:
		case icmSigYuvData:
		case icmSigLptData:
			return CSSigType_NDEV | CSSigType_EXT;

		case icmSigLuv16Data:
		case icmSigYxy16Data:
			return CSSigType_NDEV | CSSigType_EXT | CSSigType_NORM;

		case icmSigXYZ8Data:
		case icmSigXYZ16Data:
			return CSSigType_NDEV | CSSigType_EXT | CSSigType_gPCS | CSSigType_nPCS
			     | CSSigType_gXYZ | CSSigType_NORM;

		case icmSigLab8Data:
		case icmSigLabV2Data:
			return CSSigType_NDEV | CSSigType_EXT | CSSigType_gPCS | CSSigType_nPCS
			     | CSSigType_gLab | CSSigType_NORM;

		default:
			break;
	}
	return 0;
}

/* ------------------------------------------------------- */
/* Flag dump functions */
/* Note - returned buffers are static, can only be used 5 */
/* times before buffers get reused. */

/* Screening Encodings */
static char *icmScreenEncodings2str(unsigned int flags) {
	static int si = 0;			/* String buffer index */
	static char buf[5][80];		/* String buffers */
	char *bp, *cp;

	cp = bp = buf[si++];
	si %= 5;				/* Rotate through buffers */

	if (flags & icPrtrDefaultScreensTrue) {
		sprintf(cp,"Default Screen");
	} else {
		sprintf(cp,"No Default Screen");
	}
	cp = cp + strlen(cp);
	if (flags & icLinesPerInch) {
		sprintf(cp,", Lines Per Inch");
	} else {
		sprintf(cp,", Lines Per cm");
	}
	cp = cp + strlen(cp);

	return bp;
}

/* Device attributes */
static char *icmDeviceAttributes2str(unsigned int flags) {
	static int si = 0;			/* String buffer index */
	static char buf[5][80];		/* String buffers */
	char *bp, *cp;

	cp = bp = buf[si++];
	si %= 5;				/* Rotate through buffers */

	if (flags & icTransparency) {
		sprintf(cp,"Transparency");
	} else {
		sprintf(cp,"Reflective");
	}
	cp = cp + strlen(cp);
	if (flags & icMatte) {
		sprintf(cp,", Matte");
	} else {
		sprintf(cp,", Glossy");
	}
	cp = cp + strlen(cp);
	if (flags & icNegative) {
		sprintf(cp,", Negative");
	} else {
		sprintf(cp,", Positive");
	}
	cp = cp + strlen(cp);
	if (flags & icBlackAndWhite) {
		sprintf(cp,", BlackAndWhite");
	} else {
		sprintf(cp,", Color");
	}
	cp = cp + strlen(cp);

	return bp;
}

/* Profile header flags */
static char *icmProfileHeaderFlags2str(unsigned int flags) {
	static int si = 0;			/* String buffer index */
	static char buf[5][80];		/* String buffers */
	char *bp, *cp;

	cp = bp = buf[si++];
	si %= 5;				/* Rotate through buffers */

	if (flags & icEmbeddedProfileTrue) {
		sprintf(cp,"Embedded Profile");
	} else {
		sprintf(cp,"Not Embedded Profile");
	}
	cp = cp + strlen(cp);
	if (flags & icUseWithEmbeddedDataOnly) {
		sprintf(cp,", Use with embedded data only");
	} else {
		sprintf(cp,", Use anywhere");
	}
	cp = cp + strlen(cp);

	return bp;
}


static char *icmAsciiOrBinaryData2str(unsigned int flags) {
	static int si = 0;			/* String buffer index */
	static char buf[5][80];		/* String buffers */
	char *bp, *cp;

	cp = bp = buf[si++];
	si %= 5;				/* Rotate through buffers */

	if (flags & icBinaryData) {
		sprintf(cp,"Binary");
	} else {
		sprintf(cp,"Ascii");
	}
	cp = cp + strlen(cp);

	return bp;
}

static char *icmVideoCardGammaFormatData2str(unsigned int flags) {
	static int si = 0;			/* String buffer index */
	static char buf[5][80];		/* String buffers */
	char *bp, *cp;

	cp = bp = buf[si++];
	si %= 5;				/* Rotate through buffers */

	if (flags & icVideoCardGammaFormula) {
		sprintf(cp,"Formula");
	} else {
		sprintf(cp,"Table");
	}
	cp = cp + strlen(cp);

	return bp;
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* Enumeration dump functions */
/* Note - returned buffers are static, can only be used once */
/* before buffers get reused if type is unknown. */

/* tags */
static const char *icmTagSig2str_imp(icTagSignature sig, int alt) {
	switch ((icmSig) sig) {
		case icSigAToB0Tag:
			return "AToB0 (Perceptual) Multidimensional Transform";
		case icSigAToB1Tag:
			return "AToB1 (Colorimetric) Multidimensional Transform";
		case icSigAToB2Tag:
			return "AToB2 (Saturation) Multidimensional Transform";
		case icSigBlueMatrixColumnTag:		/* AKA icSigBlueColorantTag */
			return "Blue Matrix Column";	/* AKA "Blue Colorant" */
		case icSigBlueTRCTag:
			return "Blue Tone Reproduction Curve";
		case icSigBToA0Tag:
			return "BToA0 (Perceptual) Multidimensional Transform";
		case icSigBToA1Tag:
			return "BToA1 (Colorimetric) Multidimensional Transform";
		case icSigBToA2Tag:
			return "BToA2 (Saturation) Multidimensional Transform";
		case icSigBToD0Tag:
			return "BToD0 (Perceptual) Multidimensional Transform";
		case icSigBToD1Tag:
			return "BToD1 (Colorimetric) Multidimensional Transform";
		case icSigBToD2Tag:
			return "BToD2 (Saturation) Multidimensional Transform";
		case icSigBToD3Tag:
			return "BToD3 (Absolute Colorimetric) Multidimensional Transform";
		case icSigCalibrationDateTimeTag:
			return "Calibration Date & Time";
		case icSigCharTargetTag:
			return "Characterization Target";
		case icSigChromaticAdaptationTag:
			return "Chromatic Adaptation";
		case icSigChromaticityTag:
			return "Phosphor/Colorant Chromaticity";
		case icSigCicpTag:
			return "CICP's for Video Signal Type ID";
		case icSigColorantOrderTag:
			return "Laydown Order of Colorants";
		case icSigColorantTableTag:
			return "N-component Input Colorant Identification";
		case icSigColorantTableOutTag:
			return "N-component Output Colorant Identification";
		case icSigColorimetricIntentImageStateTag:
			return "Colorimetric Image State";
		case icSigCopyrightTag:
			return "Copyright";
		case icSigCrdInfoTag:
			return "CRD Info";
		case icSigDataTag:
			return "Data";
		case icSigDateTimeTag:
			return "Date & Time";
		case icSigDeviceMfgDescTag:
			return "Device Manufacturer Description";
		case icSigDeviceModelDescTag:
			return "Device Model Description";
		case icSigDeviceSettingsTag:
			return "Device Settings";
		case icSigDToB0Tag:
			return "DToB0 (Perceptual) Multidimensional Transform";
		case icSigDToB1Tag:
			return "DToB1 (Colorimetric) Multidimensional Transform";
		case icSigDToB2Tag:
			return "DToB2 (Saturation) Multidimensional Transform";
		case icSigDToB3Tag:
			return "DToB3 (Absolute Colorimetric) Multidimensional Transform";
		case icSigGamutTag:
			return "Gamut";
		case icSigGrayTRCTag:
			if (alt)
				return "Shaper Mono";
			return "Gray Tone Reproduction Curve";
		case icSigGreenMatrixColumnTag:		/* AKA icSigGreenColorantTag */
			return "Green Matrix Column";	/* AKA "Green Colorant" */
		case icSigGreenTRCTag:
			return "Green Tone Reproduction Curve";
		case icSigLuminanceTag:
			return "Luminance";
		case icSigMeasurementTag:
			return "Measurement";
		case icSigMetadataTag:
			return "Metadata";
		case icSigMediaBlackPointTag:
			return "Media Black Point";
		case icSigMediaWhitePointTag:
			return "Media White Point";
		case icSigNamedColorTag:
			return "Named Color";
		case icSigNamedColor2Tag:
			return "Named Color 2";
		case icSigOutputResponseTag:
			return "Output Device Response";
		case icSigPerceptualRenderingIntentGamutTag:
			return "Perceptual Rendering Intent Gamut";
		case icSigPreview0Tag:
			return "Preview0";
		case icSigPreview1Tag:
			return "Preview1";
		case icSigPreview2Tag:
			return "Preview2";
		case icSigProfileDescriptionTag:
			return "Profile Description";
		case icSigProfileSequenceDescTag:
			return "Profile Sequence Description";
		case icSigProfileSequenceIdentifierTag:
			return "Profile Sequence Identifier";
		case icSigPs2CRD0Tag:
			return "PS Level 2 CRD Perceptual";
		case icSigPs2CRD1Tag:
			return "PS Level 2 CRD Colorimetric";
		case icSigPs2CRD2Tag:
			return "PS Level 2 CRD Saturation";
		case icSigPs2CRD3Tag:
			return "PS Level 2 CRD Absolute";
		case icSigPs2CSATag:
			return "PS Level 2 color space array";
		case icSigPs2RenderingIntentTag:
			return "PS Level 2 Rendering Intent";
		case icSigRedMatrixColumnTag:		/* AKA icSigRedColorantTag */
			return "Red Matrix Column";		/* AKA "Red Colorant" */
		case icSigRedTRCTag:
			if (alt)
				return "Shaper Matrix";
			return "Red Tone Reproduction Curve";
		case icSigSaturationRenderingIntentGamutTag:
			return "Saturation Rendering Intent Gamut";
		case icSigScreeningDescTag:
			return "Screening Description";
		case icSigScreeningTag:
			return "Screening Attributes";
		case icSigTechnologyTag:
			return "Device Technology";
		case icSigUcrBgTag:
			return "Under Color Removal & Black Generation";
		case icSigVideoCardGammaTag:
			return "Video Card Gamma Curve";
		case icSigViewingCondDescTag:
			return "Viewing Condition Description";
		case icSigViewingConditionsTag:
			return "Viewing Condition Parameters";

		/* ArgyllCMS private tag: */
		case icmSigAbsToRelTransSpace:
			return "Absolute to Media Relative Transformation Space Matrix";

		default: {
			static char bufs[5][50];	/* String buffers */
			static int si = 0;			/* String buffer index */
			char *buf = bufs[si++];
			si %= 5;					/* Rotate through buffers */

			sprintf(buf,"Unrecognized - %s",icmtag2str(sig));
			return buf;
		}
	}
}

static const char *icmTagSig2str(icTagSignature sig) {
	return icmTagSig2str_imp(sig, 0);
}

/* Interpret alternate aliases for */
/* icmSigShaperMatrix and icmSigShaperMatrixType */
static const char *icmTagSig2str_alt(icTagSignature sig) {
	return icmTagSig2str_imp(sig, 1);
}

/* tag type signatures */
static const char *icmTypeSig2str(icTagTypeSignature sig) {
	switch ((icmSig)sig) {
		case icSigChromaticityType:
			return "Phosphor/Colorant Chromaticity";
		case icSigCrdInfoType:
			return "CRD Info";
		case icSigCurveType:
			return "Curve";
		case icSigDataType:
			return "Data";
		case icSigDateTimeType:
			return "DateTime";
		case icSigDeviceSettingsType:
			return "Device Settings";
		case icSigLut16Type:
			return "Lut16";
		case icSigLut8Type:
			return "Lut8";
		case icSigMeasurementType:
			return "Measurement";
		case icSigNamedColorType:
			return "Named Color 1";
		case icSigNamedColor2Type:
			return "Named Color 2";
		case icSigProfileSequenceDescType:
			return "Profile Sequence Description";
		case icSigResponseCurveSet16Type:
			return "Device Response Curve";
		case icSigS15Fixed16ArrayType:
			return "S15Fixed16 Array";
		case icSigScreeningType:
			return "Screening";
		case icSigSignatureType:
			return "Signature";
		case icSigTextType:
			return "Text";
		case icSigTextDescriptionType:
			return "Text Description";
		case icSigU16Fixed16ArrayType:
			return "U16Fixed16 Array";
		case icSigUcrBgType:
			return "Under Color Removal & Black Generation";
		case icSigUInt16ArrayType:
			return "UInt16 Array";
		case icSigUInt32ArrayType:
			return "UInt32 Array";
		case icSigUInt64ArrayType:
			return "UInt64 Array";
		case icSigUInt8ArrayType:
			return "UInt8 Array";
		case icSigVideoCardGammaType:
			return "Video Card Gamma";
		case icSigViewingConditionsType:
			return "Viewing Conditions";
		case icSigXYZType:
			return "XYZ";

		/* Actually V4... */
		case icSigColorantTableType:
		case icmSigAltColorantTableType:
			return "N-component Input Colorant Identification";


		/* Fake sub-tagtypes that implement Lut8Type and Lut16type */ 
		case icmSig816Curves:
			return "Lut8, Lut16 Curves";
		case icmSig816Matrix:
			return "Lut8, Lut16 Matrix";
		case icmSig816CLUT:
			return "Lut8, Lut16 cLUT";

		default: {
			static char bufs[5][50];	/* String buffers */
			static int si = 0;			/* String buffer index */
			char *buf = bufs[si++];
			si %= 5;					/* Rotate through buffers */

			sprintf(buf,"Unrecognized - %s",icmtag2str(sig));
			return buf;
		}
	}
}

/* Color Space Signatures */
static const char *icmColorSpaceSig2str(icColorSpaceSignature sig) {
	switch ((icmSig)sig) {
		case icSigXYZData:
			return "XYZ";
		case icSigLabData:
			return "Lab";
		case icSigLuvData:
			return "Luv";
		case icSigYCbCrData:
			return "YCbCr";
		case icSigYxyData:
			return "Yxy";
		case icSigRgbData:
			return "RGB";
		case icSigGrayData:
			return "Gray";
		case icSigHsvData:
			return "HSV";
		case icSigHlsData:
			return "HLS";
		case icSigCmykData:
			return "CMYK";
		case icSigCmyData:
			return "CMY";
		case icSig2colorData:
			return "2 Color";
		case icSig3colorData:
			return "3 Color";
		case icSig4colorData:
			return "4 Color";
		case icSig5colorData:
		case icSigMch5Data:
			return "5 Color";
		case icSig6colorData:
		case icSigMch6Data:
			return "6 Color";
		case icSig7colorData:
		case icSigMch7Data:
			return "7 Color";
		case icSig8colorData:
		case icSigMch8Data:
			return "8 Color";
		case icSig9colorData:
			return "9 Color";
		case icSig10colorData:
			return "10 Color";
		case icSig11colorData:
			return "11 Color";
		case icSig12colorData:
			return "12 Color";
		case icSig13colorData:
			return "13 Color";
		case icSig14colorData:
			return "14 Color";
		case icSig15colorData:
			return "15 Color";

		/* Non-standard and Pseudo spaces */
		case icSig1colorData:
		case icSigMch1Data:
			return "1 Color";

		case icmSigYuvData:
			return "Yu'v'";
		case icmSigLptData:
			return "Lpt";

		case icmSigXYZ8Data:
			return "8b Norm XYZ";
		case icmSigXYZ16Data:
			return "16b Norm XYZ";

		case icmSigLab8Data:
			return "8 bit Norm Lab";
		case icmSigLabV2Data:
			return "V2 Norm Lab";

		case icmSigLuv16Data:
			return "16b Norm Luv";
		case icmSigYxy16Data:
			return "16b Norm Yxy";
		case icmSigYCbCr16Data:
			return "16b Norm YCbCr";

#ifdef NEVER	/* Not fully implemented */
		case icmSigYData:
			return "Y";
		case icmSigY8Data:
			return "8b Norm Y";
		case icmSigY16Data:
			return "16b Norm Y";

		case icmSigLData:
			return "L";
		case icmSigL8Data:
			return "8b Norm L";
		case icmSigLV2Data:
			return "V2 Norm L";
#endif /* NEVER */

		default: {
			static char bufs[5][50];	/* String buffers */
			static int si = 0;			/* String buffer index */
			char *buf = bufs[si++];
			si %= 5;					/* Rotate through buffers */

			sprintf(buf,"Unrecognized - %s",icmtag2str(sig));
			return buf;
		}
	}
}

/* profileClass enumerations */
static const char *icmProfileClassSig2str(icProfileClassSignature sig) {
	switch (sig) {
		case icSigInputClass:
			return "Input";
		case icSigDisplayClass:
			return "Display";
		case icSigOutputClass:
			return "Output";
		case icSigLinkClass:
			return "Link";
		case icSigAbstractClass:
			return "Abstract";
		case icSigColorSpaceClass:
			return "Color Space";
		case icSigNamedColorClass:
			return "Named Color";
		default: {
			static char bufs[5][50];	/* String buffers */
			static int si = 0;			/* String buffer index */
			char *buf = bufs[si++];
			si %= 5;					/* Rotate through buffers */

			sprintf(buf,"Unrecognized - %s",icmtag2str(sig));
			return buf;
		}
	}
}

/* Platform Signatures */
static const char *icmPlatformSig2str(icPlatformSignature sig) {
	static char buf[50];
	switch ((int)sig) {
		case icSigNoPlatform:
			return "Not Specified";
		case icSigMacintosh:
			return "Macintosh";
		case icSigMicrosoft:
			return "Microsoft";
		case icSigSolaris:
			return "Solaris";
		case icSigSGI:
			return "SGI";
		case icSigTaligent:
			return "Taligent";
		case icmSig_nix:
			return "*nix";
		default:
			sprintf(buf,"Unrecognized - %s",icmtag2str(sig));
			return buf;
	}
}

/* Device Manufacturer Signatures */
static const char *icmDeviceManufacturerSig2str(icDeviceManufacturerSignature sig) {
	return icmtag2str(sig);
}

/* Device Model Signatures */
static const char *icmDeviceModelSig2str(icDeviceModelSignature sig) {
	return icmtag2str(sig);
}

/* CMM Signatures */
static const char *icmCMMSig2str(icCMMSignature sig) {
	switch ((int)sig) {
		case icSigAdobeCMM:
			return "Adobe CMM";
		case icSigAgfaCMM:
			return "Agfa CMM";
		case icSigAppleCMM:
			return "Apple CMM";
		case icSigColorGearCMM:
			return "ColorGear CMM";
		case icSigColorGearLiteCMM:
			return "ColorGear CMM Lite";
		case icSigColorGearCCMM:
			return "ColorGear CMM C";
		case icSigEFICMM:
			return "EFI CMM";
		case icSigFujiFilmCMM:
			return "Fujifilm CMM";
		case icSigExactScanCMM:
			return "ExactScan CMM";
		case icSigHarlequinCMM:
			return "Harlequin RIP CMM";
		case icSigArgyllCMM:
			return "ArgyllCMS CMM";
		case icSigLogoSyncCMM:
			return "LogoSync CMM";
		case icSigHeidelbergCMM:
			return "Heidelberg CMM";
		case icSigLittleCMSCMM:
			return "Little CMS CMM";
		case icSigReflccMAXCMM:
			return "RefIccMAX CMM";
		case icSigDemoIccMAXCMM:
			return "DemoIccMAX CMM";
		case icSigKodakCMM:
			return "Kodak CMM";
		case icSigKonicaMinoltaCMM:
			return "Konica Minolta CMM";
		case icSigMicrosoftCMM:
			return "Windows Color System CMM";
		case icSigMutohCMM:
			return "Mutoh CMM";
		case icSigOnyxGraphicsCMM:
			return "Onyx Graphics CMM";
		case icSigDeviceLinkCMM:
			return "DeviceLink CMM";
		case icSigSampleICCCMM:
			return "SampleICC CMM";
		case icSigToshibaCMM:
			return "Toshiba CMM";
		case icSigImagingFactoryCMM:
			return "the imaging factory CMM";
		case icSigVivoCMM:
			return "Vivo CMM";
		case icSigWareToGoCMM:
			return "Ware to Go CMM";
		case icSigZoranCMM:
			return "Zoran CMM";
		default: {
			static char buf[50];
			sprintf(buf,"Unrecognized - %s",icmtag2str(sig));
			return buf;
		}
	}
}

/* technology signature descriptions */
static const char *icmTechnologySig2str(icTechnologySignature sig) {
	switch (sig) {
		case icSigTechnologyUnknown:
			return "Unknown Technology";
		case icSigDigitalCamera:
			return "Digital Camera";
		case icSigFilmScanner:
			return "Film Scanner";
		case icSigReflectiveScanner:
			return "Reflective Scanner";
		case icSigInkJetPrinter:
			return "InkJet Printer";
		case icSigThermalWaxPrinter:
			return "Thermal WaxPrinter";
		case icSigElectrophotographicPrinter:
			return "Electrophotographic Printer";
		case icSigElectrostaticPrinter:
			return "Electrostatic Printer";
		case icSigDyeSublimationPrinter:
			return "DyeSublimation Printer";
		case icSigPhotographicPaperPrinter:
			return "Photographic Paper Printer";
		case icSigFilmWriter:
			return "Film Writer";
		case icSigVideoMonitor:
			return "Video Monitor";
		case icSigVideoCamera:
			return "Video Camera";
		case icSigProjectionTelevision:
			return "Projection Television";
		case icSigCRTDisplay:
			return "Cathode Ray Tube Display";
		case icSigPMDisplay:
			return "Passive Matrix Display";
		case icSigAMDisplay:
			return "Active Matrix Display";
		case icSigPhotoCD:
			return "Photo CD";
		case icSigPhotoImageSetter:
			return "Photo ImageSetter";
		case icSigGravure:
			return "Gravure";
		case icSigOffsetLithography:
			return "Offset Lithography";
		case icSigSilkscreen:
			return "Silkscreen";
		case icSigFlexography:
			return "Flexography";
		default: {
			static char buf[50];
			sprintf(buf,"Unrecognized - %s",icmtag2str(sig));
			return buf;
		}
	}
}

/* Measurement Geometry, used in the measurmentType tag */
static const char *icmMeasurementGeometry2str(icMeasurementGeometry sig) {
	switch (sig) {
		case icGeometryUnknown:
			return "Unknown";
		case icGeometry045or450:
			return "0/45 or 45/0";
		case icGeometry0dord0:
			return "0/d or d/0";
		default: {
			static char buf[50];
			sprintf(buf,"Unrecognized - 0x%x",sig);
			return buf;
		}
	}
}

/* Rendering Intents, used in the profile header */
static const char *icmRenderingIntent2str(icRenderingIntent sig) {
	static int si = 0;			/* String buffer index */
	static char buf[5][100];	/* String buffers */
	char *bp, *cp;

	cp = bp = buf[si++];
	si %= 5;				/* Rotate through buffers */

	switch(sig & 0xffff) {
		case icPerceptual:
			sprintf(cp,"Perceptual"); break;
		case icRelativeColorimetric:
    		sprintf(cp,"Relative Colorimetric"); break;
		case icSaturation:
    		sprintf(cp,"Saturation"); break;
		case icAbsoluteColorimetric:
    		sprintf(cp,"Absolute Colorimetric"); break;

		case icmAbsolutePerceptual:				/* icclib specials */
			sprintf(cp,"Absolute Perceptual"); break;
		case icmAbsoluteSaturation:				/* icclib specials */
			sprintf(cp,"Absolute Saturation"); break;
		case icmDefaultIntent:					/* icclib specials */
    		sprintf(cp,"Default Intent"); break;

		default:
			sprintf(cp,"Unrecognized - 0x%x",sig); break;
	}
	cp = cp + strlen(cp);
	sig &= ~0xffff;

	if (sig != 0) {
		sprintf(cp," + Unknown 0x%x",sig);
		cp = cp + strlen(cp);
	}
	return bp;
}

/* Different Spot Shapes currently defined, used for screeningType */
static const char *icmSpotShape2str(icSpotShape sig) {
	switch(sig) {
		case icSpotShapeUnknown:
			return "Unknown";
		case icSpotShapePrinterDefault:
			return "Printer Default";
		case icSpotShapeRound:
			return "Round";
		case icSpotShapeDiamond:
			return "Diamond";
		case icSpotShapeEllipse:
			return "Ellipse";
		case icSpotShapeLine:
			return "Line";
		case icSpotShapeSquare:
			return "Square";
		case icSpotShapeCross:
			return "Cross";
		default: {
			static char buf[50];
			sprintf(buf,"Unrecognized - 0x%x",sig);
			return buf;
		}
	}
}

/* Standard Observer, used in the measurmentType tag */
static const char *icmStandardObserver2str(icStandardObserver sig) {
	switch(sig) {
		case icStdObsUnknown:
			return "Unknown";
		case icStdObs1931TwoDegrees:
			return "1931 Two Degrees";
		case icStdObs1964TenDegrees:
			return "1964 Ten Degrees";
		default: {
				static char buf[50];
			sprintf(buf,"Unrecognized - 0x%x",sig);
			return buf;
		}
	}
}

/* Pre-defined illuminants, used in measurement and viewing conditions type */
static const char *icmIlluminant2str(icIlluminant sig) {
	switch(sig) {
		case icIlluminantUnknown:
			return "Unknown";
		case icIlluminantD50:
			return "D50";
		case icIlluminantD65:
			return "D65";
		case icIlluminantD93:
			return "D93";
		case icIlluminantF2:
			return "F2";
		case icIlluminantD55:
			return "D55";
		case icIlluminantA:
			return "A";
		case icIlluminantEquiPowerE:
			return "Equi-Power(E)";
		case icIlluminantF8:
			return "F8";
		default: {
			static char buf[50];
			sprintf(buf,"Unrecognized - 0x%x",sig);
			return buf;
		}
	}
}

/* A language code */
static const char *icmLanguageCode2str(icEnumLanguageCode sig) {
	unsigned int sigv = (unsigned int)sig;
	static char buf[80];

	switch(sig) {
		case icLanguageCodeEnglish:
			return "English";
		case icLanguageCodeGerman:
			return "German";
		case icLanguageCodeItalian:
			return "Italian";
		case icLanguageCodeDutch:
			return "Dutch";
		case icLanguageCodeSweden:
			return "Swedish";
		case icLanguageCodeSpanish:
			return "Spanish";
		case icLanguageCodeDanish:
			return "Danish";
		case icLanguageCodeNorwegian:
			return "Norwegian";
		case icLanguageCodeJapanese:
			return "Japanese";
		case icLanguageCodeFinish:
			return "Finish";
		case icLanguageCodeTurkish:
			return "Turkish";
		case icLanguageCodeKorean:
			return "Korean";
		case icLanguageCodeChinese:
			return "Chinese";
		case icLanguageCodeFrench:
			return "French";
		default:
			break;
	}

	if ((sigv & 0xff) >= 'a' && (sigv & 0xff)  <= 'z'
	 && ((sigv >> 8) & 0xff) >= 'a' && ((sigv >> 8) & 0xff)  <= 'z'
	 && (sigv >> 16) == 0) {
		sprintf(buf,"%c%c",sigv & 0xff, (sigv >> 8) & 0xff);
	} else {
		sprintf(buf,"0x%x",sig);
	}
	return buf;
}

/* A region code */
static const char *icmRegionCode2str(icEnumRegionCode sig) {
	unsigned int sigv = (unsigned int)sig;
	static char buf[50];

	switch(sig) {
		case icRegionCodeUSA:
			return "U.S.A.";
		case icRegionCodeUnitedKingdom:
			return "U.K.";
		case icRegionCodeGermany:
			return "Germany";
		case icRegionCodeItaly:
			return "Italy";
		case icRegionCodeNetherlands:
			return "Netherlands";
		case icRegionCodeSpain:
			return "Spain";
		case icRegionCodeDenmark:
			return "Denmark";
		case icRegionCodeNorway:
			return "Norway";
		case icRegionCodeJapan:
			return "Japan";
		case icRegionCodeFinland:
			return "Finland";
		case icRegionCodeTurkey:
			return "Turkey";
		case icRegionCodeKorea:
			return "Korea";
		case icRegionCodeChina:
			return "China";
		case icRegionCodeTaiwan:
			return "Taiwan";
		case icRegionCodeFrance:
			return "France";
		case icRegionCodeAustralia:
			return "Australia";
		default:
			break;
	}

	if ((sigv & 0xff) >= 'a' && (sigv & 0xff)  <= 'z'
	 && ((sigv >> 8) & 0xff) >= 'a' && ((sigv >> 8) & 0xff)  <= 'z'
	 && (sigv >> 16) == 0) {
		sprintf(buf,"%c%c",sigv & 0xff, (sigv >> 8) & 0xff);
	} else {
		sprintf(buf,"0x%x",sig);
	}
	return buf;
}

/* Microsoft platform Device Settings ID Signatures */
static const char *icmDevSetMsftID2str(icDevSetMsftIDSignature sig) {
	switch(sig) {
    	case icSigMsftResolution:
			return "Resolution";
    	case icSigMsftMedia:
			return "Media";
    	case icSigMsftHalftone:
			return "Halftone";
		default: {
			static char buf[50];
			sprintf(buf,"Unrecognized - %s",icmtag2str(sig));
			return buf;
		}
	}
}

/* Microsoft platform Media Type Encoding */
static const char *icmDevSetMsftMedia2str(icDevSetMsftMedia sig) {
	int usern;
	static char buf[80];
	/* We're allowing for 256 user settings, but this is */
	/* arbitrary, and not defined in the ICC spec. */
	if (sig > icMsftMediaUser1 && sig < (icMsftMediaUser1 + 255)) {
		usern = 1 + sig - icMsftMediaUser1;
		sig = icMsftMediaUser1;
	}
	switch(sig) {
    	case icMsftMediaStandard:
			return "Standard";
    	case icMsftMediaTrans:
			return "Transparency";
    	case icMsftMediaGloss:
			return "Glossy";
    	case icMsftMediaUser1:
			sprintf(buf,"User%d",usern);
			return buf;
		default: {
			sprintf(buf,"Unrecognized - 0x%x",sig);
			return buf;
		}
	}
}

/* Microsoft platform Halftone Values */
static const char *icmDevSetMsftDither2str(icDevSetMsftDither sig) {
	static char buf[50];
	int usern;
	/* We're allowing for 256 user settings, but this is */
	/* arbitrary, and not defined in the ICC spec. */
	if (sig > icMsftDitherUser1 && sig < (icMsftDitherUser1 + 255)) {
		usern = 1 + sig - icMsftDitherUser1;
		sig = icMsftDitherUser1;
	}
	switch(sig) {
    	case icMsftDitherNone:
			return "None";
    	case icMsftDitherCoarse:
			return "Coarse brush";
    	case icMsftDitherFine:
			return "Fine brush";
    	case icMsftDitherLineArt:
			return "Line art";
    	case icMsftDitherErrorDiffusion:
			return "Error Diffusion";
    	case icMsftDitherReserved6:
			return "Reserved 6";
    	case icMsftDitherReserved7:
			return "Reserved 7";
    	case icMsftDitherReserved8:
			return "Reserved 8";
    	case icMsftDitherReserved9:
			return "Reserved 9";
    	case icMsftDitherGrayScale:
			return "Grayscale";
    	case icMsftDitherUser1:
			sprintf(buf,"User%d",usern);
			return buf;
		default:
			sprintf(buf,"Unrecognized - 0x%x",sig);
			return buf;
	}
}

/* Measurement units for the icResponseCurveSet16Type */
static const char *icmMeasUnitsSig2str(icMeasUnitsSig sig) {
	static char buf[50];
	switch(sig) {
    	case icSigStatusA:
			return "Status A";
    	case icSigStatusE:
			return "Status E";
    	case icSigStatusI:
			return "Status I";
    	case icSigStatusT:
			return "Status T";
    	case icSigStatusM:
			return "Status M";
    	case icSigDN:
			return "DIN no polarising filter";
    	case icSigDNP:
			return "DIN with polarising filter";
    	case icSigDNN:
			return "Narrow band DIN";
    	case icSigDNNP:
			return "Narrow band DIN with polarising filter";
		default:
			sprintf(buf,"Unrecognized - %s",icmtag2str(sig));
			return buf;
	}
}

/* Colorant and Phosphor Encodings used in chromaticity type */
static const char *icmPhColEncoding2str(icPhColEncoding sig) {
	static char buf[50];
	switch(sig) {
		case icPhColUnknown:
			return "Unknown";
		case icPhColITU_R_BT_709:
			return "ITU-R BT.709";
		case icPhColSMPTE_RP145_1994:
			return "SMPTE RP145-1994";
		case icPhColEBU_Tech_3213_E:
			return "EBU Tech.3213-E";
		case icPhColP22:
			return "P22";
		case icPhColP3:
			return "P3";
		case icPhColITU_R_BT2020:
			return "ITU-R BT.2020";
		default:
			sprintf(buf,"Unrecognized - 0x%x",sig);
			return buf;
	}
}


/* Transform Lookup function */
static const char *icmLookupFunc2str(icmLookupFunc sig) {
	switch(sig) {
		case icmFwd:
			return "Forward";
		case icmBwd:
    		return "Backward";
		case icmGamut:
    		return "Gamut";
		case icmPreview:
    		return "Preview";
		default: {
			static char bufs[5][30];	/* String buffers */
			static int si = 0;			/* String buffer index */
			char *buf = bufs[si++];
			si %= 5;					/* Rotate through buffers */

			sprintf(buf,"Unrecognized - 0x%x",sig);
			return buf;
		}
	}
}

/* Lookup Order */
static const char *icmLookupOrder2str(icmLookupOrder sig) {
	switch(sig) {
		case icmLuOrdNorm:
			return "Normal";
		case icmLuOrdRev:
    		return "Reverse";
		default: {
			static char bufs[5][30];	/* String buffers */
			static int si = 0;			/* String buffer index */
			char *buf = bufs[si++];
			si %= 5;					/* Rotate through buffers */

			sprintf(buf,"Unrecognized - 0x%x",sig);
			return buf;
		}
	}
}

/* Return a text abreviation of a color lookup type */
static const char *icmLu4Type2str(icmLu4Type typ) {
	switch(typ) {
    	case icmSpaceLu4Type:
			return "ColorSpace";
    	case icmNamedLu4Type:
			return "Named Color";
		default: {
			static char bufs[5][30];	/* String buffers */
			static int si = 0;			/* String buffer index */
			char *buf = bufs[si++];
			si %= 5;					/* Rotate through buffers */

			sprintf(buf,"Unrecognized - %d",typ);
			return buf;
		}
	}
}

/* Return a text abreviation of a color lookup algorithm (legacy) */
static const char *icmLuAlgType2str(icmLuAlgType alg) {
	switch(alg) {
    	case icmMonoFwdType:
			return "MonoFwd";
    	case icmMonoBwdType:
			return "MonoBwd";
    	case icmMatrixFwdType:
			return "MatrixFwd";
    	case icmMatrixBwdType:
			return "MatrixBwd";
    	case icmLutType:
			return "Lut";
		default: {
			static char bufs[5][30];	/* String buffers */
			static int si = 0;			/* String buffer index */
			char *buf = bufs[si++];
			si %= 5;					/* Rotate through buffers */

			sprintf(buf,"Unrecognized - %d",alg);
			return buf;
		}
	}
}

/* Return a text abreviation of a lookup source tag */
static const char *icmLuSrcType2str(icTagSignature sig) {
	switch (sig) {
		case icmSigShaperMono:
			return "Mono";

		case icmSigShaperMatrix:
			return "Matrix";

		case icSigAToB0Tag:
			return "Lut_A2B0";
		case icSigAToB1Tag:
			return "Lut_A2B1";
		case icSigAToB2Tag:
			return "Lut_A2B2";
		case icSigBToA0Tag:
			return "Lut_B2A0";
		case icSigBToA1Tag:
			return "Lut_B2A1";
		case icSigBToA2Tag:
			return "Lut_B2A2";

		case icSigGamutTag:
			return "Gamut Lut";

		default: {
			static char bufs[5][30];	/* String buffers */
			static int si = 0;			/* String buffer index */
			char *buf = bufs[si++];
			si %= 5;					/* Rotate through buffers */

			sprintf(buf,"Unrecognized sig 0x%x",sig);
			return buf;
		}
	}
}

/* Return a text abreviation of a processing element */
static const char *icmPeSig2str(icmPeSignature sig) {
	switch(sig) {
		case icmSigPeNone:
			return "Not a Processing Element";
		case icmSigPeCurveSet:
			return "Group of 1d segments";
		case icmSigPeCurve:
			return "Linear/gamma/table curve";
		case icmSigPeMatrix:
			return "N x M + F matrix";
		case icmSigPeClut:
			return "N x M cLUT";
		case icmSigPeLut816:
			return "Lut8 or Lut16";

		case icmSigPeContainer:
			return "PE Sequence Container";

		case icmSigPeInverter:
			return "PE Inverter";

		case icmSigPeMono:
			return "Monochrome to PCS";

		case icmSigPeShaperMatrix:
			return "Shaper/Matrix sequence";
		case icmSigPeShaperMono:
			return "Shaper/Mono sequence";

		case icmSigPeXYZ2Lab:
			return "XYZ to Lab";
		case icmSigPeAbs2Rel:
			return "Abs to Rel";
		case icmSigPeXYZ2XYZ8:
			return "XYZ to XYZ 8 bit";
		case icmSigPeXYZ2XYZ16:
			return "XYZ to XYZ 16 bit";
		case icmSigPeLab2Lab8:
			return "Lab to Lab 8 bit";
		case icmSigPeLab2LabV2:
			return "Lab to V2 Lab 16 bit";
		case icmSigPeGeneric2Norm:
			return "Generic Normalisation";
		case icmSigPeGridAlign:
			return "Grid Alignment";
		case icmSigPeNOP:
			return "No Operation";

		default: {
			static char bufs[5][50];	/* String buffers */
			static int si = 0;			/* String buffer index */
			char *buf = bufs[si++];
			si %= 5;					/* Rotate through buffers */

			sprintf(buf,"Unrecognized Processing Element - %s",icmtag2str(sig));
			return buf;
		}
	}
}

/* Return a string description of the given enumeration value */
/* Public: */
const char *icm2str(icmEnumType etype, int enumval) {
	switch(etype) {
	    case icmScreenEncodings:
			return icmScreenEncodings2str((unsigned int) enumval);
	    case icmDeviceAttributes:
			return icmDeviceAttributes2str((unsigned int) enumval);
		case icmProfileHeaderFlags:
			return icmProfileHeaderFlags2str((unsigned int) enumval);
		case icmAsciiOrBinaryData:
			return icmAsciiOrBinaryData2str((unsigned int) enumval);
		case icmVideoCardGammaFormat:
			return icmVideoCardGammaFormatData2str((unsigned int) enumval);
		case icmTagSig:
			return icmTagSig2str((icTagSignature) enumval);
		case icmTagSig_alt:
			return icmTagSig2str_alt((icTagSignature) enumval);
		case icmTypeSig:
			return icmTypeSig2str((icTagTypeSignature) enumval);
		case icmColorSpaceSig:
			return icmColorSpaceSig2str((icColorSpaceSignature) enumval);
		case icmProfileClassSig:
			return icmProfileClassSig2str((icProfileClassSignature) enumval);
		case icmPlatformSig:
			return icmPlatformSig2str((icPlatformSignature) enumval);
		case icmDeviceManufacturerSig:
			return icmDeviceManufacturerSig2str((icDeviceManufacturerSignature) enumval);
		case icmDeviceModelSig:
			return icmDeviceModelSig2str((icDeviceModelSignature) enumval);
		case icmCMMSig:
			return icmCMMSig2str((icCMMSignature) enumval);
		case icmTechnologySig:
			return icmTechnologySig2str((icTechnologySignature) enumval);
		case icmMeasurementGeometry:
			return icmMeasurementGeometry2str((icMeasurementGeometry) enumval);
		case icmRenderingIntent:
			return icmRenderingIntent2str((icRenderingIntent) enumval);
		case icmSpotShape:
			return icmSpotShape2str((icSpotShape) enumval);
		case icmStandardObserver:
			return icmStandardObserver2str((icStandardObserver) enumval);
		case icmIlluminant:
			return icmIlluminant2str((icIlluminant) enumval);
		case icmLanguageCode:
			return icmLanguageCode2str((icEnumLanguageCode) enumval);
		case icmRegionCode:
			return icmRegionCode2str((icEnumRegionCode) enumval);
		case icmDevSetMsftID:
			return icmDevSetMsftID2str((icDevSetMsftIDSignature) enumval);
		case icmDevSetMsftMedia:
			return icmDevSetMsftMedia2str((icDevSetMsftMedia) enumval);
		case icmDevSetMsftDither:
			return icmDevSetMsftDither2str((icDevSetMsftDither) enumval);
		case icmMeasUnitsSig:
			return icmMeasUnitsSig2str((icMeasUnitsSig) enumval);
		case icmPhColEncoding:
			return icmPhColEncoding2str((icPhColEncoding) enumval);
		case icmTransformLookupFunc:
			return icmLookupFunc2str((icmLookupFunc) enumval);
		case icmTransformLookupOrder:
			return icmLookupOrder2str((icmLookupOrder) enumval);
		case icmProcessingElementOp:
			return icmPe_Op2str((icmPeOp) enumval);
		case icmProcessingElementTag:
			return icmPeSig2str((icmPeSignature) enumval);
		case icmTransformType:
			return icmLu4Type2str((icmLu4Type) enumval);			/* Lu4 */
		case icmTransformLookupAlgorithm:
			return icmLuAlgType2str((icmLuAlgType) enumval);		/* Lu2 */
		case icmTransformSourceTag:
			return icmLuSrcType2str((icTagSignature) enumval);		/* Lu4 */
		default: {
			static char bufs[5][100];	/* String buffers */
			static int si = 0;			/* String buffer index */
			char *buf = bufs[si++];
			si %= 5;					/* Rotate through buffers */

			sprintf(buf,"icm2str got unknown type, value 0x%x", enumval);
			return buf;
		}
	}
}

/* Convert icmPe_lurv to a comma separated list of descriptions */
struct {
	icmPe_lurv mask;
	char *string;

} icmPe_lurvStrings[] = {
	{ icmPe_lurv_clip, "Clip" },
	{ icmPe_lurv_num,  "Numerical" },
	{ icmPe_lurv_bwclp,"BwdFmlaClip" },
	{ icmPe_lurv_imp,  "Implimentation" },
	{ icmPe_lurv_cfg,  "Configuration" },
	{ 0, NULL },

};

char *icmPe_lurv2str(icmPe_lurv err) {
	static char buf[200];
	char *bp = buf;
	int ne = 0;
	int i;

	if (err == icmPe_lurv_OK)
		return "Ok";

	for (i = 0; icmPe_lurvStrings[i].string != NULL; i++) {
		if (err & icmPe_lurvStrings[i].mask) {
			if (ne)
				bp += sprintf(bp, ", ");
			bp += sprintf(bp, "%s",icmPe_lurvStrings[i].string);
			ne = 1;
		}
	}
	return buf;
}

char *icmPe_Op2str(icmPeOp op) {
	switch(op) {
		case icmPeOp_NOP:
			return "NOP";
		case icmPeOp_perch:
			return "Per-channel Op";
		case icmPeOp_matrix:
			return "Matrix Op";
		case icmPeOp_cLUT:
			return "cLut Op";
		case icmPeOp_fmt:
			return "Format Op";
		case icmPeOp_complex:
			return "Complex Op";

		default: {
			static char bufs[5][50];	/* String buffers */
			static int si = 0;			/* String buffer index */
			char *buf = bufs[si++];
			si %= 5;					/* Rotate through buffers */

			sprintf(buf,"Unrecognized Pe Op - %d",op);
			return buf;
		}
	}
}

char *icmPe_Attr2Str(icmPeAttr *attr) {
	static char buf[100];

	sprintf(buf, "comp %d, inv %d, norm %d, op %s, fwd %d, bwd %d",
	attr->comp, attr->inv, attr->norm, icmPe_Op2str(attr->op), attr->fwd, attr->bwd);
	return buf;
}

char *icmCSInfo2str(icmCSInfo *p)  {
	static char buf[200];
	
	sprintf(buf,"%s, nch %d, min %s, max %s",
		icm2str(icmColorSpaceSig,p->sig), p->nch,
		icmPdvf(p->nch, "%.6f", p->min), icmPdvf(p->nch, "%.6f", p->max));

	return buf;
}

/* Return a string that shows the XYZ number value */
/* Returned buffer is static */
char *icmXYZNumber2str(icmXYZNumber *p) {
#   define BUFSZ 100
	static char buf[BUFSZ];
	int rl;

	rl = snprintf(buf, BUFSZ,"%.8f, %.8f, %.8f", p->XYZ[0], p->XYZ[1], p->XYZ[2]);
	if (rl < 0 || rl >= BUFSZ)
		snprintf(buf, BUFSZ,"%g, %g, %g", p->XYZ[0], p->XYZ[1], p->XYZ[2]);
	return buf;
#   undef BUFSZ
}

/* Return a string that shows the XYZ number value, */
/* and the Lab D50 number in paren. */
/* Returned string is static */
char *icmXYZNumber_and_Lab2str(icmXYZNumber *p) {
#   define BUFSZ 100
	static char buf[BUFSZ];
	double lab[3];
	int rl;
	icmXYZ2Lab(&icmD50, lab, p->XYZ);
	rl = snprintf(buf, BUFSZ, "%.8f, %.8f, %.8f    [Lab %f, %f, %f]",
	            p->XYZ[0], p->XYZ[1], p->XYZ[2], lab[0], lab[1], lab[2]);
	if (rl < 0 || rl >= BUFSZ)
		snprintf(buf, BUFSZ, "%g, %g, %g    [Lab %g, %g, %g]",
	            p->XYZ[0], p->XYZ[1], p->XYZ[2], lab[0], lab[1], lab[2]);
	return buf;
#   undef BUFSZ
}
			

/* ======================================================================== */
/*                     Notes on _serialise functions                        */
/* ======================================================================== */
/*

 *_serialise gets passed a pointer to icmFBuf which holds file contents and
 the operation type icmSnOp:

    icmSnResize   Allocate or resize variable sized elements.
    icmSnFree     Free variable sized elements.
    icmSnSize     Return file size the element would use.
    icmSnWrite    Write elements.
    icmSnRead     Allocate and read elements.

 Primitive and compound Serialization routines are all named icmSn*

 Because file buffer is setup before calling _serialise, a Type
 can be embeded in full within another type by calling its _serialise.
 - i.e. TextDescriptionType, MultiLocalizedUnicodeType, Curve.

 The expected sequence of calls to _serialise is:

 For writing:

	1. The user code creates a Tag & TagType struct.
	loop:
		2. They set values and sizes needed.
		3. The call tagtype->allocate() which calls op == icmSnResize to allocate arrays.
		4. User code sets array values.
	5. On profile write tatype->write() which
		i.  calls _check() functions.
		ii. calls op == icmSnSize which does a dummy serialise to compute
			TagType and sub element sizes and sets corresponding offsets in headers.
			Shared data should be aligned and file allocated for op == icmSnSize || icmSnWrite,
			check shared tagtype data for match to previous elements and share offset & length
			rather than serialize.
		iii. calls op == icmSnWrite which does actual serialise into file using same
		    logic as icmSnSize, but now with correct sizes & offsets.
		iv. After icmSnSize and icmSnWrite the file offset needs to be left at the end
	        of the tagtype.
	6. TagType->del() calls op == icmSnFree which frees all arrays.

 For reading:
 
	1. Profile read creats a TagType
	2. Profiles read does a tagtype->read() which calls op == icmSnRead
	    which de-serialises from the file. 
		Before serializing arrays icmArrayRdAllocResize() is called.
		After serializing arrays ICMSNFREEARRAY() is invoked.
	   Data that is defined by offset uses b->aoff() to seek to the data
	   befor serialising it. (Seek only on op == icmSnRead).
	   Shared values are duplicated in 'C' struct.
	   _check() functions are called.
	4. TagType->del() calls op == icmSnFree which frees all arrays.

 Optimizations:

	For extra speed for alloc/de-alloc, pure serialization loops can be
	skipped, i.e.:

	if (b->op & icmSnSerialise) {
	    for (n = 0; n < p->rcount; n++) {
        	icmSn_ui_UInt16(b, &value);
		}
	}

 Range/boundary checking
 -----------------------

	Reading and writing the file buffer is fully checked,
	but a guard is needed against accessing machine storage
	that is out of range.

	With storage that is allocated as it is needed,
	icmArrayRdAllocResize() when fed with a suitable non-zero
	bsize protects against exausting virtual memory with an allocation,
	while the allocation will always be big enough for the file derived
	dimension size.

	For machine storage that is static in size (i.e. fred[MAX_CHAN]), then
	it is critical that the file dimension value be sanity checked
	when read, and before it is used to access the machine storage.
  	i.e. icmSn_check_ui_UInt32()

 Dealing with shared data areas within tags
 ------------------------------------------

  We are taking the approach of not explicitly supporting the sharing
  of variable elements in the C structure below tag types themselves.
  On read any shared values are duplicated into the C structure.
  On write we have implemented de-duplication of matching values.
  File size is minimized without any special logic in the client code. 

 Sub-buffers
 -----------

    Given a buffer, a new pseudo-buffer can be create from it
    using b->new_sub(). This uses the same actual buffer, but
    can have an offset and shorter length. When destroyed, the
    super buffer offset it updated to match the offset of the sub-buffer.
    This is useful if offsets are needed from a different
    point, or for easy sub-length calculation or checking.

 Dealing with sub-tags
 ---------------------

   Call icmSn_SubTagType() with a sub-buffer.

*/

/* ======================================================================== */
/* File Buffer for serialisation to work with. */

/* Offset the current location by a relative amount within the buffer. */
/* Note that the resulting offset is checked for wrap around and being */
/* before the start or beyond the end of the buffer. */
static void icmFBuf_roff(icmFBuf *p, INR32 off) {
	ORD8 *nbp;
	if (p->icp->e.c != ICM_ERR_OK)
		return;
	nbp = p->bp + off;
	if ((off > 0 && nbp < p->bp)
	 || (off < 0 && nbp > p->bp)
	 || nbp < p->buf
	 || nbp > p->ep) {
		icm_err(p->icp, ICM_ERR_BUFFER_BOUND, "icmFBuf_roff: bounds error");
		return;
	}
	p->bp = nbp;
}

/* Set the current location to an absolute location within the buffer. */
/* Note that the resulting offset is checked  for being before the start */
/* or beyond the end of the buffer. */
static void icmFBuf_aoff(icmFBuf *p, ORD32 off) {
	ORD8 *nbp;
	if (p->icp->e.c != ICM_ERR_OK)
		return;
	nbp = p->buf + off;

	if (nbp < p->buf
	 || nbp > p->ep) {
		icm_err(p->icp, ICM_ERR_BUFFER_BOUND, "icmFBuf_aoff: bounds error");
		return;
	}
	p->bp = nbp;
}

/* Return the current absolute offset within the buffer */
/* Returns 0 on an error */
static ORD32 icmFBuf_get_off(icmFBuf *p) {
	if (p->icp->e.c != ICM_ERR_OK)
		return 0;
	if (p->bp < p->buf
	 || p->bp > p->ep) {
		icm_err(p->icp, ICM_ERR_BUFFER_BOUND, "icmFBuf_get_off: bounds error");
		return 0;
	}
	return p->bp - p->buf;
}

/* Return the current available space within the buffer */
/* Returns 0 on an error */
static ORD32 icmFBuf_get_space(icmFBuf *p) {
	if (p->icp->e.c != ICM_ERR_OK)
		return 0;
	if (p->bp < p->buf
	 || p->bp > p->ep) {
		icm_err(p->icp, ICM_ERR_BUFFER_BOUND, "icmFBuf_get_space: bounds error");
		return 0;
	}
	return p->ep - p->bp;
}

/* Finalise (ie. Write), free object and buffer, return size used */
static ORD32 icmFBuf_done(icmFBuf *p) {
	ORD32 rv = 0;

	if (p->icp->e.c == ICM_ERR_OK) {

		if (p->super == NULL) {

			/* Write the buffer to the file */
			if (p->op == icmSnWrite) {
				ORD32 size = p->ep - p->buf;
				if (p->fp->seek(p->fp, p->of) != 0) {
					icm_err(p->icp, ICM_ERR_FILE_SEEK, "done_icmFBuf: seek to %u failed",p->of);
					p->icp->al->free(p->icp->al, p->buf);
					p->icp->al->free(p->icp->al, p);
					return rv;
				}
		       	if (p->fp->write(p->fp, p->buf, 1, size) != size) {
					icm_err(p->icp, ICM_ERR_FILE_WRITE,
					                       "done_icmFBuf: write at %u size %u failed", p->of,size);
					p->icp->al->free(p->icp->al, p->buf);
					p->icp->al->free(p->icp->al, p);
					return rv;
				}
			}

			/* We return the size arrived at by serialisation */
			if (p->bp < p->buf
			 || p->bp > p->ep) {
				icm_err(p->icp, ICM_ERR_BUFFER_BOUND, "done_icmFBuf: pointer wrapped around");
				rv = 0;
			} else
				rv = p->bp - p->buf;

		} else {	/* We're a sub-buffer - we don't write anything */

			/* Increment the super-buffer by the size arrived at by serialisation */
			if (p->bp < p->buf
			 || p->bp > p->ep) {
				icm_err(p->icp, ICM_ERR_BUFFER_BOUND, "sub done_icmFBuf: pointer wrapped around");
				rv = 0;
			} else {
				rv = p->bp - p->buf;
				p->super->bp += rv;
			}
		}
	}

	if (p->super == NULL)
		p->icp->al->free(p->icp->al, p->buf);
	p->icp->al->free(p->icp->al, p);

	return rv;
}

static icmFBuf *icmFBuf_new_sub(struct _icmFBuf *super, ORD32 size);

/* Create a new file buffer. */
/* If op & icmSnDumyBuf, then f, of and size are ignored. */
/* of is the offset of the buffer in the file */
/* Sets icmFBuf and icc op */
/* Returns NULL on error & set icp->e  */
/* If super != NULL, then create a sub-buffer */
/* Sub-buffer length is limited to be remainder of super-buffer or size > 0, whichever is smaller */
static icmFBuf *new_icmFBuf_imp(icc *icp, icmFBuf *super, icmSnOp op, icmFile *fp, unsigned int of, ORD32 size) {
	icmFBuf *p;

	if (icp->e.c != ICM_ERR_OK)
		return NULL;		/* Pre-exitsting error */

	if ((p = (icmFBuf *) icp->al->calloc(icp->al, 1, sizeof(icmFBuf))) == NULL) {
		icm_err(icp, ICM_ERR_MALLOC, "new_icmFBuf: malloc failed");
		return NULL;
	}
	p->icp       = icp;				/* icc profile */

	p->super     = super;
	p->op        = op;				/* Current operation */

	p->roff      = icmFBuf_roff;
	p->aoff      = icmFBuf_aoff;
	p->get_off   = icmFBuf_get_off;
	p->get_space = icmFBuf_get_space;
	p->new_sub   = icmFBuf_new_sub;
	p->done      = icmFBuf_done;

	if (super == NULL) {				/* We're reading or writing */
		if (p->op & icmSnDumyBuf) {		/* We're just Sizing, allocating or freeing */
			p->fp = NULL;
			p->buf = p->bp = (ORD8 *)0;	/* Allow size to compute using pointer differences */
			p->ep = ((ORD8 *)0)-1;		/* Maximum pointer value */
			p->of = 0;
			p->size = p->ep - p->buf;
	
		} else {
			p->fp = fp;
			p->of = of;
			p->size = size;

			/* Allocate the buffer. Zero it so padding is zero. */
			if ((p->buf = (ORD8 *) icp->al->calloc(icp->al, size, sizeof(ORD8))) == NULL) {
				icm_err(icp, ICM_ERR_MALLOC, "new_icmFBuf: malloc failed");
				icp->al->free(icp->al, p);
				return NULL;
			}
			p->bp = p->buf;
			p->ep = p->buf + size;
			if (p->ep < p->buf) {	/* Some calloc's have overflow bugs.. */
				icm_err(icp, ICM_ERR_FILE_SEEK, "new_icmFBuf: calloc allocated bad buffer");
				p->icp->al->free(p->icp->al, p->buf);
				p->icp->al->free(p->icp->al, p);
				return NULL;
			}

			/* Read the segment from the file */
			if (p->op == icmSnRead) {
				if (p->fp->seek(p->fp, p->of) != 0) {
					icm_err(icp, ICM_ERR_FILE_SEEK, "new_icmFBuf: seek to %u failed",p->of);
					p->icp->al->free(p->icp->al, p->buf);
					p->icp->al->free(p->icp->al, p);
					return NULL;
				}
       		if (p->fp->read(p->fp, p->buf, 1, size) != size) {
					icm_err(icp, ICM_ERR_FILE_READ,
					                          "new_icmFBuf: read at %u size %u failed",p->of,size);
					p->icp->al->free(p->icp->al, p->buf);
					p->icp->al->free(p->icp->al, p);
					return NULL;
				}
			}

		}
	} else {	/* We're a sub-buffer */
		unsigned int suboff = super->bp - super->buf;	/* Offset of sub-buffer from super */
		p->size = super->size - suboff;			/* Default remaining size in super */
		if (size > 0 && size < p->size)			/* But can be set smaller if known */
			p->size = size;
		p->fp   = super->fp;
		p->of   = super->of + suboff;
		p->buf  = super->buf + suboff;
		p->bp   = p->buf;						/* Start at begining of sub-buf */
		p->ep   = p->bp + p->size;
	}

	return p;
}

/* Create a new file buffer. */
/* If op & icmSnDumyBuf, then f, of and size are ignored. */
/* of is the offset of the buffer in the file */
/* Sets icmFBuf and icc op */
/* Returns NULL on error & set icp->e  */
static icmFBuf *new_icmFBuf(icc *icp, icmSnOp op, icmFile *fp, unsigned int of, ORD32 size) {
	return new_icmFBuf_imp(icp, NULL, op, fp, of, size);
}

/* Return a sub-buffer within the current buffer, starting at the current offset. */
/* On ->done the super-buffer will be set to the equivalent offset of the sub-buffer */
/* If size > 0 and smaller than super, then will set smaller sub limit. */
static icmFBuf *icmFBuf_new_sub(struct _icmFBuf *super, ORD32 size) {
	return new_icmFBuf_imp(super->icp, super, super->op, super->fp, super->of, size);
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* Primitives */
/* All serialisation has to be done via primitives. */

/* Primitive functions are made polimorphic so that they can be */
/* involked from a single place using a table lookup. */
/* Serialization converts between ICC style file storage types and native C types. */
/* Return the number of bytes read or written from the buffer on success, */
/* and 0 on failure (this will only happen on write if value is not representable) */

// ~8 should add check that native types are >= ICC types they conduct values for.

/* - - - - - - - - - */
/* Unsigned numbers: */

static ORD32 icmSnImp_uc_UInt8(icmSnOp op, void *vp, ORD8 *p) {
	unsigned char v;

	if (op == icmSnRead) {
		v = (unsigned char)p[0];
		*((unsigned char *)vp) = v;
	} else if (op == icmSnWrite) {
		v = *((unsigned char *)vp);
		p[0] = (ORD8)v;
	}
	return 1;
}

static ORD32 icmSnImp_us_UInt8(icmSnOp op, void *vp, ORD8 *p) {
	unsigned short v;

	if (op == icmSnRead) {
		v = (unsigned short)p[0];
		*((unsigned short *)vp) = v;
	} else if (op == icmSnWrite) {
		v = *((unsigned short *)vp);
		if (v > 255)
			return 0;
		p[0] = (ORD8)v;
	}
	return 1;
}

static ORD32 icmSnImp_ui_UInt8(icmSnOp op, void *vp, ORD8 *p) {
	unsigned int v;

	if (op == icmSnRead) {
		v = (unsigned int)p[0];
		*((unsigned int *)vp) = v;
	} else if (op == icmSnWrite) {
		v = *((unsigned int *)vp);
		if (v > 255)
			return 0;
		p[0] = (ORD8)v;
	}
	return 1;
}

static ORD32 icmSnImp_us_UInt16(icmSnOp op, void *vp, ORD8 *p) {
	unsigned short v;

	if (op == icmSnRead) {
		v = 256 * (unsigned short)p[0]
		  +       (unsigned short)p[1];
		*((unsigned short *)vp) = v;
	} else if (op == icmSnWrite) {
		v = *((unsigned short *)vp);
		p[0] = (ORD8)(v >> 8);
		p[1] = (ORD8)(v);
	}
	return 2;
}

static ORD32 icmSnImp_ui_UInt16(icmSnOp op, void *vp, ORD8 *p) {
	unsigned int v;

	if (op == icmSnRead) {
		v = 256 * (unsigned int)p[0]
		  +       (unsigned int)p[1];
		*((unsigned int *)vp) = v;
	} else if (op == icmSnWrite) {
		v = *((unsigned int *)vp);
		if (v > 65535)
			return 0;
		p[0] = (ORD8)(v >> 8);
		p[1] = (ORD8)(v);
	}
	return 2;
}

static ORD32 icmSnImp_ui_UInt32(icmSnOp op, void *vp, ORD8 *p) {
	unsigned int v;

	if (op == icmSnRead) {
		v = 16777216 * (unsigned int)p[0]
		  +    65536 * (unsigned int)p[1]
		  +      256 * (unsigned int)p[2]
		  +            (unsigned int)p[3];
		*((unsigned int *)vp) = v;
	} else if (op == icmSnWrite) {
		v = *((unsigned int *)vp);
		p[0] = (ORD8)(v >> 24);
		p[1] = (ORD8)(v >> 16);
		p[2] = (ORD8)(v >> 8);
		p[3] = (ORD8)(v);
	}
	return 4;
}

static ORD32 icmSnImp_uii_UInt64(icmSnOp op, void *vp, ORD8 *p) {
	icmUInt64 v;

	if (op == icmSnRead) {
		v.h = 16777216 * (unsigned int)p[0]
		    +    65536 * (unsigned int)p[1]
		    +      256 * (unsigned int)p[2]
		    +            (unsigned int)p[3];
		v.l = 16777216 * (unsigned int)p[4]
		    +    65536 * (unsigned int)p[5]
		    +      256 * (unsigned int)p[6]
		    +            (unsigned int)p[7];
		*((icmUInt64 *)vp) = v;
	} else if (op == icmSnWrite) {
		v = *((icmUInt64 *)vp);
		p[0] = (ORD8)(v.h >> 24);
		p[1] = (ORD8)(v.h >> 16);
		p[2] = (ORD8)(v.h >> 8);
		p[3] = (ORD8)(v.h);
		p[4] = (ORD8)(v.l >> 24);
		p[5] = (ORD8)(v.l >> 16);
		p[6] = (ORD8)(v.l >> 8);
		p[7] = (ORD8)(v.l);
	}
	return 8;
}

static ORD32 icmSnImp_d_U8Fix8(icmSnOp op, void *vp, ORD8 *p) {
	ORD32 o32;
	double v;

	if (op == icmSnRead) {
		o32 = 256 * (ORD32)p[0]		/* Read big endian 16 bit unsigned */
	        +       (ORD32)p[1];
		v = (double)o32/256.0;
		*((double *)vp) = v;
	} else if (op == icmSnWrite) {
		v = *((double *)vp);
		v = floor(v * 256.0 + 0.5);
		if (v < 0.0 || v > 65535.0)
			return 0;
		o32 = (ORD32)v;
		p[0] = (ORD8)(o32 >> 8);
		p[1] = (ORD8)(o32);
	}
	return 2;
}

static ORD32 icmSnImp_d_U1Fix15(icmSnOp op, void *vp, ORD8 *p) {
	ORD32 o32;
	double v;

	if (op == icmSnRead) {
		o32 = 256 * (ORD32)p[0]		/* Read big endian 16 bit unsigned */
	        +       (ORD32)p[1];
		v = (double)o32/32768.0;
		*((double *)vp) = v;
	} else if (op == icmSnWrite) {
		v = *((double *)vp);
		v = floor(v * 32768.0 + 0.5);
		if (v < 0.0 || v > 65535.0)
			return 0;
		o32 = (ORD32)v;
		p[0] = (ORD8)(o32 >> 8);
		p[1] = (ORD8)(o32);
	}
	return 2;
}

static ORD32 icmSnImp_d_U16Fix16(icmSnOp op, void *vp, ORD8 *p) {
	ORD32 o32;
	double v;

	if (op == icmSnRead) {
		o32 = 16777216 * (ORD32)p[0]		/* Read big endian 32 bit unsigned */
		    +    65536 * (ORD32)p[1]
		    +      256 * (ORD32)p[2]
		    +            (ORD32)p[3];
		v = (double)o32/65536.0;
		*((double *)vp) = v;
	} else if (op == icmSnWrite) {
		v = *((double *)vp);
		v = floor(v * 65536.0 + 0.5);
		if (v < 0.0 || v > 4294967295.0)
			return 0;
		o32 = (ORD32)v;
		p[0] = (ORD8)((o32) >> 24);
		p[1] = (ORD8)((o32) >> 16);
		p[2] = (ORD8)((o32) >> 8);
		p[3] = (ORD8)((o32));
	}
	return 4;
}

/* - - - - - - - - - */
/* Signed numbers: */

static ORD32 icmSnImp_c_SInt8(icmSnOp op, void *vp, ORD8 *p) {
	signed char v;

	if (op == icmSnRead) {
		v = (signed char)((INR8 *)p)[0];
		*((signed char *)vp) = v;
	} else if (op == icmSnWrite) {
		v = *((signed char *)vp);
		((INR8 *)p)[0] = (INR8)v;
	}
	return 1;
}

static ORD32 icmSnImp_s_SInt8(icmSnOp op, void *vp, ORD8 *p) {
	short v;

	if (op == icmSnRead) {
		v = (short)((INR8 *)p)[0];
		*((short *)vp) = v;
	} else if (op == icmSnWrite) {
		v = *((short *)vp);
		if (v > 127)
			return 0;
		else if (v < -128)
			return 0;
		((INR8 *)p)[0] = (INR8)v;
	}
	return 1;
}

static ORD32 icmSnImp_i_SInt8(icmSnOp op, void *vp, ORD8 *p) {
	int v;

	if (op == icmSnRead) {
		v = (int)((INR8 *)p)[0];
		*((int *)vp) = v;
	} else if (op == icmSnWrite) {
		v = *((int *)vp);
		if (v > 127)
			return 0;
		else if (v < -128)
			return 0;
		((INR8 *)p)[0] = (INR8)v;
	}
	return 1;
}

static ORD32 icmSnImp_s_SInt16(icmSnOp op, void *vp, ORD8 *p) {
	short v;

	if (op == icmSnRead) {
		v = 256 * (short)((INR8 *)p)[0]
		  +       (short)p[1];
		*((short *)vp) = v;
	} else if (op == icmSnWrite) {
		v = *((short *)vp);
		((INR8 *)p)[0] = (INR8)(v >> 8);
		p[1] = (ORD8)(v);
	}
	return 2;
}

static ORD32 icmSnImp_i_SInt16(icmSnOp op, void *vp, ORD8 *p) {
	int v;

	if (op == icmSnRead) {
		v = 256 * (int)((INR8 *)p)[0]
		  +       (int)p[1];
		*((int *)vp) = v;
	} else if (op == icmSnWrite) {
		v = *((int *)vp);
		if (v > 32767)
			return 0;
		else if (v < -32768)
			return 0;
		((INR8 *)p)[0] = (INR8)(v >> 8);
		p[1] = (ORD8)(v);
	}
	return 2;
}

static ORD32 icmSnImp_i_SInt32(icmSnOp op, void *vp, ORD8 *p) {
	int v;

	if (op == icmSnRead) {
		v = 16777216 * (int)((INR8 *)p)[0]
		  +    65536 * (int)p[1]
		  +      256 * (int)p[2]
		  +            (int)p[3];
		*((int *)vp) = v;
	} else if (op == icmSnWrite) {
		v = *((int *)vp);
		((INR8 *)p)[0] = (INR8)(v >> 24);
		p[1] = (ORD8)(v >> 16);
		p[2] = (ORD8)(v >> 8);
		p[3] = (ORD8)(v);
	}
	return 4;
}
	
static ORD32 icmSnImp_ii_SInt64(icmSnOp op, void *vp, ORD8 *p) {
	icmInt64 v;

	if (op == icmSnRead) {
		v.h = 16777216 * (int)((INR8 *)p)[0]
		    +    65536 * (int)p[1]
		    +      256 * (int)p[2]
		    +            (int)p[3];
		v.l = 16777216 * (unsigned int)p[4]
		    +    65536 * (unsigned int)p[5]
		    +      256 * (unsigned int)p[6]
		    +            (unsigned int)p[7];
		*((icmInt64 *)vp) = v;
	} else if (op == icmSnWrite) {
		v = *((icmInt64 *)vp);
		((INR8 *)p)[0] = (INR8)(v.h >> 24);
		p[1] = (ORD8)(v.h >> 16);
		p[2] = (ORD8)(v.h >> 8);
		p[3] = (ORD8)(v.h);
		p[4] = (ORD8)(v.l >> 24);
		p[5] = (ORD8)(v.l >> 16);
		p[6] = (ORD8)(v.l >> 8);
		p[7] = (ORD8)(v.l);
	}
	return 8;
}

static ORD32 icmSnImp_d_S7Fix8(icmSnOp op, void *vp, ORD8 *p) {
	INR32 i32;
	double v;

	if (op == icmSnRead) {
		i32 = 256 * (INR32)((INR8 *)p)[0]		/* Read big endian 16 bit signed */
		    +       (INR32)p[1];
		v = (double)i32/256.0;
		*((double *)vp) = v;
	} else if (op == icmSnWrite) {
		v = *((double *)vp);
		v = floor(v * 256.0 + 0.5);		/* Beware! (int)(v + 0.5) doesn't work! */
		if (v < -32768.0 || v > 32767.0)
			return 0;
		i32 = (INR32)v;
		((INR8 *)p)[0] = (INR8)((i32) >> 8);		/* Write big endian 16 bit signed */
		p[1] = (ORD8)((i32));
	}
	return 2;
}

static ORD32 icmSnImp_d_S15Fix16(icmSnOp op, void *vp, ORD8 *p) {
	INR32 i32;
	double v;

	if (op == icmSnRead) {
		i32 = 16777216 * (INR32)((INR8 *)p)[0]		/* Read big endian 32 bit signed */
		    +    65536 * (INR32)p[1]
		    +      256 * (INR32)p[2]
		    +            (INR32)p[3];
		v = (double)i32/65536.0;
		*((double *)vp) = v;
	} else if (op == icmSnWrite) {
		v = *((double *)vp);
		v = floor(v * 65536.0 + 0.5);		/* Beware! (int)(v + 0.5) doesn't work! */
		if (v < -2147483648.0 || v > 2147483647.0)
			return 0;
		i32 = (INR32)v;
		((INR8 *)p)[0] = (INR8)((i32) >> 24);		/* Write big endian 32 bit signed */
		p[1] = (ORD8)((i32) >> 16);
		p[2] = (ORD8)((i32) >> 8);
		p[3] = (ORD8)((i32));
	}
	return 4;
}

/* - - - - - - - - - - - - - - - - */
/* Normalised numbers (0.0 - 1.0) */

static ORD32 icmSnImp_d_NFix8(icmSnOp op, void *vp, ORD8 *p) {
	ORD32 o32;
	double v;

	if (op == icmSnRead) {
		o32 = (ORD32)p[0];
		v = (double)o32/255.0;
		*((double *)vp) = v;
	} else if (op == icmSnWrite) {
		v = *((double *)vp);
		v = floor(v * 255.0 + 0.5);
		if (v < 0.0 || v > 255.0)
			return 0;
		o32 = (ORD32)v;
		p[0] = (ORD8)(o32);
	}
	return 1;
}

static ORD32 icmSnImp_d_NFix16(icmSnOp op, void *vp, ORD8 *p) {
	ORD32 o32;
	double v;

	if (op == icmSnRead) {
		o32 = 256 * (ORD32)p[0]
		    +       (ORD32)p[1];
		v = (double)o32/65535.0;
		*((double *)vp) = v;
	} else if (op == icmSnWrite) {
		v = *((double *)vp);
		v = floor(v * 65535.0 + 0.5);
		if (v < 0.0 || v > 65535.0)
			return 0;
		o32 = (ORD32)v;
		p[0] = (ORD8)(o32 >> 8);
		p[1] = (ORD8)(o32);
	}
	return 2;
}

static ORD32 icmSnImp_d_NFix32(icmSnOp op, void *vp, ORD8 *p) {
	ORD32 o32;
	double v;

	if (op == icmSnRead) {
		o32 = 16777216 * (ORD32)p[0]
		  +      65536 * (ORD32)p[1]
		  +        256 * (ORD32)p[2]
		  +              (ORD32)p[3];
		v = (double)o32/4294967295.0;
		*((double *)vp) = v;
	} else if (op == icmSnWrite) {
		v = *((unsigned int *)vp);
		v = floor(v * 4294967295.0 + 0.5);
		if (v < 0.0 || v > 4294967295.0)
			return 0;
		o32 = (ORD32)v;
		p[0] = (ORD8)(o32 >> 24);
		p[1] = (ORD8)(o32 >> 16);
		p[2] = (ORD8)(o32 >> 8);
		p[3] = (ORD8)(o32);
	}
	return 4;
}

/* - - - - - - - - - - - - - - - - */
/* Rounded numbers */

static ORD32 icmSnImp_d_RFix8(icmSnOp op, void *vp, ORD8 *p) {
	ORD32 o32;
	double v;

	if (op == icmSnRead) {
		o32 = (ORD32)p[0];
		v = (double)o32;
		*((double *)vp) = v;
	} else if (op == icmSnWrite) {
		v = *((double *)vp);
		v = floor(v + 0.5);
		if (v < 0.0 || v > 255.0)
			return 0;
		o32 = (ORD32)v;
		p[0] = (ORD8)(o32);
	}
	return 1;
}

static ORD32 icmSnImp_d_RFix16(icmSnOp op, void *vp, ORD8 *p) {
	ORD32 o32;
	double v;

	if (op == icmSnRead) {
		o32 = 256 * (ORD32)p[0]
		    +       (ORD32)p[1];
		v = (double)o32;
		*((double *)vp) = v;
	} else if (op == icmSnWrite) {
		v = *((double *)vp);
		v = floor(v + 0.5);
		if (v < 0.0 || v > 65535.0)
			return 0;
		o32 = (ORD32)v;
		p[0] = (ORD8)(o32 >> 8);
		p[1] = (ORD8)(o32);
	}
	return 2;
}

static ORD32 icmSnImp_d_RFix32(icmSnOp op, void *vp, ORD8 *p) {
	ORD32 o32;
	double v;

	if (op == icmSnRead) {
		o32 = 16777216 * (ORD32)p[0]
		  +      65536 * (ORD32)p[1]
		  +        256 * (ORD32)p[2]
		  +              (ORD32)p[3];
		v = (double)o32;
		*((double *)vp) = v;
	} else if (op == icmSnWrite) {
		v = *((unsigned int *)vp);
		v = floor(v + 0.5);
		if (v < 0.0 || v > 4294967295.0)
			return 0;
		o32 = (ORD32)v;
		p[0] = (ORD8)(o32 >> 24);
		p[1] = (ORD8)(o32 >> 16);
		p[2] = (ORD8)(o32 >> 8);
		p[3] = (ORD8)(o32);
	}
	return 4;
}


/*******************************************/
/* Platform independent IEE754 conversions */
/*******************************************/

/* Convert a native double to an IEEE754 encoded single precision value, */
/* in a platform independent fashion. (ie. This works even */
/* on the rare platforms that don't use IEEE 754 floating */
/* point for their C implementation) */
static ORD32 doubletoIEEE754(double d) {
	ORD32 sn = 0, ep = 0, ma;
	ORD32 id;

	/* Convert double to IEEE754 single precision. */
	/* This would be easy if we're running on an IEEE754 architecture, */
	/* but isn't generally portable, so we use ugly code: */

	if (d < 0.0) {
		sn = 1;
		d = -d;
	}
	if (d != 0.0) {
		int ee;
		ee = (int)floor(log(d)/log(2.0));
		if (ee < -126)			/* Allow for denormalized */
			ee = -126;
		d *= pow(0.5, (double)(ee - 23));
		ee += 127;
		if (ee < 1)				/* Too small */
			ee = 0;				/* Zero or denormalised */
		else if (ee > 254) {	/* Too large */
			ee = 255;			/* Infinity */
			d = 0.0;
		}
		ep = ee;
	} else {
		ep = 0;					/* Zero */
	}
	ma = ((ORD32)d) & ((1 << 23)-1);
	id = (sn << 31) | (ep << 23) | ma;

	return id;
}

/* Convert a an IEEE754 encoded single precision value to a native double, */
/* in a platform independent fashion. (ie. This works even */
/* on the rare platforms that don't use IEEE 754 floating */
/* point for their C implementation) */
static double IEEE754todouble(ORD32 ip) {
	double op;
	ORD32 sn = 0, ep = 0, ma;

	sn = (ip >> 31) & 0x1;
	ep = (ip >> 23) & 0xff;
	ma = ip & 0x7fffff;

	if (ep == 0) { 		/* Zero or denormalised */
		op = (double)ma/(double)(1 << 23);
		op *= pow(2.0, (-126.0));
	} else {
		op = (double)(ma | (1 << 23))/(double)(1 << 23);
		op *= pow(2.0, (((int)ep)-127.0));
	}
	if (sn)
		op = -op;
	return op;
}

static ORD32 icmSnImp_d_Float32(icmSnOp op, void *vp, ORD8 *p) {
	ORD32 o32;
	double v;

	if (op == icmSnRead) {
		o32 = 16777216 * (ORD32)p[0]
		  +      65536 * (ORD32)p[1]
		  +        256 * (ORD32)p[2]
		  +              (ORD32)p[3];
		*((double *)vp) = IEEE754todouble(o32);
	} else if (op == icmSnWrite) {
		o32 = doubletoIEEE754(*((double *)vp));
		p[0] = (ORD8)(o32 >> 24);
		p[1] = (ORD8)(o32 >> 16);
		p[2] = (ORD8)(o32 >> 8);
		p[3] = (ORD8)(o32);
	}
	return 4;
}

/* PCS and other colorspace encodings are handled at a layer abover */
/* the primitives. */

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* Central primitive serialiasation implementation */

/* Identifier for primitive type. */
/* Must correspond to primitive table index below. */
/*
	First 1/2 letters are native machine type, i.e. 
	u  = unsigned ordinal
	c  = char
	s  = short
	i  = int
	ii = 64 bit int
	d  = double

	Second is file storage size & type abreviation.
	Smaller sizes can be stored in several possible native types

	Use icmSnPrim2str() display.
 */ 
typedef enum {
	icmSnPrim_pad        = 0,
	icmSnPrim_seek       = 1,
	icmSnPrim_uc_UInt8   = 2,
	icmSnPrim_us_UInt8   = 3,
	icmSnPrim_ui_UInt8   = 4,
	icmSnPrim_us_UInt16  = 5,
	icmSnPrim_ui_UInt16  = 6,
	icmSnPrim_ui_UInt32  = 7,
	icmSnPrim_uii_UInt64 = 8,
	icmSnPrim_d_U8Fix8   = 9,
	icmSnPrim_d_U1Fix15  = 10,
	icmSnPrim_d_U16Fix16 = 11,
	icmSnPrim_c_SInt8    = 12,
	icmSnPrim_s_SInt8    = 13,
	icmSnPrim_i_SInt8    = 14,
	icmSnPrim_s_SInt16   = 15,
	icmSnPrim_i_SInt16   = 16,
	icmSnPrim_i_SInt32   = 17,
	icmSnPrim_ii_SInt64  = 18,
	icmSnPrim_d_S7Fix8   = 19,
	icmSnPrim_d_S15Fix16 = 20,
	icmSnPrim_d_NFix8    = 21,
	icmSnPrim_d_NFix16   = 22,
	icmSnPrim_d_NFix32   = 23,
	icmSnPrim_d_RFix8    = 24,
	icmSnPrim_d_RFix16   = 25,
	icmSnPrim_d_RFix32   = 26,
	icmSnPrim_d_Float32  = 27
} icmSnPrim;
#define icmSnPrim_LAST icmSnPrim_d_Float32

/* In same order as above: */
struct {
	int size;		/* File size */
	ORD32 (* func)(icmSnOp op, void *vp, ORD8 *p);
	char *name;
} primitive_table[] = {
	{ 0, NULL,                "Zero Padding" },
	{ 0, NULL,                "Seek" },
	{ 1, icmSnImp_uc_UInt8,   "Unsigned Int 8" },
	{ 1, icmSnImp_us_UInt8,   "Unsigned Int 8" },
	{ 1, icmSnImp_ui_UInt8,   "Unsigned Int 8" },
	{ 2, icmSnImp_us_UInt16,  "Unsigned Int 16" },
	{ 2, icmSnImp_ui_UInt16,  "Unsigned Int 16" },
	{ 4, icmSnImp_ui_UInt32,  "Unsigned Int 32" },
	{ 8, icmSnImp_uii_UInt64, "Unsigned Int 64" },
	{ 2, icmSnImp_d_U8Fix8,   "Unsigned Fixed 8.8" },
	{ 2, icmSnImp_d_U1Fix15,  "Unsigned Fixed 1.15" },
	{ 4, icmSnImp_d_U16Fix16, "Unsigned Fixed 16.16" },
	{ 1, icmSnImp_c_SInt8,    "Signed Int 8" },
	{ 1, icmSnImp_s_SInt8,    "Signed Int 8" },
	{ 1, icmSnImp_i_SInt8,    "Signed Int 8" },
	{ 2, icmSnImp_s_SInt16,   "Signed Int 16" },
	{ 2, icmSnImp_i_SInt16,   "Signed Int 16" },
	{ 4, icmSnImp_i_SInt32,   "Signed Int 32" },
	{ 8, icmSnImp_ii_SInt64,  "Signed Int 64" },
	{ 2, icmSnImp_d_S7Fix8,   "Signed Fixed 8.8" },
	{ 4, icmSnImp_d_S15Fix16, "Signed Fixed 16.16" },
	{ 1, icmSnImp_d_NFix8,    "Normalised 8" },
	{ 2, icmSnImp_d_NFix16,   "Normalised 16" },
	{ 4, icmSnImp_d_NFix32,   "Normalised 32" },
	{ 1, icmSnImp_d_RFix8,    "Rounded 8" },
	{ 2, icmSnImp_d_RFix16,   "Rounded 16" },
	{ 4, icmSnImp_d_RFix32,   "Rounded 32" },
	{ 4, icmSnImp_d_Float32,  "Float 32" }
};


/* All primitive serialisation goes through this function, */
/* so that there is a single place to check for buffer */
/* bounding errors. size is used for padding or seeking. */
static void icmSn_primitive(icmFBuf *b, void *vp, icmSnPrim pt, INR32 size) {
	ORD8 *bp = b->bp;	/* Current pointer */
	ORD8 *nbp;			/* Buffer pointer after access */

	/* Do nothing if an error has already occurred */
	/* or we're just resizing or freeing */
	if (b->icp->e.c != ICM_ERR_OK
	 || (b->op & icmSnSerialise) == 0)
		return;

	/* Buffer pointer after this access */
	if (pt == icmSnPrim_pad
	 || pt == icmSnPrim_seek)
		nbp = b->bp + size;
	else
		nbp = b->bp + primitive_table[pt].size;

	/* Check for possible access outside buffer. */
	/* We're being paranoid, because this is one place that */
	/* file format attacks will show up. */
	/* (It's ok for nbp to land one after end of buffer) */
	if (nbp < b->bp
	 || b->bp < b->buf
	 || b->bp >= b->ep
	 || nbp < b->buf
	 || nbp > b->ep) {
# ifdef DEBUG_BOUNDARY_EXCEPTION 
		printf("icmSn_primitive:\n");
		printf("  buffer pointer %p, next buffer pointer %p\n",b->bp, nbp);
		printf("  buffer start   %p, buffer end+1        %p\n",b->buf, b->ep);
		DEBUG_BREAK;
# endif /* DEBUG_BOUNDARY_EXCEPTION */
		icm_err(b->icp, ICM_ERR_BUFFER_BOUND, "icmSn_primitive: buffer boundary exception");
		return;
	}

	/* Invoke primitive serialisation */
	if (b->op != icmSnSize && pt != icmSnPrim_seek) {

		if (pt == icmSnPrim_pad) {
			if (b->op == icmSnWrite && size > 0) {
				ORD32 i;
				unsigned int tt = 0;
				for (i = 0; i < size; i++) {
					primitive_table[icmSnPrim_ui_UInt8].func(b->op, (void *)&tt, bp + i);
				}
			}

		} else {
			ORD32 rv;
			rv = primitive_table[pt].func(b->op, vp, bp);
			if (rv != primitive_table[pt].size)
				icm_err(b->icp, ICM_ERR_ENCODING,
				    "icmSn_primitive: unable to encode value to '%s'" ,primitive_table[pt].name);
		}
	}
	b->bp = nbp;
}

static char *icmSnPrim2str(icmSnPrim pt) {
	if (pt > icmSnPrim_LAST) {
		static char buf[80];
		sprintf(buf,"Out of bounts icmSnPrim enum %d",pt);
		return buf;
	}
	return primitive_table[pt].name;
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* Public serialisation routines, used to implement tags or compund */
/* serialisable types. These provide type checking that is otherwise */
/* ignored in the implementation above. */

static int icmFmtWarn(icmFBuf *b, int sub, const char *format, ...);

/* Add zero padding of size */
static void icmSn_pad(icmFBuf *b, ORD32 size) {
	icmSn_primitive(b, NULL, icmSnPrim_pad, size);
}

/* Alight with zero padding */
static void icmSn_align(icmFBuf *b, ORD32 align) {
	ORD32 size = 0;
	if (align < 1)
		align = 1;
	size = (0 - b->get_off(b)) & (align-1); 
	if (size > 0)
		icmSn_primitive(b, NULL, icmSnPrim_pad, size);
}

/* Relative seek */
static void icmSn_rseek(icmFBuf *b, INR32 size) {
	icmSn_primitive(b, NULL, icmSnPrim_seek, size);
}

/* Unsigned char <-> unsigned int 8 bit */
static void icmSn_uc_UInt8(icmFBuf *b, unsigned char *p) {
	icmSn_primitive(b, (void *)p, icmSnPrim_uc_UInt8, 0);
}

/* Unsigned short <-> unsigned int 8 bit */
static void icmSn_us_UInt8(icmFBuf *b, unsigned short *p) {
	icmSn_primitive(b, (void *)p, icmSnPrim_us_UInt8, 0);
}

/* Unsigned int <-> unsigned int 8 bit */
static void icmSn_ui_UInt8(icmFBuf *b, unsigned int *p) {
	icmSn_primitive(b, (void *)p, icmSnPrim_ui_UInt8, 0);
}

/* Unsigned short <-> unsigned int 16 bit */
static void icmSn_us_UInt16(icmFBuf *b, unsigned short *p) {
	icmSn_primitive(b, (void *)p, icmSnPrim_us_UInt16, 0);
}

/* Unsigned int <-> unsigned int 16 bit */
static void icmSn_ui_UInt16(icmFBuf *b, unsigned int *p) {
	icmSn_primitive(b, (void *)p, icmSnPrim_ui_UInt16, 0);
}

/* Unsigned int <-> unsigned int 32 bit */
static void icmSn_ui_UInt32(icmFBuf *b, unsigned int *p) {
	icmSn_primitive(b, (void *)p, icmSnPrim_ui_UInt32, 0);
}

/* Unsigned 32+32 bit int <-> unsigned int 64 bit */
static void icmSn_uii_UInt64(icmFBuf *b, icmUInt64 *p) {
	icmSn_primitive(b, (void *)p, icmSnPrim_uii_UInt64, 0);
}

/* Double <-> unsigned 8.8 16 bit fixed point */
static void icmSn_d_U8Fix8(icmFBuf *b, double *p) {
	icmSn_primitive(b, (void *)p, icmSnPrim_d_U8Fix8, 0);
}

/* Double <-> unsigned 1.15 16 bit fixed point */
static void icmSn_d_U1Fix15(icmFBuf *b, double *p) {
	icmSn_primitive(b, (void *)p, icmSnPrim_d_U1Fix15, 0);
}

/* Double <-> unsigned 16.16 32 bit fixed point */
static void icmSn_d_U16Fix16(icmFBuf *b, double *p) {
	icmSn_primitive(b, (void *)p, icmSnPrim_d_U16Fix16, 0);
}

/* Signed char <-> signed int 8 bit */
static void icmSn_c_SInt8(icmFBuf *b, signed char *p) {
	icmSn_primitive(b, (void *)p, icmSnPrim_c_SInt8, 0);
}

/* Signed short <-> signed int 8 bit */
static void icmSn_s_SInt8(icmFBuf *b, short *p) {
	icmSn_primitive(b, (void *)p, icmSnPrim_s_SInt8, 0);
}

/* Signed int <-> signed int 8 bit */
static void icmSn_i_SInt8(icmFBuf *b, int *p) {
	icmSn_primitive(b, (void *)p, icmSnPrim_i_SInt8, 0);
}

/* Signed short <-> signed int 16 bit */
static void icmSn_s_SInt16(icmFBuf *b, short *p) {
	icmSn_primitive(b, (void *)p, icmSnPrim_s_SInt16, 0);
}

/* Signed int <-> signed int 16 bit */
static void icmSn_i_SInt16(icmFBuf *b, int *p) {
	icmSn_primitive(b, (void *)p, icmSnPrim_i_SInt16, 0);
}

/* Signed int <-> signed int 32 bit */
static void icmSn_i_SInt32(icmFBuf *b, int *p) {
	icmSn_primitive(b, (void *)p, icmSnPrim_i_SInt32, 0);
}

/* Signed 32+32 bit int <-> signed int 64 bit */
static void icmSn_ii_SInt64(icmFBuf *b, icmInt64 *p) {
	icmSn_primitive(b, (void *)p, icmSnPrim_ii_SInt64, 0);
}

/* double <-> signed 7.8 16 bit */
static void icmSn_d_S7Fix8(icmFBuf *b, double *p) {
	icmSn_primitive(b, (void *)p, icmSnPrim_d_S7Fix8, 0);
}

/* double <-> signed 15.16 32 bit */
static void icmSn_d_S15Fix16(icmFBuf *b, double *p) {
	icmSn_primitive(b, (void *)p, icmSnPrim_d_S15Fix16, 0);
}

/* double <-> Normalize 1.0-0.0 8 bit */
static void icmSn_d_NFix8(icmFBuf *b, double *p) {
	icmSn_primitive(b, (void *)p, icmSnPrim_d_NFix8, 0);
}

/* double <-> Normalize 1.0-0.0 16 bit */
static void icmSn_d_NFix16(icmFBuf *b, double *p) {
	icmSn_primitive(b, (void *)p, icmSnPrim_d_NFix16, 0);
}

/* double <-> Normalize 1.0-0.0 32 bit */
static void icmSn_d_NFix32(icmFBuf *b, double *p) {
	icmSn_primitive(b, (void *)p, icmSnPrim_d_NFix32, 0);
}

/* double <-> Rounded 8 bit */
static void icmSn_d_RFix8(icmFBuf *b, double *p) {
	icmSn_primitive(b, (void *)p, icmSnPrim_d_RFix8, 0);
}

/* double <-> Rounded 16 bit */
static void icmSn_d_RFix16(icmFBuf *b, double *p) {
	icmSn_primitive(b, (void *)p, icmSnPrim_d_RFix16, 0);
}

/* double <-> Rounded 32 bit */
static void icmSn_d_RFix32(icmFBuf *b, double *p) {
	icmSn_primitive(b, (void *)p, icmSnPrim_d_RFix32, 0);
}

/* double <-> Float 32 bit */
static void icmSn_d_Float32(icmFBuf *b, double *p) {
	icmSn_primitive(b, (void *)p, icmSnPrim_d_Float32, 0);
}

/* Size checked routines. These always result in a fatal error */
/* if the size is exceeded - i.e. they cannot be converted to warnings. */

/* Checked unsigned int <-> unsigned int 8 bit */
static void icmSn_check_ui_UInt8(icmFBuf *b, unsigned int *p, unsigned int maxsize) {
	if (b->op == icmSnWrite) {
		if (*p > maxsize) {
			icmFmtWarn(b, ICM_FMTF_VRANGE,"icmSn_check_ui_UInt8 write: value %u > limit %u",
			                                                                    *p, maxsize);
			return;
		}
	}
	if (b->op & icmSnSerialise)
		icmSn_primitive(b, (void *)p, icmSnPrim_ui_UInt8, 0);

	if (b->op == icmSnRead) {
		if (*p > maxsize) {
			*p = maxsize;
			icmFmtWarn(b, ICM_FMTF_VRANGE,"icmSn_check_ui_UInt8 read: value %u > limit %u",
			                                                                    *p, maxsize);
			return;
		}
	}
}

/* Checked unsigned int <-> unsigned int 16 bit */
static void icmSn_check_ui_UInt16(icmFBuf *b, unsigned int *p, unsigned int maxsize) {
	if (b->op == icmSnWrite) {
		if (*p > maxsize) {
			icmFmtWarn(b, ICM_FMTF_VRANGE,"icmSn_check_ui_UInt8 write: value %u > limit %u",
			                                                                    *p, maxsize);
			return;
		}
	}
	if (b->op & icmSnSerialise)
		icmSn_primitive(b, (void *)p, icmSnPrim_ui_UInt16, 0);

	if (b->op == icmSnRead) {
		if (*p > maxsize) {
			*p = maxsize;
			icmFmtWarn(b, ICM_FMTF_VRANGE,"icmSn_check_ui_UInt8 read: value %u > limit %u",
			                                                                    *p, maxsize);
			return;
		}
	}
}

/* Checked unsigned int <-> unsigned int 32 bit */
static void icmSn_check_ui_UInt32(icmFBuf *b, unsigned int *p, unsigned int maxsize) {
	if (b->op == icmSnWrite) {
		if (*p > maxsize) {
			icmFmtWarn(b, ICM_FMTF_VRANGE,"icmSn_check_ui_UInt8 write: value %u > limit %u",
			                                                                    *p, maxsize);
			return;
		}
	}
	if (b->op & icmSnSerialise)
		icmSn_primitive(b, (void *)p, icmSnPrim_ui_UInt32, 0);

	if (b->op == icmSnRead) {
		if (*p > maxsize) {
			*p = maxsize;
			icmFmtWarn(b, ICM_FMTF_VRANGE,"icmSn_check_ui_UInt8 read: value %u > limit %u",
			                                                                    *p, maxsize);
			return;
		}
	}
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* Encoding check support routine. */

/* Deal with a non immediately fatal formatting error. */
/* Sets error code if Format errors are fatal. */
/* else sets the warning cflags if set to warn. */
/* Return the current error code. */
static int icmVFormatWarning(icc *icp, int sub, const char *format, va_list vp) {
	int err;

	sub &= ICM_FMT_MASK;
	err = (icp->op == icmSnWrite) ? ICM_ERR_WR_FORMAT : ICM_ERR_RD_FORMAT;
	err |= sub;

	if ((icp->op == icmSnRead && !(icp->cflags & icmCFlagRdFormatWarn))
	 || (icp->op == icmSnWrite && !(icp->cflags & icmCFlagWrFormatWarn))
	 || sub >= ICM_FMTF) {
		icm_verr(icp, err, format, vp);
	} else {
		icp->cflags |= icp->op == icmSnRead ? icmCFlagRdWarning : icmCFlagWrWarning;
		if (icp->warning != NULL)
			icp->warning(icp, err, format, vp);
	}
	return icp->e.c;
}

/* Same as above with varargs (used by icc_check_sig() */
/* (Caller must set icp->op to icmSnRead or icmSnWrite) */
int icmFormatWarning(icc *icp, int sub, const char *format, ...) {
	int rv;
	va_list vp;
	va_start(vp, format);
	rv = icmVFormatWarning(icp, sub, format, vp);
	va_end(vp);
	return rv;
}

/* Same as above, but with buffer as argument for use in icmFBuf routines */
static int icmFmtWarn(icmFBuf *b, int sub, const char *format, ...) {
	int rv;
	va_list vp;
	va_start(vp, format);
	b->icp->op = b->op;
	rv = icmVFormatWarning(b->icp, sub, format, vp);
	va_end(vp);
	return rv;
}

/* Deal with a version mismatch error. */ 
/* sub is sub error/warning code */
/* Sets error code if Version errors are fatal. */
/* Sets the warning cflags if set to warn. */
/* (Caller must set icp->op to icmSnRead or icmSnWrite) */
/* If warn nz then force warning rather than error */
/* Return the current error code. */
static int icmVVersionWarning(icc *icp, int sub, int warn, const char *format, va_list vp) {
	int err;

	err = (icp->op == icmSnWrite) ? ICM_ERR_WR_FORMAT : ICM_ERR_RD_FORMAT;
	err |= ICM_FMT_MASK & sub;

	if (!warn &&
	 ((icp->op == icmSnRead && !(icp->cflags & icmCFlagRdVersionWarn))
	  || (icp->op == icmSnWrite && !(icp->cflags & icmCFlagWrVersionWarn)))) {
		icm_verr(icp, err, format, vp);

	} else {
		icp->cflags |= (icp->op == icmSnWrite) ? icmCFlagWrWarning : icmCFlagRdWarning;
		if (icp->warning != NULL)
			icp->warning(icp, err, format, vp);
	}
	return icp->e.c;
}

/* Same as above with varargs */
int icmVersionWarning(icc *icp, int sub, int warn, const char *format, ...) {
	int rv;
	va_list vp;
	va_start(vp, format);
	rv = icmVVersionWarning(icp, sub, warn, format, vp);
	va_end(vp);
	return rv;
}


/* Issue a quirk or general warning. */ 
/* if noset is nz, then don't set format warning flags (i.e. this is a usage warning) */
/* (Assumes cflags icmCFlagAllowQuirks is done by called if noset is z) */
/* Return the current error code. */
static int icmCQuirkWarning(icc *icp, int sub, int noset, const char *format, va_list vp) {
	int err;

	if (!noset)
		icp->cflags |= (icp->op == icmSnWrite) ? icmCFlagWrWarning : icmCFlagRdWarning;
	if (icp->warning != NULL)
		icp->warning(icp, sub, format, vp);
	return icp->e.c;
}

/* Same as above with varargs */
int icmQuirkWarning(icc *icp, int sub, int noset, const char *format, ...) {
	int rv;
	va_list vp;
	va_start(vp, format);
	rv = icmCQuirkWarning(icp, sub, noset, format, vp);
	va_end(vp);
	return rv;
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* Encoding checked serialisation functions */

/* Check screening encodings */
static int icmCheckScreening(icmFBuf *b, icScreening flags) {
	unsigned int valid;

	valid = icPrtrDefaultScreensTrue
	      | icLinesPerInch;
	if (flags & ~valid)
		icmFmtWarn(b, ICM_FMT_SCREEN, "Screen Encodings '0x%x' contains unknown flags",flags);
	return b->icp->e.c;
}

/* Screening Encoding uses 4 bytes */
static void icmSn_Screening32(icmFBuf *b, icScreening *p) {

	if (b->op == icmSnWrite) {
		if (icmCheckScreening(b, *p) != ICM_ERR_OK)
			return;
	}
	if (b->op & icmSnSerialise)
		icmSn_ui_UInt32(b, p);
	if (b->op == icmSnRead)
		icmCheckScreening(b, *p);
}


/* ICC Device Attributes encodings */
static int icmCheckDeviceAttributes(icmFBuf *b, icDeviceAttributes flags) {
	unsigned int valid;

	valid = icTransparency
	      | icMatte
	      | icNegative
	      | icBlackAndWhite;
	if (flags & ~valid)
		icmFmtWarn(b, ICM_FMT_DEVICE, "Device Attributes '0x%x' contains unknown flags",flags);
	return b->icp->e.c;
}

/* Device Attributes Encoding uses 8 bytes */
static void icmSn_DeviceAttributes64(icmFBuf *b, icmUInt64 *p) {

	if (b->op == icmSnWrite) {		/* ls 32 are defined by ICC */
		if (icmCheckDeviceAttributes(b, (icDeviceAttributes)p->l) != ICM_ERR_OK)
			return;
	}
	if (b->op & icmSnSerialise)
		icmSn_uii_UInt64(b, p);
	if (b->op == icmSnRead)
		icmCheckDeviceAttributes(b, p->l);
}


/* Check Profile Flags Encodings */
static int icmCheckProfileFlags(icmFBuf *b, icProfileFlags flags) {
	unsigned int valid;

	valid = icEmbeddedProfileTrue
	      | icUseWithEmbeddedDataOnly;
	if (flags & 0xffff & ~valid)
		icmFmtWarn(b, ICM_FMT_PROFFLGS, "Profile Flags Encodings '0x%x' contains unknown flags",
		                                                                        flags & 0xffff);
	return b->icp->e.c;
}

/* Profile Flags Encodings uses 4 bytes */
static void icmSn_ProfileFlags32(icmFBuf *b, icProfileFlags *p) {

	if (b->op == icmSnWrite) {
		if (icmCheckProfileFlags(b, *p) != ICM_ERR_OK)
			return;
	}
	if (b->op & icmSnSerialise)
		icmSn_ui_UInt32(b, p);
	if (b->op == icmSnRead)
		icmCheckProfileFlags(b, *p);
}


/* Check Ascii/Binary encoding for SigData type  */
static int icmCheckAsciiOrBinary(icmFBuf *b, icAsciiOrBinary *flags) {
	unsigned int valid;

	valid = icBinaryData;
	if (*flags & ~valid) {

		/* Correct ProfileMaker problem on read */
		if (b->op == icmSnRead && *flags == 0x01000000
		 && (b->icp->cflags & icmCFlagAllowQuirks) != 0) {
			icmQuirkWarning(b->icp, ICM_FMT_DATA_FLAG, 0, "Fixed SigDataType flag value 0x%x",
			                                                                          *flags);
			*flags = icBinaryData;
		} else {
			icmFmtWarn(b, ICM_FMT_ASCBIN,
		                   "Ascii or Binary data encodings '0x%x' contains unknown flags",*flags);
		}
	}
	return b->icp->e.c;
}

/* Ascii/Binary flag encoding uses 4 bytes */
static void icmSn_AsciiOrBinary32(icmFBuf *b, icAsciiOrBinary *p) {

	if (b->op == icmSnWrite) {
		if (icmCheckAsciiOrBinary(b, p) != ICM_ERR_OK)
			return;
	}
	if (b->op & icmSnSerialise)
		icmSn_ui_UInt32(b, p);
	if (b->op == icmSnRead)
		icmCheckAsciiOrBinary(b, p);
}


/* Check VideoCardGammaFormat encodings */
static int icmCheckVideoCardGammaFormat(icmFBuf *b, icVideoCardGammaFormat flags) {
	unsigned int valid;

	valid = icVideoCardGammaFormula;
	if (flags & ~valid)
		icmFmtWarn(b, ICM_FMT_GAMMA, "Video Card Gamma Format Encodings '0x%x' contains unknown flags",flags);
	return b->icp->e.c;
}

/* VideoCardGammaFormat uses 4 bytes */
static void icmSn_VideoCardGammaFormat32(icmFBuf *b, icVideoCardGammaFormat *p) {

	if (b->op == icmSnWrite) {
		if (icmCheckVideoCardGammaFormat(b, *p) != ICM_ERR_OK)
			return;
	}
	if (b->op & icmSnSerialise)
		icmSn_ui_UInt32(b, p);
	if (b->op == icmSnRead)
		icmCheckVideoCardGammaFormat(b, *p);
}


/* We don't check generic signatures, because custom or private tags are permitted */
static void icmSn_Sig32(icmFBuf *b, icSig *p) {
	icmSn_ui_UInt32(b, (unsigned int *)p);
}

/* We don't check tag signatures, because custom or private tags are permitted */
static void icmSn_TagSig32(icmFBuf *b, icTagSignature *p) {
	icmSn_ui_UInt32(b, (unsigned int *)p);
}

/* We don't check tag types, because custom or private tags are permitted */
static void icmSn_TagTypeSig32(icmFBuf *b, icTagTypeSignature *p) {
	icmSn_ui_UInt32(b, (unsigned int *)p);
}


/* Check a Color Space Signature */
static int icmCheckColorSpaceSig(icmFBuf *b, icColorSpaceSignature sig) {
	switch((icmSig)sig) {
		case icSigXYZData:
		case icSigLabData:
		case icSigLuvData:
		case icSigYCbCrData:
		case icSigYxyData:
		case icSigRgbData:
		case icSigGrayData:
		case icSigHsvData:
		case icSigHlsData:
		case icSigCmykData:
		case icSigCmyData:
			return b->icp->e.c;
	}

	switch((icmSig)sig) {
		case icSig2colorData:
		case icSig3colorData:
		case icSig4colorData:
		case icSig5colorData:
		case icSig6colorData:
		case icSig7colorData:
		case icSig8colorData:
		case icSig9colorData:
		case icSig10colorData:
		case icSig11colorData:
		case icSig12colorData:
		case icSig13colorData:
		case icSig14colorData:
		case icSig15colorData: {
			if (!icmVersInRange(b->icp, &icmtvrange_21_plus)) {
				icmFmtWarn(b, ICM_FMT_COLSP,
				   "ColorSpace Signature %s is not valid for file version %s (valid %s)\n",
				   icmtag2str(sig), icmProfileVers2str(b->icp),
				   icmTVersRange2str(&icmtvrange_21_plus));
			}

			return b->icp->e.c;
		}
	}

	/* ICCLIB extensions */
	if (b->icp->cflags & icmCFlagAllowExtensions) {
		switch((icmSig)sig) {

			case icSig1colorData:
			case icSigMch1Data:
			case icSigMch2Data:
			case icSigMch3Data:
			case icSigMch4Data:
			case icSigMch5Data:
			case icSigMch6Data:
			case icSigMch7Data:
			case icSigMch8Data:
			case icSigMch9Data:
			case icSigMchAData:
			case icSigMchBData:
			case icSigMchCData:
			case icSigMchDData:
			case icSigMchEData:
			case icSigMchFData:
			case icmSigYuvData:
			case icmSigLptData: {
//			case icmSigLData:
//			case icmSigYData:
				return b->icp->e.c;
			}
		}
	}

	icmFmtWarn(b, ICM_FMT_COLSP, "ColorSpace Signature %s is unknown",icmtag2str(sig));
	return b->icp->e.c;
}

/* Color Space signature uses 4 bytes */
static void icmSn_ColorSpaceSig32(icmFBuf *b, icColorSpaceSignature *p) {

	if (b->op == icmSnWrite) {
		if (icmCheckColorSpaceSig(b, *p) != ICM_ERR_OK)
			return;
	}
	if (b->op & icmSnSerialise)
		icmSn_ui_UInt32(b, (unsigned int *)p);
	if (b->op == icmSnRead)
		icmCheckColorSpaceSig(b, *p);
}


/* Check a Profile Class signature */
static int icmCheckProfileClassSig(icmFBuf *b, icProfileClassSignature sig) {

	switch((icmSig)sig) {
		case icSigInputClass:
		case icSigDisplayClass:
		case icSigOutputClass:
		case icSigLinkClass:
		case icSigColorSpaceClass:
		case icSigAbstractClass:
		case icSigNamedColorClass:
			return b->icp->e.c;
	}
	icmFmtWarn(b, ICM_FMT_PROF, "Profile Class Signature %s is unknown",icmtag2str(sig));
	return b->icp->e.c;
}

/* Profile Class signature uses 4 bytes */
static void icmSn_ProfileClassSig32(icmFBuf *b, icProfileClassSignature *p) {

	if (b->op == icmSnWrite) {
		if (icmCheckProfileClassSig(b, *p) != ICM_ERR_OK)
			return;
	}
	if (b->op & icmSnSerialise)
		icmSn_ui_UInt32(b, (unsigned int *)p);
	if (b->op == icmSnRead)
		icmCheckProfileClassSig(b, *p);
}


/* Check a Platform Signature */
static int icmCheckPlatformSig(icmFBuf *b, icPlatformSignature sig) {
	switch((icmSig)sig) {
		case icSigMacintosh:
		case icSigMicrosoft:
		case icSigSolaris:
		case icSigSGI:
		case icSigTaligent:
			return b->icp->e.c;
	}

	if (icmVersInRange(b->icp, &icmtvrange_22_plus)
	 && ((icmSig)sig) == icSigNoPlatform) {
		return b->icp->e.c;
	}

	/* ICCLIB extensions */
	if (b->icp->cflags & icmCFlagAllowExtensions) {
		switch((icmSig)sig) {
			case icmSig_nix:
				return b->icp->e.c;
		}
	}

	icmFmtWarn(b, ICM_FMT_PLAT, "Platform Signature %s is unknown",icmtag2str(sig));
	return b->icp->e.c;
}

/* Platform signature uses 4 bytes */
static void icmSn_PlatformSig32(icmFBuf *b, icPlatformSignature *p) {

	if (b->op == icmSnWrite) {
		if (icmCheckPlatformSig(b, *p) != ICM_ERR_OK)
			return;
	}
	if (b->op & icmSnSerialise)
		icmSn_ui_UInt32(b, (unsigned int *)p);
	if (b->op == icmSnRead)
		icmCheckPlatformSig(b, *p);
}


/* Check a Device Manufacturer Signature */
static int icmCheckDeviceManufacturerSig(icmFBuf *b, icDeviceManufacturerSignature sig) {
	/* Note that 0x0 is permitted */
	/* (Omitted due to difficulty in getting a machine readable list) */
	return b->icp->e.c;
}

/* Device Manufacturer signature uses 4 bytes */
static void icmSn_DeviceManufacturerSig32(icmFBuf *b, icDeviceManufacturerSignature *p) {

	if (b->op == icmSnWrite) {
		if (icmCheckDeviceManufacturerSig(b, *p) != ICM_ERR_OK)
			return;
	}
	if (b->op & icmSnSerialise)
		icmSn_ui_UInt32(b, (unsigned int *)p);
	if (b->op == icmSnRead)
		icmCheckDeviceManufacturerSig(b, *p);
}


/* Check a Device Model Signature */
static int icmCheckDeviceModel(icmFBuf *b, icDeviceModelSignature sig) {
	/* Note that 0x0 is permitted */
	/* (Omitted due to difficulty in getting a machine readable list) */
	return b->icp->e.c;
}

/* DeviceModel signature uses 4 bytes */
static void icmSn_DeviceModel32(icmFBuf *b, icDeviceModelSignature *p) {

	if (b->op == icmSnWrite) {
		if (icmCheckDeviceModel(b, *p) != ICM_ERR_OK)
			return;
	}
	if (b->op & icmSnSerialise)
		icmSn_ui_UInt32(b, (unsigned int *)p);
	if (b->op == icmSnRead)
		icmCheckDeviceModel(b, *p);
}


/* Check a CMM Signature */
static int icmCheckCMMSig(icmFBuf *b, icCMMSignature sig) {

	switch((icmSig)sig) {
		case icSigAdobeCMM:
		case icSigAgfaCMM:
		case icSigAppleCMM:
		case icSigColorGearCMM:
		case icSigColorGearLiteCMM:
		case icSigColorGearCCMM:
		case icSigEFICMM:
		case icSigFujiFilmCMM:
		case icSigExactScanCMM:
		case icSigHarlequinCMM:
		case icSigArgyllCMM:
		case icSigLogoSyncCMM:
		case icSigHeidelbergCMM:
		case icSigLittleCMSCMM:
		case icSigReflccMAXCMM:
		case icSigDemoIccMAXCMM:
		case icSigKodakCMM:
		case icSigKonicaMinoltaCMM:
		case icSigMicrosoftCMM:
		case icSigMutohCMM:
		case icSigOnyxGraphicsCMM:
		case icSigDeviceLinkCMM:
		case icSigSampleICCCMM:
		case icSigToshibaCMM:
		case icSigImagingFactoryCMM:
		case icSigVivoCMM:
		case icSigWareToGoCMM:
		case icSigZoranCMM:
			return b->icp->e.c;
	}

	icmFmtWarn(b, ICM_FMT_PLAT, "CMM Signature %s is unknown",icmtag2str(sig));
	return b->icp->e.c;
}

/* CMM signature uses 4 bytes */
static void icmSn_CMMSig32(icmFBuf *b, icCMMSignature *p) {

	if (b->op == icmSnWrite) {
		if (icmCheckCMMSig(b, *p) != ICM_ERR_OK)
			return;
	}
	if (b->op & icmSnSerialise)
		icmSn_ui_UInt32(b, (unsigned int *)p);
	if (b->op == icmSnRead)
		icmCheckCMMSig(b, *p);
}



/* Check a technology signature */
static int icmCheckTechnologySig(icmFBuf *b, icTechnologySignature sig) {

	switch((icmSig)sig) {
		case icSigTechnologyUnknown:
		case icSigDigitalCamera:
		case icSigFilmScanner:
		case icSigReflectiveScanner:
		case icSigInkJetPrinter:
		case icSigThermalWaxPrinter:
		case icSigElectrophotographicPrinter:
		case icSigElectrostaticPrinter:
		case icSigDyeSublimationPrinter:
		case icSigPhotographicPaperPrinter:
		case icSigFilmWriter:
		case icSigVideoMonitor:
		case icSigVideoCamera:
		case icSigProjectionTelevision:
		case icSigCRTDisplay:
		case icSigPMDisplay:
		case icSigAMDisplay:
		case icSigPhotoCD:
		case icSigPhotoImageSetter:
		case icSigGravure:
		case icSigOffsetLithography:
		case icSigSilkscreen:
		case icSigFlexography:
			return b->icp->e.c;
	}
	icmFmtWarn(b, ICM_FMT_TECH, "Technology Signature %s is unknown",icmtag2str(sig));
	return b->icp->e.c;
}

/* Technology signature uses 4 bytes */
static void icmSn_TechnologySig32(icmFBuf *b, icTechnologySignature *p) {

	if (b->op == icmSnWrite) {
		if (icmCheckTechnologySig(b, *p) != ICM_ERR_OK)
			return;
	}
	if (b->op & icmSnSerialise)
		icmSn_ui_UInt32(b, (unsigned int *)p);
	if (b->op == icmSnRead)
		icmCheckTechnologySig(b, *p);
}


/* Check a Measurement Geometry */
static int icmCheckMeasurementGeometry(icmFBuf *b, icMeasurementGeometry sig) {

	switch((icmSig)sig) {
		case icGeometryUnknown:
		case icGeometry045or450:
		case icGeometry0dord0:
			return b->icp->e.c;
	}

	icmFmtWarn(b, ICM_FMT_MESGEOM, "Measurement Geometry 0x%x is unknown",sig);
	return b->icp->e.c;
}

/* Measurement Geometry uses 4 bytes */
static void icmSn_MeasurementGeometry32(icmFBuf *b, icMeasurementGeometry *p) {

	if (b->op == icmSnWrite) {
		if (icmCheckMeasurementGeometry(b, *p) != ICM_ERR_OK)
			return;
	}
	if (b->op & icmSnSerialise)
		icmSn_ui_UInt32(b, (unsigned int *)p);
	if (b->op == icmSnRead)
		icmCheckMeasurementGeometry(b, *p);
}

/* Check a Rendering Intent */
static int icmCheckRenderingIntent(icmFBuf *b, icRenderingIntent sig) {

	switch(sig & 0xffff) {
		case icPerceptual:
		case icRelativeColorimetric:
		case icSaturation:
		case icAbsoluteColorimetric:
			return b->icp->e.c;
	}

	icmFmtWarn(b, ICM_FMT_INTENT, "Rendering Intent 0x%x is unknown",sig & 0xffff);
	return b->icp->e.c;
}

/* Rendering Intent uses 4 bytes */
static void icmSn_RenderingIntent32(icmFBuf *b, icRenderingIntent *p) {

	if (b->op == icmSnWrite) {
		if (icmCheckRenderingIntent(b, *p) != ICM_ERR_OK)
			return;
	}
	if (b->op & icmSnSerialise)
		icmSn_ui_UInt32(b, (unsigned int *)p);
	if (b->op == icmSnRead)
		icmCheckRenderingIntent(b, *p);
}


/* Check a Spot Shape */
static int icmCheckSpotShape(icmFBuf *b, icSpotShape sig) {

	switch((icmSig)sig) {
		case icSpotShapeUnknown:
		case icSpotShapePrinterDefault:
		case icSpotShapeRound:
		case icSpotShapeDiamond:
		case icSpotShapeEllipse:
		case icSpotShapeLine:
		case icSpotShapeSquare:
		case icSpotShapeCross:
			return b->icp->e.c;
	}

	icmFmtWarn(b, ICM_FMT_SPSHAPE, "Spot Shape 0x%x is unknown",sig);
	return b->icp->e.c;
}

/*  Spot Shape uses 4 bytes */
static void icmSn_SpotShape32(icmFBuf *b, icSpotShape *p) {

	if (b->op == icmSnWrite) {
		if (icmCheckSpotShape(b, *p) != ICM_ERR_OK)
			return;
	}
	if (b->op & icmSnSerialise)
		icmSn_ui_UInt32(b, (unsigned int *)p);
	if (b->op == icmSnRead)
		icmCheckSpotShape(b, *p);
}


/* Check a Standard Observer */
static int icmCheckStandardObserver(icmFBuf *b, icStandardObserver sig) {

	switch((icmSig)sig) {
		case icStdObsUnknown:
		case icStdObs1931TwoDegrees:
		case icStdObs1964TenDegrees:
			return b->icp->e.c;
	}

	icmFmtWarn(b, ICM_FMT_STOBS, "Standard Observer 0x%x is unknown",sig);
	return b->icp->e.c;
}

/*  Standard Observer uses 4 bytes */
static void icmSn_StandardObserver32(icmFBuf *b, icStandardObserver *p) {

	if (b->op == icmSnWrite) {
		if (icmCheckStandardObserver(b, *p) != ICM_ERR_OK)
			return;
	}
	if (b->op & icmSnSerialise)
		icmSn_ui_UInt32(b, (unsigned int *)p);
	if (b->op == icmSnRead)
		icmCheckStandardObserver(b, *p);
}


/* Check a Predefined Illuminant */
static int icmCheckPredefinedIlluminant(icmFBuf *b, icIlluminant sig) {

	switch((icmSig)sig) {
		case icIlluminantUnknown:
		case icIlluminantD50:
		case icIlluminantD65:
		case icIlluminantD93:
		case icIlluminantF2:
		case icIlluminantD55:
		case icIlluminantA:
		case icIlluminantEquiPowerE:
		case icIlluminantF8:
			return b->icp->e.c;
	}

	icmFmtWarn(b, ICM_FMT_PRILL, "Predefined Illuminant 0x%x is unknown",sig);
	return b->icp->e.c;
}


/*  PredefinedIlluminant uses 4 bytes */
static void icmSn_PredefinedIlluminant32(icmFBuf *b, icIlluminant *p) {

	if (b->op == icmSnWrite) {
		if (icmCheckPredefinedIlluminant(b, *p) != ICM_ERR_OK)
			return;
	}
	if (b->op & icmSnSerialise)
		icmSn_ui_UInt32(b, (unsigned int *)p);
	if (b->op == icmSnRead)
		icmCheckPredefinedIlluminant(b, *p);
}


/* Check a Language Code */
static int icmCheckLanguageCode(icmFBuf *b, icEnumLanguageCode sig) {
	unsigned int sigv = (unsigned int)sig;

	/* We only have a selection of codes in the enumeration, */
	/* and they can be changed in the ISO registry anyway. */
	/* Do a sanity check that they are two lower case alphabetic letters */

	if ((sigv & 0xff) >= 'a' && (sigv & 0xff)  <= 'z'
	 && ((sigv >> 8) & 0xff) >= 'a' && ((sigv >> 8) & 0xff)  <= 'z'
	 && (sigv >> 16) == 0) {
		return b->icp->e.c;
	}

	icmFmtWarn(b, ICM_FMT_LANG, "Language Code 0x%x is unknown",sig);
	return b->icp->e.c;
}

/*  Language Code uses 2 bytes */
static void icmSn_LanguageCode16(icmFBuf *b, icEnumLanguageCode *p) {

	if (b->op == icmSnWrite) {
		if (icmCheckLanguageCode(b, *p) != ICM_ERR_OK)
			return;
	}
	if (b->op & icmSnSerialise)
		icmSn_ui_UInt16(b, (unsigned int *)p);
	if (b->op == icmSnRead)
		icmCheckLanguageCode(b, *p);
}


/* Check a Region Code */
static int icmCheckRegionCode(icmFBuf *b, icEnumRegionCode sig) {
	unsigned int sigv = (unsigned int)sig;

	/* We only have a selection of codes in the enumeration, */
	/* and they can be changed in the ISO registry anyway. */
	/* Do a sanity check that they are two upper case alphabetic letters */

	if ((sigv & 0xff) >= 'A' && (sigv & 0xff)  <= 'Z'
	 && ((sigv >> 8) & 0xff) >= 'A' && ((sigv >> 8) & 0xff)  <= 'Z'
	 && (sigv >> 16) == 0) {
		return b->icp->e.c;
	}

	icmFmtWarn(b, ICM_FMT_REGION, "Region Code 0x%x is unknown",sig);
	return b->icp->e.c;
}

/*  Region Code uses 2 bytes */
static void icmSn_RegionCode16(icmFBuf *b, icEnumRegionCode *p) {

	if (b->op == icmSnWrite) {
		if (icmCheckRegionCode(b, *p) != ICM_ERR_OK)
			return;
	}
	if (b->op & icmSnSerialise)
		icmSn_ui_UInt16(b, (unsigned int *)p);
	if (b->op == icmSnRead)
		icmCheckRegionCode(b, *p);
}


/* Check a Microsoft platform Device Settings ID Signature */
static int icmCheckDevSetMsftIDSig(icmFBuf *b, icDevSetMsftIDSignature sig) {
	unsigned int sigv = (unsigned int)sig;

	switch((icmSig)sig) {
    	case icSigMsftResolution:
    	case icSigMsftMedia:
    	case icSigMsftHalftone:
			return b->icp->e.c;
	}
	icmFmtWarn(b, ICM_FMT_MSDEVID, "Microsoft platform Device Settings ID Signature %s is unknown",icmtag2str(sig));
	return b->icp->e.c;
}

/* Microsoft platform Device Settings ID signature uses 4 bytes */
static void icmSn_DevSetMsftIDSig32(icmFBuf *b, icDevSetMsftIDSignature *p) {

	if (b->op == icmSnWrite) {
		if (icmCheckDevSetMsftIDSig(b, *p) != ICM_ERR_OK)
			return;
	}
	if (b->op & icmSnSerialise)
		icmSn_ui_UInt32(b, (unsigned int *)p);
	if (b->op == icmSnRead)
		icmCheckDevSetMsftIDSig(b, *p);
}


/* Check a Microsoft platform Media Type Encoding */
static int icmCheckDevSetMsftMedia(icmFBuf *b, icDevSetMsftMedia sig) {

	/* We're allowing for 256 user settings, but this is */
	/* arbitrary, and not defined in the ICC spec. */
	if (sig > icMsftMediaUser1 && sig < (icMsftMediaUser1 + 255))
		sig = icMsftMediaUser1;

	switch((icmSig)sig) {
    	case icMsftMediaStandard:
    	case icMsftMediaTrans:
    	case icMsftMediaGloss:
    	case icMsftMediaUser1:
			return b->icp->e.c;
	}
	icmFmtWarn(b, ICM_FMT_MSMETYP, "Microsoft platform Media Type Encoding 0x%x is unknown",sig);
	return b->icp->e.c;
}

/* Microsoft platform Media Type Encoding uses 4 bytes */
static void icmSn_DevSetMsftMedia32(icmFBuf *b, icDevSetMsftMedia *p) {

	if (b->op == icmSnWrite) {
		if (icmCheckDevSetMsftMedia(b, *p) != ICM_ERR_OK)
			return;
	}
	if (b->op & icmSnSerialise)
		icmSn_ui_UInt32(b, (unsigned int *)p);
	if (b->op == icmSnRead)
		icmCheckDevSetMsftMedia(b, *p);
}


/* Check a Microsoft platform Halftone Encoding */
static int icmCheckDevSetMsftDither(icmFBuf *b, icDevSetMsftDither sig) {

	/* We're allowing for 256 user settings, but this is */
	/* arbitrary, and not defined in the ICC spec. */
	if (sig > icMsftDitherUser1 && sig < (icMsftDitherUser1 + 255)) 
		sig = icMsftDitherUser1;
	switch((icmSig)sig) {
    	case icMsftDitherNone:
    	case icMsftDitherCoarse:
    	case icMsftDitherFine:
    	case icMsftDitherLineArt:
    	case icMsftDitherErrorDiffusion:
    	case icMsftDitherReserved6:
    	case icMsftDitherReserved7:
    	case icMsftDitherReserved8:
    	case icMsftDitherReserved9:
    	case icMsftDitherGrayScale:
    	case icMsftDitherUser1:
			return b->icp->e.c;
	}
	icmFmtWarn(b, ICM_FMT_MSHALFTN, "Microsoft platform Halftone Encoding 0x%x is unknown",sig);
	return b->icp->e.c;
}

/* Microsoft platform Halftone Encoding uses 4 bytes */
static void icmSn_DevSetMsftDither32(icmFBuf *b, icDevSetMsftDither *p) {

	if (b->op == icmSnWrite) {
		if (icmCheckDevSetMsftDither(b, *p) != ICM_ERR_OK)
			return;
	}
	if (b->op & icmSnSerialise)
		icmSn_ui_UInt32(b, (unsigned int *)p);
	if (b->op == icmSnRead)
		icmCheckDevSetMsftDither(b, *p);
}


/* Check a Measurement units for the icResponseCurveSet16Type signature */
static int icmCheckMeasUnitsSig(icmFBuf *b, icMeasUnitsSig sig) {
	unsigned int sigv = (unsigned int)sig;

	switch((icmSig)sig) {
    	case icSigStatusA:
    	case icSigStatusE:
    	case icSigStatusI:
    	case icSigStatusT:
    	case icSigStatusM:
    	case icSigDN:
    	case icSigDNP:
    	case icSigDNN:
    	case icSigDNNP:
			return b->icp->e.c;
	}
	icmFmtWarn(b, ICM_FMT_RESCVUNITS, "ResponseCurve Measurement units Signature %s is unknown",
	                                                                             icmtag2str(sig));
	return b->icp->e.c;
}

/* Measurement units for the icResponseCurveSet16Type signature uses 4 bytes */
static void icmSn_MeasUnitsSig32(icmFBuf *b, icMeasUnitsSig *p) {

	if (b->op == icmSnWrite) {
		if (icmCheckMeasUnitsSig(b, *p) != ICM_ERR_OK)
			return;
	}
	if (b->op & icmSnSerialise)
		icmSn_ui_UInt32(b, (unsigned int *)p);
	if (b->op == icmSnRead)
		icmCheckMeasUnitsSig(b, *p);
}


/* Check Phosphor and Colorant Encodings used in chromaticity type */
static int icmCheckPhCol(icmFBuf *b, icPhColEncoding sig) {

	switch((icmSig)sig) {
		case icPhColUnknown:
		case icPhColITU_R_BT_709:
		case icPhColSMPTE_RP145_1994:
		case icPhColEBU_Tech_3213_E:
		case icPhColP22:
		case icPhColP3:
		case icPhColITU_R_BT2020:
			return b->icp->e.c;
	}
	icmFmtWarn(b, ICM_FMT_COLPHOS, "Phosphor and Colorant Encoding 0x%x is unknown",sig);
	return b->icp->e.c;
}

/* Phosphor and Colorant Encodings uses 2 bytes */
static void icmSn_PhCol16(icmFBuf *b, icPhColEncoding *p) {

	if (b->op == icmSnWrite) {
		if (icmCheckPhCol(b, *p) != ICM_ERR_OK)
			return;
	}
	if (b->op & icmSnSerialise)
		icmSn_ui_UInt16(b, (unsigned int *)p);
	if (b->op == icmSnRead)
		icmCheckPhCol(b, *p);
}



/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* Compound serialisation functions */

/* XYZ number uses 12 bytes */
static void icmSn_XYZNumber12b(icmFBuf *b, icmXYZNumber *p) {
	if (b->op & icmSnSerialise) {
		icmSn_d_S15Fix16(b, &p->XYZ[0]);
		icmSn_d_S15Fix16(b, &p->XYZ[1]);
		icmSn_d_S15Fix16(b, &p->XYZ[2]);
	}
}

/* xy coordinate uses 8 bytes */
static void icmSn_xyCoordinate8b(icmFBuf *b, icmxyCoordinate *p) {
	if (b->op & icmSnSerialise) {
		icmSn_d_U16Fix16(b, &p->xy[0]);
		icmSn_d_U16Fix16(b, &p->xy[1]);
	}
}

/* PositionNumber uses 8 bytes */
static void icmSn_PositionNumbe8b(icmFBuf *b, icmPositionNumber *p) {
	if (b->op & icmSnSerialise) {
		icmSn_ui_UInt32(b, &p->offset);
		icmSn_ui_UInt32(b, &p->size);
	}
}

/* Response16Number uses 8 bytes */
static void icmSn_Response16Number8b(icmFBuf *b, icmResponse16Number *p) {
	if (b->op & icmSnSerialise) {
		icmSn_d_NFix16(b, &p->deviceValue);
		icmSn_pad(b, 2);
		icmSn_d_S15Fix16(b, &p->measurement);
	}
}


static int icmCheckFixDateTimeNumber(icmFBuf *b, icmDateTimeNumber *p) {

	/* Sanity check the date and time */
	if (p->year >= 1900 && p->year <= 3000
	 && p->month != 0 && p->month <= 12
	 && p->day != 0 && p->day <= 31
	 && p->hours <= 23
	 && p->minutes <= 59
	 && p->seconds <= 59) {
		return b->icp->e.c;
	}

	/* Correct the date/time on read */ 
	if (b->op == icmSnRead && (b->icp->cflags & icmCFlagAllowQuirks) != 0) {

		/* Check for Adobe swap problem */
		if (p->month >= 1900 && p->month <= 3000
		 && p->year != 0 && p->year <= 12
		 && p->hours != 0 && p->hours <= 31
		 && p->day <= 23
		 && p->seconds <= 59
		 && p->minutes <= 59) {
			unsigned int tt; 
	
			icmQuirkWarning(b->icp, ICM_FMT_DATETIME, 0, "Fixed bad DateTime value '%s'",
			                                                     icmDateTimeNumber2str(p));

			/* Correct Adobe's faulty profile */
			tt = p->month; p->month = p->year; p->year = tt;
			tt = p->hours; p->hours = p->day; p->day = tt;
			tt = p->seconds; p->seconds = p->minutes; p->minutes = tt;
			return b->icp->e.c;
		}

		icmQuirkWarning(b->icp, ICM_FMT_DATETIME, 0, "Limited bad DateTime value '%s'",
			                                                     icmDateTimeNumber2str(p));

		/* Hmm. some other sort of corruption. Limit values to sane */
		if (p->year < 1900) {
			if (p->year < 100)			/* Hmm. didn't use 4 digit year, guess it's 19xx ? */
				p->year += 1900;
			else
				p->year = 1900;
		} else if (p->year > 3000)
			p->year = 3000;
	
		if (p->month == 0)
			p->month = 1;
		else if (p->month > 12)
			p->month = 12;
	
		if (p->day == 0)
			p->day = 1;
		else if (p->day > 31)
			p->day = 31;
	
		if (p->hours > 23)
			p->hours = 23;
	
		if (p->minutes > 59)
			p->minutes = 59;
	
		if (p->seconds > 59)
			p->seconds = 59;


	} else {
		icmFmtWarn(b, ICM_FMT_DATETIME, "Bad date time '%s'", icmDateTimeNumber2str(p));
	}
	return b->icp->e.c;
}

/* DateTime uses 12 bytes */
static void icmSn_DateTimeNumber12b(icmFBuf *b, icmDateTimeNumber *p) {

	if (b->op == icmSnWrite) {
		if (icmCheckFixDateTimeNumber(b, p) != ICM_ERR_OK)
			return;
	}

	if (b->op & icmSnSerialise) {
		icmSn_ui_UInt16(b, &p->year);
		icmSn_ui_UInt16(b, &p->month);
		icmSn_ui_UInt16(b, &p->day);
		icmSn_ui_UInt16(b, &p->hours);
		icmSn_ui_UInt16(b, &p->minutes);
		icmSn_ui_UInt16(b, &p->seconds);
	}

	if (b->op == icmSnRead)
		icmCheckFixDateTimeNumber(b, p);
}

/* Check a file version number is within the known valid range */
static int icmCheckVersionNumber(icmFBuf *b, icmVers *p) {
	if (p->majv != 2 && p->majv  != 4) {
		icmFmtWarn(b, ICM_FMT_MAJV, "Major version '%d' is not recognized",p->majv);
	} else {
		if (p->majv == 2 && p->minv > 4) {
			icmFmtWarn(b, ICM_FMT_MINV, "Minor version '%d.%d' is not recognized",
			                                                            p->majv,p->minv);
		}
		/* There are no known bfv != 0, but this should be ignored */
	}
	return b->icp->e.c;
}

/* Version Number uses 4 bytes */
static void icmSn_VersionNumber4b(icmFBuf *b, icmVers *p) {
	unsigned int t1, t2;

	if (b->op == icmSnWrite) {
		if (icmCheckVersionNumber(b, p) != ICM_ERR_OK)
			return;
	    t1 = ((p->majv/10) << 4) + (p->majv % 10);
		t2 = (p->minv << 4) + p->bfv;
	}
	if (b->op & icmSnSerialise) {
		icmSn_ui_UInt8(b, &t1);				/* 0: Raw major version number */
		icmSn_ui_UInt8(b, &t2);				/* 1: Raw minor/bfv version number */
		icmSn_pad(b, 2);					/* 2-3: Zero */
	}
	if (b->op == icmSnRead) {
		if ((t1 & 0xf) > 9 || ((t1 >> 4) & 0xf) > 9) {
			icm_err(b->icp, ICM_ERR_ENCODING, "Major Version BCD coding error (0x%x)",t1);
			return;
		}
		if ((t2 & 0xf) > 9 || ((t2 >> 4) & 0xf) > 9) {
			icm_err(b->icp, ICM_ERR_ENCODING, "Minor/Bugfix Version BCD coding error (0x%x)",t2);
			return;
		}
	    p->majv  = (t1 >> 4) * 10 + (t1 & 0xf);	/* Integer major version number */
	    p->minv  = (t2 >> 4);					/* Integer minor version number */
		p->bfv   = (t2 & 0xf);					/* Integer bug fix version number */
		icmCheckVersionNumber(b, p);
	}
}

/* ---------------------------------------------------------------- */
/* Higher level text string serialization support code */

/* printf magic to add padding at start of output */
/* (Implemented in embeddable tagtypes) */
#define PAD(XX) "%*s" XX ,pad,""

/* Serialise a ASCIIZ string. Houskeeping of the tagtype offsets */
/* and allocations is taken care of. */
static void icmSn_ASCIIZ(
	icmFBuf *b,
	unsigned int  *p_count,		/* 'C' structure ASCIIZ string current allocation size */
	unsigned int  *pcount,		/* 'C' structure requested/current size */
	char         **pdesc,		/* 'C' structure string (Assumed possible UTF-8) */
	unsigned int  *pfcount,		/* String file size, including nul. Should be NULL if fxlen != 0 */
	int			   fxlen,		/* If > 0, fixed size buffer length.  */
								/* If < 0, maximum buffer length.  */
	char *id					/* String identifying tagtype or function */
) {
	unsigned int fcount = 0;	/* Dummy file count if pfcount == NULL */
	icmUTFerr utferr;
	
	if (fxlen != 0 && pfcount == NULL) {
		fcount = fxlen > 0 ? fxlen : -fxlen;
		pfcount = &fcount;
	} 

	if (b->op == icmSnSize || b->op == icmSnWrite) {

		*pfcount = icmUTF8toASCIIZSn(&utferr, b, (icmUTF8 *)*pdesc, *pcount, fxlen);

		if (utferr != icmUTF_ok) {
			icmQuirkWarning(b->icp, 1, 1, "%s write: utf-8 to ASCIIZ translate returned error '%s'",
			                                                              id,icmUTFerr2str(utferr));
			return;
		}

	} else {	/* Alloc/Free/Read */

		/* Figure length needed for UTF-8 */
		if (b->op == icmSnRead) {
			ORD32 off;

			off = b->get_off(b);
			*pcount = icmASCIIZSntoUTF8(NULL, NULL, b, *pfcount, fxlen);
			b->aoff(b, off);
		}

		/* Allocate UTF-8 string on read or for API client */
		if (icmArrayAllocResize(b, p_count, pcount,
	   	 (void **)pdesc, sizeof(icmUTF8), id))
			return;

		/* Read ASCIIZ and (possibly) repair it */
		if (b->op == icmSnRead) {
			icmASCIIZSntoUTF8(&utferr, (icmUTF8 *)*pdesc, b, *pfcount, fxlen);

			if (utferr != icmUTF_ok) {
				if ((b->icp->cflags & icmCFlagAllowQuirks) == 0) {
					icmFormatWarning(b->icp, ICM_FMT_TEXT_UERR,"%s read: ASCIIZ to utf-8 translate returned error '%s'",id,icmUTFerr2str(utferr));
					return;
				}
				icmQuirkWarning(b->icp, ICM_FMT_TEXT_UERR, 0, "%s read: ASCIIZ to utf-8 translate returned error '%s'",id,icmUTFerr2str(utferr)); 
			}
		}
		ICMSNFREEARRAY(b, *p_count, *pdesc)
	}
}

/* Dump a text description of an ASCIIZ string */
static void icmASCIIZ_dump(
	char *desc,			/* ASCIIZ */
	unsigned int count,	/* length */
	icmFile *op,		/* Output to dump to */
	int verb,			/* Verbosity level */
	int pad				/* Extra padding at start */
) {
	unsigned int i, r, c;

	if (verb <= 0)
		return;

	if (desc != NULL && count > 0) {
		unsigned int size = count > 0 ? count-1 : 0;
		op->printf(op,PAD("  ASCII data, length %u chars:\n"),count);

		i = 0;
		for (r = 1;; r++) {		/* count rows */
			if (i >= size) {
				op->printf(op,"\n");
				break;
			}
			if (r > 1 && verb <= 1) {
				op->printf(op,PAD("    ...\n"));
				break;			/* Print 1 row if not verbose */
			}
			c = 1;
			op->printf(op,PAD("    0x%04lx: "),i);
			c += 10;
			while (i < size && c < 75) {
				if (isprint(desc[i])) {
					op->printf(op,"%c",desc[i]);
					c++;
				} else {
					op->printf(op,"\\%03o",desc[i]);
					c += 4;
				}
				i++;
			}
			if (i < size)
				op->printf(op,"\n");
		}
	} else {
		op->printf(op,PAD("  No ASCII data\n"));		/* Shouldn't happen ? */
	}
}

/* Serialise a Unicode string. Houskeeping of the tagtype offsets */
/* and allocations is taken care of. */
/* If nonul is nz, then UTF-16 has not null terminator, and uc16count is bytes. */
/* If !nonul write and if utf-8 is NULL or zero length, then no utf-16 is written. */
/* If !nonul read and if utf-16 is zero length, then the utf-8 will be NULL and zero length. */
static void icmSn_Unicode(
	icmFBuf *b,
	unsigned int  *p_uc8count,	/* 'C' structure UTF-8 string allocation */
	unsigned int  *puc8count,	/* 'C' structure UTF-8 string wanted size */
	icmUTF8       **puc8desc,	/* 'C' structure UTF-8 string buffer */
	unsigned int   *puc16off,	/* UTF-16 file offset (may be NULL) */
	unsigned int *puc16count,	/* UTF-16 file length (chars or bytes) */
	char *id,					/* String identifying tagtype or function */
	int nonul
) {
	icmUTFerr utferr = icmUTF_ok;

	if (b->op == icmSnSize || b->op == icmSnWrite) {

		if (puc16off != NULL)
			*puc16off = b->get_off(b);

		if (!nonul && (*puc8desc == NULL || *puc8count == 0)) 
			*puc16count = 0;
		else 
			*puc16count = icmUTF8toUTF16Sn(&utferr, b, *puc8desc, *puc8count, nonul) / (nonul ? 1 : 2);

		if (utferr != icmUTF_ok) {
			icm_err(b->icp, 1,"%s %s: utf-8 to utf-16 translate returned error '%s'",id,b->op == icmSnSize ? "size" : "write", icmUTFerr2str(utferr));
			return;
		}

	} else {	/* ReSize/Free/Read */

		/* Figure length needed for UTF-8 */
		if (b->op == icmSnRead) {

			if (puc16off != NULL)
				b->aoff(b, *puc16off);		/* Go to UTF-16 string location */

			if (!nonul && *puc16count == 0)
				*puc8count = 0;
			else {
				ORD32 off;
	
				off = b->get_off(b);
				*puc8count = icmUTF16SntoUTF8(NULL, NULL, b, *puc16count * (nonul ? 1 : 2), nonul);
				b->aoff(b, off);
			}
		}

		/* Allocate UTF-8 string on read or for API client */
		if (icmArrayAllocResize(b, p_uc8count, puc8count,
	   	 (void **)puc8desc, sizeof(icmUTF8), id))
			return;

		if (b->op == icmSnResize && !nonul && *p_uc8count == 0)
			*puc8desc = NULL;		/* Overwrite ZMARKER */

		/* Read UTF-16 and translate to UTF-8 */
		if (b->op == icmSnRead && (nonul || *puc16count > 0)) {
			icmUTF16SntoUTF8(&utferr, *puc8desc, b, *puc16count * (nonul ? 1 : 2), nonul);

			if (utferr != icmUTF_ok) {
				if ((b->icp->cflags & icmCFlagAllowQuirks) == 0) {
					icmFormatWarning(b->icp, ICM_FMT_TEXT_UERR,"%s read: utf-16 to utf-8 translate returned error '%s'",id,icmUTFerr2str(utferr));
					return;
				}
				icmQuirkWarning(b->icp, ICM_FMT_TEXT_UERR, 0, "%s read: utf-16 to utf-8 translate returned error '%s'",id,icmUTFerr2str(utferr)); 
			}
		}
		ICMSNFREEARRAY(b, *p_uc8count, *puc8desc)
	}
}

/* Dump a text description of a unicode string */
static void icmUnicode_dump(
	icmUTF8 *uc8desc,		/* UTF-8 unicode */
	unsigned int uc8count,	/* UTF-8 length */
	unsigned int lang,		/* Language code, icMaxEnumLanguageCode if none */
	unsigned int country,	/* Country, icMaxEnumRegionCode if none */
	unsigned int uc16off,	/* UTF-16 offset, icmMaxOff if none */
	unsigned int uc16count,	/* UTF-16 character count */
	icmFile *op,			/* Output to dump to */
	int verb,				/* Verbosity level */
	int pad					/* Extra padding at start */
) {
	unsigned int i, r, c;

	if (verb <= 0)
		return;

	/* Don't want to dump Unicode or ScriptCode to console, as its interpretation */
	/* will be unknown. */
	if (uc8desc != NULL && uc8count > 0) {
		unsigned int size = uc8count > 0 ? uc8count-1 : 0;

		op->printf(op,PAD("  Unicode Data, "));

		if (lang != icMaxEnumLanguageCode)
			op->printf(op,"Language code '%s', ",icmLanguageCode2str(lang));

		if (country != icMaxEnumRegionCode)
			op->printf(op,"Country code '%s', ",icmRegionCode2str(country));

		op->printf(op,"UTF-16 ");

		if (uc16off != icmMaxOff)
			op->printf(op,"offset %u, ",uc16off);

		op->printf(op,"length %u chars\n",uc16count);

		op->printf(op,PAD("  UTF-8 unicode translation length %u chars:\n"), size);
		i = 0;
		for (r = 1;; r++) {		/* count rows */
			if (i >= size) {
				op->printf(op,"\n");
				break;
			}
			if (r > 1 && verb <= 1) {
				op->printf(op,PAD("    ...\n"));
				break;			/* Print 1 row if not verbose */
			}
			c = 1;
			op->printf(op,PAD("    0x%04lx: "),i);
			c += 10;
			while (i < size && c < 75) {
				if (isprint(uc8desc[i])) {
					op->printf(op,"%c",uc8desc[i]);
					c++;
				} else {
					op->printf(op,"\\%03o",uc8desc[i]);
					c += 4;
				}
				i++;
			}
			if (i < size)
				op->printf(op,"\n");
		}
	} else {
		op->printf(op,PAD("  No Unicode data\n"));
	}
}

/* Serialise a ScriptCode string. Houskeeping of the tagtype offsets */
/* and allocations is taken care of. */
/* If write and desc is NULL or zero length, then no ScriptCode is written. */
/* If read and ScriptCode is zero length, then the scDesc will be NULL and zero length. */
/* (In principle we could do a ScriptCode<->Unicode translation using Apples conversion */
/*  tables and documentation. This is fairly complex. ICCV2 profiles with non-Roman */
/*  ScriptCode seem very rare in modern use though...) */
static void icmSn_ScriptCode(
	icmFBuf *b,
	unsigned int  *p_count,		/* 'C' structure ScriptCode string current allocation size */
	unsigned int  *pcount,		/* 'C' structure requested/current size */
	unsigned char **pdesc,		/* 'C' structure string */
	unsigned int  *pocount,		/* Occupied size inc. nul */
	char *id					/* String identifying tagtype or function */
) {
	icmUTFerr utferr = icmUTF_ok;

	if (b->op == icmSnSize || b->op == icmSnWrite) {
		*pocount = icmScriptCodetoSn(&utferr, b, (icmUTF8 *)*pdesc, *pcount);

		if (utferr != icmUTF_ok) {
			icm_err(b->icp, 1,"%s write: ScriptCode translate returned error '%s'",id,icmUTFerr2str(utferr));
			return;
		}

	} else {	/* Alloc/Free/Read */

		/* Figure length needed for reading ScriptCode */
		if (b->op == icmSnRead) {
			ORD32 off;

			off = b->get_off(b);
			*pcount = icmSntoScriptCode(NULL, NULL, b, *pocount);
			b->aoff(b, off);
		}

		/* Allocate ScriptCode string on read or for API client */
		if (icmArrayAllocResize(b, p_count, pcount,
	   	 (void **)pdesc, sizeof(icmUTF8), id))
			return;

		/* Read ScriptCode and (possibly) repair it */
		if (b->op == icmSnRead) {
			icmSntoScriptCode(&utferr, (icmUTF8 *)*pdesc, b, *pocount);

			if (utferr != icmUTF_ok) {
				if ((b->icp->cflags & icmCFlagAllowQuirks) == 0) {
					icmFormatWarning(b->icp, ICM_FMT_TEXT_UERR,"%s read: ScriptCode translate returned error '%s'",id,icmUTFerr2str(utferr));
					return;
				}
				icmQuirkWarning(b->icp, ICM_FMT_TEXT_UERR, 0, "%s read: ScriptCode translate returned error '%s'",id,icmUTFerr2str(utferr)); 
			}
		}
		ICMSNFREEARRAY(b, *p_count, *pdesc)
	}
}

/* ---------------------------------------------------------------- */
/* Higher level sub-tagtype serialization support code */

static icmBase *icc_new_ttype_imp(icc *p, icTagTypeSignature ttype,
                              icTagTypeSignature pttype, int rdff);
static int icc_compare_ttype(icc *p, icmBase *idst, icmBase *isrc);

/* Serialize a sub-tagtype */
static void icmSn_SubTagType(
	icmFBuf *b,
	unsigned int *off,			/* If not NULL, return/seek to this offset */
	unsigned int *count,		/* If not NULL, return size of sub-tag */
	icmBase **psub,				/* pointer to sub-structure */
	icTagTypeSignature ttype,	/* Sub Tag Type to create if not optional and not client supplied */
	icTagTypeSignature pttype,	/* Parent TagType */
	int opt,					/* If 0, sub-tag will be created on ->allocate() */
								/* If 1, sub-tag is optional (don't create on ->allocate()) */
								/* and create on read if off && count > 0. */
								/* If 2, sub-tag is always created by client, */
								/* and always expected to be read. */
	void (*init)(icmFBuf *b, icmBase *p), /* If not NULL, initialise a new sub-struct on create */
	int rdff,					/* Was read from file */
	int xpad					/* Extra dump padding */
) {
	if (b->op == icmSnFree) {
		if (*psub != NULL)
			(*psub)->del(*psub);		/* This does a serialise(icmSnFree) */
	} else {
		icmFBuf *bb;

		if (b->op == icmSnSize && opt == 2 && *psub == NULL) {
			icmFmtWarn(b, ICM_FMT_SUB_MISSING,
				          "icmSn_SubTagType: parent ttype %s missing sub-tag on write\n",
				                                             icmTypeSig2str(pttype));
			*psub = NULL;
			return;
		}

		/* Make sure it exist. If not optional then we created it. */
		if (*psub == NULL && (b->op & icmSnAlloc)
		 && (opt == 0
		     || (opt == 1 && b->op == icmSnRead && off != NULL && *off > 0
	                                && count != NULL && *count > 0)
		     || (opt == 2 && b->op == icmSnRead))) {
			unsigned int i, j;

			/* If we are reading, figure out what type it is */
			if (b->op == icmSnRead) {
				ORD32 coff;

				if (off != NULL)
					b->aoff(b, *off);

				coff = b->get_off(b);
			    icmSn_TagTypeSig32(b, &ttype);
				b->aoff(b, coff);
			}

			if ((*psub = icc_new_ttype_imp(b->icp, ttype, pttype, rdff)) == NULL) {
				icmFmtWarn(b, ICM_FMT_SUB_TYPE_UNKN, "Sub-TagType %s not created()",
				                                 icmTypeSig2str(ttype));
				*psub = NULL;
				return;
			}

			(*psub)->emb = 1;

			if (init != NULL)
				init(b, *psub);

		}

		if (b->icp->e.c != ICM_ERR_OK)
			return;

		if (*psub != NULL) {
			unsigned int subsize = 0;

			(*psub)->dp = xpad;	/* Set extra text dump() padding */

			/* Sub element is in common data area... */
			if (off != NULL) {
				if (b->op == icmSnSize || b->op == icmSnWrite) {
					*off = b->get_off(b);
				} else if (b->op == icmSnRead) {
					b->aoff(b, *off);
				}
			}

			if (b->op == icmSnRead && count != NULL)
				subsize = *count;		/* We know how big sub-type should be */

			bb = b->new_sub(b, subsize);
			if ((*psub)->serialise == NULL) {
				icm_err(b->icp, ICM_ERR_TTYPE_SLZE, "TagType %s has no serialise()",
				                                    icmTypeSig2str((*psub)->ttype));
				*psub = NULL;			/* No TagType object */
				return;
			}
				
			(*psub)->serialise((*psub), bb);
	
			if (count != NULL
			 && (b->op == icmSnSize || b->op == icmSnWrite)) {
				*count = bb->get_off(bb);
			}
			bb->done(bb);

		/* if empty element */
		} else {
			if (b->op == icmSnSize || b->op == icmSnWrite) {
				if (off != NULL)
					*off = 0;
				if (count != NULL)
					*count = 0;
			}
		}
	}
}

/* Serialize a MultiProcessElements sub-tag */
static void icmSn_PeSubTag(
	icmFBuf *b,
	unsigned int *off,			/* If not NULL, return/seek to this offset */
	unsigned int *count,		/* If not NULL, return size of sub-tag */
	icmPe **psub,				/* pointer to sub-structure */
	icTagTypeSignature pttype,	/* Parent TagType */
	int rdff,					/* Was read from file */
	int xpad					/* Extra dump padding */
) {
	icTagTypeSignature ttype = icmSigUnknownType;
	if (*psub != NULL)
		pttype = (*psub)->ttype;

	icmSn_SubTagType(b, off, count, (icmBase **)psub, ttype, pttype, 2, NULL, rdff, xpad);

	if (b->op == icmSnRead && *psub == NULL) {
		icmFmtWarn(b, ICM_FMT_SUB_MISSING,
				          "icmSn_PeSubTag: parent ttype %s missing sub-tag on read\n",
				                                             icmTypeSig2str(pttype));
	} 
}

/* ================================================================ */
/* Default implementation of the tag type methods             */
/* The default implementation uses the serialise method */
/* to do all the work, reducing the size of most */
/* tag type implementations. */

/* Return the number of bytes needed to write this tag */
/* Return 0 on error */
static unsigned int icmGeneric_get_size(icmBase *p) {
	if (p->serialise != NULL) {
		ICM_TAG_NEW_SIZE_BUFFER
		p->serialise(p, b);
		return b->done(b);
	}
	return 0;
}

/* Read the object, return error code */
static int icmGeneric_read(icmBase *p, unsigned int size, unsigned int of) {
	if (p->serialise != NULL) {
		ICM_TAG_NEW_READ_BUFFER
		p->serialise(p, b);
   		b->done(b);
	}
	return p->icp->e.c;
}

/* Write the object. return error code */
static int icmGeneric_write(icmBase *p, unsigned int size, unsigned int of, unsigned int pad) {
	if (p->serialise != NULL) {
		ICM_TAG_NEW_WRITE_BUFFER
		p->serialise(p, b);
		if (pad > 0) {	/* Pad out to size */
			icmSn_pad(b, pad);
		}
		b->done(b);
	}
	return p->icp->e.c;
}

/* Increase the reference count because the */
/* object is being shared. */
/* Call ->del() to decrement reference and delete */
/* the object if it the last one. */
static icmBase *icmGeneric_reference(icmBase *p) {
	p->refcount++;
	return p;
}

/* If this is the last reference, free all storage */
/* in the object and the object itself. */
static void icmGeneric_delete(icmBase *p) {
	if (p->refcount > 0 && --p->refcount == 0) {
		if (p->serialise != NULL) {
			ICM_TAG_NEW_FREE_BUFFER
			p->serialise(p, b);
			b->done(b);
		}
		p->icp->al->free(p->icp->al, p);
	}
}


/* Allocate variable sized data elements */
/* Return error code */
static int icmGeneric_allocate(icmBase *p) {
	if (p->serialise != NULL) {
		ICM_TAG_NEW_RESIZE_BUFFER
		p->serialise(p, b);
   		b->done(b);
	}
	return p->icp->e.c;
}

/* ---------------------------------------------------------- */
/* Implementation of a general array resizing utility function,         */
/* used within XXXX__serialise() functions.                   */

/* The allocation is sanity checked against the remaining file buffer size, */
/* so can't be used for allocations that aren't directly related to file contents. */
/* Return zero on sucess, nz if there is an error */
static int icmArrayRdAllocResize(
	icmFBuf *b,					/* Buffer we're dealing with */
	icmAResizeCountType ct,		/* Explicit count or count from buffer size flag ? */

	unsigned int *p_count,		/* Pointer to current count of items */
	unsigned int *pcount,		/* Pointer to new count of items (~8 why is this a pointer ?) */
	void **pdata,				/* Pointer to array of items allocation */
	unsigned int isize,			/* Item size */

								/* Count value sanity check data: */
	unsigned int maxsize,		/* Maximum size permitted for buffer (use UINT_MAX if not less */
								/* than remaining in buffer) */
	int fixedsize,				/* Further fixed size from current location before variable */
								/* elements. Space allowed for variable elements is */
								/* avail = min(maxsize, buf->space - fixedsize) */
	unsigned int bsize,			/* File buffer (ie. serialized) size of each item. */
								/* Use minumum if variable ? */
								/* Error if (*pcount * bsize) > avail */
	char *tagdesc				/* Stringification of tag type used for warning/error reporting */
) {
	if (b->icp->e.c != ICM_ERR_OK)
		return b->icp->e.c;

	/* Check the new size for sanity */
	if (b->op == icmSnRead) {
		unsigned int tsize, tavail;

		tavail = b->get_space(b);

		if (ct == icmAResizeByCount) {

			if (tavail > maxsize)
				tavail = maxsize;
	
			tsize = sat_mul(*pcount, bsize);
			tsize = sat_add_us(tsize, fixedsize);
	
			if (tsize > tavail) {
				return icm_err(b->icp, ICM_ERR_BUFFER_BOUND,
				                 "%s tag read array count %u is too big for buffer (tsize %u > tavail %u)",
				                                          tagdesc, *pcount, tsize, tavail);
			}

		} else if (ct == icmAResizeBySize) {
			if (tavail < fixedsize) {
				return icm_err(b->icp, ICM_ERR_BUFFER_BOUND,
				                 " %s tag size %u is too small",tagdesc, b->size);
			}
			*pcount = (tavail - fixedsize) / bsize;
			tsize = *pcount * bsize + fixedsize;
			if (tsize != tavail) {
				icmFmtWarn(b, ICM_FMT_PARTIALEL,
				         "%s (imp) tag has a partial array element (%u/%u bytes)",
				         tagdesc, tsize - tavail, bsize);
			}

		} else {
			return icm_err(b->icp, ICM_ERR_INTERNAL,
			        "icmArrayRdAllocResize: unknown icmAResizeCountType");
		}
	}

	/* Resize or Read - resize the array */
	if (b->op & icmSnAlloc) {
		if (*pcount != *p_count) {
			void *_np;
			if ((_np = (void *) b->icp->al->recalloc(b->icp->al, *pdata,
			     *p_count, isize, *pcount, isize)) == NULL) {
				return icm_err(b->icp, ICM_ERR_MALLOC,
				         "Allocating %s data size %d failed",tagdesc, *pcount);
			}
			*pdata = _np;
			*p_count = *pcount;
		}
	}

	return b->icp->e.c;
}

/* This version can be used where allocations aren't directly related to file contents. */
/* Return zero on sucess, nz if there is an error */
static int icmArrayAllocResize(
	icmFBuf *b,					/* Buffer we're dealing with */

	unsigned int *p_count,		/* Pointer to current count of items */
	unsigned int *pcount,		/* Pointer to new count of items */
	void **pdata,				/* Pointer to array of items allocation */
	unsigned int isize,			/* Item size */

	char *tagdesc				/* Stringification of tag type used for warning/error reporting */
) {
	if (b->icp->e.c != ICM_ERR_OK)
		return b->icp->e.c;

	/* Resize or Read - resize the array */
	if (b->op & icmSnAlloc) {
		if (*pcount != *p_count) {
			void *_np;
			if ((_np = (void *) b->icp->al->recalloc(b->icp->al, *pdata,
			     *p_count, isize, *pcount, isize)) == NULL) {
				return icm_err(b->icp, ICM_ERR_MALLOC,
				         "Allocating %s data size %d failed",tagdesc, *pcount);
			}
			*pdata = _np;
			*p_count = *pcount;
		}
	}

	return b->icp->e.c;
}

/* This version does an immediate realloc (i.e. ignores op) */

/* Return zero on sucess, nz if there is an error */
static int icmArrayResize(
	icc *icp,

	unsigned int *p_count,		/* Pointer to current count of items */
	unsigned int *pcount,		/* Pointer to new count of items */
	void **pdata,				/* Pointer to array of items allocation */
	unsigned int isize,			/* Item size */

	char *tagdesc				/* Stringification of tag type used for warning/error reporting */
) {
	if (icp->e.c != ICM_ERR_OK)
		return icp->e.c;

	if (*pcount != *p_count) {
		void *_np;
		if ((_np = (void *) icp->al->recalloc(icp->al, *pdata,
		     *p_count, isize, *pcount, isize)) == NULL) {
			return icm_err(icp, ICM_ERR_MALLOC,
			         "Allocating %s data size %d failed",tagdesc, *pcount);
		}
		*pdata = _np;
		*p_count = *pcount;
	}

	return icp->e.c;
}

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

#ifdef NEVER		// Not clear if this will get used...

/* Implementation of a general matrix resizing utility function,         */
/* used within XXXX__serialise() functions.                   */

/* Return zero on sucess, nz if there is an error */
int icmMatrixRdAllocResize(
	icmFBuf *b,					/* Buffer we're dealing with */

	unsigned int *p_rcount,		/* Pointer to current row count */
	unsigned int *prcount,		/* Pointer to new row count */
	unsigned int *p_ccount,		/* Pointer to current column count */
	unsigned int *pccount,		/* Pointer to new column count */
	void ***pdata,				/* Pointer to matrix of items allocation */
	unsigned int isize,			/* Item size */

								/* Count value sanity check data: */
	unsigned int maxsize,		/* Maximum size permitted for buffer (use UINT_MAX if not less than
								/* remaining in buffer) */
	unsigned int fixedsize,		/* Further fixed size from current location before variable */
								/* elements. Space allowed for variable elements is */
								/* avail = min(maxsize, buf->space - fixedsize) */
								/* Can be -ve if array is bigger than number of elements to read. */
	unsigned int bsize,			/* File buffer (ie. serialized) size of each item. */
								/* Use minumum if variable ? */
								/* Error if (*pcount * bsize) > avail */
	char *tagdesc				/* Stringification of tag type used for warning/error reporting */
) {
	if (b->icp->e.c != ICM_ERR_OK)
		return b->icp->e.c;

	/* Check the new size for sanity */
	if (b->op == icmSnRead) {
		unsigned int tsize, tavail;

		if (b->icp->e.c != ICM_ERR_OK)
			return 1;

		tavail = b->get_space(b);

		if (tavail > maxsize)
			tavail = maxsize;
	
		tsize = sat_add(fixedsize, sat_mul(sat_mul(*prcount, *pccount), bsize));

		if (tsize > tavail)
			return icm_err(b->icp, ICM_ERR_BUFFER_BOUND,
			                 "%s tag read matrix %u rows %u columns /size %u is too big for buffer (tsize %u > tavail %u)",
			                                          tagdesc, *prcount, *pccount, tsize, tavail);
	}

	/* Check & re-allocate rows and columns */
	if (b->op & icmSnAlloc) {
		unsigned int j;

		// If number of rows has changed
		if (*prcount != *p_rcount) {
			void **_np;
		
			/* If we've reduced rows, free row contents */
			if (*prcount < *p_rcount) {
				for (j = *prcount; j < *p_rcount; j++) {
					free ((*pdata)[j]);
					(*pdata)[j] = NULL;
				}  
			}
		
			/* re-alloc larger or smaller array of pointers to row data */
			if ((_np = (void **) b->icp->al->recalloc(b->icp->al, *pdata,
			     *p_rcount, sizeof(void *), *prcount, sizeof(void *))) == NULL) {
				return icm_err(b->icp, ICM_ERR_MALLOC,
				         "Allocating %s row pointers size %d failed",tagdesc, *prcount);
			}
			*pdata = _np;
			*p_rcount = *prcount;
		}

		// If num columns changed, re-alloc all row contents
		if (*pccount != *p_ccount) {
			for (j = 0; j < *p_rcount; j++) {
				void *_np;

				if ((_np = (void **) b->icp->al->recalloc(b->icp->al, (*pdata)[j],
				     (*pdata)[j] == NULL ? 0 : *p_ccount, isize, *pccount, isize)) == NULL) {
					return icm_err(b->icp, ICM_ERR_MALLOC,
					         "Allocating %s row %d contents size %d failed",tagdesc, j,*pccount);
				}
				(*pdata)[j] = _np;
			}
			*p_ccount = *pccount;

		/* If num columns not changed, just alloc any new row contents */
		} else if (*prcount > *p_rcount) {
			for (j = *p_rcount; j < *prcount; j++) {
				void *_np;

				if ((_np = (void **) b->icp->al->recalloc(b->icp->al, (*pdata)[j],
				     0, isize, *pccount, isize)) == NULL) {
					return icm_err(b->icp, ICM_ERR_MALLOC,
					         "Allocating %s row %d contents size %d failed",tagdesc, j,*pccount);
				}
				(*pdata)[j] = _np;
			}
		}
	}

	return b->icp->e.c;
}

#endif /* NEVER */

/* ---------------------------------------------------------- */
/* Auiliary function - return a string that represents a tag 32 bit value */
/* as a 4 character string or hex number. */
/* Note - returned buffers are static, can only be used 5 */
/* times before buffers get reused. */
char *icmtag2str(
	unsigned int tag
) {
	int i;
	static int si = 0;			/* String buffer index */
	static char buf[5][50];		/* String buffers */
	char *bp;
	unsigned char c[4];

	bp = buf[si++];
	si %= 5;				/* Rotate through buffers */

	c[0] = 0xff & (tag >> 24);
	c[1] = 0xff & (tag >> 16);
	c[2] = 0xff & (tag >> 8);
	c[3] = 0xff & (tag >> 0);
	for (i = 0; i < 4; i++) {	/* Can we represent it as a string ? */
		if (!isprint(c[i]))
			break;
	}
	if (i < 4) {	/* Not printable - use hex */
		sprintf(bp,"0x%x",tag);
	} else {		/* Printable */
		sprintf(bp,"'%c%c%c%c'",c[0],c[1],c[2],c[3]);
	}
	return bp;
}

/* Auiliary function - return a tag created from a string */
/* Note there is also the icmMakeTag() macro */
unsigned int icmstr2tag(
	const char *str
) {
	unsigned int tag;
	tag = (((unsigned int)str[0]) << 24)
	    + (((unsigned int)str[1]) << 16)
	    + (((unsigned int)str[2]) << 8)
	    + (((unsigned int)str[3]));
	return tag;
}

/* ========================================================== */
/* Object I/O routines                                        */
/* ========================================================== */

/* Forward declarations */

/* Default implementation of the tag type methods, that all use */
/* serialise as their implementation. */
static unsigned int icmGeneric_get_size(icmBase *p);
static int icmGeneric_read(icmBase *p, unsigned int size, unsigned int of);
static int icmGeneric_write(icmBase *p, unsigned int size, unsigned int of, unsigned int pad);
static void icmGeneric_del(icmBase *p);
static int icmGeneric_allocate(icmBase *p);

static int icc_check_sig(icc *p, unsigned int *ttix, int rd,
		icTagSignature sig, icTagTypeSignature ttype, icTagTypeSignature uttype,
        int rdff);

/* ---------------------------------------------------------- */
/* icmUnknown object */

/* The tag table icmTagRec.ttype retains the actual ttype, */
/* while the icmUnknown.ttype is icmSigUnknownType, */
/* and has a uttype of the actual type. */

/* Serialise this tag type */
static void icmUnknown_serialise(icmUnknown *p, icmFBuf *b) {
	unsigned int i;

    icmSn_TagTypeSig32(b, &p->uttype);		/* 0-3: Unknown Tag Type signature */
	icmSn_pad(b, 4);						/* 4-7: Zero padding */
	if (icmArrayRdAllocResize(b, icmAResizeBySize, &p->_count, &p->count,
	    (void **)&p->data, sizeof(unsigned char),
	    UINT_MAX, 0, 1, "icmUnknown"))
		return;
	if (b->op & icmSnSerialise)				/* (Optimise for speed) */
		for (i = 0; i < p->count; i++)
			icmSn_uc_UInt8(b, &p->data[i]);	/* 8-n: Unknown tag data */
	ICMSNFREEARRAY(b, p->_count, p->data)
	ICMRDCHECKCONSUMED(icmUnknown)
}

/* Dump a text description of the object */
static void icmUnknown_dump(icmUnknown *p, icmFile *op, int verb) {
	unsigned int i, ii, r, ph;

	if (verb <= 0)
		return;

	op->printf(op,"Unknown:\n");
	op->printf(op,"  Payload size in bytes = %u\n",p->count);

	/* Print one row of binary and ASCII interpretation if verb == 1, All if == 2 */
	/* else print all of it. */
	ii = i = ph = 0;
	for (r = 1;; r++) {		/* size rows */
		int c = 1;			/* Character location */

		c = 1;
		if (ph != 0) {	/* Print ASCII under binary */
			op->printf(op,"           ");
			i = ii;				/* Swap */
			c += 11;
		} else {
			op->printf(op,"    0x%04lx: ",i);
			ii = i;				/* Swap */
			c += 10;
		}
		while (i < p->count && c < 75) {
			if (ph == 0) 
				op->printf(op,"%02x ",p->data[i]);
			else {
				if (isprint(p->data[i]))
					op->printf(op," %c ",p->data[i]);
				else
					op->printf(op,"   ",p->data[i]);
			}
			c += 3;
			i++;
		}
		if (ph == 0 || i < p->count)
			op->printf(op,"\n");

		if (ph == 1 && i >= p->count) {
			op->printf(op,"\n");
			break;
		}
		if (ph == 1 && r > 1 && verb <= 1) {
			op->printf(op,"    ...\n");
			break;			/* Print 1 row if not verbose */
		}

		if (ph == 0)
			ph = 1;
		else
			ph = 0;

	}
}

/* Check Unknown */
static int icmUnknown_check(icmUnknown *p, icTagSignature sig, int rd) {
	return p->icp->e.c;
}

/* Create an empty object. Return null on error */
static icmBase *new_icmUnknown(icc *icp) {
	ICM_BASE_ALLOCINIT(icmUnknown, icmSigUnknownType)
	return (icmBase *)p;
}

/* ---------------------------------------------------------- */
/* icmUInt8Array object */

static void icmUInt8Array_serialise(icmUInt8Array *p, icmFBuf *b) {
    icmSn_TagTypeSig32(b, &p->ttype);		/* 0-3: Data Tag Type signature */
	icmSn_pad(b, 4);						/* 4-7: Zero padding */

	if (icmArrayRdAllocResize(b, icmAResizeBySize, &p->_count, &p->count,
	    (void **)&p->data, sizeof(unsigned int),
	    UINT_MAX, 0, 1, "icmUInt8Array"))
		return;
	if (b->op & icmSnSerialise) {				/* (Optimise for speed) */
		unsigned int i;
		for (i = 0; i < p->count; i++)
			icmSn_ui_UInt8(b, &p->data[i]);		/* 8-n: 8 bit unsigned data */
	}
	ICMSNFREEARRAY(b, p->_count, p->data)
	ICMRDCHECKCONSUMED(icmUInt8Array)
}

/* Dump a text description of the object */
static void icmUInt8Array_dump(
	icmUInt8Array *p,
	icmFile *op,	/* Output to dump to */
	int   verb		/* Verbosity level */
) {
	if (verb <= 0)
		return;

	op->printf(op,"UInt8Array:\n");
	op->printf(op,"  No. elements = %u\n",p->count);
	if (verb >= 2) {
		unsigned int i;
		for (i = 0; i < p->count; i++)
			op->printf(op,"    %u:  %u\n",i,p->data[i]);
	}
}

/* Check UInt8Array */
static int icmUInt8Array_check(icmUInt8Array *p, icTagSignature sig, int rd) {
	return p->icp->e.c;
}

/* Create an empty object. Return null on error */
static icmBase *new_icmUInt8Array(icc *icp, icTagTypeSignature ttype) {
	ICM_BASE_ALLOCINIT(icmUInt8Array, ttype)
	return (icmBase *)p;
}

/* ---------------------------------------------------------- */
/* icmUInt16Array object */

static void icmUInt16Array_serialise(icmUInt16Array *p, icmFBuf *b) {
    icmSn_TagTypeSig32(b, &p->ttype);		/* 0-3: Data Tag Type signature */
	icmSn_pad(b, 4);						/* 4-7: Zero padding */

	if (icmArrayRdAllocResize(b, icmAResizeBySize, &p->_count, &p->count,
	    (void **)&p->data, sizeof(unsigned int),
	    UINT_MAX, 0, 2, "icmUInt16Array"))
		return;
	if (b->op & icmSnSerialise) {				/* (Optimise for speed) */
		unsigned int i;
		for (i = 0; i < p->count; i++)
			icmSn_ui_UInt16(b, &p->data[i]);	/* 8-n: 16 bit unsigned data */
	}
	ICMSNFREEARRAY(b, p->_count, p->data)
	ICMRDCHECKCONSUMED(icmUInt16Array)
}

/* Dump a text description of the object */
static void icmUInt16Array_dump(
	icmUInt16Array *p,
	icmFile *op,	/* Output to dump to */
	int   verb		/* Verbosity level */
) {
	if (verb <= 0)
		return;

	op->printf(op,"UInt16Array:\n");
	op->printf(op,"  No. elements = %u\n",p->count);
	if (verb >= 2) {
		unsigned int i;
		for (i = 0; i < p->count; i++)
			op->printf(op,"    %u:  %u\n",i,p->data[i]);
	}
}

/* Check UInt16Array */
static int icmUInt16Array_check(icmUInt16Array *p, icTagSignature sig, int rd) {
	return p->icp->e.c;
}

/* Create an empty object. Return null on error */
static icmBase *new_icmUInt16Array(icc *icp, icTagTypeSignature ttype) {
	ICM_BASE_ALLOCINIT(icmUInt16Array, ttype)
	return (icmBase *)p;
}

/* ---------------------------------------------------------- */
/* icmUInt32Array object */

static void icmUInt32Array_serialise(icmUInt32Array *p, icmFBuf *b) {
    icmSn_TagTypeSig32(b, &p->ttype);		/* 0-3: Data Tag Type signature */
	icmSn_pad(b, 4);						/* 4-7: Zero padding */

	if (icmArrayRdAllocResize(b, icmAResizeBySize, &p->_count, &p->count,
	    (void **)&p->data, sizeof(unsigned int),
	    UINT_MAX, 0, 4, "icmUInt32Array"))
		return;
	if (b->op & icmSnSerialise) {				/* (Optimise for speed) */
		unsigned int i;
		for (i = 0; i < p->count; i++)
			icmSn_ui_UInt32(b, &p->data[i]);		/* 8-n: 32 bit unsigned data */
	}
	ICMSNFREEARRAY(b, p->_count, p->data)
	ICMRDCHECKCONSUMED(icmUInt32Array)
}

/* Dump a text description of the object */
static void icmUInt32Array_dump(
	icmUInt32Array *p,
	icmFile *op,	/* Output to dump to */
	int   verb		/* Verbosity level */
) {
	if (verb <= 0)
		return;

	op->printf(op,"UInt32Array:\n");
	op->printf(op,"  No. elements = %u\n",p->count);
	if (verb >= 2) {
		unsigned int i;
		for (i = 0; i < p->count; i++)
			op->printf(op,"    %u:  %u\n",i,p->data[i]);
	}
}

/* Check UInt32Array */
static int icmUInt32Array_check(icmUInt32Array *p, icTagSignature sig, int rd) {
	return p->icp->e.c;
}

/* Create an empty object. Return null on error */
static icmBase *new_icmUInt32Array(icc *icp, icTagTypeSignature ttype) {
	ICM_BASE_ALLOCINIT(icmUInt32Array, ttype)
	return (icmBase *)p;
}

/* ---------------------------------------------------------- */
/* icmUInt64Array object */

static void icmUInt64Array_serialise(icmUInt64Array *p, icmFBuf *b) {
    icmSn_TagTypeSig32(b, &p->ttype);		/* 0-3: Data Tag Type signature */
	icmSn_pad(b, 4);						/* 4-7: Zero padding */

	if (icmArrayRdAllocResize(b, icmAResizeBySize, &p->_count, &p->count,
	    (void **)&p->data, sizeof(icmUInt64),
	    UINT_MAX, 0, 8, "icmUInt64Array"))
		return;
	if (b->op & icmSnSerialise) {				/* (Optimise for speed) */
		unsigned int i;
		for (i = 0; i < p->count; i++)
			icmSn_uii_UInt64(b, &p->data[i]);	/* 8-n: 64 bit unsigned data */
	}
	ICMSNFREEARRAY(b, p->_count, p->data)
	ICMRDCHECKCONSUMED(icmUInt64Array)
}

/* Dump a text description of the object */
static void icmUInt64Array_dump(
	icmUInt64Array *p,
	icmFile *op,	/* Output to dump to */
	int   verb		/* Verbosity level */
) {
	if (verb <= 0)
		return;

	op->printf(op,"UInt64Array:\n");
	op->printf(op,"  No. elements = %u\n",p->count);
	if (verb >= 2) {
		unsigned int i;
		for (i = 0; i < p->count; i++)
			op->printf(op,"    %u:  h=%u, l=%u\n",i,p->data[i].h,p->data[i].l);
	}
}

/* Check UInt64Array */
static int icmUInt64Array_check(icmUInt64Array *p, icTagSignature sig, int rd) {
	return p->icp->e.c;
}

/* Create an empty object. Return null on error */
static icmBase *new_icmUInt64Array(icc *icp, icTagTypeSignature ttype) {
	ICM_BASE_ALLOCINIT(icmUInt64Array, ttype)
	return (icmBase *)p;
}

/* ---------------------------------------------------------- */
/* icmU16Fixed16Array object */

static void icmU16Fixed16Array_serialise(icmU16Fixed16Array *p, icmFBuf *b) {
    icmSn_TagTypeSig32(b, &p->ttype);		/* 0-3: Data Tag Type signature */
	icmSn_pad(b, 4);						/* 4-7: Zero padding */

	if (icmArrayRdAllocResize(b, icmAResizeBySize, &p->_count, &p->count,
	    (void **)&p->data, sizeof(double),
	    UINT_MAX, 0, 4, "icmU16Fixed16Array"))
		return;
	if (b->op & icmSnSerialise) {				/* (Optimise for speed) */
		unsigned int i;
		for (i = 0; i < p->count; i++)
			icmSn_d_U16Fix16(b, &p->data[i]);		/* 8-n: 16.16 bit unsigned data */
	}
	ICMSNFREEARRAY(b, p->_count, p->data)
	ICMRDCHECKCONSUMED(icmU16Fixed16Array)
}

/* Dump a text description of the object */
static void icmU16Fixed16Array_dump(
	icmU16Fixed16Array *p,
	icmFile *op,	/* Output to dump to */
	int   verb		/* Verbosity level */
) {
	if (verb <= 0)
		return;

	op->printf(op,"U16Fixed16Array:\n");
	op->printf(op,"  No. elements = %u\n",p->count);
	if (verb >= 2) {
		unsigned int i;
		for (i = 0; i < p->count; i++)
			op->printf(op,"    %u:  %.8f\n",i,p->data[i]);
	}
}

/* Check U16Fixed16Array */
static int icmU16Fixed16Array_check(icmU16Fixed16Array *p, icTagSignature sig, int rd) {
	return p->icp->e.c;
}

/* Create an empty object. Return null on error */
static icmBase *new_icmU16Fixed16Array(icc *icp, icTagTypeSignature ttype) {
	ICM_BASE_ALLOCINIT(icmU16Fixed16Array, ttype)
	return (icmBase *)p;
}

/* ---------------------------------------------------------- */
/* icmS15Fixed16Array object */

static void icmS15Fixed16Array_serialise(icmS15Fixed16Array *p, icmFBuf *b) {
    icmSn_TagTypeSig32(b, &p->ttype);		/* 0-3: Data Tag Type signature */
	icmSn_pad(b, 4);						/* 4-7: Zero padding */

	if (icmArrayRdAllocResize(b, icmAResizeBySize, &p->_count, &p->count,
	    (void **)&p->data, sizeof(double),
	    UINT_MAX, 0, 4, "icmS15Fixed16Array"))
		return;
	if (b->op & icmSnSerialise) {				/* (Optimise for speed) */
		unsigned int i;
		for (i = 0; i < p->count; i++)
			icmSn_d_S15Fix16(b, &p->data[i]);		/* 8-n: 15.16 bit signed data */
	}
	ICMSNFREEARRAY(b, p->_count, p->data)
	ICMRDCHECKCONSUMED(icmS15Fixed16Array)
}

/* Dump a text description of the object */
static void icmS15Fixed16Array_dump(
	icmS15Fixed16Array *p,
	icmFile *op,	/* Output to dump to */
	int   verb		/* Verbosity level */
) {
	if (verb <= 0)
		return;

	op->printf(op,"S15Fixed16Array:\n");
	op->printf(op,"  No. elements = %u\n",p->count);
	if (verb >= 2) {
		unsigned int i;
		for (i = 0; i < p->count; i++)
			op->printf(op,"    %u:  %.8f\n",i,p->data[i]);
	}
}

/* Check S15Fixed16Array */
static int icmS15Fixed16Array_check(icmS15Fixed16Array *p, icTagSignature sig, int rd) {
	return p->icp->e.c;
}

/* Create an empty object. Return null on error */
static icmBase *new_icmS15Fixed16Array(icc *icp, icTagTypeSignature ttype) {
	ICM_BASE_ALLOCINIT(icmS15Fixed16Array, ttype)
	return (icmBase *)p;
}

/* ---------------------------------------------------------- */
/* icmXYZArray object */

static void icmXYZArray_serialise(icmXYZArray *p, icmFBuf *b) {
    icmSn_TagTypeSig32(b, &p->ttype);		/* 0-3: Data Tag Type signature */
	icmSn_pad(b, 4);						/* 4-7: Zero padding */

	if (icmArrayRdAllocResize(b, icmAResizeBySize, &p->_count, &p->count,
	    (void **)&p->data, sizeof(icmXYZNumber),
	    UINT_MAX, 0, 12, "icmXYZArray"))
		return;
	if (b->op & icmSnSerialise) {				/* (Optimise for speed) */
		unsigned int i;
		for (i = 0; i < p->count; i++)
			icmSn_XYZNumber12b(b, &p->data[i]);		/* 8-n: 3 x 15.16 bit signed data */
	}
	ICMSNFREEARRAY(b, p->_count, p->data)
	ICMRDCHECKCONSUMED(icmXYZArray)
}

/* Dump a text description of the object */
static void icmXYZArray_dump(
	icmXYZArray *p,
	icmFile *op,	/* Output to dump to */
	int   verb		/* Verbosity level */
) {
	if (verb <= 0)
		return;

	op->printf(op,"XYZArray:\n");
	op->printf(op,"  No. elements = %u\n",p->count);
	if (verb >= 2) {
		unsigned int i;
		for (i = 0; i < p->count; i++) {
			op->printf(op,"    %u:  %s\n",i,icmXYZNumber_and_Lab2str(&p->data[i]));
			
		}
	}
}

/* Check XYZArray */
static int icmXYZArray_check(icmXYZArray *p, icTagSignature sig, int rd) {
	return p->icp->e.c;
}

/* Create an empty object. Return null on error */
static icmBase *new_icmXYZArray(icc *icp, icTagTypeSignature ttype) {
	ICM_BASE_ALLOCINIT(icmXYZArray, ttype)
	return (icmBase *)p;
}

/* ---------------------------------------------------------- */
/* icmChromaticity object */

/* Serialise this tag type */
static void icmChromaticity_serialise(icmChromaticity *p, icmFBuf *b) {
	unsigned int i;

    icmSn_TagTypeSig32(b, &p->ttype);		/* 0-3: Chromaticity Tag Type signature */
	icmSn_pad(b, 4);						/* 4-7: Zero padding */
	icmSn_ui_UInt16(b, &p->count);			/* 8-9: Number of device channels */
	icmSn_PhCol16(b, &p->enc);				/* 10-11: Colorant encoding */

	if (icmArrayRdAllocResize(b, icmAResizeByCount, &p->_count, &p->count,
	    (void **)&p->data, sizeof(icmxyCoordinate),
	    UINT_MAX, 0, 8, "icmChromaticity"))
		return;
	if (b->op & icmSnSerialise)				/* (Optimise for speed) */
		for (i = 0; i < p->count; i++)
			icmSn_xyCoordinate8b(b, &p->data[i]);	/* 12-n: Chromaticity coordinate data */
	ICMSNFREEARRAY(p, p->_count, p->data)
	ICMRDCHECKCONSUMED(icmChromaticity)
}

/* Dump a text description of the object */
static void icmChromaticity_dump(icmChromaticity *p, icmFile *op, int verb) {
	unsigned int i;

	if (verb <= 0)
		return;

	op->printf(op,"Chromaticity:\n");
	op->printf(op,"  No. device channels = %u\n",p->count);
	if (verb >= 1) {
		for (i = 0; i < p->count; i++) {
			op->printf(op,"    Colorant %u, x = %f, y = %f:\n",i,p->data[i].xy[0],p->data[i].xy[1]);
		}
	}
}

/* Check tags validity */
static int icmChromaticity_check(icmChromaticity *p, icTagSignature sig, int rd) {
	icc *icp = p->icp;
	unsigned int dchan = icmCSSig2nchan(icp->header->colorSpace);

	/* Does number of channels match header device space encoding ? */
	if (p->count != dchan) {
		icmFormatWarning(icp, ICM_FMT_CHRMCHAN,
		                       "Chromaticity no. channels %u doesn't match header %u",
		                                                              p->count,dchan);
	}

	/* Does number of channels match encoding ? */
	switch(p->enc) {
		case icPhColUnknown:
		case icMaxEnumPhCol:
			break;
		case icPhColITU_R_BT_709:
		case icPhColSMPTE_RP145_1994:
		case icPhColEBU_Tech_3213_E:
		case icPhColP22:
		case icPhColP3:
		case icPhColITU_R_BT2020:
			if (p->count != 3)
				icmFormatWarning(icp, ICM_FMT_CHRMCHAN,
					   "Chromaticity channels %u doesn't match encoding %s",
				       p->count,icmPhColEncoding2str(p->enc));
			if (icSigRgbData != icp->header->colorSpace)
				icmFormatWarning(icp, ICM_FMT_CHRMENC,
				       "Chromaticity encoding %s doesn't match header device colorspace %s",
				       icmPhColEncoding2str(p->enc),
				       icmColorSpaceSig2str(icp->header->colorSpace));
			break;
	}

	/* Do chromaticities matches encoding ? */
	if (p->count >= 3) {
		switch(p->enc) {
			case icPhColUnknown:
			case icMaxEnumPhCol:
				break;
			case icPhColITU_R_BT_709:
				if (diff_u16f16(p->data[0].xy[0], 0.640) || diff_u16f16(p->data[0].xy[1], 0.330)
				 || diff_u16f16(p->data[1].xy[0], 0.300) || diff_u16f16(p->data[1].xy[1], 0.600)
				 || diff_u16f16(p->data[2].xy[0], 0.150) || diff_u16f16(p->data[2].xy[1], 0.060))
				icmFormatWarning(icp, ICM_FMT_CHRMVALS,
				                                 "Chromaticity values for ITU_R_BT_709 are wrong");
				break;
			case icPhColSMPTE_RP145_1994:
				if (diff_u16f16(p->data[0].xy[0], 0.630) || diff_u16f16(p->data[0].xy[1], 0.340)
				 || diff_u16f16(p->data[1].xy[0], 0.310) || diff_u16f16(p->data[1].xy[1], 0.595)
				 || diff_u16f16(p->data[2].xy[0], 0.155) || diff_u16f16(p->data[2].xy[1], 0.070))
				icmFormatWarning(icp, ICM_FMT_CHRMVALS,
				                                 "Chromaticity values for SMPTE_RP145_1994 are wrong");
				break;
			case icPhColEBU_Tech_3213_E:
				if (diff_u16f16(p->data[0].xy[0], 0.640) || diff_u16f16(p->data[0].xy[1], 0.330)
				 || diff_u16f16(p->data[1].xy[0], 0.290) || diff_u16f16(p->data[1].xy[1], 0.600)
				 || diff_u16f16(p->data[2].xy[0], 0.150) || diff_u16f16(p->data[2].xy[1], 0.060))
				icmFormatWarning(icp, ICM_FMT_CHRMVALS,
				                                 "Chromaticity values for EBU_Tech_3213_E are wrong");
				break;
			case icPhColP22:
				if (diff_u16f16(p->data[0].xy[0], 0.625) || diff_u16f16(p->data[0].xy[1], 0.340)
				 || diff_u16f16(p->data[1].xy[0], 0.280) || diff_u16f16(p->data[1].xy[1], 0.605)
				 || diff_u16f16(p->data[2].xy[0], 0.155) || diff_u16f16(p->data[2].xy[1], 0.070))
				icmFormatWarning(icp, ICM_FMT_CHRMVALS,
				                                 "Chromaticity values for P22 are wrong");
				break;
		case icPhColP3:
				if (diff_u16f16(p->data[0].xy[0], 0.680) || diff_u16f16(p->data[0].xy[1], 0.320)
				 || diff_u16f16(p->data[1].xy[0], 0.265) || diff_u16f16(p->data[1].xy[1], 0.690)
				 || diff_u16f16(p->data[2].xy[0], 0.150) || diff_u16f16(p->data[2].xy[1], 0.060))
				icmFormatWarning(icp, ICM_FMT_CHRMVALS,
				                                 "Chromaticity values for P3 are wrong");
				break;
		case icPhColITU_R_BT2020:
				if (diff_u16f16(p->data[0].xy[0], 0.780) || diff_u16f16(p->data[0].xy[1], 0.292)
				 || diff_u16f16(p->data[1].xy[0], 0.170) || diff_u16f16(p->data[1].xy[1], 0.797)
				 || diff_u16f16(p->data[2].xy[0], 0.131) || diff_u16f16(p->data[2].xy[1], 0.046))
				icmFormatWarning(icp, ICM_FMT_CHRMVALS,
				                                 "Chromaticity values for ITU_R_BT2020 are wrong");
				break;
		}
	}
	return icp->e.c;
}

/* Setup the tag with the defined phoshor encoding. */
/* (This is utility for the client code) */
static int icmChromaticity_setup(icmChromaticity *p) {
	icc *icp = p->icp;

	switch(p->enc) {
		case icPhColUnknown:
		case icPhColITU_R_BT_709:
		case icPhColSMPTE_RP145_1994:
		case icPhColEBU_Tech_3213_E:
		case icPhColP22:
		case icPhColP3:
		case icPhColITU_R_BT2020:
			break;
		default:
			return icm_err(icp, ICM_ERR_UNKNOWN_COLORANT_ENUM,
			               "icmChromaticity_setup() Unknown colorant enum 0x%x",p->enc);
	}

	p->count = 3;
	if (p->allocate(p) != ICM_ERR_OK)
		return icp->e.c;

	switch(p->enc) {
		case icPhColITU_R_BT_709:
			p->data[0].xy[0] = 0.640;
			p->data[0].xy[1] = 0.330;
			p->data[1].xy[0] = 0.300;
			p->data[1].xy[1] = 0.600;
			p->data[2].xy[0] = 0.150;
			p->data[2].xy[1] = 0.060;
			break;
		case icPhColSMPTE_RP145_1994:
			p->data[0].xy[0] = 0.630;
			p->data[0].xy[1] = 0.340;
			p->data[1].xy[0] = 0.310;
			p->data[1].xy[1] = 0.595;
			p->data[2].xy[0] = 0.155;
			p->data[2].xy[1] = 0.070;
			break;
		case icPhColEBU_Tech_3213_E:
			p->data[0].xy[0] = 0.640;
			p->data[0].xy[1] = 0.330;
			p->data[1].xy[0] = 0.290;
			p->data[1].xy[1] = 0.600;
			p->data[2].xy[0] = 0.150;
			p->data[2].xy[1] = 0.060;
			break;
		case icPhColP22:
			p->data[0].xy[0] = 0.625;
			p->data[0].xy[1] = 0.340;
			p->data[1].xy[0] = 0.280;
			p->data[1].xy[1] = 0.605;
			p->data[2].xy[0] = 0.155;
			p->data[2].xy[1] = 0.070;
			break;
		case icPhColP3:
			p->data[0].xy[0] = 0.680;
			p->data[0].xy[1] = 0.320;
			p->data[1].xy[0] = 0.265;
			p->data[1].xy[1] = 0.690;
			p->data[2].xy[0] = 0.150;
			p->data[2].xy[1] = 0.060;
			break;
		case icPhColITU_R_BT2020:
			p->data[0].xy[0] = 0.780;
			p->data[0].xy[1] = 0.292;
			p->data[1].xy[0] = 0.170;
			p->data[1].xy[1] = 0.797;
			p->data[2].xy[0] = 0.131;
			p->data[2].xy[1] = 0.046;
			break;
		default:
			break;
	}
	return icp->e.c;
}

/* Create an empty object. Return null on error */
icmBase *new_icmChromaticity(icc *icp, icTagTypeSignature ttype) {
	ICM_BASE_ALLOCINIT(icmChromaticity, ttype)
	p->setup = icmChromaticity_setup;
	return (icmBase *)p;
}

/* ---------------------------------------------------------- */
/* icmData object */

static void icmData_serialise(icmData *p, icmFBuf *b) {
    icmSn_TagTypeSig32(b, &p->ttype);		/* 0-3: Data Tag Type signature */
	icmSn_pad(b, 4);						/* 4-7: Zero padding */
	icmSn_AsciiOrBinary32(b, &p->flag);		/* 8-11: Ascii/Binary flag */

	/* ASCIIZ text */
	if (p->flag == icAsciiData) {
		p->fcount = b->size - 12;				/* Implied */ 
		icmSn_ASCIIZ(b, &p->_count, &p->count, (char **)&p->data,
		                &p->fcount, 0, "icmData");

	/* Binary data */
	} else if (p->flag == icBinaryData) {

		if (icmArrayRdAllocResize(b, icmAResizeBySize, &p->_count, &p->count,
		    (void **)&p->data, sizeof(unsigned char),
		    UINT_MAX, 0, 1, "icmData"))
			return;
		if (b->op & icmSnSerialise) {				/* (Optimise for speed) */
			unsigned int i;
			for (i = 0; i < p->count; i++)
				icmSn_uc_UInt8(b, &p->data[i]);		/* 12-n: ASCII or Binary data */
		}
		ICMSNFREEARRAY(b, p->_count, p->data)
	} else {
		icmFormatWarning(p->icp, ICM_FMT_DATA_FLAG, 0, "Unknown SigData flag value 0x%x", p->flag);
		return;
	}
	ICMRDCHECKCONSUMED(icmData)
}

/* Dump a text description of the object */
static void icmData_dump(
	icmData *p,
	icmFile *op,	/* Output to dump to */
	int   verb		/* Verbosity level */
) {
	unsigned int i, r, c, ii, size = 0;
	int ph = 0;		/* Phase */

	if (verb <= 0)
		return;

	op->printf(op,"Data:\n");
	switch(p->flag) {
		case icAsciiData:
			op->printf(op,"  ASCII data\n");
			size = p->count > 0 ? p->count-1 : 0;
			break;
		case icBinaryData:
			op->printf(op,"  Binary data\n");
			size = p->count;
			break;
		default:
			op->printf(op,"  Undefined data\n");
			size = p->count;
			break;
	}
	op->printf(op,"  No. elements = %u\n",p->count);

	ii = i = 0;
	for (r = 1;; r++) {		/* count rows */
		if (i >= size) {
			op->printf(op,"\n");
			break;
		}
		if (r > 1 && verb < 2) {
			op->printf(op,"...\n");
			break;			/* Print 1 row if not verbose */
		}

		c = 1;
		if (ph != 0) {	/* Print ASCII under binary */
			op->printf(op,"           ");
			i = ii;
			c += 11;
		} else {
			op->printf(op,"    0x%04lx: ",i);
			ii = i;
			c += 10;
		}
		while (i < size && c < 75) {
			if (p->flag == icAsciiData) {
				if (isprint(p->data[i])) {
					op->printf(op,"%c",p->data[i]);
					c++;
				} else {
					op->printf(op,"\\%03o",p->data[i]);
					c += 4;
				}
			} else {
				if (ph == 0) 
					op->printf(op,"%02x ",p->data[i]);
				else {
					if (isprint(p->data[i]))
						op->printf(op," %c ",p->data[i]);
					else
						op->printf(op,"   ",p->data[i]);
				}
				c += 3;
			}
			i++;
		}
		if (i < size)
			op->printf(op,"\n");
		if (verb > 2 && p->flag != icAsciiData && ph == 0)
			ph = 1;
		else
			ph = 0;
	}
}

/* Check Data */
static int icmData_check(icmData *p, icTagSignature sig, int rd) {
	return p->icp->e.c;
}

/* Create an empty object. Return null on error */
static icmBase *new_icmData(icc *icp, icTagTypeSignature ttype) {
	ICM_BASE_ALLOCINIT(icmData, ttype)
	return (icmBase *)p;
}

/* ---------------------------------------------------------- */
/* icmText object */

/* Serialise this tag type */

static void icmText_serialise(icmText *p, icmFBuf *b) {
    icmSn_TagTypeSig32(b, &p->ttype);		/* 0-3: Text Tag Type signature */
	icmSn_pad(b, 4);						/* 4-7: Zero padding */

	p->fcount = b->size - 8;				/* Implied */ 

	/* ASCIIZ text */
	icmSn_ASCIIZ(b, &p->_count, &p->count, &p->desc,
	                &p->fcount, 0, "icmText");
	ICMRDCHECKCONSUMED(icmText)
}

/* Dump a text description of the object */
static void icmText_dump(
	icmText *p,
	icmFile *op,	/* Output to dump to */
	int   verb		/* Verbosity level */
) {
	int pad = p->dp;

	if (verb <= 0)
		return;

	op->printf(op,PAD("Text:\n"));
	icmASCIIZ_dump(p->desc, p->count, op, verb, p->dp);
}

/* Check Text */
static int icmText_check(icmText *p, icTagSignature sig, int rd) {
	return p->icp->e.c;
}

/* Create an empty object. Return null on error */
static icmBase *new_icmText(icc *icp, icTagTypeSignature ttype) {
	ICM_BASE_ALLOCINIT(icmText, ttype)
	return (icmBase *)p;
}


/* ============================================================ */

/* Return a string that shows the given date and time */
/* This can only be called once before reusing returned static buffer */
char *icmDateTimeNumber2str(icmDateTimeNumber *p) {
	static const char *mstring[13] = {"Bad", "Jan","Feb","Mar","Apr","May","Jun",
					  "Jul","Aug","Sep","Oct","Nov","Dec"};
	static char buf[80];

	sprintf(buf,"%d %s %4d, %d:%02d:%02d", 
	                p->day, mstring[p->month > 12 ? 0 : p->month], p->year,
	                p->hours, p->minutes, p->seconds);
	return buf;
}

/* NOTE that these standard time routines are not thread safe, */
/* and only operate correctly for dates in the UNIX epoc, 1970-2038 */
/* Set the DateTime structure to the current date and time */

/* Set the DateTimeNumber to the current (UTC) date and time */
void icmDateTimeNumber_setcur(icmDateTimeNumber *p) {
	time_t cclk;
	struct tm *ctm;
	
	cclk = time(NULL);
	ctm = gmtime(&cclk);
	
	p->year    = ctm->tm_year + 1900;
	p->month   = ctm->tm_mon + 1;
	p->day     = ctm->tm_mday;
	p->hours   = ctm->tm_hour;
	p->minutes = ctm->tm_min;
	p->seconds = ctm->tm_sec;
}

/* Convert a DateTimeNumber from UTC to local time */
void icmDateTimeNumber_tolocal(icmDateTimeNumber *d, icmDateTimeNumber *s) {
	time_t cclk = 0, dclk;
	struct tm *ctm;
	
	/* Since not all systems support the timegm() or mkgmtime() function, */
	/* there is no standard way to convert a UTC tm to time_t, */
	/* so we use an offset trick that makes use of gmtime() and mktime(). */

	/* Transfer UTC icmDateTimeNumber to tm */
	cclk = time(NULL);
	ctm = localtime(&cclk);		/* Get the pointer */

	ctm->tm_year  = s->year - 1900;
	ctm->tm_mon   = s->month - 1;
	ctm->tm_mday  = s->day;
	ctm->tm_hour  = s->hours;   
	ctm->tm_min   = s->minutes; 
	ctm->tm_sec   = s->seconds;
	ctm->tm_isdst = -1;
	/* Convert time as if local time to time_t */
	if ((cclk = mktime(ctm)) == (time_t)-1) {
		d->year    = 1900;		/* Can't be represented by time_t */
		d->month   = 1;
		d->day     = 1;
		d->hours   = 0;
		d->minutes = 0;
		d->seconds = 0;

	} else {
		ctm = gmtime(&cclk);		/* time_t to UTC */
		dclk = cclk - mktime(ctm);	/* Compute adjustment of local to time_t */
		cclk += dclk;				/* Adjust from local to UTC */
		ctm = localtime(&cclk);		/* Convert from UTC to local */
	
		d->year    = ctm->tm_year + 1900;
		d->month   = ctm->tm_mon + 1;
		d->day     = ctm->tm_mday;
		d->hours   = ctm->tm_hour;
		d->minutes = ctm->tm_min;
		d->seconds = ctm->tm_sec;
	}
}

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

/* Serialise this tag type */
static void icmDateTime_serialise(icmDateTime *p, icmFBuf *b) {
	unsigned int i;

    icmSn_TagTypeSig32(b, &p->ttype);		/* 0-3:  DateTime Tag Type signature */
	icmSn_pad(b, 4);						/* 4-7:  Zero padding */
	icmSn_DateTimeNumber12b(b, &p->date);	/* 8-19: Date/Time */
	ICMRDCHECKCONSUMED(icmDateTime)
}

/* Dump a text description of the object */
static void icmDateTime_dump(icmDateTime *p, icmFile *op, int verb) {
	icmDateTimeNumber l;

	if (verb <= 0)
		return;

	icmDateTimeNumber_tolocal(&l, &p->date);
	op->printf(op,"DateTimeNumber:\n");
	op->printf(op,"  UTC   Date&Time = %s\n", icmDateTimeNumber2str(&p->date));
	op->printf(op,"  Local Date&Time = %s\n", icmDateTimeNumber2str(&l));
}

/* Check tags validity */
static int icmDateTime_check(icmDateTime *p, icTagSignature sig, int rd) {
	return p->icp->e.c;
}

/* Create an empty object. Return null on error */
icmBase *new_icmDateTime(icc *icp, icTagTypeSignature ttype) {
	ICM_BASE_ALLOCINIT(icmDateTime, ttype)
	return (icmBase *)p;
}


/* ---------------------------------------------------------- */
/* Measurement */

static void icmMeasurement_serialise(icmMeasurement *p, icmFBuf *b) {
	unsigned int n;

    icmSn_TagTypeSig32(b, &p->ttype);				/* 0-3: Measurement Tag Type signature */
	icmSn_pad(b, 4);								/* 4-7: Zero padding */

	icmSn_StandardObserver32(b, &p->observer);		/* 8-11: Encoded value for standard observer */
	icmSn_XYZNumber12b(b, &p->backing);				/* 12-23: CIEXYZ of measurement backing */
	icmSn_MeasurementGeometry32(b, &p->geometry);
	icmSn_d_U16Fix16(b, &p->flare);					/* 28-31: 16.16 encoded measurement flare */
	icmSn_PredefinedIlluminant32(b, &p->illuminant); /* 32-35: Encoded standard illuminant */

	ICMRDCHECKCONSUMED(icmMeasurement)
}

/* Dump a text description of the object */
static void icmMeasurement_dump(
	icmMeasurement *p,
	icmFile *op,	/* Output to dump to */
	int   verb		/* Verbosity level */
) {
	if (verb <= 0)
		return;

	op->printf(op,"Measurement:\n");
	op->printf(op,"  Standard Observer = %s\n", icmStandardObserver2str(p->observer));
	op->printf(op,"  XYZ for Measurement Backing = %s\n", icmXYZNumber_and_Lab2str(&p->backing));
	op->printf(op,"  Measurement Geometry = %s\n", icmMeasurementGeometry2str(p->geometry));
	op->printf(op,"  Measurement Flare = %5.1f%%\n", p->flare * 100.0);
	op->printf(op,"  Standard Illuminant = %s\n", icmIlluminant2str(p->illuminant));
}

/* Check Measurement */
static int icmMeasurement_check(icmMeasurement *p, icTagSignature sig, int rd) {
	/* Check measurement flare is between  0 and 100% */
	if (p->flare < 0.0 || p->flare > 1.0) {
		icmFormatWarning(p->icp, ICM_FMT_FLARERANGE,
		                       "Measurement flare %5.1f%% is out of range", p->flare * 100.0);
	}
	return p->icp->e.c;
}

/* Create an empty object. Return null on error */
static icmBase *new_icmMeasurement(icc *icp, icTagTypeSignature ttype) {
	ICM_BASE_ALLOCINIT(icmMeasurement, ttype)
	return (icmBase *)p;
}

/* ---------------------------------------------------------- */
/* icmNamedColor object */

static icmPe *new_icmSig2NormPe(icc *icp, icmSnPrim *pt, icColorSpaceSignature sig,
                                      icmEncLabVer ver, icmEncBytes bytes);
static void icmSn_d_n_prim(icmFBuf *b, icmPe *npe, icmSnPrim pt, double *p);

/* Serialise this tag type */
static void icmNamedColor_serialise(icmNamedColor *p, icmFBuf *b) {
	icc *icp = p->icp;
	int txtlen;
	int minvalsize;
	icmPe *pcs_npe = NULL; icmSnPrim pcs_pt; 
	icmPe *dev_npe = NULL; icmSnPrim dev_pt; 
	unsigned int i, j;

    icmSn_TagTypeSig32(b, &p->ttype);			/* 0-3: NamedColor Tag Type signature */
	icmSn_pad(b, 4);							/* 4-7: Zero padding */

	icmSn_ui_UInt32(b, &p->vendorFlag);			/* 8-11: Vendor Specific Flags */
	icmSn_ui_UInt32(b, &p->count);				/* 12-15: Number of named colors */

	if (p->ttype == icSigNamedColorType) {
		if (b->op & icmSnSerialise)
			p->nDeviceCoords = icmCSSig2nchan(icp->header->colorSpace);
		txtlen = -32;		/* Up to 32 */
		minvalsize = 1 + p->nDeviceCoords;	/* nul + n channel of 8 bits */
	} else {
		icmSn_check_ui_UInt32(b, &p->nDeviceCoords, MAX_CHAN);
		txtlen = 32;		/* Always 32 */
		minvalsize = 38 + 2 * p->nDeviceCoords;	/* 32 byte root + 6 byte PCS + 2n Dev channel */
	}

	icmSn_ASCIIZ(b, &p->_pcount, &p->pcount, &p->prefix,
		                NULL, txtlen, "NamedColor");	/* Prefix */
	icmSn_ASCIIZ(b, &p->_scount, &p->scount, &p->suffix,
		                NULL, txtlen, "NamedColor");	/* Suffix */

	if (icmArrayRdAllocResize(b, icmAResizeByCount, &p->_count, &p->count,
	    (void **)&p->data, sizeof(icmNamedColorVal),
	    UINT_MAX, 0, minvalsize, "icmNamedColor"))
		return;


	if (b->op & icmSnSerialise) {
		/* NamedColor is always ICCV2 encoding. */
		if (p->ttype != icSigNamedColorType) {
			pcs_npe = new_icmSig2NormPe(icp, &pcs_pt, icp->header->pcs, icmEncV2, icmEnc16bit); 
			dev_npe = new_icmSig2NormPe(icp, &dev_pt, icp->header->colorSpace, icmEncV2, icmEnc16bit); 
		} else 
			dev_npe = new_icmSig2NormPe(icp, &dev_pt, icp->header->colorSpace, icmEncV2, icmEnc8bit); 
		if (p->icp->e.c != ICM_ERR_OK)
			return;			/* there was an error */
	}

	for (i = 0; i < p->count; i++) {
		icmNamedColorVal *pp = &p->data[i];

		icmSn_ASCIIZ(b, &pp->_rcount, &pp->rcount, &pp->root,
			                NULL, txtlen, "NamedColor");	/* Root name */

		if (b->op & icmSnSerialise) {						/* If cssn's inited */
		 	if (p->ttype != icSigNamedColorType)					/* NamedColor2 */
				icmSn_d_n_prim(b, pcs_npe, pcs_pt, pp->pcsCoords);	/* Color PCS */
			icmSn_d_n_prim(b, dev_npe, dev_pt, pp->deviceCoords);	/* Device values */
		}
	}
	if (b->op & icmSnSerialise) {
		if (pcs_npe != NULL)
			pcs_npe->del(pcs_npe);
		dev_npe->del(dev_npe);
	}
	ICMSNFREEARRAY(p, p->_count, p->data)
	ICMRDCHECKCONSUMED(icmNamedColor)
}

/* Dump a text description of the object */
static void icmNamedColor_dump(
	icmNamedColor *p,
	icmFile *op,	/* Output to dump to */
	int   verb		/* Verbosity level */
) {
	icc *icp = p->icp;
	if (verb <= 0)
		return;

	if (p->ttype == icSigNamedColorType)
		op->printf(op,"NamedColor:\n");
	else
		op->printf(op,"NamedColor2:\n");
	op->printf(op,"  Vendor Flag = 0x%x\n",p->vendorFlag);
	op->printf(op,"  No. colors  = %u\n",p->count);
	op->printf(op,"  No. dev. coords = %u\n",p->nDeviceCoords);
	op->printf(op,"  Name prefix = '%s'\n",p->prefix);
	op->printf(op,"  Name suffix = '%s'\n",p->suffix);
	if (verb >= 2) {
		unsigned int i, n;
		icmNamedColorVal *vp;
		for (i = 0; i < p->count; i++) {
			vp = p->data + i;
			op->printf(op,"    Color %u:\n",i);
			op->printf(op,"      Name root = '%s'\n",vp->root);

			if (p->ttype == icSigNamedColor2Type) {
				switch(icp->header->pcs) {
					case icSigXYZData:
							op->printf(op,"      XYZ = %.8f, %.8f, %.8f\n",
							        vp->pcsCoords[0],vp->pcsCoords[1],vp->pcsCoords[2]);
						break;
			    	case icSigLabData:
							op->printf(op,"      Lab = %f, %f, %f\n",
							        vp->pcsCoords[0],vp->pcsCoords[1],vp->pcsCoords[2]);
						break;
					default:
							op->printf(op,"      Unexpected PCS\n");
						break;
				}
			}
			if (p->nDeviceCoords > 0) {
				op->printf(op,"      Device Coords = ");
				for (n = 0; n < p->nDeviceCoords; n++) {
					if (n > 0)
						op->printf(op,", ");
					op->printf(op,"%.8f",vp->deviceCoords[n]);
				}
				op->printf(op,"\n");
			}
		}
	}
}


/* Check tags validity */
static int icmNamedColor_check(icmNamedColor *p, icTagSignature sig, int rd) {
	if (p->ttype != icSigNamedColorType
	 && p->nDeviceCoords != icmCSSig2nchan(p->icp->header->colorSpace)) {
		icmFormatWarning(p->icp, ICM_FMT_NCOL_NCHAN,
		                       "Named Color number of channnels %d doesn't match header %d",
								p->nDeviceCoords, icmCSSig2nchan(p->icp->header->colorSpace)); 
	}
	return p->icp->e.c;
}

/* Create an empty object. Return null on error */
static icmBase *new_icmNamedColor(icc *icp, icTagTypeSignature ttype) {
	ICM_BASE_ALLOCINIT(icmNamedColor, ttype)
	return (icmBase *)p;
}

/* ---------------------------------------------------------- */
/* Colorant table structure read/write support */
/* (This is really ICC V4, but icclibv2 had suport for it) */

/* Serialise this tag type */
static void icmColorantTable_serialise(icmColorantTable *p, icmFBuf *b) {
	icc *icp = p->icp;
	unsigned int i;
	icmPe *pcs_npe; icmSnPrim pcs_pt;
	icColorSpaceSignature encSpace = icMaxEnumColorSpace;

	/* Setup the PCS serialiser */
	if (b->op & icmSnSerialise) {
		encSpace = icp->header->pcs;
		if (p->icp->header->deviceClass == icSigLinkClass)
			encSpace = icSigLabData;
		pcs_npe = new_icmSig2NormPe(icp, &pcs_pt, encSpace, icmEncProf, icmEnc16bit);
		if (p->icp->e.c != ICM_ERR_OK)
			return;			/* there was an error */

	    icmSn_TagTypeSig32(b, &p->ttype);		/* 0-3:  ColorantTable Tag Type signature */
		icmSn_pad(b, 4);						/* 4-7:  Zero padding */
		icmSn_ui_UInt32(b, &p->count);			/* 8-11: Number of device channels */
	}

	if (icmArrayRdAllocResize(b, icmAResizeByCount, &p->_count, &p->count,
	    (void **)&p->data, sizeof(icmColorantTableVal),
	    UINT_MAX, 0, 38, "icmColorantTable"))
		return;

	for (i = 0; i < p->count; i++) {
		icmColorantTableVal *pp = &p->data[i];

		icmSn_ASCIIZ(b, &pp->_ncount, &pp->ncount, &pp->name,
		                NULL, 32, "icmColorantTableVal");	/* 12 + 38 * n: Colorant name */
		if (b->op & icmSnSerialise) {
			if (b->op == icmSnWrite) {
				double pcsCoords[3];
				if (icmClipPCS16(icp, pcsCoords, pp->pcsCoords, encSpace, icmEncProf))
					fprintf(stderr,"Warning: Clipped ColorantTable value [%d] from %f %f %f -> %f %f %f\n",
					                        i, pp->pcsCoords[0], pp->pcsCoords[1], pp->pcsCoords[2],
					                        pcsCoords[0], pcsCoords[1], pcsCoords[2]);
				icmSn_d_n_prim(b, pcs_npe, pcs_pt, pcsCoords);/* 12 + 38 * n + 32: Colorant color */
			} else {
				icmSn_d_n_prim(b, pcs_npe, pcs_pt, pp->pcsCoords);/* 12 + 38 * n + 32: Colorant color */
			}
		}
	}
	ICMSNFREEARRAY(b, p->_count, p->data)
	ICMRDCHECKCONSUMED(icmColorantTable)
	if (b->op & icmSnSerialise) {
		pcs_npe->del(pcs_npe);
	}
}

/* Dump a text description of the object */
static void icmColorantTable_dump(
	icmColorantTable *p,
	icmFile *op,	/* Output to dump to */
	int   verb		/* Verbosity level */
) {
	icc *icp = p->icp;
	icColorSpaceSignature pcs;

	if (icp->header->deviceClass != icSigLinkClass)
		pcs = icp->header->pcs;
	else
		pcs = icSigLabData;

	if (verb <= 0)
		return;

	if (p->ttype == icSigColorantTableType
	 || p->ttype == icmSigAltColorantTableType)
		op->printf(op,"ColorantTable:\n");
	op->printf(op,"  No. colorants  = %u\n",p->count);
	if (verb >= 1) {
		unsigned int i;
		icmColorantTableVal *vp;
		for (i = 0; i < p->count; i++) {
			vp = p->data + i;
			op->printf(op,"    Colorant %u:\n",i);
			op->printf(op,"      Name = '%s'\n",vp->name);

			if (p->ttype == icSigColorantTableType
			 || p->ttype == icmSigAltColorantTableType) {
			
				switch(pcs) {
					case icSigXYZData:
							op->printf(op,"      XYZ = %.8f, %.8f, %.8f\n",
							        vp->pcsCoords[0],vp->pcsCoords[1],vp->pcsCoords[2]);
						break;
			    	case icSigLabData:
							op->printf(op,"      Lab = %f, %f, %f\n",
							        vp->pcsCoords[0],vp->pcsCoords[1],vp->pcsCoords[2]);
						break;
					default:
							op->printf(op,"      Unexpected PCS\n");
						break;
				}
			}
		}
	}
}

/* Check ColorantTable */
static int icmColorantTable_check(icmColorantTable *p, icTagSignature sig, int rd) {
	unsigned int cchan;

	if (sig == icSigColorantTableOutTag)
		cchan = icmCSSig2nchan(p->icp->header->pcs);		/* Output space */
	else
		cchan = icmCSSig2nchan(p->icp->header->colorSpace);	/* Device/input space */

	/* Does number of channels match header device space encoding ? */
	if (p->count != cchan)
		icmFormatWarning(p->icp, ICM_FMT_CORDCHAN,
		                       "ColorantTable channels %u doesn't match header",p->count);
	return p->icp->e.c;
}

/* Create an empty object. Return null on error */
static icmBase *new_icmColorantTable(icc *icp, icTagTypeSignature ttype) {
	ICM_BASE_ALLOCINIT(icmColorantTable, ttype)
	return (icmBase *)p;
}

/* ---------------------------------------------------------- */
/* textDescription */

static void icmTextDescription_serialise(icmTextDescription *p, icmFBuf *b) {

    icmSn_TagTypeSig32(b, &p->ttype);		/* 0-3: TextDescription Tag Type signature */
	icmSn_pad(b, 4);						/* 4-7: Zero padding */

	/* ASCIIZ text */
	icmSn_ui_UInt32(b, &p->fcount);			/* ASCIIZ character count inc. nul */
	icmSn_ASCIIZ(b, &p->_count, &p->count, &p->desc,
	                &p->fcount, 0, "icmTextDescription");

	/* Unicode */
	icmSn_ui_UInt32(b, &p->ucLangCode);		/* UTF-16 language code */
	icmSn_ui_UInt32(b, &p->uc16count);		/* UTF-16 character count inc. nul */
	icmSn_Unicode(b, &p->_uc8count, &p->uc8count, &p->uc8desc,
	                 NULL, &p->uc16count, "icmTextDescription", 0); 

	/* ScriptCode */
	icmSn_us_UInt16(b, &p->scCode);			/* ScriptCode language code */
	icmSn_ui_UInt8(b, &p->scFcount);		/* ScriptCode character count */

	icmSn_ScriptCode(b, &p->_scCount, &p->scCount, &p->scDesc,
	                &p->scFcount, "icmTextDescription");

	if (b->super == NULL) {		/* Hmm. We don't have knowledge of expected sub-type size... */
		ICMRDCHECKCONSUMED(icmTextDescription)
	}
}

/* Dump a text description of the object */
static void icmTextDescription_dump(
	icmTextDescription *p,
	icmFile *op,	/* Output to dump to */
	int   verb		/* Verbosity level */
) {
	int pad = p->dp;
	unsigned int i, r, c;

	if (verb <= 0)
		return;

	/* ASCII */
	op->printf(op,PAD("TextDescription:\n"));
	icmASCIIZ_dump(p->desc, p->count, op, verb, pad); 

	/* Unicode */
	/* Don't want to dump Unicode or ScriptCode to console, as its interpretation */
	/* will be unknown. */
	icmUnicode_dump(p->uc8desc, p->uc8count, p->ucLangCode, icMaxEnumRegionCode, icmMaxOff,
	                                                         p->uc16count, op, verb, p->dp);
	/* ScriptCode */
	if (p->scCount > 0) {
		unsigned int size = p->scCount > 0 ? p->scCount-1 : 0;
		op->printf(op,PAD("  ScriptCode Data, Code 0x%x, length %u chars:\n"),
		        p->scCode, p->scCount);
		i = 0;
		for (r = 1;; r++) {		/* count rows */
			if (i >= size) {
				op->printf(op,"\n");
				break;
			}
			if (r > 1 && verb <= 1) {
				op->printf(op,PAD("    ...\n"));
				break;			/* Print 1 row if not verbose */
			}
			c = 1;
			op->printf(op,PAD("    0x%04lx: "),i);
			c += 10;
			while (i < size && c < 75) {
				if (isprint(p->scDesc[i])) {
					op->printf(op,"%c",p->scDesc[i]);
					c++;
				} else {
					op->printf(op,"\\%03o",p->scDesc[i]);
					c += 4;
				}
				i++;
			}
			if (i < size)
				op->printf(op,"\n");
		}
	} else {
		op->printf(op,PAD("  No ScriptCode data\n"));
	}
}

/* Check TextDescription */
static int icmTextDescription_check(icmTextDescription *p, icTagSignature sig, int rd) {
	return p->icp->e.c;
}

/* Compare another with this */
static int icmTextDescription_cmp(icmTextDescription *dst, icmTextDescription *src) {

	if (dst->ttype != src->ttype) {
		icm_err(dst->icp, ICM_ERR_UNIMP_TTYPE_NOTSAME,"icmTextDescription_cmp: different tagtypes");
		return 1;
	}

	if (dst->count != src->count)
		return 1;
	if (dst->count > 0 && strcmp(dst->desc, src->desc))
		return 1;

	if (dst->ucLangCode != src->ucLangCode)
		return 1;
	if (dst->uc8count != src->uc8count)
		return 1;
	if (dst->uc8count > 0 && strcmp((char *)dst->uc8desc, (char *)src->uc8desc))
		return 1;

	if (dst->scCount != src->scCount)
		return 1;
	if (dst->scCode != src->scCode)
		return 1;
	if (dst->scCount > 0 && strcmp((char *)dst->scDesc, (char *)src->scDesc))
		return 1;

	return 0;
}

/* Copy or translate from source to this ttype */
static int icmTextDescription_cpy(icmTextDescription *dst, icmBase *isrc) {
	icc *p = dst->icp;

	/* If straight copy */
	if (dst->ttype == icSigTextDescriptionType
	 && isrc->ttype == icSigTextDescriptionType) {
		icmTextDescription *src = (icmTextDescription *)isrc;

		/* Easy */
		dst->count = src->count;
		dst->uc8count = src->uc8count;
		dst->scCount = src->scCount;
		if (dst->allocate(dst))
			return p->e.c;

		if (src->count > 0)
			strcpy(dst->desc, src->desc);

		dst->ucLangCode = src->ucLangCode;
		if (src->uc8count > 0)
			strcpy((char *)dst->uc8desc, (char *)src->uc8desc);

		dst->scCode = src->scCode;
		if (src->scCount > 0)
			strcpy((char *)dst->scDesc, (char *)src->scDesc);

		return ICM_ERR_OK;

	}

	return icm_err(p, ICM_ERR_UNIMP_TTYPE_COPY,"icmTextDescription_cpy: unimplemented tagtype");
}

/* Create an empty object. Return null on error */
static icmBase *new_icmTextDescription(icc *icp, icTagTypeSignature ttype) {
	ICM_BASE_ALLOCINIT(icmTextDescription, ttype)
	p->cmp = icmTextDescription_cmp;
	p->cpy = icmTextDescription_cpy;
	return (icmBase *)p;
}

/* ---------------------------------------------------------- */
/* icmProfileSequenceDesc object */

/* Initialiser for TextDescriptionType sub-tag */
static void icmProfileSequenceDesc_Textinit(icmFBuf *b, icmBase *p) {

	/* Default TextDescription to placeholder "" */
	if (p->ttype == icSigTextDescriptionType) {
		icmTextDescription *pp = (icmTextDescription *)(p);

		if (pp->count == 0) {
			pp->count = 1;

			/* Allocate UTF-8 string on read or for API client */
			if (icmArrayResize(p->icp, &pp->_count, &pp->count,
		   	 (void **)&pp->desc, sizeof(icmUTF8), "icmTextDescription default"))
				return;

			pp->desc[0] = '\000';
		}
	}
}


/* Serialise this tag type */
static void icmProfileSequenceDesc_serialise(icmProfileSequenceDesc *p, icmFBuf *b) {
	icc *icp = p->icp;
	unsigned int n;

    icmSn_TagTypeSig32(b, &p->ttype);		/* 0-3: ProfileSequenceDesc Tag Type signature */
	icmSn_pad(b, 4);						/* 4-7: Zero padding */
	icmSn_ui_UInt32(b, &p->count);			/* 8-11: Number of string records */

	/* icmPSDescStruct array */
	if (icmArrayRdAllocResize(b, icmAResizeByCount, &p->_count, &p->count,
	    (void **)&p->data, sizeof(icmPSDescStruct),
	    UINT_MAX, 0, 20 + 2 * 16, "icmProfileSequenceDesc array"))
		return;

	for (n = 0; n < p->count; n++) {
		icmPSDescStruct *pp = &p->data[n];

		if (icp->e.c != ICM_ERR_OK)
			return;

		icmSn_DeviceManufacturerSig32(b, &pp->deviceMfg);
		icmSn_DeviceModel32(b, &pp->deviceModel);
		icmSn_DeviceAttributes64(b, &pp->attributes);
		icmSn_TechnologySig32(b, &pp->technology);

		/* Deal with sub-tags */
		icmSn_SubTagType(b, NULL, NULL, (icmBase **)(&pp->mfgDesc), icmSigCommonTextDescriptionType,
		                 p->ttype, 0, icmProfileSequenceDesc_Textinit, p->rdff, 4);
		icmSn_SubTagType(b, NULL, NULL, (icmBase **)&pp->modelDesc, icmSigCommonTextDescriptionType,
		                 p->ttype, 0, icmProfileSequenceDesc_Textinit, p->rdff, 4);
	}

	ICMSNFREEARRAY(b, p->_count, p->data)
	ICMRDCHECKCONSUMED(ProfileSequenceDesc)
}

/* Dump a text description of the object */
static void icmProfileSequenceDesc_dump(
	icmProfileSequenceDesc *p,
	icmFile *op,	/* Output to dump to */
	int   verb		/* Verbosity level */
) {
	if (verb < 1)
		return;

	op->printf(op,"ProfileSequenceDesc:\n");
	op->printf(op,"  No. elements = %u\n",p->count);
	if (verb >= 1) {
		unsigned int n;
		for (n = 0; n < p->count; n++) {
			icmPSDescStruct *pp = &p->data[n];

			op->printf(op,"Element %u:\n",n);
			op->printf(op,"  Dev. Manufacturer = %s\n",icmDeviceManufacturerSig2str(pp->deviceMfg));
			op->printf(op,"  Dev. Model        = %s\n",icmDeviceModelSig2str(pp->deviceModel));
			op->printf(op,"  Dev. Attrbts      = %s\n", icmDeviceAttributes2str(pp->attributes.l));
			op->printf(op,"  Dev. Technology   = %s\n", icmTechnologySig2str(pp->technology));
			if (verb >= 2) {
				op->printf(op,"  Dev. Manufacturer Description:\n");
				pp->mfgDesc->dump(pp->mfgDesc, op, verb-1);
				op->printf(op,"  Dev. Model Description:\n");
				pp->modelDesc->dump(pp->modelDesc, op, verb-1);
			}
		}
	}
}

/* Check ProfileSequenceDesc */
static int icmProfileSequenceDesc_check(icmProfileSequenceDesc *p, icTagSignature sig, int rd) {
	unsigned int n;

	/* Check that the *device and *model are correct TagType for profile version */
	for (n = 0; n < p->count; n++) {
		icmPSDescStruct *pp = &p->data[n];
		icTagTypeSignature ttype;

		ttype = pp->mfgDesc->ttype;
		if (icc_check_sig(p->icp, NULL, rd, icmSigUnknown, ttype, ttype, p->rdff) != ICM_ERR_OK) {
			return p->icp->e.c;
		}

		ttype = pp->modelDesc->ttype;
		if (icc_check_sig(p->icp, NULL, rd, icmSigUnknown, ttype, ttype, p->rdff) != ICM_ERR_OK) {
			return p->icp->e.c;
		}
	}
	return p->icp->e.c;
}

/* Create an empty object. Return null on error */
static icmBase *new_icmProfileSequenceDesc(icc *icp, icTagTypeSignature ttype) {
	ICM_BASE_ALLOCINIT(icmProfileSequenceDesc, ttype)
	return (icmBase *)p;
}

/* ---------------------------------------------------------- */
/* icmResponseCurveSet16 object */

/* Serialise this tag type */
static void icmResponseCurveSet16_serialise(icmResponseCurveSet16 *p, icmFBuf *b) {
	icc *icp = p->icp;
	unsigned int m;

    icmSn_TagTypeSig32(b, &p->ttype);		/* 0-3: ResponseCurveSet16 Tag Type signature */
	icmSn_pad(b, 4);						/* 4-7: Zero padding */

	icmSn_ui_UInt16(b, &p->nchan);			/* 8-11: Number of channels */
	icmSn_ui_UInt16(b, &p->typeCount);		/* 12-15: Number of measurement types */

	/* (This is hard to follow because we are creating an array of */
	/*  matricies (3 dimensions total) where the final dimension */
	/*  may vary with the first two, i.e.: */
	/*  typedata[typeCount] { response[ nchan ][ nMeas[typeCount][nchan] ] }, */
	/*  so the array houskeeping itself needs allocating...) */

	/* icmRCS16Struct array, one for each measurement type */
	if (icmArrayRdAllocResize(b, icmAResizeByCount, &p->_typeCount, &p->typeCount,
	    (void **)&p->typeData, sizeof(icmRCS16Struct),
	    UINT_MAX, 0, 4, "icmResponseCurveSet16 array"))
		return;

	for (m = 0; m < p->typeCount; m++) {
		icmRCS16Struct *pp = &p->typeData[m];
		icmSn_ui_UInt32(b, &pp->off);		/* Offset to each record */
	}

	for (m = 0; m < p->typeCount; m++) {
		icmRCS16Struct *pp = &p->typeData[m];
		unsigned int n, q;

		if (icp->e.c != ICM_ERR_OK)
			return;

		if (b->op == icmSnSize || b->op == icmSnWrite)
			pp->off = b->get_off(b);
		else if (b->op == icmSnRead)
			b->aoff(b, pp->off);

		icmSn_MeasUnitsSig32(b, &pp->measUnit);		/* Measurement unit signature */

		/* Separate nMeas[nchan] array allocation tracking for each struct */
		if (icmArrayRdAllocResize(b, icmAResizeByCount, &pp->__nMeasNchan, &p->nchan,
		    (void **)&pp->_nMeas, sizeof(unsigned int),
		    UINT_MAX, 0, 16, "icmResponseCurveSet16 _nMeas array"))
			return;
		if (icmArrayRdAllocResize(b, icmAResizeByCount, &pp->_nchan, &p->nchan,
		    (void **)&pp->nMeas, sizeof(unsigned int),
		    UINT_MAX, 0, 16, "icmResponseCurveSet16 nMeas array"))
			return;

		/* Column pointers to arrays of response values */
		if (icmArrayRdAllocResize(b, icmAResizeByCount, &pp->_respNchan, &p->nchan,
		    (void **)&pp->response, sizeof(icmResponse16Number *),
		    UINT_MAX, 0, 16, "icmResponseCurveSet16 response pointer array"))
			return;

		/* PCS XYZ value of maximum colorant value */
		if (icmArrayRdAllocResize(b, icmAResizeByCount, &pp->_pcsNchan, &p->nchan,
		    (void **)&pp->pcsData, sizeof(icmXYZNumber),
		    UINT_MAX, 0, 16, "icmResponseCurveSet16 pcsData array"))
			return;

		/* number or measurements for this channel */
		for (n = 0; n < p->nchan; n++) {
			icmSn_ui_UInt32(b, &pp->nMeas[n]);
		}

		/* PCS XYZ value of maximum colorant value */
		for (n = 0; n < p->nchan; n++) {
			icmSn_XYZNumber12b(b, &pp->pcsData[n]);
		}

		for (n = 0; n < p->nchan; n++) {
			/* Arrays of response values */
			if (icmArrayRdAllocResize(b, icmAResizeByCount, &pp->_nMeas[n], &pp->nMeas[n],
			    (void **)&(pp->response[n]), sizeof(icmResponse16Number),
			    UINT_MAX, 0, 8, "icmResponseCurveSet16 response data array"))
				return;

			/* Response numbers */
			for (q = 0; q < pp->nMeas[n]; q++) {
				icmSn_Response16Number8b(b, &pp->response[n][q]);
			} 
		}

		for (n = 0; n < p->nchan; n++) {
			ICMSNFREEARRAY(b, pp->_nMeas[n], pp->response[n])
		}
		ICMSNFREEARRAY(b, pp->_pcsNchan, pp->pcsData)
		ICMSNFREEARRAY(b, pp->_respNchan, pp->response)
		ICMSNFREEARRAY(b, pp->_nchan, pp->nMeas)
		ICMSNFREEARRAY(b, pp->__nMeasNchan, pp->_nMeas)
	}

	ICMSNFREEARRAY(b, p->_typeCount, p->typeData)
	// Can't ICMRDCHECKCONSUMED because we randomly seek on read
}

/* Dump a text description of the object */
static void icmResponseCurveSet16_dump(
	icmResponseCurveSet16 *p,
	icmFile *op,	/* Output to dump to */
	int   verb		/* Verbosity level */
) {
	if (verb <= 0)
		return;

	op->printf(op,"ResponseCurveSet16:\n");
	op->printf(op,"  No. device channels   = %u\n",p->nchan);
	op->printf(op,"  No. Measurement Types = %u\n",p->typeCount);
	if (verb >= 1) {
		unsigned int m;
		for (m = 0; m < p->typeCount; m++) {
			icmRCS16Struct *pp = &p->typeData[m];
			unsigned int n, q;

			op->printf(op,"  Measurement index %u: Units = %s\n",m,icmMeasUnitsSig2str(pp->measUnit));

			for (n = 0; n < p->nchan; n++) {
				unsigned int q;

				op->printf(op,"    Channel index %u:\n",n);
				op->printf(op,"    Max Colorant XYZ =  %s\n",icmXYZNumber_and_Lab2str(&pp->pcsData[n]));
				op->printf(op,"    No. of responses %u\n",pp->nMeas[n]);

				if (verb < 2)
					continue;
				op->printf(op,"    Response: Index, Device Value, Measurement Reading\n");
				for (q = 0; q < pp->nMeas[n]; q++) {
					op->printf(op,"      %u:  %f, %f\n",q,pp->response[n][q].deviceValue,
					                                    pp->response[n][q].measurement);
				}
			}
			op->printf(op,"\n");
		}
	}
}

/* Check ResponseCurveSet16 */
static int icmResponseCurveSet16_check(icmResponseCurveSet16 *p, icTagSignature sig, int rd) {
	icc *icp = p->icp;
	unsigned int dchan = icmCSSig2nchan(icp->header->colorSpace);

	/* Does number of channels match header device space encoding ? */
	if (p->nchan != dchan) {
		icmFormatWarning(icp, ICM_FMT_CHRMCHAN,
	                       "ResponseCurveSet16 no. channels %u doesn't match header %u",
		                                                              p->nchan,dchan);
	}
	return icp->e.c;
}

/* Create an empty object. Return null on error */
static icmBase *new_icmResponseCurveSet16(icc *icp, icTagTypeSignature ttype) {
	ICM_BASE_ALLOCINIT(icmResponseCurveSet16, ttype)
	return (icmBase *)p;
}

/* ---------------------------------------------------------- */
/* icmDeviceSettings object */

/* Serialise this tag type */
static void icmDeviceSettings_serialise(icmDeviceSettings *p, icmFBuf *b) {
	icc *icp = p->icp;
	unsigned int h, i, j, k, m;

	/* Device Settings */
    icmSn_TagTypeSig32(b, &p->ttype);		/* 0-3:  DeviceSettings Tag Type signature */
	icmSn_pad(b, 4);						/* 4-7:  Zero padding */
	icmSn_ui_UInt32(b, &p->count);			/* 8-11: Number of platforms */

	if (icmArrayRdAllocResize(b, icmAResizeByCount, &p->_count, &p->count,
	    (void **)&p->data, sizeof(icmPlatformEntry),
	    UINT_MAX, 0, 12, "icmDeviceSettings"))
		return;

	for (h = 0; h < p->count; h++) {
		/* Platform entry */
		icmPlatformEntry *pep = &p->data[h];
		unsigned int pe_mark = b->get_off(b);
		icmSn_PlatformSig32(b, &pep->platform);		/* 0-3:  Platform sig. */
		icmSn_ui_UInt32(b, &pep->size);				/* 4-7:  Size of this subpart in bytes */
		icmSn_ui_UInt32(b, &pep->count);			/* 8-11: Count of setting combinations */

		if (icmArrayRdAllocResize(b, icmAResizeByCount, &pep->_count, &pep->count,
		    (void **)&pep->data, sizeof(icmSettingComb),
		    pep->size, 0, 8, "icmDeviceSettings"))
			return;

		for (i = 0; i < pep->count; i++) {
			/* Setting combination */
			icmSettingComb *scp = &pep->data[i];
			unsigned int sc_mark = b->get_off(b);
			icmSn_ui_UInt32(b, &scp->size);	/* 0-3: Size of subpart in bytes */
			icmSn_ui_UInt32(b, &scp->count);	/* 4-7: Count of settings */

			if (icmArrayRdAllocResize(b, icmAResizeByCount, &scp->_count, &scp->count,
			    (void **)&scp->data, sizeof(icmSettingStruct),
			    scp->size, 0, 12, "icmDeviceSettings"))
				return;

			for (j = 0; j < scp->count; j++) {
				/* Setting */
				icmSettingStruct *ssp = &scp->data[j];

				if (pep->platform == icSigMicrosoft) {
					icmSn_DevSetMsftIDSig32(b, &ssp->msft.sig);
					icmSn_ui_UInt32(b, &ssp->size);	/* Per setting size */
					icmSn_ui_UInt32(b, &ssp->count);

					if (icSigMsftResolution == ssp->msft.sig) {
						if (b->op == icmSnRead && ssp->size != 8)
							icmFormatWarning(icp, ICM_FMT_DSSIZE,
							   "DeviceSettings MsftResolution setting size mismatch %u != 8",
							    ssp->size);
						if (b->op == icmSnSize)
							ssp->size = 8;
						ssp->ssize = sizeof(struct icmMsfResolution);
						if (icmArrayRdAllocResize(b, icmAResizeByCount, &ssp->_count, &ssp->count,
						    (void **)&ssp->msft.resolution, ssp->ssize,
						    scp->size-4, 0, 8, "icmDeviceSettings"))
							return;
						if (b->op & icmSnSerialise)		/* (Optimise for speed) */
							for (k = 0; k < ssp->count; k++) {
							 	icmSn_ui_UInt32(b, &ssp->msft.resolution[k].yres);
							 	icmSn_ui_UInt32(b, &ssp->msft.resolution[k].xres);
							}
						ICMSNFREEARRAY(b, ssp->_count, ssp->msft.resolution)
					} else if (icSigMsftMedia == ssp->msft.sig) {
						if (b->op == icmSnRead && ssp->size != 4)
							icmFormatWarning(icp, ICM_FMT_DSSIZE,
							   "DeviceSettings MsftMedia setting size mismatch %u != 4",
							    ssp->size);
						if (b->op == icmSnSize)
							ssp->size = 4;
						ssp->ssize = sizeof(icDevSetMsftMedia);
						if (icmArrayRdAllocResize(b, icmAResizeByCount, &ssp->_count, &ssp->count,
						    (void **)&ssp->msft.media, ssp->ssize,
						    scp->size-4, 0, 4, "icmDeviceSettings"))
							return;
						if (b->op & icmSnSerialise)		/* (Optimise for speed) */
							for (k = 0; k < ssp->count; k++)
								icmSn_DevSetMsftMedia32(b, &ssp->msft.media[k]);
						ICMSNFREEARRAY(b, ssp->_count, ssp->msft.media);
					} else if (icSigMsftHalftone == ssp->msft.sig) {
						if (b->op == icmSnRead && ssp->size != 4)
							icmFormatWarning(icp, ICM_FMT_DSSIZE,
							   "DeviceSettings MsftDither setting size mismatch %u != 4",
							    ssp->size);
						if (b->op == icmSnSize)
							ssp->size = 4;
						ssp->ssize = sizeof(icDevSetMsftDither);
						if (icmArrayRdAllocResize(b, icmAResizeByCount, &ssp->_count, &ssp->count,
						    (void **)&ssp->msft.halftone, ssp->ssize,
						    scp->size-4, 0, 4, "icmDeviceSettings"))
							return;
						if (b->op & icmSnSerialise)		/* (Optimise for speed) */
							for (k = 0; k < ssp->count; k++)
								icmSn_DevSetMsftDither32(b, &ssp->msft.halftone[k]); 
						ICMSNFREEARRAY(b, ssp->_count, ssp->msft.halftone)
					} else {
						/* User has set ssize for unknown */
						if (b->op == icmSnRead)
							ssp->ssize = ssp->size;		/* Set unknown byte size on read */
						if (b->op == icmSnSize)
							ssp->size = ssp->ssize;		/* Caller set size */
						if (icmArrayRdAllocResize(b, icmAResizeByCount, &ssp->_count, &ssp->count,
						    (void **)&ssp->msft.unknown, ssp->ssize,
						    scp->size-4, 0, ssp->size, "icmDeviceSettings"))
							return;
						if (b->op & icmSnSerialise) {	/* (Optimise for speed) */
							for (k = 0; k < ssp->count; k++)
								for (m = 0; m < ssp->ssize; m++)
									icmSn_uc_UInt8(b, &ssp->msft.unknown[k * ssp->size + m]); 
						}
						ICMSNFREEARRAY(b, ssp->_count, ssp->msft.unknown)
					}
				} else {
					icmSn_ui_UInt32(b, &ssp->unknown.sig);
					icmSn_ui_UInt32(b, &ssp->size);	/* Per setting size */
					icmSn_ui_UInt32(b, &ssp->count);

					/* User has set ssize for unknown */
					if (b->op == icmSnRead)
						ssp->ssize = ssp->size;		/* Set unknown byte size on read */
					if (b->op == icmSnSize)
						ssp->size = ssp->ssize;
					if (icmArrayRdAllocResize(b, icmAResizeByCount, &ssp->_count, &ssp->count,
					    (void **)&ssp->unknown.unknown, ssp->ssize,
					    scp->size-4, 0, ssp->size, "icmDeviceSettings"))
						return;
					if (b->op & icmSnSerialise) {	/* (Optimise for speed) */
						for (k = 0; k < ssp->count; k++)
							for (m = 0; m < ssp->ssize; m++)
								icmSn_uc_UInt8(b, &ssp->unknown.unknown[k * ssp->size + m]); 
					}
					ICMSNFREEARRAY(b, ssp->_count, ssp->unknown.unknown)
				}
			}
			if (b->op == icmSnRead && scp->size != (b->get_off(b) - sc_mark))
				icmFormatWarning(icp, ICM_FMT_DSSIZE,
				   "DeviceSettings sub-structure size mismatch %u != %u",
				                                    scp->size, b->get_off(b) - sc_mark);
			if (b->op == icmSnSize)		/* Compute size of setting combination sub-structure */
				scp->size = b->get_off(b) - sc_mark;
			ICMSNFREEARRAY(b, scp->_count, scp->data)
		}
		if (b->op == icmSnRead && pep->size != (b->get_off(b) - pe_mark))
			icmFormatWarning(icp, ICM_FMT_DSSIZE,
			   "DeviceSettings platform entry size mismatch %u != %u",
			                                    pep->size, b->get_off(b) - pe_mark);
		if (b->op == icmSnSize)		/* Compute size of platform entry sub-structure */
			pep->size = b->get_off(b) - pe_mark;
		ICMSNFREEARRAY(b, pep->_count, pep->data)
	}
	ICMRDCHECKCONSUMED(icmDeviceSettings)
	ICMSNFREEARRAY(b, p->_count, p->data)
}

/* Dump a text description of the object */
static void icmDeviceSettings_dump(icmDeviceSettings *p, icmFile *op, int verb) {
	unsigned int h, i, j, k, m;

	if (verb <= 0)
		return;

	op->printf(op,"DeviceSettings:\n");
	op->printf(op,"  No. platforms = %u\n",p->count);

	for (h = 0; h < p->count; h++) {
		icmPlatformEntry *pep = &p->data[h];
		op->printf(op,"    Platform = %s\n",icmPlatformSig2str(pep->platform));
		op->printf(op,"    No. setting combinations = %u\n",pep->count);
		if (verb <= 1)
			continue;
		for (i = 0; i < pep->count; i++) {
			icmSettingComb *scp = &pep->data[i];
			op->printf(op,"      Setting combination %u\n",i+1);
			op->printf(op,"      No. settings = %u\n",scp->count);
			for (j = 0; j < scp->count; j++) {
				icmSettingStruct *ssp = &scp->data[j];
				if (pep->platform == icSigMicrosoft) {
					if (icSigMsftResolution == ssp->msft.sig) {
						op->printf(op,"        No. of Microsoft Resolution values = %u: \n",
				                                                                   ssp->count);
						for (k = 0; k < ssp->count; k++)
							op->printf(op,"          %u: X = %u, Y = %u\n",
						                                    k+1, ssp->msft.resolution[k].xres,
						                                         ssp->msft.resolution[k].yres);
					} else if (icSigMsftMedia == ssp->msft.sig) {
						op->printf(op,"        No. of Microsoft Media values = %u: \n", ssp->count);
						for (k = 0; k < ssp->count; k++)
							op->printf(op,"          %u: '%s'\n", k+1, icmDevSetMsftMedia2str(
							                                           ssp->msft.media[k]));
					} else if (icSigMsftHalftone == ssp->msft.sig) {
						op->printf(op,"        No. of Microsoft Halftone values = %u: \n",
				                                                            k+1, ssp->count);
						for (k = 0; k < ssp->count; k++)
							op->printf(op,"          %u: '%s'\n", k+1, icmDevSetMsftDither2str(
							                                       ssp->msft.halftone[k]));
					} else {
						op->printf(op,"        No. of Microsoft unknown values = %u, size %u: \n",
						                                                ssp->count, ssp->ssize);
						// ~~~ replace with data dump ?
						for (k = 0; k < ssp->count; k++)
							for (m = 0; m < ssp->ssize; m++)
							op->printf(op,"          %u[%u]: 0x%x\n",
								k+1, m+1, ssp->msft.unknown[k * ssp->size + m]); 
					}
				} else {
					op->printf(op,"        Unknown sig = %s\n", icmtag2str(ssp->unknown.sig));
					op->printf(op,"        No. of Unknown values = %u, size %u: \n",
					                                           ssp->count, ssp->ssize);
					for (k = 0; k < ssp->count; k++)
						// ~8 replace with data dump ?
						for (m = 0; m < ssp->ssize; m++)
							op->printf(op,"          %u[%u]: 0x%x\n",
								k+1, m+1, ssp->unknown.unknown[k * ssp->size + m]); 
				}
			}
		}
	}
}

/* Check tags validity */
static int icmDeviceSettings_check(icmDeviceSettings *p, icTagSignature sig, int rd) {
	return p->icp->e.c;
}

/* Create an empty object. Return null on error */
static icmBase *new_icmDeviceSettings(icc *icp, icTagTypeSignature ttype) {
	ICM_BASE_ALLOCINIT(icmDeviceSettings, ttype)
	return (icmBase *)p;
}

/* ---------------------------------------------------------- */
/* Signature */

static void icmSignature_serialise(icmSignature *p, icmFBuf *b) {
	icc *icp = p->icp;
	unsigned int h, i, j, k, m;

	/* Device Settings */
    icmSn_TagTypeSig32(b, &p->ttype);		/* 0-3:  Signature Tag Type signature */
	icmSn_pad(b, 4);						/* 4-7:  Zero padding */

	icmSn_Sig32(b, &p->sig);				/* 8-11: Signature */
}

/* Dump a text description of the object */
static void icmSignature_dump(
	icmSignature *p,
	icmFile *op,	/* Output to dump to */
	int   verb		/* Verbosity level */
) {
	if (verb <= 0)
		return;

	op->printf(op,"Signature\n");
	switch (p->creatorsig) {
		case icSigTechnologyTag:
			op->printf(op,"  Technology = %s\n", icmTechnologySig2str(p->sig));
			break;

		default:
			op->printf(op,"  Sig = %s\n", icmtag2str(p->sig));
			break;
	}
}

/* Check tags validity */
static int icmSignature_check(icmSignature *p, icTagSignature sig, int rd) {
	return p->icp->e.c;
}

/* Create an empty object. Return null on error */
static icmBase *new_icmSignature(icc *icp, icTagTypeSignature ttype) {
	ICM_BASE_ALLOCINIT(icmSignature, ttype)
	return (icmBase *)p;
}

/* ---------------------------------------------------------- */
/* icmScreening object */

/* Serialise this tag type */
static void icmScreening_serialise(icmScreening *p, icmFBuf *b) {
	unsigned int i;

    icmSn_TagTypeSig32(b, &p->ttype);			/* 0-3: Screening Tag Type signature */
	icmSn_pad(b, 4);							/* 4-7: Zero padding */
	icmSn_Screening32(b, &p->screeningFlags);	/* 8-11: Screening flags */
	icmSn_ui_UInt32(b, &p->channels);			/* 12-15: Number of device channels */

	if (icmArrayRdAllocResize(b, icmAResizeByCount, &p->_channels, &p->channels,
	    (void **)&p->data, sizeof(icmScreeningData),
	    UINT_MAX, 0, 12, "icmScreening"))
		return;
	if (b->op & icmSnSerialise) {				/* (Optimise for speed) */
		for (i = 0; i < p->channels; i++) {
			icmSn_d_S15Fix16(b, &p->data[i].frequency);	/* Frequency */
			icmSn_d_S15Fix16(b, &p->data[i].angle);		/* Angle */
			icmSn_SpotShape32(b, &p->data[i].spotShape);	/* Spot shape */
		}
	}
	ICMSNFREEARRAY(p, p->_channels, p->data)
	ICMRDCHECKCONSUMED(icmScreening)
}

/* Dump a text description of the object */
static void icmScreening_dump(
	icmScreening *p,
	icmFile *op,	/* Output to dump to */
	int   verb		/* Verbosity level */
) {
	if (verb <= 0)
		return;

	op->printf(op,"Screening:\n");
	op->printf(op,"  Flags = %s\n", icmScreenEncodings2str(p->screeningFlags));
	op->printf(op,"  No. channels = %u\n",p->channels);
	if (verb >= 2) {
		unsigned int i;
		for (i = 0; i < p->channels; i++) {
			op->printf(op,"    %u:\n",i);
			op->printf(op,"      Frequency:  %f\n",p->data[i].frequency);
			op->printf(op,"      Angle:      %f\n",p->data[i].angle);
			op->printf(op,"      Spot shape: %s\n", icmSpotShape2str(p->data[i].spotShape));
		}
	}
}

/* Dump a text description of the object */
/* Check tags validity */
static int icmScreening_check(icmScreening *p, icTagSignature sig, int rd) {
	icc *icp = p->icp;
	unsigned int dchan = icmCSSig2nchan(icp->header->colorSpace);

	/* Does number of channels match header device space encoding ? */
	if (p->channels != dchan) {
		icmFormatWarning(icp, ICM_FMT_CHRMCHAN,
		                       "Screening no. channels %u doesn't match header %u",
		                                                              p->channels,dchan);
	}
	return icp->e.c;
}

/* Create an empty object. Return null on error */
icmBase *new_icmScreening(icc *icp, icTagTypeSignature ttype) {
	ICM_BASE_ALLOCINIT(icmScreening, ttype)
	return (icmBase *)p;
}

/* ---------------------------------------------------------- */
/* icmUcrBg object */

static void icmUcrBg_serialise(icmUcrBg *p, icmFBuf *b) {
	icc *icp = p->icp;
	unsigned int i;

	/* Device Settings */
    icmSn_TagTypeSig32(b, &p->ttype);		/* 0-3:  UcrBg Tag Type signature */
	icmSn_pad(b, 4);						/* 4-7:  Zero padding */

	icmSn_ui_UInt32(b, &p->UCRcount);		/* 8-11: Count of UCR entries */
	if (icmArrayRdAllocResize(b, icmAResizeByCount, &p->_UCRcount, &p->UCRcount,
	    (void **)&p->UCRcurve, sizeof(double),
	    UINT_MAX, 0, 2, "icmUcrBg"))
		return;
	if (b->op & icmSnSerialise) {			/* (Optimise for speed) */
		if (p->UCRcount == 1) {
			icmSn_d_RFix16(b, &p->UCRcurve[0]); 
		} else {
			for (i = 0; i < p->UCRcount; i++) {
				icmSn_d_NFix16(b, &p->UCRcurve[i]); 
			}
		}
	}
	ICMSNFREEARRAY(b, p->_UCRcount, p->UCRcurve)

	icmSn_ui_UInt32(b, &p->BGcount);		/* 8-11: Count of BG entries */
	if (icmArrayRdAllocResize(b, icmAResizeByCount, &p->_BGcount, &p->BGcount,
	    (void **)&p->BGcurve, sizeof(double),
	    UINT_MAX, 0, 2, "icmUcrBg"))
		return;
	if (b->op & icmSnSerialise) {			/* (Optimise for speed) */
		if (p->BGcount == 1) {
			icmSn_d_RFix16(b, &p->BGcurve[0]); 
		} else {
			for (i = 0; i < p->BGcount; i++)
				icmSn_d_NFix16(b, &p->BGcurve[i]); 
		}
	}
	ICMSNFREEARRAY(b, p->_BGcount, p->BGcurve)

	p->fcount = b->get_space(b);			/* Implied */
	icmSn_ASCIIZ(b, &p->_count, &p->count, &p->string,
	                &p->fcount, 0, "icmUcrBg");

	ICMRDCHECKCONSUMED(icmUcrBg)
}

/* Dump a text description of the object */
static void icmUcrBg_dump(
	icmUcrBg *p,
	icmFile *op,	/* Output to dump to */
	int   verb		/* Verbosity level */
) {
	if (verb <= 0)
		return;

	op->printf(op,"Undercolor Removal Curve & Black Generation:\n");

	if (p->UCRcount == 0) {
		op->printf(op,"  UCR: Not specified\n");
	} else if (p->UCRcount == 1) {
		op->printf(op,"  UCR: %f%%\n",p->UCRcurve[0]);
	} else {
		op->printf(op,"  UCR curve no. elements = %u\n",p->UCRcount);
		if (verb >= 2) {
			unsigned int i;
			for (i = 0; i < p->UCRcount; i++)
				op->printf(op,"  %3lu:  %f\n",i,p->UCRcurve[i]);
		}
	}
	if (p->BGcount == 0) {
		op->printf(op,"  BG: Not specified\n");
	} else if (p->BGcount == 1) {
		op->printf(op,"  BG: %f%%\n",p->BGcurve[0]);
	} else {
		op->printf(op,"  BG curve no. elements = %u\n",p->BGcount);
		if (verb >= 2) {
			unsigned int i;
			for (i = 0; i < p->BGcount; i++)
				op->printf(op,"  %3lu:  %f\n",i,p->BGcurve[i]);
		}
	}

	{
		unsigned int i, r, c, size;
		op->printf(op,"  Description:\n");
		op->printf(op,"    No. chars = %u\n",p->count);
	
		size = p->count > 0 ? p->count-1 : 0;
		i = 0;
		for (r = 1;; r++) {		/* count rows */
			if (i >= size) {
				op->printf(op,"\n");
				break;
			}
			if (r > 1 && verb < 2) {
				op->printf(op,"      ...\n");
				break;			/* Print 1 row if not verbose */
			}
			c = 1;
			op->printf(op,"      0x%04lx: ",i);
			c += 10;
			while (i < size && c < 73) {
				if (isprint(p->string[i])) {
					op->printf(op,"%c",p->string[i]);
					c++;
				} else {
					op->printf(op,"\\%03o",p->string[i]);
					c += 4;
				}
				i++;
			}
			if (i < size)
				op->printf(op,"\n");
		}
	}
}

/* Check tags validity */
static int icmUcrBg_check(icmUcrBg *p, icTagSignature sig, int rd) {
	return p->icp->e.c;
}

/* Create an empty object. Return null on error */
static icmBase *new_icmUcrBg(icc *icp, icTagTypeSignature ttype) {
	ICM_BASE_ALLOCINIT(icmUcrBg, ttype)
	return (icmBase *)p;
}

/* ---------------------------------------------------------- */
/* VideoCardGamma 'vcgt' (ColorSync 2.5 specific) */

static void icmVideoCardGamma_serialise(icmVideoCardGamma *p, icmFBuf *b) {
	icc *icp = p->icp;

	/* Device Settings */
    icmSn_TagTypeSig32(b, &p->ttype);		/* 0-3:  VideoCardGamma Tag Type signature */
	icmSn_pad(b, 4);						/* 4-7:  Zero padding */

	icmSn_VideoCardGammaFormat32(b, &p->tagType);	/* 8-11: Table or Formula  */

	/* This will probably go badly wrong if client alloc's and then */
	/* changes tagType, writes table then changes back and re-alloc's/free's.... */
	/* It will also leak if ->entryCount is changes after alloc'ing and */
	/* before free'ing. */

	if (p->tagType == icVideoCardGammaTable) {
		unsigned int c, i;

		icmSn_check_ui_UInt16(b, &p->u.table.channels, 3);
		icmSn_ui_UInt16(b, &p->u.table.entryCount);
		icmSn_ui_UInt16(b, &p->u.table.entrySize);

		if ((b->op & icmSnAlloc) || b->op == icmSnFree) {
			for (c = 0; c < p->u.table.channels; c++) { 
				if (icmArrayRdAllocResize(b, icmAResizeByCount, &p->u.table._entryCount[c],
				    &p->u.table.entryCount, (void **)&p->u.table.data[c], sizeof(double),
				    UINT_MAX, 0, p->u.table.entrySize, "icmVideoCardGamma"))
					return;
			
				ICMSNFREEARRAY(b, p->u.table._entryCount[c], p->u.table.data[c])
			}
		}

		if (p->u.table.entrySize == 1) {
			for (c = 0; c < p->u.table.channels; c++)
				for (i = 0; i < p->u.table.entryCount; i++)
					icmSn_d_NFix8(b, &p->u.table.data[c][i]);
		} else if (p->u.table.entrySize == 2) {
			for (c = 0; c < p->u.table.channels; c++)
				for (i = 0; i < p->u.table.entryCount; i++)
					icmSn_d_NFix16(b, &p->u.table.data[c][i]);
		} else {
			icmFormatWarning(icp, ICM_FMT_VCGT_ENTRYSZ,"Unknown VideoCardGamma table entry size %d",p->u.table.entrySize);
			return;
		}

	} else if (p->tagType == icVideoCardGammaFormula) {
		unsigned int i;

		for (i = 0; i < 3; i++) {
			icmSn_d_S15Fix16(b, &p->u.formula.gamma[i]);
			icmSn_d_S15Fix16(b, &p->u.formula.min[i]);
			icmSn_d_S15Fix16(b, &p->u.formula.max[i]);
		}
	} else {
		icmFormatWarning(icp, ICM_FMT_VCGT_FORMAT,"Unknown VideoCardGamma format %d",p->tagType);
		return;
	}

	ICMRDCHECKCONSUMED(icmVideoCardGamma)
}

/* Dump a text description of the object */
static void icmVideoCardGamma_dump(
	icmVideoCardGamma *p,
	icmFile *op,	/* Output to dump to */
	int   verb		/* Verbosity level */
) {
	unsigned int c, i;

	if (verb <= 0)
		return;

	if (p->tagType == icVideoCardGammaTable) {
		op->printf(op,"VideoCardGammaTable:\n");
		op->printf(op,"  channels  = %d\n", p->u.table.channels);
		op->printf(op,"  entries   = %d\n", p->u.table.entryCount);
		op->printf(op,"  entrysize = %d\n", p->u.table.entrySize);
		if (verb >= 2) {
			for (c = 0; c < p->u.table.channels; c++) {
				op->printf(op,"  channel #%d\n",c);
				for (i = 0; i < p->u.table.entryCount; i++) {
					op->printf(op,"    %d: %f\n",i, p->u.table.data[c][i]);
				}
			}
		}
	} else if (p->tagType == icVideoCardGammaFormula) {
		char *cname[3] = { "red", "green", "blue" };
		op->printf(op,"VideoCardGammaFormula:\n");
		for (i = 0; i < 3; i++) {
			op->printf(op,"  %s gamma   = %.8f\n", cname[i],p->u.formula.gamma[i]);
			op->printf(op,"  %s min     = %.8f\n", cname[i],p->u.formula.min[i]);
			op->printf(op,"  %s max     = %.8f\n", cname[i],p->u.formula.max[i]);
		}
	} else {
		op->printf(op,"  Unknown tag format\n");
	}
}

/* Translate a value throught the gamma lookup */
static double icmVideoCardGamma_lookup(
	icmVideoCardGamma *p,
	int chan,		/* Channel, 0, 1 or 2 */
	double iv		/* Input value 0.0 - 1.0 */
) {
	double ov = 0.0;

	if (chan < 0 || chan > (p->u.table.channels-1)
	 || iv < 0.0 || iv > 1.0)
		return iv;

	if (p->tagType == icVideoCardGammaTable && p->u.table.entryCount == 0) {
		/* Deal with siliness */
		ov = iv;
	} else if (p->tagType == icVideoCardGammaTable) {
		/* Use linear interpolation */
		unsigned int ix;
		double val0, val1, w;
		double inputEnt_1 = (double)(p->u.table.entryCount-1);

		val0 = iv * inputEnt_1;
		if (val0 < 0.0)
			val0 = 0.0;
		else if (val0 > inputEnt_1)
			val0 = inputEnt_1;
		ix = (unsigned int)floor(val0);		/* Coordinate */
		if (ix > (p->u.table.entryCount-2))
			ix = (p->u.table.entryCount-2);
		w = val0 - (double)ix;		/* weight */
		val0 = p->u.table.data[chan][ix + 0];
		val1 = p->u.table.data[chan][ix + 1];
		ov = val0 + w * (val1 - val0);

	} else if (p->tagType == icVideoCardGammaFormula) {

		/* The Apple OSX doco confirms this is the formula: */
		ov = p->u.formula.min[chan] + (p->u.formula.max[chan] - p->u.formula.min[chan])
		                            * pow(chan, p->u.formula.gamma[chan]);
	}
	return ov;
}

/* Check tags validity */
static int icmVideoCardGamma_check(icmVideoCardGamma *p, icTagSignature sig, int rd) {
	return p->icp->e.c;
}

/* Create an empty object. Return null on error */
static icmBase *new_icmVideoCardGamma(icc *icp, icTagTypeSignature ttype) {
	ICM_BASE_ALLOCINIT(icmVideoCardGamma, ttype)
	p->lookup = icmVideoCardGamma_lookup;
	return (icmBase *)p;
}

/* ---------------------------------------------------------- */
/* ViewingConditions */

static void icmViewingConditions_serialise(icmViewingConditions *p, icmFBuf *b) {
	unsigned int n;

    icmSn_TagTypeSig32(b, &p->ttype);				/* 0-3: ViewingConditions Tag Type signature */
	icmSn_pad(b, 4);								/* 4-7: Zero padding */

	icmSn_XYZNumber12b(b, &p->illuminant);				/* 8-19: CIEXYZ of measurement backing */
	icmSn_XYZNumber12b(b, &p->surround);				/* 20-31: CIEXYZ of measurement backing */
	icmSn_PredefinedIlluminant32(b, &p->stdIlluminant); /* 32-35: Encoded standard illuminant */

	ICMRDCHECKCONSUMED(icmViewingConditions)
}

/* Dump a text description of the object */
static void icmViewingConditions_dump(
	icmViewingConditions *p,
	icmFile *op,	/* Output to dump to */
	int   verb		/* Verbosity level */
) {
	if (verb <= 0)
		return;

	op->printf(op,"Viewing Conditions:\n");
	op->printf(op,"  XYZ value of illuminant in cd/m^2 = %s\n", icmXYZNumber2str(&p->illuminant));
	op->printf(op,"  XYZ value of surround in cd/m^2   = %s\n", icmXYZNumber2str(&p->surround));
	op->printf(op,"  Illuminant type = %s\n", icmIlluminant2str(p->stdIlluminant));
}

/* Check ViewingConditions */
static int icmViewingConditions_check(icmViewingConditions *p, icTagSignature sig, int rd) {
	return p->icp->e.c;
}

/* Create an empty object. Return null on error */
static icmBase *new_icmViewingConditions(icc *icp, icTagTypeSignature ttype) {
	ICM_BASE_ALLOCINIT(icmViewingConditions, ttype)
	return (icmBase *)p;
}

/* ---------------------------------------------------------- */
/* icmCrdInfo object, postscript Color Rendering Dictionary names type */

/* Serialise this tag type */
static void icmCrdInfo_serialise(icmCrdInfo *p, icmFBuf *b) {
	unsigned int i;

    icmSn_TagTypeSig32(b, &p->ttype);		/* 0-3:  CrdInfo Tag Type signature */
	icmSn_pad(b, 4);						/* 4-7:  Zero padding */


	icmSn_ui_UInt32(b, &p->fppcount);		/* 8-11: Number of product name characters */
	icmSn_ASCIIZ(b, &p->_ppcount, &p->ppcount, &p->ppname,
	                &p->fppcount, 0, "icmCrdInfo");


	for (i = 0; i < 4; i++) {
		icmSn_ui_UInt32(b, &p->fcrdcount[i]);		/* Number of crdN name characters */
		icmSn_ASCIIZ(b, &p->_crdcount[i], &p->crdcount[i], &p->crdname[i],
		                &p->fcrdcount[i], 0, "icmCrdInfo");
	}

	ICMRDCHECKCONSUMED(icmCrdInfo)
}

/* Dump a text description of the object */
static void icmCrdInfo_dump(
	icmCrdInfo *p,
	icmFile *op,	/* Output to dump to */
	int   verb		/* Verbosity level */
) {
	unsigned int i;

	if (verb <= 0)
		return;

	op->printf(op,"PostScript Product name and CRD names:\n");

	op->printf(op,"  Product name:\n");
	icmASCIIZ_dump(p->ppname, p->ppcount, op, verb, p->dp + 2);

	for (i = 0; i < 4; i++) {	/* For all 4 intents */
		op->printf(op,"  CRD%d name:\n",i);
		icmASCIIZ_dump(p->crdname[i], p->crdcount[i], op, verb, p->dp + 2);
	}
}

/* Check CrdInfo */
static int icmCrdInfo_check(icmCrdInfo *p, icTagSignature sig, int rd) {
	return p->icp->e.c;
}

/* Create an empty object. Return null on error */
static icmBase *new_icmCrdInfo(icc *icp, icTagTypeSignature ttype) {
	ICM_BASE_ALLOCINIT(icmCrdInfo, ttype)
	return (icmBase *)p;
}

/* ======================================== */

/* icmPe transform implemenntation */
#include "icc_xf.c"

/* icmProcessing Element implementation */
#include "icc_pe.c"

/* ========================================================== */
/* icmHeader object */
/* ========================================================== */

static void icmHeader_serialise(icmHeader *p, icmFBuf *b) {
	unsigned int tt;

	/* On read, check that the magic number is right before doing anything else. */
	if (b->op == icmSnRead) {

		tt = 0;
		b->aoff(b, 36);
		icmSn_ui_UInt32(b, &tt);
		b->aoff(b, 0);

		if (tt != icMagicNumber) {				/* Check magic number */
			icm_err(b->icp, ICM_ERR_MAGIC_NUMBER, "ICC profile has bad magic number");
			return;
		}
	}

	icmSn_ui_UInt32(b, &p->size);				/* 0-3:  Profile size in bytes */
    icmSn_ui_UInt32(b, &p->cmmId);				/* 4-7:  CMM for profile */
	icmSn_VersionNumber4b(b, &p->vers);			/* 8-11: Version number */
	if (b->icp->e.c != ICM_ERR_OK)
		return;									/* Don't continue if we don't understand version */
    icmSn_ProfileClassSig32(b, &p->deviceClass);/* 12-15: Type of profile */
    icmSn_ColorSpaceSig32(b, &p->colorSpace);	/* 16-19: Color space of data (input space) */
    icmSn_ColorSpaceSig32(b, &p->pcs);			/* 20-23: PCS: XYZ or Lab (output space) */
	icmSn_DateTimeNumber12b(b, &p->date);		/* 24-35: Creation Date */
	if (b->op == icmSnWrite)
		tt = icMagicNumber;
	icmSn_ui_UInt32(b, &tt);					/* 36-39: magic number */
    icmSn_PlatformSig32(b, &p->platform);		/* 40-43: Primary platform */
	if (p->doid) {
		icProfileFlags zf = 0;
	    icmSn_ProfileFlags32(b, &zf);			/* 44-47: Various flag bits */
	} else {
	    icmSn_ProfileFlags32(b, &p->flags);		/* 44-47: Various flag bits */
	}
    icmSn_ui_UInt32(b, &p->manufacturer);		/* 48-51: Dev manufacturer */
    icmSn_ui_UInt32(b, &p->model);				/* 52-55: Dev model */
    icmSn_DeviceAttributes64(b, &p->attributes);/* 56-63: Device attributes */
	if (b->op == icmSnWrite) {
		p->extraRenderingIntent = (~0xffff & p->extraRenderingIntent)
		                         | (0xffff & p->renderingIntent);
	}
	if (p->doid) {
		icRenderingIntent zri = 0;
	    icmSn_RenderingIntent32(b, &zri);		/* 64-67: Rendering intent */
	} else {
	    icmSn_RenderingIntent32(b, &p->extraRenderingIntent);
												/* 64-67: Rendering intent */
	}
	if (b->op == icmSnRead)
		p->renderingIntent = 0xffff & p->extraRenderingIntent;
	icmSn_XYZNumber12b(b, &p->illuminant);		/* 68-79: Profile illuminant */
    icmSn_ui_UInt32(b, &p->creator);			/* 80-83: Profile creator */

	if (p->vers.majv >= 4) {
		if (p->doid) {
			unsigned char id[16] = { 0 };
			for (tt = 0; tt < 16; tt++)
				icmSn_uc_UInt8(b, &id[tt]);		/* 84-99: Profile ID */	
		} else {
			for (tt = 0; tt < 16; tt++)
				icmSn_uc_UInt8(b, &p->id[tt]);	/* 84-99: Profile ID */	
		}
		icmSn_pad(b, 28);						/* 100-127 Zero */
	} else {
		if (b->op == icmSnRead) {
			for (tt = 0; tt < 16; tt++)
				p->id[tt] = 0;
		}
		icmSn_pad(b, 44);						/* 84-127 Zero */
	}
	if ((b->op & icmSnSerialise) && b->get_off(b) != 128)			/* Assert */
		icm_err(b->icp, ICM_ERR_HEADER_LENGTH, "Internal: ICC profile header is wrong length");

	if (b->op == icmSnRead
	 && p->icp->e.c == ICM_ERR_OK
	 && p->vers.majv >= 4)
		fprintf(stderr,"Warning: ICC V4 not supported!\n");
}

/* Dump a text description of the object */
static void icmHeader_dump(
	icmHeader *p,
	icmFile *op,	/* Output to dump to */
	int   verb		/* Verbosity level */
) {
	icmDateTimeNumber l;
	icRenderingIntent fullri;
	int i;
	if (verb <= 0)
		return;

	op->printf(op,"Header:\n");
	op->printf(op,"  Profile size    = %d bytes\n",p->size);
	op->printf(op,"  CMM             = %s\n",icmtag2str(p->cmmId));
	op->printf(op,"  Version         = %d.%d.%d\n",p->vers.majv, p->vers.minv, p->vers.bfv);
	op->printf(op,"  Device Class    = %s\n", icmProfileClassSig2str(p->deviceClass));
	op->printf(op,"  Color Space     = %s\n", icmColorSpaceSig2str(p->colorSpace));
	op->printf(op,"  Conn. Space     = %s\n", icmColorSpaceSig2str(p->pcs));
	op->printf(op,"  UTC Date&Time   = %s\n", icmDateTimeNumber2str(&p->date));
	icmDateTimeNumber_tolocal(&l, &p->date);
	op->printf(op,"  Local Date&Time = %s\n", icmDateTimeNumber2str(&l));
	op->printf(op,"  Platform        = %s\n", icmPlatformSig2str(p->platform));
	op->printf(op,"  Flags           = %s\n", icmProfileHeaderFlags2str(p->flags));
	op->printf(op,"  Dev. Mnfctr.    = %s\n", icmDeviceManufacturerSig2str(p->manufacturer));
	op->printf(op,"  Dev. Model      = %s\n", icmDeviceModelSig2str(p->model));
	op->printf(op,"  Dev. Attrbts    = %s\n", icmDeviceAttributes2str(p->attributes.l));
	fullri = (~0xffff & p->extraRenderingIntent) | (0xffff & p->renderingIntent);
	op->printf(op,"  Rndrng Intnt    = %s\n", icmRenderingIntent2str(fullri));
	op->printf(op,"  Illuminant      = %s\n", icmXYZNumber_and_Lab2str(&p->illuminant));
	op->printf(op,"  Creator         = %s\n", icmtag2str(p->creator));	/* ~~~ */
	if (p->vers.majv >= 4) {			/* V4.0+ feature */
		for (i = 0; i < 16; i++) {		/* Check if ID has been set */
			if (p->id[i] != 0)
				break;
		}
		if (i < 16)
			op->printf(op,"  ID              = %02X%02X%02X%02X%02X%02X%02X%02X"
			                                  "%02X%02X%02X%02X%02X%02X%02X%02X\n",
				p->id[0], p->id[1], p->id[2], p->id[3], p->id[4], p->id[5], p->id[6], p->id[7],
				p->id[8], p->id[9], p->id[10], p->id[11], p->id[12], p->id[13], p->id[14], p->id[15]);
		else
			op->printf(op,"  ID           = <Not set>\n");
	}
	op->printf(op,"\n");
}

static int icmHeader_check(icmHeader *p, icTagSignature sig, int rd) {
	// ~8 should check that size is multiple of 4 
	// ~8 add header check code here. Move in-line check code to here ???
	return p->icp->e.c;
}

static void icmHeader_del(icmHeader *p) {
	p->icp->al->free(p->icp->al, p);
}

/* Create an empty object. On error, set icp->e and return NULL */
static icmHeader *new_icmHeader(icc *icp) {
	int i;

	/* Allocate *p, and do base class init */
	ICM_BASE_ALLOCINIT(icmHeader, 0)

	/* Characteristic values */
	p->hsize = 128;		/* Header is 128 bytes for ICC */

	/* Values that must be set before writing */
	p->deviceClass = icMaxEnumClass;/* Type of profile - must be set! */
    p->colorSpace = icMaxEnumColorSpace;	/* Clr space of data - must be set! */
    p->pcs = icMaxEnumColorSpace;			/* PCS: XYZ or Lab - must be set! */
    p->renderingIntent = icMaxEnumIntent;	/* Rendering intent - must be set ! */

	/* Values that should be set before writing */
	p->manufacturer = -1;			/* Dev manufacturer - should be set ! */
    p->model = -1;					/* Dev model number - should be set ! */
    p->attributes.l = 0;			/* ICC Device attributes - should set ! */
    p->flags = 0;					/* Embedding flags - should be set ! */
	
	/* Values that may be set before writing */
    p->attributes.h = 0;			/* Dev Device attributes - may be set ! */
    p->creator = icmstr2tag("argl");/* Profile creator - Argyll - may be set ! */

	/* Init default values in header */
	p->cmmId = icmstr2tag("argl");	/* CMM for profile - Argyll CMM */
    p->vers.majv = 2;				/* Default version 2.2.0 */
	p->vers.minv = 2;
	p->vers.bfv  = 0;
	icmDateTimeNumber_setcur(&p->date);/* Creation Date */
#ifdef NT
	p->platform = icSigMicrosoft;
#endif
#ifdef __APPLE__
	p->platform = icSigMacintosh;
#endif
#if defined(UNIX) && !defined(__APPLE__)
	p->platform = icmSig_nix;
#endif
    p->illuminant = icmD50;			/* Profile illuminant - D50 */

	/* Values that will be created automatically */
	for (i = 0; i < 16; i++)
		p->id[i] = 0;

	return p;
}

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

const icmTVRange icmtvrange_all = ICMTVRANGE_ALL;
const icmTVRange icmtvrange_none = ICMTVRANGE_NONE;

const icmTVRange icmtvrange_20_plus = ICMTVRANGE_20_PLUS;
const icmTVRange icmtvrange_21_plus = ICMTVRANGE_21_PLUS;
const icmTVRange icmtvrange_22_plus = ICMTVRANGE_22_PLUS;
const icmTVRange icmtvrange_23_plus = ICMTVRANGE_23_PLUS;
const icmTVRange icmtvrange_24_plus = ICMTVRANGE_24_PLUS;
const icmTVRange icmtvrange_40_plus = ICMTVRANGE_40_PLUS;
const icmTVRange icmtvrange_41_plus = ICMTVRANGE_41_PLUS;
const icmTVRange icmtvrange_42_plus = ICMTVRANGE_42_PLUS;
const icmTVRange icmtvrange_43_plus = ICMTVRANGE_43_PLUS;
const icmTVRange icmtvrange_44_plus = ICMTVRANGE_44_PLUS;


/* Return a string that shows the icc's version */
char *icmICCvers2str(icc *p) {
	static char buf[50];
	sprintf(buf,"%d.%d.%d", p->header->vers.majv, p->header->vers.minv, p->header->vers.bfv);
	return buf;
}

/* Return a string that shows the given version */
/* This can only be called 5 times before reusing returned static buffer */
char *icmTVers2str(icmTV tv) {
	static int si = 0;			/* String buffer index */
	static char buf[5][80];
	char *bp, *cp;

	cp = bp = buf[si++];
	si %= 5;				/* Rotate through buffers */

	sprintf(bp,"%d.%d.%d", tv / 10000, (tv/100) % 100, tv % 100);
	return bp;
}

/* Return a string that shows the given version range. */
/* This can only be called once before reusing returned static buffer */
char *icmTVersRange2str(const icmTVRange *tvr) {
	static char buf[100];
	if (tvr->min == ICMTV_MAX && tvr->max == ICMTV_MIN) {
		return "for no versions";
	} else if (tvr->min == ICMTV_MIN && tvr->max == ICMTV_MAX) {
		return "for all versions";
	} else if (tvr->min == ICMTV_MIN && tvr->max != ICMTV_MIN) {
		sprintf(buf,"if %d.%d.%d or less",tvr->max / 10000, (tvr->max/100) % 100, (tvr->max) % 100);
	} else if (tvr->max == ICMTV_MAX) {
		sprintf(buf,"if %d.%d.%d or more", tvr->min / 10000, (tvr->min/100) % 100, (tvr->min) % 100);
	} else
	{
		sprintf(buf,"over %d.%d.%d - %d.%d.%d",
			tvr->min / 10000, (tvr->min/100) % 100, (tvr->min) % 100, 
			tvr->max / 10000, (tvr->max/100) % 100, (tvr->max) % 100);
	}
	return buf;
}

/* Return true if icc version is within TV range */
int icmVersInRange(struct _icc *icp, const icmTVRange *tvr) {
	int rv;
	icmTV icctv = ICMVERS2TV(icp->header->vers);
	rv = ICMTV_IN_RANGE(icctv, tvr);
	return rv;
}

/* Return true if the two version ranges have an overlap */
int icmVersOverlap(icmTVRange *tvr1, const icmTVRange *tvr2) {
	return (tvr1->min <= tvr2->max && tvr2->min <= tvr1->max);
}

/* Return a string that shows the icc version */
char *icmProfileVers2str(struct _icc *icp) {
	icmTV icctv = ICMVERS2TV(icp->header->vers);
	return icmTVers2str(icctv);
}

/* Table of Tag Types valid version range and constructor. */
/* Last has ttype icMaxEnumTagType */
static icmTagTypeVersConstrRec icmTagTypeTable[] = {
	{icSigChromaticityType,         ICMTVRANGE_23_PLUS,			new_icmChromaticity},
	{icSigCrdInfoType,              {ICMTV_21, ICMTV_40},		new_icmCrdInfo},
	{icSigCurveType,                ICMTVRANGE_20_PLUS,			new_icmPeCurve},
	{icSigDataType,                 ICMTVRANGE_20_PLUS,			new_icmData},
	{icSigDateTimeType,             ICMTVRANGE_20_PLUS,			new_icmDateTime},
	{icSigDeviceSettingsType,		{ICMTV_22, ICMTV_40},		new_icmDeviceSettings},
	{icSigLut16Type,				ICMTVRANGE_20_PLUS,			new_icmLut1},
	{icSigLut8Type,					ICMTVRANGE_20_PLUS,			new_icmLut1},
	{icSigMeasurementType,			ICMTVRANGE_20_PLUS,			new_icmMeasurement},
	{icSigNamedColorType,			{ICMTV_20, ICMTV_24},		new_icmNamedColor},
	{icSigNamedColor2Type,          ICMTVRANGE_20_PLUS,			new_icmNamedColor},
	{icSigProfileSequenceDescType,	ICMTVRANGE_20_PLUS,			new_icmProfileSequenceDesc},
	{icSigResponseCurveSet16Type,	ICMTVRANGE_22_PLUS,			new_icmResponseCurveSet16},
	{icSigS15Fixed16ArrayType,		ICMTVRANGE_20_PLUS,			new_icmS15Fixed16Array},
	{icSigScreeningType,			{ICMTV_21, ICMTV_40},		new_icmScreening},
	{icSigSignatureType,			ICMTVRANGE_20_PLUS,			new_icmSignature},
	{icSigTextType,					ICMTVRANGE_20_PLUS,			new_icmText},
	{icSigTextDescriptionType,		{ICMTV_20, ICMTV_24},		new_icmTextDescription},
	{icSigU16Fixed16ArrayType,		ICMTVRANGE_20_PLUS,			new_icmU16Fixed16Array},
	{icSigUcrBgType,				{ICMTV_20, ICMTV_40},		new_icmUcrBg},
	{icSigUInt16ArrayType,			ICMTVRANGE_20_PLUS,			new_icmUInt16Array},
	{icSigUInt32ArrayType,			ICMTVRANGE_20_PLUS,			new_icmUInt32Array},
	{icSigUInt64ArrayType,			ICMTVRANGE_20_PLUS,			new_icmUInt64Array},
	{icSigUInt8ArrayType,			ICMTVRANGE_20_PLUS,			new_icmUInt8Array},
	{icSigVideoCardGammaType,		ICMTVRANGE_20_PLUS,			new_icmVideoCardGamma},
	{icSigViewingConditionsType,	ICMTVRANGE_20_PLUS,			new_icmViewingConditions},
	{icSigXYZArrayType,				ICMTVRANGE_20_PLUS,			new_icmXYZArray},
	/* This is really ICC V4, but icclibv2 had suport for it... */
	{icSigColorantTableType,        ICMTVRANGE_40_PLUS,			new_icmColorantTable},
	{icmSigAltColorantTableType,    ICMTVRANGE_40_PLUS,			new_icmColorantTable},
	/* Private aliases for Lut8 and Lut16 */
	{icmSig816Curves,				ICMTVRANGE_20_PLUS,			new_icmPeCurveSet},
	{icmSig816Curve,				ICMTVRANGE_20_PLUS,			new_icmPeCurve},
	{icmSig816Matrix,				ICMTVRANGE_20_PLUS,			new_icmPeMatrix},
	{icmSig816CLUT,					ICMTVRANGE_20_PLUS,			new_icmPeClut},
	{ icMaxEnumTagType }
};

/* Table of Tags valid version range and possible Tag Types. */
/* Version after each TagType is intersected with Tag Type version range. */ 
/* Last has sig icMaxEnumTag */
static icmTagSigVersTypesRec icmTagSigTable[] = {

	{icSigAToB0Tag,	ICMTVRANGE_20_PLUS,	icmTPLutFwd,
											{{ icSigLut16Type,			ICMTVRANGE_ALL },
											 { icSigLut8Type,			ICMTVRANGE_ALL },
											 { icMaxEnumTagType}}},
	{icSigAToB1Tag,	ICMTVRANGE_20_PLUS,	icmTPLutFwd,
											{{ icSigLut16Type,			ICMTVRANGE_ALL },
											 { icSigLut8Type,			ICMTVRANGE_ALL },
											 { icMaxEnumTagType}}},
	{icSigAToB2Tag,	ICMTVRANGE_20_PLUS,	icmTPLutFwd,
											{{ icSigLut16Type,			ICMTVRANGE_ALL },
											 { icSigLut8Type,			ICMTVRANGE_ALL },
											 { icMaxEnumTagType}}},
	{icSigBlueMatrixColumnTag,	ICMTVRANGE_20_PLUS,	icmTPNone,
											{{ icSigXYZType,				ICMTVRANGE_ALL },
											 { icMaxEnumTagType}}},
	{icSigBlueTRCTag, 			ICMTVRANGE_20_PLUS, icmTPNone,				
											{{ icSigCurveType,			ICMTVRANGE_ALL },
											 { icMaxEnumTagType}}},
	{icSigBToA0Tag,	ICMTVRANGE_20_PLUS,	icmTPLutBwd,
											{{ icSigLut16Type,			ICMTVRANGE_ALL },
											 { icSigLut8Type,			ICMTVRANGE_ALL },
											 { icMaxEnumTagType}}},
	{icSigBToA1Tag,	ICMTVRANGE_20_PLUS,	icmTPLutBwd,
											{{ icSigLut16Type,			ICMTVRANGE_ALL },
											 { icSigLut8Type,			ICMTVRANGE_ALL },
											 { icMaxEnumTagType}}},
	{icSigBToA2Tag,	ICMTVRANGE_20_PLUS,	icmTPLutBwd,
											{{ icSigLut16Type,			ICMTVRANGE_ALL },
											 { icSigLut8Type,			ICMTVRANGE_ALL },
											 { icMaxEnumTagType}}},
	{icSigCalibrationDateTimeTag,	ICMTVRANGE_20_PLUS,	icmTPNone,
											{{ icSigDateTimeType,			ICMTVRANGE_ALL },
											 { icMaxEnumTagType}}},
	{icSigCharTargetTag,			ICMTVRANGE_20_PLUS,	icmTPNone,
											{{ icSigTextType,				ICMTVRANGE_ALL },
											 { icMaxEnumTagType}}},
	{icSigChromaticAdaptationTag,	ICMTVRANGE_24_PLUS, icmTPNone,
											{{ icSigS15Fixed16ArrayType,	ICMTVRANGE_ALL },
											 { icMaxEnumTagType}}},
	{icSigChromaticityTag,			ICMTVRANGE_23_PLUS, icmTPNone,
											{{ icSigChromaticityType,		ICMTVRANGE_ALL },
											 { icMaxEnumTagType}}},
	{icSigColorantTableTag,         ICMTVRANGE_40_PLUS,	icmTPNone,
											{{ icSigColorantTableType,		ICMTVRANGE_ALL },
											 { icmSigAltColorantTableType,	ICMTVRANGE_ALL },
											 { icMaxEnumTagType}}},
	{icSigColorantTableOutTag,      ICMTVRANGE_40_PLUS, icmTPNone,
											{{ icSigColorantTableType,		ICMTVRANGE_ALL },
											 { icmSigAltColorantTableType,	ICMTVRANGE_ALL },
											 { icMaxEnumTagType}}},
	{icSigCopyrightTag,				ICMTVRANGE_20_PLUS, icmTPNone,
											{{ icSigTextType,			{ ICMTV_20, ICMTV_24 }},
											 { icMaxEnumTagType}}},
	{icSigCrdInfoTag,				{ ICMTV_21, ICMTV_40 },	icmTPNone,
											{{ icSigCrdInfoType,			ICMTVRANGE_ALL },		
											 { icMaxEnumTagType}}},
#ifdef NEVER	/* Not clear if these two were every official... */
	{icSigDataTag,					{ ICMTV_20, ICMTV_24 },	icmTPNone,
											{{ icSigDataType,				ICMTVRANGE_ALL },
											 { icMaxEnumTagType}}},
	{icSigDateTimeTag,				{ ICMTV_20, ICMTV_24 },	icmTPNone,
											{{ icSigDataType,				ICMTVRANGE_ALL },
											 { icMaxEnumTagType}}},
#endif
	{icSigDeviceMfgDescTag,			ICMTVRANGE_20_PLUS, icmTPNone,
											{{ icSigTextDescriptionType,	{ ICMTV_20, ICMTV_24 }},
											 { icMaxEnumTagType}}},
	{icSigDeviceModelDescTag,		ICMTVRANGE_20_PLUS, icmTPNone,
											{{ icSigTextDescriptionType,	{ ICMTV_20, ICMTV_24 }},
											 { icMaxEnumTagType}}},
	{icSigDeviceSettingsTag,		{ ICMTV_22, ICMTV_40 },	icmTPNone,
											{{ icSigDeviceSettingsType,			ICMTVRANGE_ALL },
											 { icMaxEnumTagType}}},
	{icSigGamutTag,		ICMTVRANGE_20_PLUS,	icmTPLutGamut,
											{{ icSigLut16Type,				ICMTVRANGE_ALL },
											 { icSigLut8Type,				ICMTVRANGE_ALL },
											 { icMaxEnumTagType}}},
	{icSigGrayTRCTag,	ICMTVRANGE_20_PLUS, icmTPNone,
											{{ icSigCurveType,				ICMTVRANGE_ALL },
											 { icMaxEnumTagType}}},
	{icSigGreenMatrixColumnTag,	ICMTVRANGE_20_PLUS,	icmTPNone,
											{{ icSigXYZType,				ICMTVRANGE_ALL },
											 { icMaxEnumTagType}}},
	{icSigGreenTRCTag, 			ICMTVRANGE_20_PLUS, icmTPNone,				
											{{ icSigCurveType,				ICMTVRANGE_ALL },
											 { icMaxEnumTagType}}},
	{icSigLuminanceTag,			ICMTVRANGE_20_PLUS, icmTPNone,
											{{ icSigXYZType,				ICMTVRANGE_ALL },
											 { icMaxEnumTagType }}},
	{icSigMeasurementTag,		ICMTVRANGE_20_PLUS, icmTPNone,
											{{ icSigMeasurementType,		ICMTVRANGE_ALL },
											 { icMaxEnumTagType }}},
	{icSigMediaBlackPointTag,	{ ICMTV_20, ICMTV_42 }, icmTPNone,
											{{ icSigXYZType,				ICMTVRANGE_ALL },
											 { icMaxEnumTagType }}},
	{icSigMediaWhitePointTag,	ICMTVRANGE_20_PLUS, icmTPNone,
											{{ icSigXYZType,				ICMTVRANGE_ALL },
											 { icMaxEnumTagType}}},
	{icSigNamedColorTag,		{ ICMTV_20, ICMTV_20 }, icmTPNone,
											{{ icSigNamedColorType,			ICMTVRANGE_ALL },
											 { icMaxEnumTagType }}},
	{icSigNamedColor2Tag,		ICMTVRANGE_21_PLUS, icmTPNone,
											{{ icSigNamedColor2Type,		ICMTVRANGE_ALL },
											 { icMaxEnumTagType }}},
	{icSigOutputResponseTag,	ICMTVRANGE_22_PLUS, icmTPNone,
											{{ icSigResponseCurveSet16Type,	ICMTVRANGE_ALL },
											 { icMaxEnumTagType }}},
	{icSigPreview0Tag,		ICMTVRANGE_20_PLUS,	icmTPLutPreview,
											{{ icSigLut16Type,			ICMTVRANGE_ALL },
											 { icSigLut8Type,			ICMTVRANGE_ALL },
											 { icMaxEnumTagType}}},
	{icSigPreview1Tag,		ICMTVRANGE_20_PLUS,	icmTPLutPreview,
											{{ icSigLut16Type,			ICMTVRANGE_ALL },
											 { icSigLut8Type,			ICMTVRANGE_ALL },
											 { icMaxEnumTagType}}},
	{icSigPreview2Tag,		ICMTVRANGE_20_PLUS,	icmTPLutPreview,
											{{ icSigLut16Type,			ICMTVRANGE_ALL },
											 { icSigLut8Type,			ICMTVRANGE_ALL },
											 { icMaxEnumTagType}}},
	{icSigProfileDescriptionTag,		ICMTVRANGE_20_PLUS, icmTPNone,
											{{ icSigTextDescriptionType,		ICMTVRANGE_ALL },
											 { icMaxEnumTagType}}},
	{icSigProfileSequenceDescTag,		ICMTVRANGE_20_PLUS, icmTPNone,
											{{ icSigProfileSequenceDescType,	ICMTVRANGE_ALL },
											 { icMaxEnumTagType}}},
	{icSigPs2CRD0Tag,				{ ICMTV_20, ICMTV_40 }, icmTPNone,
											{{ icSigDataType,			ICMTVRANGE_ALL },
											 { icMaxEnumTagType}}},
	{icSigPs2CRD1Tag,				{ ICMTV_20, ICMTV_40 },	icmTPNone,
											{{ icSigDataType,			ICMTVRANGE_ALL },
											 { icMaxEnumTagType}}},
	{icSigPs2CRD2Tag,				{ ICMTV_20, ICMTV_40 },	icmTPNone,
											{{ icSigDataType,			ICMTVRANGE_ALL },
											 { icMaxEnumTagType}}},
	{icSigPs2CRD3Tag,				{ ICMTV_20, ICMTV_40 },	icmTPNone,
											{{ icSigDataType,			ICMTVRANGE_ALL },
											 { icMaxEnumTagType}}},
	{icSigPs2CSATag,				{ ICMTV_20, ICMTV_40 },	icmTPNone,
											{{ icSigDataType,			ICMTVRANGE_ALL },
											 { icMaxEnumTagType}}},
	{icSigPs2RenderingIntentTag,	{ ICMTV_20, ICMTV_40 },	icmTPNone,
											{{ icSigDataType,			ICMTVRANGE_ALL },
											 { icMaxEnumTagType}}},
	{icSigRedMatrixColumnTag,	ICMTVRANGE_20_PLUS,	icmTPNone,
											{{ icSigXYZType,				ICMTVRANGE_ALL },
											 { icMaxEnumTagType}}},
	{icSigRedTRCTag, 			ICMTVRANGE_20_PLUS, icmTPNone,				
											{{ icSigCurveType,				ICMTVRANGE_ALL },
											 { icMaxEnumTagType}}},
	{icSigScreeningDescTag,		{ ICMTV_20, ICMTV_40 }, icmTPNone,
											{{ icSigTextDescriptionType,	ICMTVRANGE_ALL },
											 { icMaxEnumTagType }}},
	{icSigScreeningTag,			{ ICMTV_20, ICMTV_40 },	icmTPNone,
											{{ icSigScreeningType,			ICMTVRANGE_ALL },
											 { icMaxEnumTagType }}},
	{icSigTechnologyTag,		ICMTVRANGE_20_PLUS,	icmTPNone,
											{{ icSigSignatureType,			ICMTVRANGE_ALL },
											 { icMaxEnumTagType }}},
	{icSigUcrBgTag,				{ ICMTV_20, ICMTV_40 },	icmTPNone,
											{{ icSigUcrBgType,				ICMTVRANGE_ALL },
											 { icMaxEnumTagType }}},
	{icSigVideoCardGammaTag,	ICMTVRANGE_20_PLUS,	icmTPNone,
											{{ icSigVideoCardGammaType,		ICMTVRANGE_ALL },
											 { icMaxEnumTagType }}},
	{icSigViewingCondDescTag,		{ ICMTV_20, ICMTV_40 }, icmTPNone,
											{{ icSigTextDescriptionType,	ICMTVRANGE_ALL },
											 { icMaxEnumTagType }}},
	{icSigViewingConditionsTag,	ICMTVRANGE_20_PLUS,	icmTPNone,
											{{ icSigViewingConditionsType,	ICMTVRANGE_ALL },
											 { icMaxEnumTagType }}},
	{icmSigAbsToRelTransSpace,	ICMTVRANGE_20_PLUS, icmTPNone,
											{{ icSigS15Fixed16ArrayType,	ICMTVRANGE_ALL },
											 { icMaxEnumTagType }}},
	{icMaxEnumTag}
};

/*
	Additional tag requirement that can't be coded in icmClassTagTable:

	if (icSigMediaWhitePointTag) and if data is adopted white is not D50,
	then shall have icSigChromaticAdaptationTag.
	icSigChromaticAdaptationTag is currentlty listed in the optional list.

*/

/* Table of Class Signature instance requirements. */
/* All profiles shall have the required tags, and may have the optional tags. */
/* If any have tags in the icmTransTagTable[] list and are not optional, */
/* then this is a warning. */
/* Need to check that profile has all tags of at least one matching record. */
/* Matching record has matching Class Sig + Src + Dst color signatures within version range, */
/* Tags are only required if in Tag version + icmTagSigVersRec.vrange. */
/* Last has sig icMaxEnumClass */
/* A profile may match more than one record, so all matching records */
/* need to be checked. Order is specific to general, so that any error/warning */
/* is the most general one. */
static icmRequiredTagType icmClassTagTable[] = {
    {icSigInputClass,			ICMCSMR_ANY(1),	ICMCSMR_PCS,	ICMTVRANGE_ALL, 
		/* Required: */
	 	{{ icSigProfileDescriptionTag,		ICMTVRANGE_ALL },
		 { icSigGrayTRCTag,					ICMTVRANGE_ALL },
		 { icSigMediaWhitePointTag,			ICMTVRANGE_ALL },
		 { icSigCopyrightTag,				ICMTVRANGE_ALL }, 
		 { icMaxEnumTag }
		}, {
		/* Optional: */
		 { icSigChromaticAdaptationTag, 	ICMTVRANGE_40_PLUS },
		 { icSigAToB0Tag,					ICMTVRANGE_ALL },
		 { icSigAToB1Tag,					ICMTVRANGE_23_PLUS },
		 { icSigAToB2Tag,					ICMTVRANGE_23_PLUS },
		 { icSigBToA0Tag,					ICMTVRANGE_23_PLUS },
		 { icSigBToA1Tag,					ICMTVRANGE_23_PLUS },
		 { icSigBToA2Tag,					ICMTVRANGE_23_PLUS },
		 { icMaxEnumTag }
		}
	},

    {icSigInputClass,			ICMCSMR_ANY(3),	ICMCSMR_XYZ,	ICMTVRANGE_ALL, 
		/* Required: */
	 	{{ icSigProfileDescriptionTag,		ICMTVRANGE_ALL },
		 { icSigRedColorantTag,				ICMTVRANGE_ALL },
		 { icSigGreenColorantTag,			ICMTVRANGE_ALL },
		 { icSigBlueColorantTag,			ICMTVRANGE_ALL },
		 { icSigRedTRCTag,					ICMTVRANGE_ALL },
		 { icSigGreenTRCTag,				ICMTVRANGE_ALL },
		 { icSigBlueTRCTag,					ICMTVRANGE_ALL },
		 { icSigMediaWhitePointTag,			ICMTVRANGE_ALL },
		 { icSigCopyrightTag,				ICMTVRANGE_ALL },
		 { icMaxEnumTag }
		}, {
		/* Optional: */
		 { icSigChromaticAdaptationTag, 	ICMTVRANGE_40_PLUS },
		 { icSigAToB0Tag,					ICMTVRANGE_ALL },
		 { icSigAToB1Tag,					ICMTVRANGE_23_PLUS },
		 { icSigAToB2Tag,					ICMTVRANGE_23_PLUS },
		 { icSigBToA0Tag,					ICMTVRANGE_23_PLUS },
		 { icSigBToA1Tag,					ICMTVRANGE_23_PLUS },
		 { icSigBToA2Tag,					ICMTVRANGE_23_PLUS },
		 { icSigGamutTag,					ICMTVRANGE_23_PLUS },		/* Why ? */
		 { icMaxEnumTag }
		}
	},

    {icSigInputClass,     		ICMCSMR_ANYN,	ICMCSMR_PCS,	ICMTVRANGE_ALL,
		/* Required: */
	 	{{ icSigProfileDescriptionTag,		ICMTVRANGE_ALL },
		 { icSigAToB0Tag,					ICMTVRANGE_ALL },
		 { icSigMediaWhitePointTag,			ICMTVRANGE_ALL },
		 { icSigCopyrightTag, 				ICMTVRANGE_ALL },
		 { icMaxEnumTag },
		}, {
		/* Optional: */
		 { icSigChromaticAdaptationTag, 	ICMTVRANGE_40_PLUS },
		 { icSigAToB1Tag,					ICMTVRANGE_23_PLUS },
		 { icSigAToB2Tag,					ICMTVRANGE_23_PLUS },
		 { icSigBToA0Tag,					ICMTVRANGE_23_PLUS },
		 { icSigBToA1Tag,					ICMTVRANGE_23_PLUS },
		 { icSigBToA2Tag,					ICMTVRANGE_23_PLUS },
		 { icSigGamutTag,					ICMTVRANGE_23_PLUS },		/* Why ? */
		 { icMaxEnumTag }
		}
	},


    {icSigDisplayClass,			ICMCSMR_ANY(1),	ICMCSMR_PCS,	ICMTVRANGE_ALL,
		/* Required: */
	 	{{ icSigProfileDescriptionTag,		ICMTVRANGE_ALL },
		 { icSigGrayTRCTag,					ICMTVRANGE_ALL },
		 { icSigMediaWhitePointTag,			ICMTVRANGE_ALL },
		 { icSigCopyrightTag,				ICMTVRANGE_ALL },
		 { icMaxEnumTag }
		}, {
		/* Optional: */
		 { icSigChromaticAdaptationTag, 	ICMTVRANGE_40_PLUS },
		 { icSigAToB0Tag,					ICMTVRANGE_ALL },
		 { icSigAToB1Tag,					ICMTVRANGE_23_PLUS },
		 { icSigAToB2Tag,					ICMTVRANGE_23_PLUS },
		 { icSigBToA0Tag,					ICMTVRANGE_23_PLUS },
		 { icSigBToA1Tag,					ICMTVRANGE_23_PLUS },
		 { icSigBToA2Tag,					ICMTVRANGE_23_PLUS },
		 { icMaxEnumTag }
		}
	},

    {icSigDisplayClass,     	ICMCSMR_ANY(3),	ICMCSMR_XYZ,    ICMTVRANGE_ALL,
		/* Required: */
	 	{{ icSigProfileDescriptionTag,		ICMTVRANGE_ALL },
		 { icSigRedColorantTag,				ICMTVRANGE_ALL },
		 { icSigGreenColorantTag,			ICMTVRANGE_ALL },
		 { icSigBlueColorantTag,			ICMTVRANGE_ALL },
		 { icSigRedTRCTag,					ICMTVRANGE_ALL },
		 { icSigGreenTRCTag,				ICMTVRANGE_ALL },
		 { icSigBlueTRCTag,					ICMTVRANGE_ALL },
		 { icSigMediaWhitePointTag,			ICMTVRANGE_ALL },
		 { icSigCopyrightTag,				ICMTVRANGE_ALL },
		 { icMaxEnumTag }
		}, {
		/* Optional: */
		 { icSigChromaticAdaptationTag, 	ICMTVRANGE_40_PLUS },
		 { icSigAToB0Tag,					ICMTVRANGE_ALL },
		 { icSigAToB1Tag,					ICMTVRANGE_23_PLUS },
		 { icSigAToB2Tag,					ICMTVRANGE_23_PLUS },
		 { icSigBToA0Tag,					ICMTVRANGE_23_PLUS },
		 { icSigBToA1Tag,					ICMTVRANGE_23_PLUS },
		 { icSigBToA2Tag,					ICMTVRANGE_23_PLUS },
		 { icSigGamutTag,					ICMTVRANGE_23_PLUS },
		 { icMaxEnumTag }
		}
	},

    {icSigDisplayClass,     	ICMCSMR_ANYN, 	ICMCSMR_PCS,    ICMTVRANGE_ALL,
		/* Required: */
	 	{{ icSigProfileDescriptionTag,		ICMTVRANGE_ALL },
		 { icSigAToB0Tag,					ICMTVRANGE_ALL },
		 { icSigBToA0Tag,					ICMTVRANGE_ALL },
		 { icSigMediaWhitePointTag,			ICMTVRANGE_ALL },
		 { icSigCopyrightTag,				ICMTVRANGE_ALL },
		 { icMaxEnumTag }
		}, {
		/* Optional: */
		 { icSigChromaticAdaptationTag, 	ICMTVRANGE_40_PLUS },
		 { icSigAToB1Tag,					ICMTVRANGE_23_PLUS },
		 { icSigAToB2Tag,					ICMTVRANGE_23_PLUS },
		 { icSigBToA1Tag,					ICMTVRANGE_23_PLUS },
		 { icSigBToA2Tag,					ICMTVRANGE_23_PLUS },
		 { icSigGamutTag,					ICMTVRANGE_23_PLUS },
		 { icMaxEnumTag }
		}
	},


    {icSigOutputClass,			ICMCSMR_ANY(1),    ICMCSMR_PCS,    ICMTVRANGE_ALL,
		/* Required: */
	 	{{ icSigProfileDescriptionTag,		ICMTVRANGE_ALL },
		 { icSigGrayTRCTag,					ICMTVRANGE_ALL },
		 { icSigMediaWhitePointTag,			ICMTVRANGE_ALL },
		 { icSigCopyrightTag,				ICMTVRANGE_ALL },
		 { icMaxEnumTag }
		}, {
		/* Optional: */
		 { icSigChromaticAdaptationTag, 	ICMTVRANGE_40_PLUS },
		 { icSigAToB0Tag,					ICMTVRANGE_ALL },
		 { icSigAToB1Tag,					ICMTVRANGE_ALL },
		 { icSigAToB2Tag,					ICMTVRANGE_ALL },
		 { icSigBToA0Tag,					ICMTVRANGE_ALL },
		 { icSigBToA1Tag,					ICMTVRANGE_ALL },
		 { icSigBToA2Tag,					ICMTVRANGE_ALL },
		 { icMaxEnumTag }
		}
	},

    {icSigOutputClass,			ICMCSMR_NOT_NCOL,	ICMCSMR_PCS,    ICMTVRANGE_ALL,
		/* Required: */
	 	{{ icSigProfileDescriptionTag,		ICMTVRANGE_ALL },
		 { icSigAToB0Tag,					ICMTVRANGE_ALL },
		 { icSigBToA0Tag,					ICMTVRANGE_ALL },
		 { icSigAToB1Tag,					ICMTVRANGE_ALL },
		 { icSigBToA1Tag,					ICMTVRANGE_ALL },
		 { icSigAToB2Tag,					ICMTVRANGE_ALL },
		 { icSigBToA2Tag,					ICMTVRANGE_ALL },
		 { icSigGamutTag,					ICMTVRANGE_ALL },
		 { icSigMediaWhitePointTag,			ICMTVRANGE_ALL },
		 { icSigCopyrightTag,				ICMTVRANGE_ALL },
		 { icMaxEnumTag }
		}, {
		/* Optional: */
		 { icSigChromaticAdaptationTag, 	ICMTVRANGE_40_PLUS },
		 { icMaxEnumTag }
		}
	},

    {icSigOutputClass,			ICMCSMR_NCOL,	ICMCSMR_PCS,    ICMTVRANGE_ALL,
		/* Required: */
	 	{{ icSigProfileDescriptionTag,		ICMTVRANGE_ALL },
		 { icSigAToB0Tag,					ICMTVRANGE_ALL },
		 { icSigBToA0Tag,					ICMTVRANGE_ALL },
		 { icSigAToB1Tag,					ICMTVRANGE_ALL },
		 { icSigBToA1Tag,					ICMTVRANGE_ALL },
		 { icSigAToB2Tag,					ICMTVRANGE_ALL },
		 { icSigBToA2Tag,					ICMTVRANGE_ALL },
		 { icSigGamutTag,					ICMTVRANGE_ALL },
		 { icSigColorantTableTag,			ICMTVRANGE_40_PLUS },
		 { icSigMediaWhitePointTag,			ICMTVRANGE_ALL },
		 { icSigCopyrightTag,				ICMTVRANGE_ALL },
		 { icMaxEnumTag }
		}, {
		/* Optional: */
		 { icSigChromaticAdaptationTag, 	ICMTVRANGE_40_PLUS },
		 { icMaxEnumTag }
		}
	},


    {icSigLinkClass,	ICMCSMR_NOT_NCOL,	ICMCSMR_NOT_NCOL,	ICMTVRANGE_ALL,
		/* Required: */
	 	{{ icSigProfileDescriptionTag,		ICMTVRANGE_ALL },
		 { icSigAToB0Tag,					ICMTVRANGE_ALL },
		 { icSigProfileSequenceDescTag,		ICMTVRANGE_ALL },
		 { icSigCopyrightTag,				ICMTVRANGE_ALL },
		 { icMaxEnumTag }
		}, {
		/* Optional: */
		 { icMaxEnumTag }
		}
	},

    {icSigLinkClass,		ICMCSMR_NCOL,	ICMCSMR_NOT_NCOL,	ICMTVRANGE_ALL,
		/* Required: */
	 	{{ icSigProfileDescriptionTag,		ICMTVRANGE_ALL },
		 { icSigAToB0Tag,					ICMTVRANGE_ALL },
		 { icSigColorantTableTag,			ICMTVRANGE_40_PLUS },
		 { icSigProfileSequenceDescTag,		ICMTVRANGE_ALL },
		 { icSigCopyrightTag,				ICMTVRANGE_ALL },
		 { icMaxEnumTag }
		}, {
		/* Optional: */
		 { icMaxEnumTag }
		}
	},

    {icSigLinkClass,		ICMCSMR_NOT_NCOL,	ICMCSMR_NCOL,	ICMTVRANGE_ALL,
		/* Required: */
	 	{{ icSigProfileDescriptionTag,		ICMTVRANGE_ALL },
		 { icSigAToB0Tag,					ICMTVRANGE_ALL },
		 { icSigColorantTableOutTag,		ICMTVRANGE_40_PLUS },
		 { icSigProfileSequenceDescTag,		ICMTVRANGE_ALL },
		 { icSigCopyrightTag,				ICMTVRANGE_ALL },
		 { icMaxEnumTag }
		}, {
		/* Optional: */
		 { icMaxEnumTag }
		}
	},

    {icSigLinkClass,			ICMCSMR_NCOL,	ICMCSMR_NCOL,	ICMTVRANGE_ALL,
		/* Required: */
	 	{{ icSigProfileDescriptionTag,		ICMTVRANGE_ALL },
		 { icSigAToB0Tag,					ICMTVRANGE_ALL },
		 { icSigColorantTableTag,			ICMTVRANGE_40_PLUS },
		 { icSigProfileSequenceDescTag,		ICMTVRANGE_ALL },
		 { icSigColorantTableOutTag,		ICMTVRANGE_40_PLUS },
		 { icSigCopyrightTag,				ICMTVRANGE_ALL },
		 { icMaxEnumTag }
		}, {
		/* Optional: */
		 { icMaxEnumTag }
		}
	},

    {icSigColorSpaceClass,		ICMCSMR_ANYN,	ICMCSMR_PCS,    ICMTVRANGE_ALL,
		/* Required: */
	 	{{ icSigProfileDescriptionTag,		ICMTVRANGE_ALL },
		 { icSigBToA0Tag,					ICMTVRANGE_ALL },
		 { icSigAToB0Tag,					ICMTVRANGE_ALL },
		 { icSigMediaWhitePointTag,			ICMTVRANGE_ALL },
		 { icSigCopyrightTag,				ICMTVRANGE_ALL },
		 { icMaxEnumTag }
		}, {
		/* Optional: */
		 { icSigAToB1Tag,					ICMTVRANGE_23_PLUS },
		 { icSigAToB2Tag,					ICMTVRANGE_23_PLUS },
		 { icSigBToA1Tag,					ICMTVRANGE_23_PLUS },
		 { icSigBToA2Tag,					ICMTVRANGE_23_PLUS },
		 { icSigGamutTag,					ICMTVRANGE_23_PLUS },		/* Why ? */
		 { icMaxEnumTag }
		}
	},

    {icSigAbstractClass,		ICMCSMR_PCS,	ICMCSMR_PCS,    ICMTVRANGE_ALL,
		/* Required: */
	 	{{ icSigProfileDescriptionTag,		ICMTVRANGE_ALL },
		 { icSigAToB0Tag,					ICMTVRANGE_ALL },
		 { icSigMediaWhitePointTag,			ICMTVRANGE_ALL },
		 { icSigCopyrightTag,				ICMTVRANGE_ALL },
		 { icMaxEnumTag }
		}, {
		/* Optional: */
		 { icMaxEnumTag }
		}
	},

    {icSigNamedColorClass,		ICMCSMR_ANYN,    ICMCSMR_PCS,    ICMTVRANGE_ALL,
		/* Required: */
	 	{{ icSigProfileDescriptionTag,		ICMTVRANGE_ALL },
		 { icSigNamedColorTag,				ICMTVRANGE_ALL },
		 { icSigMediaWhitePointTag,			ICMTVRANGE_ALL },
		 { icSigCopyrightTag,				ICMTVRANGE_ALL },
		 { icMaxEnumTag }
		}, {
		/* Optional: */
		 { icMaxEnumTag }
		}
	},

    {icSigNamedColorClass,		ICMCSMR_ANYN,    ICMCSMR_PCS,    ICMTVRANGE_ALL,
		/* Required: */
	 	{{ icSigProfileDescriptionTag,		ICMTVRANGE_ALL },
		 { icSigNamedColor2Tag,				ICMTVRANGE_ALL },
		 { icSigMediaWhitePointTag,			ICMTVRANGE_ALL },
		 { icSigCopyrightTag,				ICMTVRANGE_ALL },
		 { icMaxEnumTag }
		}, {
		/* Optional: */
		 { icMaxEnumTag }
		}
	},

	{icMaxEnumClass}
};

/* Table of transform tags. */
/* None of these tags should be present if they are not */
/* in the required or optional list. */
icmTagSigVersRec icmTransTagTable[] = { 
	{ icSigRedColorantTag,		ICMTVRANGE_ALL },
	{ icSigGreenColorantTag,	ICMTVRANGE_ALL },
	{ icSigBlueColorantTag,		ICMTVRANGE_ALL },
	{ icSigRedTRCTag,			ICMTVRANGE_ALL },
	{ icSigGreenTRCTag,			ICMTVRANGE_ALL },
	{ icSigBlueTRCTag,			ICMTVRANGE_ALL },

	{ icSigAToB0Tag, 			ICMTVRANGE_ALL },
	{ icSigAToB1Tag, 			ICMTVRANGE_ALL },
	{ icSigAToB2Tag, 			ICMTVRANGE_ALL },
	{ icSigBToA0Tag, 			ICMTVRANGE_ALL },
	{ icSigBToA1Tag, 			ICMTVRANGE_ALL },
	{ icSigBToA2Tag, 			ICMTVRANGE_ALL },


	{ icMaxEnumTag },
};

/* Table of Types and valid Sub-Types to check against. */
/* (Note sub-types may be pseudo-type) */
/* Used by icc_new_ttype() */
/* Also used by icc_new_pe() starting at PESUBTAGTYPESTABLESTART entry */
static struct {
	icTagTypeSignature pttype;		/* Parent ttype */
	icTagTypeSignature ttypes[8];	/* Sub-ttypes */
} icmValidSubTagTypesTable[] = {
	{ icSigDictType, {icSigMultiLocalizedUnicodeType, icMaxEnumTagType} },

	{ icSigProfileSequenceDescType, {icmSigCommonTextDescriptionType,
	                                 icSigTextDescriptionType,
	                                 icMaxEnumTagType} },
	{ icSigProfileSequenceIdentifierType, {icmSigCommonTextDescriptionType,
	                                       icSigTextDescriptionType,
	                                       icMaxEnumTagType} },

	/* Pe sub tags. Tables used by icc_new_pe() from here: */
# define PESUBTAGTYPESTABLESTART icSigLut8Type
	{ icSigLut8Type, {icmSig816Matrix, icmSig816Curves, icmSig816CLUT, icMaxEnumTagType} },
	{ icSigLut16Type, {icmSig816Matrix, icmSig816Curves, icmSig816CLUT, icMaxEnumTagType} },
	{ icmSig816Curves, {icmSig816Curve, icMaxEnumTagType} },

	{ icMaxEnumTagType }
};

/* ===================================================================== */
/* icc profile object implementation */

/*

	~8 Loose ends:

*/

/*
	Profile check functionality:
	see icc_write_check()

	Should check on read and before write.
	Check tag, tagtype on adding a tag too.

	+ Header layout, alignment etc.
	+ Header enumerations

	+ Tag table layout
	+ Tag table alignments, padding etc.
	+ Tag type layout, alignment, enumerations etc.

	Overall:

	+ Tags are only used once
	+ Check tag known, version, tagtype (cmTagSigTable[])
	+ Check tagType known, version (icmTagTypeTable[])
	+ Required tags (icmClassTagTable[] & icmTransTagTable[])

	Possible response to errors:

		* Fatal. Can't functionally proceed
	  These can be configured as errors, warnings, supressed:
		* Not to spec.
		* Known extension
		* Unknown extension
 */

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* Tag table functions */

/* tag table Serialisation */
static void tagtable_serialise(icc *p, icmFBuf *b) {
	unsigned int i;

	icmSn_ui_UInt32(b, &p->count);	/* 0-3: Tag count */
	
	if (icmArrayAllocResize(b, &p->_count, &p->count, (void **)&p->data,
	                           sizeof(icmTagRec), "tagTable"))   
		return;

	/* For each table entry */
	for(i = 0; i < p->count; i++) {
		icmSn_TagSig32(b, &p->data[i].sig);		/* 0-3:  Signture */
		icmSn_ui_UInt32(b, &p->data[i].offset);	/* 4-7:  Tag offset */
		icmSn_ui_UInt32(b, &p->data[i].size);	/* 8-11: Tag size */
		if (b->op == icmSnRead) {
	    	p->data[i].pad = 0;				/* Not determined on read */
	    	p->data[i].objp = NULL;			/* Read on demand */
		}
	}
}

// ~8 is there any point in a tagtable_check() ? - see icc_write_check()

/* Return the size of the tag table in the object */
static unsigned int tagtable_get_size(icc *p) {
	icmFBuf *b;
	if ((b = new_icmFBuf(p, icmSnSize, NULL, 0, 0)) == NULL)
		return 0;
	tagtable_serialise(p, b);
    return b->done(b);
}

/* Read the tag table and the tag types. */
/* Be completely paranoid about the tag table and its contents */
/* of is the file offset it should be read from. */
/* Return error code */
static int tagtable_read(icc *p, unsigned int size, unsigned int of) {
	unsigned int i, j;
	icmFBuf *b;
	
	/* Read the tag count */
	if ((b = new_icmFBuf(p, icmSnRead, p->rfp, of, 4)) == NULL)
		return p->e.c;
	icmSn_ui_UInt32(b, &p->count);
    b->done(b);
	if (p->e.c != ICM_ERR_OK)
		return p->e.c;

	/* And sanity check it */
	if (p->count > 357913940	/* (2^32-5)/12 */
	 || (p->count * 12 + 4) > p->maxs) {
		return icm_err(p, ICM_ERR_RD_FORMAT, "Tag count %d is too big for nominated file size",p->count);
	}
	size = 4 + p->count * 12;		/* Tag table size */
	p->mino += size;				/* Minimum offset of tags */
	p->maxs -= size;				/* maximum size for tags */

	/* Read the tag table */
	if ((b = new_icmFBuf(p, icmSnRead, p->rfp, of, size)) == NULL)
		return p->e.c;
	tagtable_serialise(p, b);
    b->done(b);
	if (p->e.c != ICM_ERR_OK)
		return p->e.c;

	/* Check that each tag lies within the nominated space available, */
	/* and has sane values. */
	for (i = 0; i < p->count; i++) {
		if (p->data[i].offset < p->mino
		 || p->data[i].offset > p->header->size
		 || p->data[i].size < 4
		 || p->data[i].size > p->maxs
		 || (p->data[i].offset + p->data[i].size) < p->data[i].offset	/* Overflow */
		 || (p->data[i].offset + p->data[i].size) > p->header->size) {
			return icm_err(p, ICM_ERR_RD_FORMAT, "Tag %d is out of range of the nominated file size",i);
		}
	}
		
	/* Check that each tag does not overlap another tag. */
	/* It is permitted to enclose or be enclosed by another tag though. */
	for (i = 0; i < p->count; i++) {
		for(j = i+1; j < p->count; j++) {
			unsigned int x0, x1, y0, y1;
			x0 = p->data[i].offset;
			x1 = p->data[i].offset + p->data[i].size;
			y0 = p->data[j].offset;
			y1 = p->data[j].offset + p->data[j].size;
			if ( (x1 > y0 && x1 <= y1 && x0 < y0)
			  || (x0 >= y0 && x0 < y1 && x1 > y1)) {
//				return icm_err(p, ICM_ERR_RD_FORMAT | ICM_FMT_TAGOLAP, "Tag %d overlaps tag %d",i,j);
				icmFormatWarning(p, ICM_FMT_TAGOLAP, "Tag %d overlaps tag %d",i,j);
			}
		}
	}

// ~~~~999 should check that all tags start on an aligned boundary

// ~~~~999 should check that any gaps between header & tags & tags is
//         the minimal needed for padding. Need to sort for this ??

// ~~~~999 should check that all padding is zero too.

	/* Seek to, and then read each tag type signature */
	for (i = 0; i < p->count; i++) {
		if ((b = new_icmFBuf(p, icmSnRead, p->rfp, p->of + p->data[i].offset, 4)) == NULL)
			return p->e.c;
		icmSn_TagTypeSig32(b, &p->data[i].ttype);
	    b->done(b);
	}

	return p->e.c;
}


/* Write the tagtable. size is the size in bytes to be written, */
/* of is the file offset it should be written to. */
/* Return error code */
static int tagtable_write(icc *p, unsigned int size, unsigned int of) {
	icmFBuf *b;
	if ((b = new_icmFBuf(p, icmSnWrite, p->wfp, of, size)) == NULL)
		return p->e.c;
	tagtable_serialise(p, b);
    b->done(b);
	return p->e.c;
}

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

static void icc_set_defaults(icc *p);

/* Return the reference to current read fp (if any) */
/* Delete after finished with it */
/* (Beware of any interactions with icc that is sharing it!) */
static icmFile *icc_get_rfp(icc *p) {
	if (p->rfp == NULL)
		return NULL;
	return p->rfp->reference(p->rfp);
}

/* Return file version in icmTV format, 0 on error */
icmTV icc_get_version(icc *p) {
	if (p->header == NULL) {
		icm_err(p, ICM_ERR_INTERNAL, "icc_get_version: No Header available");
		return ICMTV_MIN;
	}

	return ICMVERS2TV(p->header->vers);
}

/* Change the version to be non-default (ie. not 2.2.0), */
/* Return 0 if OK */
/* Return 1 on error */
static int icc_set_version(icc *p, icmTV ver) {

	if (p->header == NULL) {
		return icm_err(p, ICM_ERR_INTERNAL, "icc_set_version: No Header available");
	}

	switch (ver) {
		case ICMTV_20:
		case ICMTV_21:
		case ICMTV_22:
		case ICMTV_23:
		case ICMTV_24:
		    p->header->vers.majv = ver/10000;
			p->header->vers.minv = (ver/100) % 100;
			p->header->vers.bfv  = ver % 100;
			break;
		default:
			return icm_err(p, ICM_ERR_UNKNOWN_VERSION, "icc_set_version: Unsupported version %s",
			                                                              icmTVers2str(ver));
	}

	icc_set_defaults(p);

	return p->e.c;
}

/* Check the profiles MD5 ID. We assume the file has already been read. */
/* Return 0 if OK, 1 if no ID to check, 2 if doesn't match, 3 if some other error. */
/* NOTE: this reads the whole file again, to compute the checksum. */
static int icc_check_id(
	icc *p,
	ORD8 *rid		/* Optionaly return computed ID */
) {
	unsigned char buf[128];
	ORD8 id[16];
	icmMD5 *md5 = NULL;
	unsigned int len;
	
	if (p->header == NULL) {
		return icm_err(p, ICM_ERR_INTERNAL, "icc_check_id: No Header available");
	}

	/* See if there is an ID to compare against */
	for (len = 0; len < 16; len++) {
		if (p->header->id[len] != 0)
			break;
	}
	if (len >= 16) {
		return 1; 
	}

	if ((md5 = new_icmMD5_a(&p->e, p->al)) == NULL) {
		return p->e.c;
	}
		
	/* Check the header */
	if (p->rfp->seek(p->rfp, p->of) != 0) {
		return icm_err(p, ICM_ERR_FILE_SEEK, "icc_check_id: Seek to header failed");
	}
	if (p->rfp->read(p->rfp, buf, 1, 128) != 128) {
		return icm_err(p, ICM_ERR_FILE_READ, "icc_check_id: Read of header failed");
	}

	/* Zero the appropriate bytes in the header */
	buf[44] = buf[45] = buf[46] = buf[47] = 0;
	buf[64] = buf[65] = buf[66] = buf[67] = 0;
	buf[84] = buf[85] = buf[86] = buf[87] =
	buf[88] = buf[89] = buf[90] = buf[91] =
	buf[92] = buf[93] = buf[94] = buf[95] =
	buf[96] = buf[97] = buf[98] = buf[99] = 0;

	md5->add(md5, buf, 128);

	len = p->header->size - 128;

	/* Suck in the rest of the profile */
	for (;len > 0;) {
		unsigned int rsize = 128;
		if (rsize > len)
			rsize = len;
		if (p->rfp->read(p->rfp, buf, 1, rsize) != rsize) {
			return icm_err(p, ICM_ERR_FILE_READ, "icc_check_id: Read of file chunk failed");
		}
		md5->add(md5, buf, rsize);
		len -= rsize;
	}

	md5->get(md5, id);
	md5->del(md5);

	if (rid != NULL) {
		for (len = 0; len < 16; len++)
			rid[len] = id[len];
	}

	/* Check the ID */
	for (len = 0; len < 16; len++) {
		if (p->header->id[len] != id[len])
			break;
	}
	if (len >= 16) {
		return 0;		/* Matched */ 
	}
	return 2;			/* Didn't match */
}

/* Return true if sig & chans match icmCSMR */
static int icmCSMR_match(icmCSMR *p, icColorSpaceSignature sig, int nchans) {

	if (p->min == 0
	 || p->max == 0
	 || (nchans >= p->min && nchans <= p->max)) {
		unsigned int mask = icmCSSig2type(sig);

		switch(p->mf) {
    		case icmCSMF_ANY:
				return 1;
				
    		case icmCSMF_XYZ:
				return (sig == icSigXYZData);
				
    		case icmCSMF_Lab:
				return (sig == icSigLabData);
				
    		case icmCSMF_PCS:
				return (mask & CSSigType_PCS);
				
    		case icmCSMF_DEV:
				return (mask & CSSigType_DEV);
				
    		case icmCSMF_NCOL:
				return (mask & CSSigType_NCOL);
				
    		case icmCSMF_NOT_NCOL:
				return !(mask & CSSigType_NCOL);
		}
	}

	return 0;
}


/*
	~8 Stuff that is missing ?

	From V2.2+:
		A Tag can only appear once in a profile
*/

/* Check that the ICC profile looks like it will be legal to write. */
/* Return non-zero and set error string if not */
static int icc_write_check(
	icc *p
) {
	int i, j, k;
	icProfileClassSignature clsig;
	icColorSpaceSignature colsig;
	int      	          dchans;
	icColorSpaceSignature pcssig;
	int      	          pchans;

	p->op = icmSnWrite;			/* Let check & quirk know direction */

	if (p->header == NULL) {
		return icm_err(p, 1, "icc_check: Header is missing");
	}

	/* Check the header */
	if (p->header->check(p->header, icmSigUnknown, 0) != ICM_ERR_OK)
		return p->e.c;
	
	/* Check tags and tagtypes */
	for (i = 0; i < p->count; i++) {	/* For all the tag element data */
		icTagTypeSignature ttype, uttype;

		if (p->data[i].objp == NULL)
			return icm_err(p, ICM_ERR_INTERNAL, "icc_write_check: NULL tag element");

		uttype = p->data[i].ttype;			/* Actual type */
		ttype = p->data[i].objp->ttype;		/* Actual or icmSigUnknownType */ 

		/* Do tag & tagtype check */
		if (icc_check_sig(p, NULL, 0, p->data[i].sig, ttype, uttype, p->data[i].objp->rdff)
			                                                                   != ICM_ERR_OK)
			return p->e.c;

		/* If there are shared tagtypes, check compatibility */
		if (p->data[i].objp->refcount > 1) {	/* Hint that it might be a link */
			int k;
			/* Search for shared tagtypes */
			for (k = 0; k < p->count; k++) {
				if (i == k || p->data[k].objp != p->data[i].objp)
					continue;

				/* Check the tagtype purposes are compatible */
				if (p->get_tag_lut_purpose(p, p->data[i].sig) != p->get_tag_lut_purpose(p, p->data[k].sig)) {
					return icm_err(p, ICM_ERR_PURPMISMATCH,
					     "icc_write_check: Tag '%s' is link to incompatible tag '%s'",
				          icmTagSig2str(p->data[i].sig), icmTagSig2str(p->data[k].sig));
				}
			}
		}

		/* Involke any other write checking for this tag */
		if (p->data[i].objp->check != NULL)
			p->data[i].objp->check(p->data[i].objp, p->data[i].sig, 0); 
		if (p->e.c != ICM_ERR_OK)
			return p->e.c;
	}

	/* Check that required tags are present */
	if ((p->cflags & icmCFlagNoRequiredCheck) == 0) {
		int quirk = 0;				/* nz if this is a quirk warning */
		icmErr e = { 0 };			/* Any warnings found */

		clsig  = p->header->deviceClass;
		colsig = p->header->colorSpace;
		dchans = icmCSSig2nchan(colsig);
		pcssig = p->header->pcs;
		pchans = icmCSSig2nchan(pcssig);

//	printf("dchans %d, pchans %d\n",dchans,pchans);

		/* Find a matching class entry. A profile may match more than one entry, */
		/* so we go through all matching entries looking for one */
		/* it is complient with, and noting any warnings for those it doesn't. */
		for (i = 0; p->classtagtable[i].clsig != icMaxEnumTagType; i++) {
			if (p->classtagtable[i].clsig == clsig
			 && icmCSMR_match(&p->classtagtable[i].colsig, colsig, dchans)
			 && icmCSMR_match(&p->classtagtable[i].pcssig, pcssig, pchans)
			 && icmVersInRange(p, &p->classtagtable[i].vrange)) {

				/* Found entry, so now check that all the required tags are present. */
				for (j = 0; p->classtagtable[i].tags[j].sig != icMaxEnumTagType; j++) {
					icTagSignature sig = p->classtagtable[i].tags[j].sig;

			 		if (!icmVersInRange(p, &p->classtagtable[i].tags[j].vrange))
						continue;		/* Only if applicable to this file version */

					if (p->find_tag(p, sig) != 0) {	/* Not present! */
						quirk = 0;
						e.c = ICM_FMT_REQUIRED_TAG;
						sprintf(e.m, "icc_write_check: deviceClass %s is missing required tag %s",
						               icmtag2str(clsig), icmtag2str(sig));
						break;
					}
				}

				/* we found an error */
				if (p->classtagtable[i].tags[j].sig != icMaxEnumTagType) {
					continue;		/* Look for more matching classes */
				}

				/* Found all required tags. */
				/* Check that any transtagtable[] tags are expected in this class */
				for (j = 0; p->transtagtable[j].sig != icMaxEnumTagType; j++) {
					icTagSignature sig = p->transtagtable[j].sig;

			 		if (!icmVersInRange(p, &p->transtagtable[j].vrange))
						continue;		/* Only if applicable to this file version */

					if (p->find_tag(p, sig) == 0) {

						/* See if this tag is in the required list */
						for (k = 0; p->classtagtable[i].tags[k].sig != icMaxEnumTagType; k++) {
					 		if (!icmVersInRange(p, &p->classtagtable[i].tags[k].vrange))
								continue;		/* Only if applicable to this file version */
							if (sig == p->classtagtable[i].tags[k].sig)
								break;			/* Found it */
						}
						if (p->classtagtable[i].tags[k].sig != icMaxEnumTagType) {
							continue;		/* Yes it is */
						}

						/* See if this tag is in the optional list */
						for (k = 0; p->classtagtable[i].otags[k].sig != icMaxEnumTagType; k++) {
					 		if (!icmVersInRange(p, &p->classtagtable[i].otags[k].vrange))
								continue;		/* Only if applicable to this file version */
							if (sig == p->classtagtable[i].otags[k].sig)
								break;			/* Found it */
						}
						if (p->classtagtable[i].otags[k].sig != icMaxEnumTagType) {
							continue;		/* Yes it is */
						}
		
						/* Naughty tag */
						if (e.c == ICM_ERR_OK || quirk) {
							quirk = 1;
							e.c = ICM_FMT_UNEXPECTED_TAG;
							sprintf(e.m, "icc_write_check: deviceClass %s has unexpected tag %s",
						               icmtag2str(clsig), icmtag2str(sig));
							break;
						}
					}
				}
				/* Found matching class with no errors */
				if (p->transtagtable[j].sig == icMaxEnumTagType) {
					return ICM_ERR_OK;
				}
				/* Found an unexpected tag */
			}
		}	/* next class */

		/* Either we didn't fin a matching class or we found an error */
		if (e.c != ICM_ERR_OK) {
			if (quirk) {
				icmQuirkWarning(p, e.c, 0, e.m); 
				return ICM_ERR_OK;
			} else {
				return icmFormatWarning(p, e.c, e.m);
			}
		}
		
		/* Didn't find matching class entry */
		return icmFormatWarning(p, ICM_FMT_UNKNOWN_CLASS,
		          "icc_write_check: deviceClass %s for PCS '%s' Dev '%s' is not known\n",
		               icmtag2str(clsig), icmColorSpaceSig2str(pcssig),
						                icmColorSpaceSig2str(colsig));
	}	/* End of checking required tags */

	return ICM_ERR_OK;	/* Assume anything is ok */
}


static void icc_setup_wpchtmx(icc *p);
void icmQuantize3x3S15Fixed16(double targ[3], double mat[3][3], double in[3]);

/* See if there is an ArgyllCMS 'arts' tag, and if so setup the wpchtmx[][] matrix from it. */
/* See if there is an 'chad' tag, and if so setup the chadmx[][] matrix from it. */
static void icc_read_arts_chad(icc *p) {

	/* Check if there is an ArgyllCMS 'arts' tag, and setup the wpchtmx[][] matrix from it. */
	{
		icmS15Fixed16Array *artsTag;
	
		if ((artsTag = (icmS15Fixed16Array *)p->read_tag(p, icmSigAbsToRelTransSpace)) != NULL
		 && artsTag->ttype == icSigS15Fixed16ArrayType
		 && artsTag->count >= 9) {
		
			p->wpchtmx[0][0] = artsTag->data[0];
			p->wpchtmx[0][1] = artsTag->data[1];
			p->wpchtmx[0][2] = artsTag->data[2];
			p->wpchtmx[1][0] = artsTag->data[3];
			p->wpchtmx[1][1] = artsTag->data[4];
			p->wpchtmx[1][2] = artsTag->data[5];
			p->wpchtmx[2][0] = artsTag->data[6];
			p->wpchtmx[2][1] = artsTag->data[7];
			p->wpchtmx[2][2] = artsTag->data[8];

			icmInverse3x3(p->iwpchtmx, p->wpchtmx);

			p->useArts = 1;		/* Save it if it was in profile */

		} else {

			/* If an ArgyllCMS created profile, or if it's a Display profile, */
			/* use Bradford. This makes sRGB and AdobeRGB etc. work correctly */
			/* for absolute colorimetic. Note that for display profiles that set */
			/* the WP to D50 and store their chromatic transform in the 'chad' tag, */
			/* (i.e. some V2 profiles and all V4 profiles) this will have no effect */
			/* on the Media Relative WP Transformation since D50 -> D50, and */
			/* the 'chad' tag will be used to set the internal MediaWhite value */
			/* and transform matrix. */
			if (p->header->creator == icmstr2tag("argl")
			 || p->header->deviceClass == icSigDisplayClass) {

				icmCpy3x3(p->wpchtmx, icmBradford);
				icmInverse3x3(p->iwpchtmx, p->wpchtmx);
	
			/* Default to ICC standard Wrong Von Kries */
			/* for non-ArgyllCMS, non-Display profiles. */
			} else {
				icmCpy3x3(p->wpchtmx, icmWrongVonKries);
				icmCpy3x3(p->iwpchtmx, icmWrongVonKries);
			}
		
			p->useArts = 0;		/* Don't save it, as it wasn't in profile */
		}

		p->wpchtmx_class = p->header->deviceClass;		/* It's set for this class now */
	}

	/* If this is a Display or Output profile, check if there is a 'chad' tag, and read it */
	/* in if it exists. We will use this latter when we interpret absolute colorimetric, */
	/* and this also prevents auto creation of a chad tag on write if wrD/OChad is set. */
	{
		icmS15Fixed16Array *chadTag;

	 	if ((p->header->deviceClass == icSigDisplayClass
	 	  || p->header->deviceClass == icSigOutputClass)
		 && (chadTag = (icmS15Fixed16Array *)p->read_tag(p, icSigChromaticAdaptationTag)) != NULL
		 && chadTag->ttype == icSigS15Fixed16ArrayType
		 && chadTag->count == 9) {
			
			p->chadmx[0][0] = chadTag->data[0];
			p->chadmx[0][1] = chadTag->data[1];
			p->chadmx[0][2] = chadTag->data[2];
			p->chadmx[1][0] = chadTag->data[3];
			p->chadmx[1][1] = chadTag->data[4];
			p->chadmx[1][2] = chadTag->data[5];
			p->chadmx[2][0] = chadTag->data[6];
			p->chadmx[2][1] = chadTag->data[7];
			p->chadmx[2][2] = chadTag->data[8];

			p->naturalChad = 1;
			p->chadmxValid = 1;
		}
	}

	/* It would be nice to have an option to convert 'chad' based profile */
	/* into non-chad profiles, but this is non trivial, since the wpchtmx would */
	/* need to be determined from the chad matrix. While this is technically */
	/* possible (see chex.c for an attempt at this), it is not easy, and */
	/* it's possible for the chad matrix to be a non Von Kries type transformation, */
	/* which cannot be exactly decomposed into a cone space matrix + Von Kries scaling. */ 
}

static icmBase *icc_add_tag_imp(icc *p, icTagSignature sig, icTagTypeSignature ttype, int rdff);

/* Add any automatically created tags. */
/* If this is called from icc_get_size() then wr = z, */
/* If wr is nz, modify white point value (i.e. in middle of ->write()) */
/* The 'chad' tag is only added if there is no natural 'chad' tag, */
/* and will be remove once the write is complete. */
static int icc_add_auto_tags(icc *p, int wr) {

	p->op = wr ? icmSnWrite : icmSnRead;		/* Let check know direction */

	/* If we're using the ArgyllCMS 'arts' tag to record the chromatic */
	/* adapation cone matrix used for the Media Relative WP Transformation, */ 
	/* create it and set it from the wpchtmx[][] matrix. */
	/* Don't write it if there is no 'wtpt' tag (i.e. it's a device link) */
	if (p->useArts
	 && p->find_tag(p, icSigMediaWhitePointTag) == 0)  {
		int rv;
		icmBase *tag;
		icmS15Fixed16Array *artsTag;

		/* Make sure wpchtmx[][] has been set correctly for device class */
		if (p->wpchtmx_class != p->header->deviceClass) {
			icc_setup_wpchtmx(p);
		}

		/* Add an 'arts' tag if one doesn't exist or is the wrong tagtype */
		if ((tag = p->read_tag(p, icmSigAbsToRelTransSpace)) == NULL
		  || tag->ttype != icSigS15Fixed16ArrayType) {
			if (tag != NULL) {
				if (p->delete_tag_quiet(p, icmSigAbsToRelTransSpace) != ICM_ERR_OK) {
					return icm_err(p, 1,"icc_write: Deleting existing 'arts' tag failed");
				}
			}
			if ((artsTag = (icmS15Fixed16Array *)icc_add_tag_imp(p, icmSigAbsToRelTransSpace,
			                                     icSigS15Fixed16ArrayType, 0)) == NULL) {
				return icm_err(p, 1,"icc_write: Adding 'arts' tag failed");
			}
		} else {
			artsTag = (icmS15Fixed16Array *)tag;
		}
		artsTag->count = 9;
		if ((rv = artsTag->allocate(artsTag)	) != 0) {
			return icm_err(p, 1,"icc_write: Allocating 'arts' tag failed");
		}

		/* The cone matrix is assumed to be arranged conventionaly for matrix */
		/* times vector multiplication. */
		/* Consistent with ICC usage, the dimension corresponding to the matrix */
		/* rows varies least rapidly while the one corresponding to the matrix */
		/* columns varies most rapidly. */
		artsTag->data[0] = p->wpchtmx[0][0];
		artsTag->data[1] = p->wpchtmx[0][1];
		artsTag->data[2] = p->wpchtmx[0][2];
		artsTag->data[3] = p->wpchtmx[1][0];
		artsTag->data[4] = p->wpchtmx[1][1];
		artsTag->data[5] = p->wpchtmx[1][2];
		artsTag->data[6] = p->wpchtmx[2][0];
		artsTag->data[7] = p->wpchtmx[2][1];
		artsTag->data[8] = p->wpchtmx[2][2];
	}

	/* If this is a Display profile, and we have been told to save it in */
	/* ICC V4 style, then set the media white point tag to D50 and save */
	/* the chromatic adapation matrix to the 'chad' tag. */
	{
		int rv;
		icmXYZArray *whitePointTag;
		icmS15Fixed16Array *chadTag;
		
	 	if (p->header->deviceClass == icSigDisplayClass
		 && p->wrDChad && !p->naturalChad
		 && (whitePointTag = (icmXYZArray *)p->read_tag(p, icSigMediaWhitePointTag)) != NULL
		 && whitePointTag->ttype == icSigXYZType
		 && whitePointTag->count >= 1) {
	
			/* If we've set this profile, not just read it, */
			/* compute the fromAbs/chad matrix from media white point and cone matrix */
			if (!p->chadmxValid) {
				double wp[3];
				p->chromAdaptMatrix(p, ICM_CAM_NONE, NULL, p->chadmx,
				                       icmD50, whitePointTag->data[0]);  

				/* Optimally quantize chad matrix to preserver white point */
				icmXYZ2Ary(wp, whitePointTag->data[0]);
				icmQuantize3x3S15Fixed16(icmD50_ary3, p->chadmx, wp);
				p->chadmxValid = 1;
			}
	
			/* Make sure no 'chad' tag currently exists */
			if (p->delete_tag_quiet(p, icSigChromaticAdaptationTag) != ICM_ERR_OK) {
				return icm_err(p, 1,"icc_write: Deleting existing 'chad' tag failed");
			}
	
			/* Add one */
			if ((chadTag = (icmS15Fixed16Array *)icc_add_tag_imp(p, icSigChromaticAdaptationTag,
				                                     icSigS15Fixed16ArrayType, 0)) == NULL) {
				return icm_err(p, 1,"icc_write: Adding 'chad' tag failed");
			}
			chadTag->count = 9;
			if ((rv = chadTag->allocate(chadTag)	) != 0) {
				return icm_err(p, 1,"icc_write: Allocating 'chad' tag failed");
			}

			p->tempChad = 1;
	
			if (wr) {
				icmXYZArray *blackPointTag;

				/* Save in ICC matrix order */
				chadTag->data[0] = p->chadmx[0][0];
				chadTag->data[1] = p->chadmx[0][1];
				chadTag->data[2] = p->chadmx[0][2];
				chadTag->data[3] = p->chadmx[1][0];
				chadTag->data[4] = p->chadmx[1][1];
				chadTag->data[5] = p->chadmx[1][2];
				chadTag->data[6] = p->chadmx[2][0];
				chadTag->data[7] = p->chadmx[2][1];
				chadTag->data[8] = p->chadmx[2][2];

				/* Set 'chad' adjusted white point */
				p->tempWP = whitePointTag->data[0];
				whitePointTag->data[0] = icmD50;

				/* Track change in black point too */
				if ((blackPointTag = (icmXYZArray *)p->read_tag(p, icSigMediaBlackPointTag)) != NULL
				 && blackPointTag->ttype == icSigXYZType
				 && blackPointTag->count >= 1) {
					double bp[3];

					p->tempBP = blackPointTag->data[0];
					icmXYZ2Ary(bp, blackPointTag->data[0]);
					icmMulBy3x3(bp, p->chadmx, bp); 
					icmAry2XYZ(blackPointTag->data[0], bp);
				}
			}
		}
	}

	/* If this is an Output profile with a non-standard illuminant set, */
	/* and we have been told to save it using a 'chad' tag to represent */
	/* the illuminant difference, then adjust the media white point tag */
	/* for the illuminant, and change the 'chad' tag. */
	{
		int rv;
		icmXYZArray *whitePointTag;
		icmS15Fixed16Array *chadTag;
		
	 	if (p->header->deviceClass == icSigOutputClass
		 && p->chadmxValid
		 && p->wrOChad && !p->naturalChad
		 && (whitePointTag = (icmXYZArray *)p->read_tag(p, icSigMediaWhitePointTag)) != NULL
		 && whitePointTag->ttype == icSigXYZType
		 && whitePointTag->count >= 1) {
			double wp[3];
	
			/* Make sure no 'chad' tag currently exists */
			if (p->delete_tag_quiet(p, icSigChromaticAdaptationTag) != ICM_ERR_OK) {
				return icm_err(p, 1,"icc_write: Deleting existing 'chad' tag failed");
			}
	
			/* Add one */
			if ((chadTag = (icmS15Fixed16Array *)icc_add_tag_imp(p, icSigChromaticAdaptationTag,
				                                     icSigS15Fixed16ArrayType, 0)) == NULL) {
				return icm_err(p, 1,"icc_write: Adding 'chad' tag failed");
			}
			chadTag->count = 9;
			if ((rv = chadTag->allocate(chadTag)) != 0) {
				return icm_err(p, 1,"icc_write: Allocating 'chad' tag failed");
			}
	
			p->tempChad = 1;

			if (wr) {
				icmXYZArray *blackPointTag;

				/* Save in ICC matrix order */
				chadTag->data[0] = p->chadmx[0][0];
				chadTag->data[1] = p->chadmx[0][1];
				chadTag->data[2] = p->chadmx[0][2];
				chadTag->data[3] = p->chadmx[1][0];
				chadTag->data[4] = p->chadmx[1][1];
				chadTag->data[5] = p->chadmx[1][2];
				chadTag->data[6] = p->chadmx[2][0];
				chadTag->data[7] = p->chadmx[2][1];
				chadTag->data[8] = p->chadmx[2][2];
	
				/* Transform white point to take 'chad' into account */
				p->tempWP = whitePointTag->data[0];
				icmXYZ2Ary(wp, whitePointTag->data[0]);
				icmMulBy3x3(wp, p->chadmx, wp);
				icmAry2XYZ(whitePointTag->data[0], wp);

				/* Track change in black point too */
				if ((blackPointTag = (icmXYZArray *)p->read_tag(p, icSigMediaBlackPointTag)) != NULL
				 && blackPointTag->ttype == icSigXYZType
				 && blackPointTag->count >= 1) {
					double bp[3];

					p->tempBP = blackPointTag->data[0];
					icmXYZ2Ary(bp, blackPointTag->data[0]);
					icmMulBy3x3(bp, p->chadmx, bp); 
					icmAry2XYZ(blackPointTag->data[0], bp);
				}
			}
		}
	}

	return 0;
}

/* Restore profile after creating temporary 'chad' tag, and */
/* modifying the white point. */
static int icc_rem_temp_tags(icc *p) {

	/* Restore profile if Display 'chad' has been temporarily added. */
	{
		int rv;
		icmXYZArray *whitePointTag;
		icmXYZArray *blackPointTag;
		icmS15Fixed16Array *chadTag;
		
	 	if (p->header->deviceClass == icSigDisplayClass
		 && p->tempChad && p->wrDChad && !p->naturalChad
		 && (whitePointTag = (icmXYZArray *)p->read_tag(p, icSigMediaWhitePointTag)) != NULL
		 && whitePointTag->ttype == icSigXYZType
		 && whitePointTag->count >= 1) {
	
			/* Remove temporary 'chad' tag */
			if (p->delete_tag_quiet(p, icSigChromaticAdaptationTag) != ICM_ERR_OK) {
				return icm_err(p, 1,"icc_write: Deleting temporary 'chad' tag failed");
			}
	
			/* Restore original white point */
			whitePointTag->data[0] = p->tempWP;
			p->tempChad = 0;

			/* Restore original black point */
			if ((blackPointTag = (icmXYZArray *)p->read_tag(p, icSigMediaBlackPointTag)) != NULL
			 && blackPointTag->ttype == icSigXYZType
			 && blackPointTag->count >= 1) {
				blackPointTag->data[0] = p->tempBP;
			}
		}
	}

	/* Restore profile if Output 'chad' has been temporarily added. */
	{
		int rv;
		icmXYZArray *whitePointTag;
		icmS15Fixed16Array *chadTag;
		
	 	if (p->header->deviceClass == icSigOutputClass
		 && p->tempChad && p->wrOChad && !p->naturalChad
		 && (whitePointTag = (icmXYZArray *)p->read_tag(p, icSigMediaWhitePointTag)) != NULL
		 && whitePointTag->ttype == icSigXYZType
		 && whitePointTag->count >= 1) {
			double wp[3];
			icmXYZArray *blackPointTag;
	
			/* Remove temporary 'chad' tag */
			if (p->delete_tag_quiet(p, icSigChromaticAdaptationTag) != ICM_ERR_OK) {
				return icm_err(p, 1,"icc_write: Deleting temporary 'chad' tag failed");
			}

			/* Restore original white point */
			whitePointTag->data[0] = p->tempWP;
			p->tempChad = 0;

			/* Restore original black point */
			if ((blackPointTag = (icmXYZArray *)p->read_tag(p, icSigMediaBlackPointTag)) != NULL
			 && blackPointTag->ttype == icSigXYZType
			 && blackPointTag->count >= 1) {
				blackPointTag->data[0] = p->tempBP;
			}
		}
	}

	return 0;
}

/* Return the total size needed for the profile. */
/* Sets size and padded size values in various objects, */
/* and re-computes the reference counts. */
/* This will add any automatic tags such as 'arts' tag, */
/* so the current information needs to be final enough */
/* for the automatic tag creation to be correct. */
/* Auto 'chad' tag will be added and remove if so configured. */
/* Return 0 on error, and set error code and message in tag manager. */
static unsigned int icc_get_size(icc *p) {
	unsigned int i, size = 0, nsize;

	/* Compute the total size and tag element data offsets */
	if (p->header == NULL) {
		icm_err(p, ICM_ERR_INTERNAL, "icc_get_size: No Header available");
		return 0;
	}

	/* Add 'arts' tag and temporary 'chad' tag if so configured */
	icc_add_auto_tags(p, 0);

	nsize = sat_add(size, (p->header->hsize = p->header->get_size(p->header)));		/* Header */
	p->header->hsize = nsize - size;
	nsize = sat_align(p->align, nsize);
	p->header->phsize = nsize - size;
	/* (Could call tagtable_serialise() with icmSnSize to compute tag table size) */
	size = sat_addaddmul(nsize, 4, p->count, 12);	/* Tag table length */
	size = sat_align(p->align, size);				/* Header + tag table length */
	p->pttsize = size - nsize;						/* Padded tag table size */

	if (size == UINT_MAX) {
		icm_err(p, 1,"icc_get_size: size overflow");
		return 0;
	}

	/* Check that every tag has an object */
	for (i = 0; i < p->count; i++) {
		if (p->data[i].objp == NULL) {
			icm_err(p, ICM_ERR_INTERNAL, "icc_get_size: NULL tag element");
			icc_rem_temp_tags(p);
			return 0;
		}
	}

	/* Reset touched flag for each tag type */
	for (i = 0; i < p->count; i++)
		p->data[i].objp->touched = 0;

	for (i = 0; i < p->count; i++) {
		if (p->data[i].objp->touched == 0) {	/* Not seen this previously */
			p->data[i].offset = size;			/* Profile relative target */
			p->data[i].size = p->data[i].objp->get_size(p->data[i].objp);
			if (p->e.c != ICM_ERR_OK) {
				icc_rem_temp_tags(p);
				return 0;
			}
			size = sat_add(size, p->data[i].size);
			nsize = sat_align(p->align, size);
			p->data[i].pad = nsize - size;
			size = nsize;
			p->data[i].objp->touched = 1;	/* Don't account for this again */

		} else { /* must be linked - copy file allocation info. */
			unsigned int k;
			for (k = 0; k < p->count; k++) {	/* Find linked tag */
				if (p->data[k].objp == p->data[i].objp)
					break;
			}
			if (k == p->count) {	/* Didn't find link */
				icm_err(p, ICM_ERR_INTERNAL, "icc_get_size: Corrupted tag-tag link");
				icc_rem_temp_tags(p);
				return 0;
			}
		    p->data[i].offset = p->data[k].offset;
		    p->data[i].size   = p->data[k].size;
		    p->data[i].pad    = p->data[k].pad;
		}
	}

	icc_rem_temp_tags(p);

	return size;	/* Total size needed, or UINT_MAX if overflow */
}

/* read the profile, return 0 on success, error code on fail. */
/* NOTE: this doesn't read the tag types, it just reads the header */
/* and the tag table. Actual tag data is read on demand, or */
/* by calling read_all_tags(). The file object that was used */
/* for this read is saved and used for read_tag(), read_all_tags() and dump(). */
/* NOTE: fp ownership is taken even if the function fails. */
static int icc_read(
	icc *p,
	icmFile *fp,			/* File to read from */
	unsigned int of		/* File offset to read from */
) {
	p->rfp = fp->reference(fp);
	p->of = of;

	p->op = icmSnRead;		// ~8 ?

	if (p->header == NULL) {
		return icm_err(p, ICM_ERR_INTERNAL, "icc_read: Internal, no header defined");
	}

	/* Read the header. */
	if (p->header->read(p->header, p->header->hsize, of) != ICM_ERR_OK)
		return p->e.c;

	/* Set values needed by tag table read */ 
	p->mino = p->header->hsize;				/* Minimum offset allowing for header */
	p->maxs = p->header->size - p->mino;	/* Maximum size for tag table and tags */

	if (p->header->size < p->mino) {
		return icm_err(p, ICM_ERR_RD_FORMAT, "ICC Format error - size of file is smaller than header");
	}
	if (p->maxs < 4) {
		return icm_err(p, ICM_ERR_RD_FORMAT, "ICC Format error - size of file too small for tag table");
	}

	/* Read in the tag table */
	if (tagtable_read(p, 4, of + p->header->hsize) != ICM_ERR_OK)
		return p->e.c;

	/* Read any 'arts' and 'chad' and setup related matrices */
	icc_read_arts_chad(p);

	return p->e.c;
}

/* Write the contents of the profile. Return error code on failure */
/* NOTE: fp ownership is taken even if the function fails. */
static int icc_write(
	icc *p,
	icmFile *fp,	/* File to write to */
	unsigned int of	/* File offset to write to */
) {
	unsigned int len;
	unsigned int i, size;
	int rv;

	/* Add 'arts' tag and temporary 'chad' tag and modify white point, if so configured */
	if ((rv = icc_add_auto_tags(p, 1)) != 0)
		return rv;

	p->wfp = fp->reference(fp);	/* Open file pointer */
	p->of = of;					/* Offset of ICC profile */

	p->op = icmSnSize;		// ~8 ?

	/* Compute the total size and tag element data offsets, and */
	/* setup sizes, offsets, other dependent flags etc. */
	p->header->size = size = icc_get_size(p);

	p->op = icmSnWrite;		// ~8 ?

	/* Before writing, run a complete validity check */
	if (p->write_check(p) != ICM_ERR_OK) {
		icc_rem_temp_tags(p);
		return p->e.c;
	}

	/* Reset touched flag for each tag type */
	for (i = 0; i < p->count; i++)
		p->data[i].objp->touched = 0;

	/* If V4.0+, Compute the MD5 id for the profile. */
	/* We do this by writing to a fake icmFile */
	if (p->header->vers.majv >= 4) {
		icmMD5 *md5 = NULL;
		icmFile *ofp, *dfp = NULL;

		if ((md5 = new_icmMD5_a(&p->e, p->al)) == NULL) {
			icc_rem_temp_tags(p);
			return icm_err(p, 2,"icc_write: new_icmMD5 failed");
		}
		
		if ((dfp = new_icmFileMD5_a(md5, p->al)) == NULL) {
			md5->del(md5);
			icc_rem_temp_tags(p);
			return icm_err(p, 2,"icc_write: new_icmFileMD5 failed");
		}
		
		ofp = p->wfp;
		p->wfp = dfp;

		p->op = icmSnWrite;

		/* Dummy write the header */
		p->header->doid = 1;
		if (p->header->write(p->header, p->header->phsize, of, 0) != ICM_ERR_OK) {
			p->header->doid = 0;
			icc_rem_temp_tags(p);
			return p->e.c;
		}
		p->header->doid = 0;
	
		/* Dummy write the tag table */
		if (tagtable_write(p, p->pttsize, of + p->header->phsize) != ICM_ERR_OK) {
			icc_rem_temp_tags(p);
			return p->e.c;
		}
		
		/* Dummy write all the tag element data */
		for (i = 0; i < p->count; i++) {	/* For all the tag element data */
			if (p->data[i].objp->touched != 0)
				continue;		/* Must be linked, and we've already written it */
			if (p->data[i].objp->write(
			          p->data[i].objp, p->data[i].size, of + p->data[i].offset, p->data[i].pad)
				                                                                 != ICM_ERR_OK) { 
				icc_rem_temp_tags(p);
				return p->e.c;
			}
			p->data[i].objp->touched = 1;	/* Written it, so don't write it again. */
		}
	
		if (p->wfp->flush(p->wfp) != 0) {
			icc_rem_temp_tags(p);
			return icm_err(p, ICM_ERR_FILE_WRITE, "icc_write: file flush failed");
		}

		/* Get the MD5 checksum ID */
		md5->get(md5, p->header->id);

		/* Restore fp */
		dfp->del(dfp);
		md5->del(md5);
		p->wfp = ofp;

		/* Reset touched flag for each tag type again */
		for (i = 0; i < p->count; i++)
			p->data[i].objp->touched = 0;
	}

	/* Now write out the profile for real. */
	/* Although it may appear like we're seeking for each element, */
	/* in fact elements will be written in file order. */

	/* Write the header */
	if (p->header->write(p->header, p->header->phsize, of, 0) != ICM_ERR_OK) {
		icc_rem_temp_tags(p);
		return p->e.c;
	}

	/* Write the tag table */
	if (tagtable_write(p, p->pttsize, of + p->header->phsize) != ICM_ERR_OK) {
		icc_rem_temp_tags(p);
		return p->e.c;
	}
	
	/* Write all the tag element data */
	for (i = 0; i < p->count; i++) {	/* For all the tag element data */
		if (p->data[i].objp->touched != 0)
			continue;		/* Must be linked, and we've already written it */
		if (p->data[i].objp->write(
		          p->data[i].objp, p->data[i].size, of + p->data[i].offset, p->data[i].pad)
			                                                                 != ICM_ERR_OK) { 
			icc_rem_temp_tags(p);
			return p->e.c;
		}
		p->data[i].objp->touched = 1;	/* Written it, so don't write it again. */
	}

	if (p->wfp->flush(p->wfp) != 0) {
		return icm_err(p, ICM_ERR_FILE_WRITE, "icc_write: file flush failed");
	}

	icc_rem_temp_tags(p);

	return p->e.c;
}

static icmBase *icc_read_tag_ix(icc *p, unsigned int i);
static int icc_unread_tag_ix(icc *p, unsigned int i); 

/* Dump whole object */
static void icc_dump(
	icc *p,
	icmFile *op,	/* Output to dump to */
	int   verb		/* Verbosity level, 0 = print nothing. */
	                /* 1 = minimal (nothing from individual tags) */
	                /* 2 = moderate (some from individual tags) */
	                /* 3 = all (complete contents) */
) {
	unsigned int i;

	if (verb <= 0)
		return;

	op->printf(op,"icc:\n");

	/* Dump the header */
	if (p->header != NULL)
		p->header->dump(p->header, op, verb);

	/* Dump all the tag elements */
	for (i = 0; i < p->count; i++) {	/* For all the tag element data */
		icmBase *ob;
		int tr = 0;						/* Flag, temporarily loaded */
		op->printf(op,"tag %d:\n",i);
		op->printf(op,"  sig      %s\n",icmtag2str(p->data[i].sig)); 
		op->printf(op,"  type     %s\n",icmtag2str(p->data[i].ttype)); 
		op->printf(op,"  offset   %d\n", p->data[i].offset);
		op->printf(op,"  size     %d\n", p->data[i].size);

		if (p->data[i].objp == NULL) {
			/* The object is not loaded, so load it then free it */
			if (icc_read_tag_ix(p, i) == NULL) {
				op->printf(op,"Got read error 0x%x, '%s'\n",p->e.c, p->e.m);
				p->clear_err(p);		/* Keep message to tag */
			}
			tr = 1;
		}
		if ((ob = p->data[i].objp) != NULL) {
			/* op->printf(op,"  refcount %d\n", ob->refcount); */
			ob->dump(ob,op,verb-1);

			if (tr != 0) {		/* Cleanup if temporary */
				icc_unread_tag_ix(p, i);
			}
		}
		op->printf(op,"\n");
	}
}

/* Delete the icc */
static void icc_del(
	icc *p
) {
	unsigned int i;
	icmAlloc *al = p->al;

	/* ~8 Free up color transforms */

	/* Free up the header */
	if (p->header != NULL)
		p->header->del(p->header);

	/* Free up the tag data objects */
	for (i = 0; i < p->count; i++) {
		if (p->data[i].objp != NULL) {
			p->data[i].objp->del(p->data[i].objp);	/* Free if last reference */
	  	  	p->data[i].objp = NULL;
		}
	}

	/* Free tag table */
	al->free(al, p->data);

	/* Delete file objects if these are the last refernces to them */
	if (p->rfp != NULL)
		p->rfp->del(p->rfp);
	if (p->wfp != NULL)
		p->wfp->del(p->wfp);

	/* This object */
	al->free(al, p);

	/* Delete allocator if this is the last reference to it */
	al->del(al);
}

/* Translate a pseudo tag type to a real type */
static int icc_translate_pseudotype(
	icc *p,
    icTagSignature sig,			/* Tag signature, possibly icmSigUnknown */
	icTagTypeSignature ttype	/* Tag type, possibly icmSigUnknownType */
) {
	unsigned int i, j;

	if (ttype == icmSigCommonTextDescriptionType) {


		if (sig != icmSigUnknown) {

			/* Find the sig in the tagsigtable */ 
			for (i = 0; p->tagsigtable[i].sig != sig
			         && p->tagsigtable[i].sig != icMaxEnumTagType; i++)
				;
	
			if (p->tagsigtable[i].sig != icMaxEnumTagType) {	/* Known signature */
	
				/* See if icSigTextType or icSigTextDescriptionType are expected */
				for (j = 0; p->tagsigtable[i].ttypes[j].ttype != icSigTextDescriptionType
					     && p->tagsigtable[i].ttypes[j].ttype != icSigTextType
				         && p->tagsigtable[i].ttypes[j].ttype != icMaxEnumTagType; j++)
					;
	
				if (p->tagsigtable[i].ttypes[j].ttype != icMaxEnumTagType) { /* found it */
					return p->tagsigtable[i].ttypes[j].ttype;
				}
			}
		}
		/* For an unknown tag and ICCV2, return icSigTextDescriptionType */
		return  icSigTextDescriptionType;
	}

	return ttype;
}

/* Check that a given tag and tagtype look valid. */
/* A ttype of icmSigUnknownType will be ignored. */
/* Set e.c and e.m if ttagtype is not recognized. */
/* If the sig is know, check that the tagtype is valid for that sig. */
static int icc_check_sig(
	icc *p,
	unsigned int *ttix,			/* If not NULL, return the tagtypetable index */
	int rd,						/* Flag, NZ if reading, else writing */
    icTagSignature sig,			/* Tag signature, possibly icmSigUnknown */
	icTagTypeSignature ttype,	/* Tag type, possibly icmSigUnknownType */
	icTagTypeSignature uttype,	/* Actual tag type if Unknown */
	int rdff					/* Tag was read from file */
) {
	unsigned int i, j;

	if (ttix != NULL)
		*ttix = 0xffffffff;			// Out of range value

	/* Check if the tag type is a known type */
	if (ttype != icmSigUnknownType) {

		for (j = 0; p->tagtypetable[j].ttype != ttype
		         && p->tagtypetable[j].ttype != icMaxEnumTagType; j++)
			;
	
		if (p->tagtypetable[j].ttype == icMaxEnumTagType) {	/* Unknown tagtype */
			return icm_err(p, rd ? ICM_ERR_RD_FORMAT : ICM_ERR_WR_FORMAT,
			        "icc_check_sig: Tag Type '%s' is not known", icmTypeSig2str(ttype));
		}

		/* Check that this tagtype is valid for the version. */
		if (!icmVersInRange(p, &p->tagtypetable[j].vrange)
		 && (  p->op != icmSnWrite											/* Not write */
		    || (p->cflags & icmCFlagAllowWrVersion) == 0					/* Not allow */
			|| !icmVersOverlap(&p->tagtypetable[j].vrange, &p->vcrange))) {	/* Not o'lap range */
			int warn = 0;		/* Force warning rather than error */

			/* Warn rather than error if icmCFlagWrFileRdWarn and tag was read from file */
			if (p->op == icmSnWrite
			 && (p->cflags & icmCFlagWrFileRdWarn)
			 && rdff)
				warn = 1;

			/* Special case for backwards compatibility */
			if (ttype != icSigColorantTableType
			 || p->op != icmSnWrite || getenv("ARGYLL_CREATE_V2COLORANT_TABLE") == NULL) {

				if (icmVersionWarning(p, ICM_VER_TYPEVERS, warn,
					  "Tag Type '%s' is not valid for file version %s (valid %s)\n",
					  icmTypeSig2str(ttype), icmProfileVers2str(p),
					  icmTVersRange2str(&p->tagtypetable[j].vrange)) != ICM_ERR_OK) {
					return p->e.c;
				}
			}
		}

		if (ttix != NULL)
			*ttix = j;
	}
	

	if (sig == icmSigUnknown)	/* Not known, so nothing to check */
		return p->e.c;

	/* Check if the tag signature is known. */
	/* If it is, then it should use one of the valid range of tagtypes for that tag sig. */
	/* If not, the tagtype could be icmSigUnknownType or any known type. */ 
	for (i = 0; p->tagsigtable[i].sig != sig
	         && p->tagsigtable[i].sig != icMaxEnumTagType; i++)
		;

	if (p->tagsigtable[i].sig != icMaxEnumTagType) {	/* Known signature */

		/* Check that this tag signature is valid for this version of file */
		if (!icmVersInRange(p, &p->tagsigtable[i].vrange)
		 && (  p->op != icmSnWrite											/* Not write */
		    || (p->cflags & icmCFlagAllowWrVersion) == 0
			|| !icmVersOverlap(&p->tagsigtable[i].vrange, &p->vcrange))) {

			/* Special case for backwards compatibility */
			if ((sig != icSigColorantTableTag && sig != icSigColorantTableOutTag)
			 || getenv("ARGYLL_CREATE_V2COLORANT_TABLE") == NULL) {
			/* Allow MediaBlackPointTag if quirks allowed */
			if (sig == icSigMediaBlackPointTag
			 && (p->cflags & icmCFlagAllowQuirks) != 0) {

				icmQuirkWarning(p, ICM_VER_TYPEVERS, 0, 
				  "Tag Sig '%s' is not valid for file version %s (valid %s)\n",
				  icmTagSig2str(sig), icmProfileVers2str(p),
				  icmTVersRange2str(&p->tagsigtable[i].vrange));

			} else {
				int warn = 0;		/* Force warning rather than error */
	
				/* Warn rather than error if icmCFlagWrFileRdWarn and tag was read from file */
				if (p->op == icmSnWrite
				 && (p->cflags & icmCFlagWrFileRdWarn)
				 && rdff)
					warn = 1;

				if (icmVersionWarning(p, ICM_VER_SIGVERS, warn, 
					  "Tag Sig '%s' is not valid for file version %s (valid %s)\n",
					  icmTagSig2str(sig), icmProfileVers2str(p),
					  icmTVersRange2str(&p->tagsigtable[i].vrange)) != ICM_ERR_OK)
					return p->e.c;
			}
			}
		}
	
		/* Check that the tag type is permitted for the tag signature */ 
		/* (icmSigUnknownType will not be in this table) */
		for (j = 0; p->tagsigtable[i].ttypes[j].ttype != ttype
		         && p->tagsigtable[i].ttypes[j].ttype != icMaxEnumTagType; j++)
			;

		if (p->tagsigtable[i].ttypes[j].ttype == icMaxEnumTagType) { /* unexpected tagtype */

			if (ttype == icmSigUnknownType) {		/* Permit UnknownType with a warning */ 
				icmQuirkWarning(p, ICM_FMT_SIG2TYPE, 0, 
			      "Tag Sig '%s' uses unexpected Tag Type '%s'",
		          icmTagSig2str(sig), icmTypeSig2str(uttype));
				return p->e.c;						/* OK */
			}

			if (icmFormatWarning(p, ICM_FMT_SIG2TYPE, 
			      "Tag Sig '%s' uses unexpected Tag Type '%s'",
		          icmTagSig2str(sig), icmTypeSig2str(uttype)) != ICM_ERR_OK)
				return p->e.c;						/* Error or OK */

		} else {	/* Recognised ttype */
			/* Check that the signature to type is permitted for this version of file */
			if (!icmVersInRange(p, &p->tagsigtable[i].ttypes[j].vrange)
			 && (  p->op != icmSnWrite											/* Not write */
			    || (p->cflags & icmCFlagAllowWrVersion) == 0
				|| !icmVersOverlap(&p->tagsigtable[i].ttypes[j].vrange, &p->vcrange))) {

				if (icmVersionWarning(p, ICM_VER_SIG2TYPEVERS, 0, 
					  "Tag Sig '%s' can't use Tag Type '%s' in file version %s (valid %s)",
			          icmTagSig2str(sig), icmTypeSig2str(uttype), icmProfileVers2str(p),
					  icmTVersRange2str(&p->tagsigtable[i].ttypes[j].vrange)) != ICM_ERR_OK)
					return p->e.c;					/* Error or OK */
			}
		}
	}

	return p->e.c;
}

/* Return the lut purpose value for a tag signature */
/* Return 0 if there is not sigtypetable setup, */
/* or the signature is not recognised. */
static icmLutPurpose icc_get_tag_lut_purpose(
	icc *p,
    icTagSignature sig			/* Tag signature */
) {
	unsigned int i;

	if (p->tagsigtable == NULL)
		return 0;

	for (i = 0; p->tagsigtable[i].sig != sig
	         && p->tagsigtable[i].sig != icMaxEnumTagType; i++)
		;

	if (p->tagsigtable[i].sig == icMaxEnumTagType)
		return 0;

	return p->tagsigtable[i].purp;
}

/* Search for a tag signature in the profile. */
/* return: */
/* 0 if found */
/* 1 if found but not handled type */
/* 2 if not found */
/* NOTE: doesn't set icc->e.c or icc->e.m[]. */
/* NOTE: we don't handle tag duplication - you'll always get the first in the file. */
static int icc_find_tag(
	icc *p,
    icTagSignature sig			/* Tag signature - may be unknown */
) {
	unsigned int i;
	int j;

	/* Search for signature */
	for (i = 0; i < p->count; i++) {
		if (p->data[i].sig == sig)		/* Found it */
			break;
	}
	if (i == p->count)
		return 2;						/* Not found */

	/* See if we can handle this type */
	for (j = 0; p->tagtypetable[j].ttype != icMaxEnumTagType; j++) {
		if (p->tagtypetable[j].ttype == p->data[i].ttype)
			break;
	}
	if (p->tagtypetable[j].ttype == icMaxEnumTagType)
		return 1;						/* Not handled */

	/* Check if this type is handled by this version format */
	/* (We don't warn as this should have been done on read or add) */
	if (!icmVersInRange(p, &p->tagtypetable[j].vrange)) {
		return 1;
	}

	return 0;						/* Found and handled */
}

/* Read as specific tag element data, and return a pointer to the object. */
/* (This is an internal function) */
/* Returns NULL if the tag doesn't exist. (Doesn't set e.c, e.m) */
/* Returns NULL on other error, details in e.c, e.m */
/* Returns an icmSigUnknownType object if the tag type isn't handled by a */
/* specific object and icmCFlagRdAllowUnknown */
/* icc owns returned object, and will delete it on icc deletion. */
/* NOTE: we don't handle tag duplication - you'll always get the first in the file */
static icmBase *icc_read_tag_ix(
	icc *p,
	unsigned int i				/* Index of tag to read */
) {
	icTagTypeSignature ttype;	/* Tag type we will create */
	icTagTypeSignature uttype;	/* Underlying ttype if Unknown */
	icmBase *nob;
	unsigned int j, k;

	p->op = icmSnRead;			/* Let check know direction */

	if (i >= p->count) {
		return NULL;
	}

	/* See if it's already been read */
    if (p->data[i].objp != NULL) {
		return p->data[i].objp;		/* Just return it */
	}
	
	ttype = uttype = p->data[i].ttype;

	/* If unknown types are allowed, convert an unrecognized type */
	/* into icmSigUnknownType */
	// ~8 should we regard tags outside version as unrecognized ?
	if (p->cflags & icmCFlagRdAllowUnknown) {
		for (j = 0; p->tagtypetable[j].ttype != icMaxEnumTagType; j++) {
			if (p->tagtypetable[j].ttype == p->data[i].ttype)
				break;
		}

		if (p->tagtypetable[j].ttype == icMaxEnumTagType)
			ttype = icmSigUnknownType;
	}

	/* See if this is in fact a link */
	/* (data[].ttype is actual type, not icmSigUnknownType) */
	for (k = 0; k < p->count; k++) {
		if (i == k)
			continue;
	    if (p->data[i].ttype  == p->data[k].ttype	/* Exact match and already read */
	     && p->data[i].offset == p->data[k].offset
	     && p->data[i].size   == p->data[k].size
	     && p->data[k].objp != NULL)
			break;
	}
	if (k < p->count) {		/* Mark it as a link */

		/* Check that the tag signature and tag type are compatible */
		if (icc_check_sig(p, NULL, 1, p->data[i].sig, ttype, uttype, p->data[k].objp->rdff)
			                                                                   != ICM_ERR_OK) {
			return NULL;
		}

		/* Check the lut classes (if any) are compatible */
		if (p->get_tag_lut_purpose(p, p->data[i].sig) != p->get_tag_lut_purpose(p, p->data[k].sig)) {
			icm_err(p, ICM_ERR_PURPMISMATCH,
			     "icc_read_tag_ix: Tag '%s' is link to incompatible tag '%s'",
		          icmTagSig2str(p->data[i].sig), icmTagSig2str(p->data[k].sig));
			return NULL;
		}

		nob = p->data[k].objp;

		/* Re-check this tags validity with different sig */
		if (nob->check != NULL) {
			if (nob->check(nob, p->data[i].sig, 1) != ICM_ERR_OK) {
				return NULL;
			}
		}

		p->data[i].objp = nob;
		nob->refcount++;			/* Bump reference count */

		return nob;			/* Done */
	}

	p->rdff = 1;		/* Objects being created during reading a file */

	/* See if we can handle this type */
	if (icc_check_sig(p, &j, 1, p->data[i].sig, ttype, uttype, p->rdff) != ICM_ERR_OK) {
		return NULL;
	}

	/* Create and read in the object */
	if (ttype == icmSigUnknownType)
		nob = new_icmUnknown(p);
	else
		nob = p->tagtypetable[j].new_obj(p, ttype);

	if (nob == NULL) {
		p->rdff = 0;
		return NULL;
	}

	nob->creatorsig = p->data[i].sig;		/* Some types need to know this.. */

	if ((nob->read(nob, p->data[i].size, p->of + p->data[i].offset)) != 0) {
		nob->del(nob);		/* Failed, so destroy it */
		p->rdff = 0;
		return NULL;
	}
	p->rdff = 0;

	/* Check this tags validity */
	if (nob->check != NULL) {
		if (nob->check(nob, p->data[i].sig, 1) != ICM_ERR_OK) {
			nob->del(nob);	
			return NULL;
		}
	}
    p->data[i].objp = nob;
	return nob;
}

/* Read the tag element data, and return a pointer to the object. */
/* Returns NULL if the tag doesn't exist. (Doesn't set e.c, e.m) */
/* Returns NULL on other error, details in e.c, e.m */
/* Returns an icmSigUnknownType object if the tag type isn't handled by a specific object. */ 
/* icc owns returned object, and will delete it on icc deletion. */
/* NOTE: we don't handle tag duplication - you'll always get the first in the file */
static icmBase *icc_read_tag(
	icc *p,
    icTagSignature sig			/* Tag signature */
) {
	unsigned int i;

	/* Search for signature */
	for (i = 0; i < p->count; i++) {
		if (p->data[i].sig == sig)		/* Found it */
			break;
	}
	if (i >= p->count) {
		return NULL;
	}
	return icc_read_tag_ix(p, i);
}

/* Read the tag element data of the first matching, and return a pointer to the object */
/* Returns NULL if error. */
/* Returns an icmSigUnknownType object if the tag type isn't handled by a specific object. */
/* (Alternatively set icmCFlagRdAllowUnknown flag and use ->read_tag()) */ 
/* icc owns returned object, and will delete it on icc deletion. */
/* NOTE: we don't handle tag duplication - you'll always get the first in the file. */
static icmBase *icc_read_tag_any(
	icc *p,
    icTagSignature sig			/* Tag signature - may be unknown */
) {
	unsigned int i;
	icmBase *ob;
	icmCompatFlags cflags;

	/* Search for signature */
	for (i = 0; i < p->count; i++) {
		if (p->data[i].sig == sig)		/* Found it */
			break;
	}
	if (i >= p->count) {
		return NULL;
	}

	cflags = p->cflags;
	p->cflags |= icmCFlagRdAllowUnknown;
	ob = icc_read_tag_ix(p, i);
	p->cflags = cflags;

	return ob;
}

/* Create and add a tag with the given signature. */
/* Any pseudo-tagtypes are translated to the actual tagtype. */
/* Returns a pointer to the element object */
/* Caller should set p->op SnRead/SnWrite for icc_check_sig() */ 
/* Returns NULL if error - icc->e.c will contain */
/* 2 on system error, */
/* 3 if unknown tag */
/* 4 if duplicate tag */
/* NOTE: that we prevent tag duplication. */
/* NOTE: to create a tag type icmSigUnknownType, set ttype to icmSigUnknownType, */
/* and set the actual tag type in icmSigUnknownType->uttype. (Will get warning) */
static icmBase *icc_add_tag_imp(
	icc *p,
    icTagSignature sig,			/* Tag signature - may be unknown */
	icTagTypeSignature ttype,	/* Tag type - may be icmSigUnknownType */
	int rdff					/* Is read from file */
) {
	icmTagRec *tp;
	icmBase *nob;
	unsigned int i, j;

	/* Convert any pseudo-type to an actual type */
	ttype = icc_translate_pseudotype(p, sig, ttype);

	/* Check that the tag signature and tag type are reasonable */
	/* (Handles icmSigUnknownType appropriately) */
	if (icc_check_sig(p, &j, 0, sig, ttype, ttype, rdff) != ICM_ERR_OK) {
		return NULL;
	}

	/* Check that this tag doesn't already exist. */
	/* (Should we allow this if file version is < 2.2 ?) */ 
	for (i = 0; i < p->count; i++) {
		if (p->data[i].sig == sig) {
			icm_err(p, ICM_ERR_DUPLICATE, "icc_add_tag: Already have tag %s in profile",
			                                                          icmtag2str(p->data[i].sig));
			return NULL;
		}
	}

	/* Make space in tag table for new tag item */
	if (p->data == NULL)
		tp = (icmTagRec *)p->al->malloc(p->al, (p->count+1) * sizeof(icmTagRec));
	else
		tp = (icmTagRec *)p->al->realloc(p->al, (void *)p->data, (p->count+1) * sizeof(icmTagRec));
	if (tp == NULL) {
		icm_err(p, ICM_ERR_MALLOC, "icc_add_tag: Tag table realloc() failed");
		return NULL;
	}
	p->data = tp;

	if (ttype == icmSigUnknownType) {
		if ((nob = new_icmUnknown(p)) == NULL) {
			return NULL;
		}
	} else {
		/* Allocate the empty object */
		if ((nob = p->tagtypetable[j].new_obj(p, ttype)) == NULL) {
			return NULL;
		}
	}

	/* Fill out our tag table entry */
    p->data[p->count].sig = sig;		/* The tag signature */
    nob->creatorsig = sig;				/* Tag that created tagtype */
	p->data[p->count].ttype = ttype;	/* The tag type signature */
    p->data[p->count].offset = 0;		/* Unknown offset yet */
    p->data[p->count].size = 0;			/* Unknown size yet */
    p->data[p->count].objp = nob;		/* Empty object */
	p->count++;

	/* Track whether we have a natural 'chad' tag */
	if (sig == icSigChromaticAdaptationTag)
		p->naturalChad = 1;

	return nob;
}

/* Public version */
/* See above for arguments */
static icmBase *icc_add_tag(
	icc *p,
    icTagSignature sig,			/* Tag signature - may be unknown */
	icTagTypeSignature ttype	/* Tag type - may be icmSigUnknownType */
) {
	p->op = icmSnWrite;			/* Let check know assumed direction */

	return icc_add_tag_imp(p, sig, ttype, 0);
}

/* Rename a tag signature */
/* Return error code */
static int icc_rename_tag(
	icc *p,
    icTagSignature sig,			/* Existing Tag signature - may be unknown */
    icTagSignature sigNew		/* New Tag signature - may be unknown */
) {
	unsigned int k;
	int rdff = 0;

	p->op = icmSnWrite;			/* Let check know direction */

	/* Search for signature in the tag table */
	for (k = 0; k < p->count; k++) {
		if (p->data[k].sig == sig)		/* Found it */
			break;
	}
	if (k >= p->count) {
		return icm_err(p, ICM_ERR_NOT_FOUND, "icc_rename_tag: Tag '%s' not found",
		                                                         icmTagSig2str(sig));
	}

	/* Get rdff flag if known */
	if (p->data[k].objp != NULL)
		rdff = p->data[k].objp->rdff;

	/* Check that the tag signature and tag type are reasonable */
	if (icc_check_sig(p, NULL, 0, sigNew, p->data[k].ttype, p->data[k].ttype, rdff) != ICM_ERR_OK)
		return p->e.c;

	/* Check the classes are compatible */
	if (p->get_tag_lut_purpose(p, sig) != p->get_tag_lut_purpose(p, sigNew)) {
		return icm_err(p, ICM_ERR_PURPMISMATCH,
		     "icc_rename_tag: New tag '%s' doesn't have the same purpose as old tag '%s'",
		                            icmTagSig2str(sigNew), icmTagSig2str(sig));
	}

	/* change its signature */
	p->data[k].sig = sigNew;

	/* Track whether we have a natural 'chad' tag */
	if (sig == icSigChromaticAdaptationTag)
		p->naturalChad = 0;
	if (sigNew == icSigChromaticAdaptationTag)
		p->naturalChad = 1;

	return p->e.c;
}

/* Create and add a tag which is a link to an existing (loaded) tag. */
/* Returns a pointer to the element object */
/* Returns NULL if error - icc->e.c will contain */
/* 3 or code if incompatible tag */
/* NOTE: that we prevent tag duplication */
static icmBase *icc_link_tag(
	icc *p,
    icTagSignature sig,			/* Tag signature to create */
    icTagSignature ex_sig		/* Existing tag signature to link to */
) {
	icmTagRec *tp;
	unsigned int i;

	p->op = icmSnWrite;			/* Let check know direction */

	/* Check that the new tag doesn't already exist. */
	for (i = 0; i < p->count; i++) {
		if (p->data[i].sig == sig) {
			icm_err(p, ICM_ERR_DUPLICATE, "icc_link_tag: Already have tag %s in profile",
			                                                          icmtag2str(p->data[i].sig));
			return NULL;
		}
	}

	/* Search for existing tag signature */
	for (i = 0; i < p->count; i++) {
		if (p->data[i].sig == ex_sig)		/* Found it */
			break;
	}
	if (i >= p->count) {
		icm_err(p, ICM_ERR_NOT_FOUND, "icc_link_tag: Can't find existing tag '%s'",
		                                                         icmTagSig2str(ex_sig));
		return NULL;
	}

	/* Check existing tag is loaded. */
    if (p->data[i].objp == NULL) {
		icm_err(p, ICM_ERR_NOT_FOUND, "icc_link_tag: Existing tag '%s' isn't loaded",
		                                                                   icmtag2str(ex_sig)); 
		return NULL;
	}

	/* Check that the new tag signature and linked tag type are reasonable */
	if (icc_check_sig(p, NULL, 0, sig, p->data[i].objp->ttype, p->data[i].ttype,
		                                 p->data[i].objp->rdff) != ICM_ERR_OK)
		return NULL;

	/* Check the LUT classes are compatible */
	if (p->get_tag_lut_purpose(p, sig) != p->get_tag_lut_purpose(p, ex_sig)) {
		icm_err(p, ICM_ERR_PURPMISMATCH,
		     "icc_link_tag: Link tag '%s' doesn't have the same LUT purpose as tag '%s'",
		                            icmTagSig2str(sig), icmTagSig2str(ex_sig));
		return NULL;
	}

	/* Make space in tag table for new tag item */
	if (p->data == NULL)
		tp = (icmTagRec *)p->al->malloc(p->al, (p->count+1) * sizeof(icmTagRec));
	else
		tp = (icmTagRec *)p->al->realloc(p->al, (void *)p->data, (p->count+1) * sizeof(icmTagRec));
	if (tp == NULL) {
		icm_err(p, ICM_ERR_MALLOC, "icc_link_tag: Tag table realloc() failed");
		return NULL;
	}
	p->data = tp;

	/* Fill out our tag table entry */
    p->data[p->count].sig  = sig;		/* The tag signature */
	p->data[p->count].ttype  = p->data[i].ttype;	/* The tag type signature */
    p->data[p->count].offset = p->data[i].offset;	/* Same offset (may not be allocated yet) */
    p->data[p->count].size = p->data[i].size;		/* Same size (may not be allocated yet) */
    p->data[p->count].objp = p->data[i].objp;		/* Shared object */
	p->data[i].objp->refcount++;					/* Bump reference count on tag type */
	p->count++;

	/* Track whether we have a natural 'chad' tag */
	if (sig == icSigChromaticAdaptationTag)
		p->naturalChad = 1;

	return p->data[i].objp;
}

/* Delete the tag, and free the underlying tag type, */
/* if this was the last reference to it. */
/* (This is an internal function) */
/* Returns non-zero on error: */
/* tag not found - icc->e.c will contain 2 */
static int icc_unread_tag_ix(
	icc *p,
	unsigned int i
) {

	if (i >= p->count) {
		return icm_err(p, ICM_ERR_NOT_FOUND, "icc_unread_tag_ix: Index %d is out of range", i);
	}

	/* See if it's been read */
    if (p->data[i].objp == NULL) {
		return icm_err(p, 2,"icc_unread_tag: Tag '%s' not currently loaded",icmTagSig2str(p->data[i].sig));
	}
	
	(p->data[i].objp->del)(p->data[i].objp);	/* Free if last reference */
  	p->data[i].objp = NULL;

	return 0;
}

/* Unread a specific tag, and free the underlying tag type data */
/* if this was the last reference to it. */
/* Returns non-zero on error: */
/* tag not found - icc->e.c will contain 2 */
/* tag not read - icc->e.c will contain 2 */
static int icc_unread_tag(
	icc *p,
    icTagSignature sig		/* Tag signature - may be unknown */
) {
	unsigned int i;

	/* Search for signature */
	for (i = 0; i < p->count; i++) {
		if (p->data[i].sig == sig)		/* Found it */
			break;
	}
	if (i >= p->count) {
		return icm_err(p, ICM_ERR_NOT_FOUND, "icc_unread_tag: Tag '%s' not found",
		                                                        icmTagSig2str(sig));
	}

	return icc_unread_tag_ix(p, i);
}

/* Read all the tags into memory. */
/* Returns non-zero on error. */
static int icc_read_all_tags(
	icc *p
) {
	unsigned int i;

	for (i = 0; i < p->count; i++) {	/* For all the tag element data */
		icmBase *ob;
		if ((ob = icc_read_tag_ix(p, i)) == NULL) {
			return p->e.c;
		}
	}
	return p->e.c;
}


/* Delete a specific tag, and free the underlying tag type, */
/* if this was the last reference to it. */
/* (This is an internal function) */
/* Returns error code on error: */
/* tag not found - icc->e.c will contain 2 */
static int icc_delete_tag_imp(
	icc *p,
    icTagSignature sig,		/* Tag signature - may be unknown */
	int quiet				/* If nz don't error if the tag doesn't exist */
) {
	unsigned int i;
	int rv;

	/* Search for signature */
	for (i = 0; i < p->count; i++) {
		if (p->data[i].sig == sig)		/* Found it */
			break;
	}

	if (i >= p->count) {
		if (quiet)
			return ICM_ERR_OK;
		return icm_err(p, ICM_ERR_NOT_FOUND, "icc_delete_tag: Tag '%s' not found",
		                                                  icmTagSig2str(sig));
	}

	/* We know that count > 0 now... */

	/* If the tagtype is loaded, decrement the reference count */
    if (p->data[i].objp != NULL) {
		p->data[i].objp->del(p->data[i].objp);	/* Free if last reference */
  		p->data[i].objp = NULL;
	}
	
	/* Now remove it from the tag list */
	for (; i < (p->count-1); i++)
		p->data[i] = p->data[i+1];	/* Copy the structure down */

	p->count--;		/* One less tag in list */

	/* Track whether we still have a natural 'chad' tag */
	if (sig == icSigChromaticAdaptationTag)
		p->naturalChad = 0;

	return p->e.c;
}

/* Delete the tag, and free the underlying tag type, */
/* if this was the last reference to it. */
/* Returns error code on error. */
/* NOTE: we don't handle tag duplication - you'll always read delete the first in the file */
static int icc_delete_tag(
	icc *p,
    icTagSignature sig		/* Tag signature - may be unknown */
) {
	return icc_delete_tag_imp(p, sig, 0);
}

/* Delete the tag quietly, and free the underlying tag type, */
/* if this was the last reference to it. */
/* Doesn't return error if tag doesn't exist */
/* Returns error code on error. */
/* NOTE: we don't handle tag duplication - you'll always delete the first in the file */
static int icc_delete_tag_quiet(
	icc *p,
    icTagSignature sig		/* Tag signature - may be unknown */
) {
	return icc_delete_tag_imp(p, sig, 1);
}

/* (Internal implementation) */
/* Create tagtype for embedding in another tagtype (i.e. ProfileSequenceDescType). */
/* Returns pointer to object or NULL on error. */
static icmBase *icc_new_ttype_imp(
	icc *p,
	icTagTypeSignature ttype,		/* Sub-tagtype to create */
	icTagTypeSignature pttype,		/* tagtype of creator */
	int rdff						/* Is read from file */
) {
	icmBase *nob;
	unsigned int k;

	/* Convert any pseudo-type to an actual type */
	ttype = icc_translate_pseudotype(p, icmSigUnknown, ttype);

	/* Check that the tag type is reasonable */
	if (icc_check_sig(p, &k, 0, icmSigUnknown, ttype, ttype, rdff) != ICM_ERR_OK) {
		return NULL;
	}

	if (ttype == icmSigUnknownType) {
		if ((nob = new_icmUnknown(p)) == NULL)
			return NULL;
	} else {
		unsigned int i, j;

		/* Check that this combo of parent and sub-tagtype is valid */
		/* Find parent ttype */
		for (i = 0; icmValidSubTagTypesTable[i].pttype != icMaxEnumTagType; i++) {
			if (icmValidSubTagTypesTable[i].pttype == pttype)
				break;
		}
		if (icmValidSubTagTypesTable[i].pttype == icMaxEnumTagType) {
			icmFormatWarning(p, ICM_FMT_PAR_TYPE_INVALID,
			          "icc_new_ttype_imp: parent ttype %s cannot have sub-tags\n",
			                                             icmTypeSig2str(pttype));
			return NULL;
		}
		for (j = 0; icmValidSubTagTypesTable[i].ttypes[j] != icMaxEnumTagType; j++) {
			if (icmValidSubTagTypesTable[i].ttypes[j] == ttype)
				break;
		}
		if (icmValidSubTagTypesTable[i].ttypes[j] == icMaxEnumTagType) {
			icmFormatWarning(p, ICM_FMT_SUB_TYPE_INVALID,
			          "icc_new_ttype_imp: sub ttype %s is invalid for parent %s\n",
			                       icmTypeSig2str(ttype), icmTypeSig2str(pttype));
			return NULL;
		}
		
		/* Allocate the empty object */
		if ((nob = p->tagtypetable[k].new_obj(p, ttype)) == NULL)
			return NULL;
	}

	return nob;
}

/* Create tagtype for embedding in another tagtype (i.e. ProfileSequenceDescType). */
/* Returns pointer to object or NULL on error. */
static icmBase *icc_new_ttype(
	icc *p,
	icTagTypeSignature ttype,		/* Sub-tagtype to create */
	icTagTypeSignature pttype		/* tagtype of creator */
) {
	return icc_new_ttype_imp(p, ttype, pttype, 0);
}

/* Create icmPe for embedding (internal implementation). */
/* Returns pointer to object or NULL on error. */
/* (This has the same logic as icc_new_ttype with extra checking, since icmPe */
/*  inherets from icmBase) */
static icmPe *icc_new_pe_imp(
	icc *p,
	icTagTypeSignature ttype,		/* Sub-tagtype to create */
	icTagTypeSignature pttype,		/* tagtype of creator */
	int rdff						/* Is read from file */
) {
	icmBase *nob;
	unsigned int k, i, j;

	/* Check that the tag type is reasonable */
	if (icc_check_sig(p, &k, 0, icmSigUnknown, ttype, ttype, rdff) != ICM_ERR_OK) {
		return NULL;
	}

	/* Locate part of check table that has just Pe sub-types in it */
	for (i = 0; icmValidSubTagTypesTable[i].pttype != PESUBTAGTYPESTABLESTART; i++)
		;

	/* Check that this combo of parent and sub-tagtype is valid */
	/* Find parent ttype */
	for (; icmValidSubTagTypesTable[i].pttype != icMaxEnumTagType; i++) {
		if (icmValidSubTagTypesTable[i].pttype == pttype)
			break;
	}
	if (icmValidSubTagTypesTable[i].pttype == icMaxEnumTagType) {
		icmFormatWarning(p, ICM_FMT_PAR_TYPE_INVALID,
		          "icc_new_pe_imp: parent ttype %s cannot have sub-tags\n",
		                                             icmTypeSig2str(pttype));
		return NULL;
	}
	for (j = 0; icmValidSubTagTypesTable[i].ttypes[j] != icMaxEnumTagType; j++) {
		if (icmValidSubTagTypesTable[i].ttypes[j] == ttype)
			break;
	}
	if (icmValidSubTagTypesTable[i].ttypes[j] == icMaxEnumTagType) {
		icmFormatWarning(p, ICM_FMT_SUB_TYPE_INVALID,
		          "icc_new_pe_imp: sub ttype %s is invalid for parent %s\n",
		                       icmTypeSig2str(ttype), icmTypeSig2str(pttype));
		return NULL;
	}
		
	/* Allocate the empty object */
	if ((nob = p->tagtypetable[k].new_obj(p, ttype)) == NULL) {
		return NULL;
	}
	nob->emb = 1;

	return (icmPe *)nob;
}

/* Create icmPe for embedding. */
/* Returns pointer to object or NULL on error. */
static icmPe *icc_new_pe(
	icc *p,
	icTagTypeSignature ttype,		/* Sub-tagtype to create */
	icTagTypeSignature pttype		/* tagtype of creator */
) {
	return icc_new_pe_imp(p, ttype, pttype, 0);
}

/* Copy & possibly translate a TagTypes contents from one TagType to another. */
/* The source Type may be from another profile, the destination must for */
/* this profile. (Also used in testing de-duplication.) */
/* Only a small number of Types are supported: */
/* TextDescriptionType */
/* Returns error code. */
static int icc_copy_ttype(
	icc *p,
	icmBase *idst,
	icmBase *isrc
) {

	/* Check that the destination is for this icc */
	if (idst->icp != p) {
		return icm_err(p, ICM_ERR_FOREIGN_TTYPE,"icc_copy_ttype: dst is not for this icc");
	}

	if (idst->cpy == NULL) {
		return icm_err(p, ICM_ERR_UNIMP_TTYPE_COPY,"icc_copy_ttype: unimplemented for %s",
		                                                      icmTypeSig2str(idst->ttype));
	}

	return idst->cpy(idst, isrc);
}

/* Compare two TagTypes contents to see if they are the same. */
/* Will return Z on same, NZ on different/error */
/* Error will be set if they are different types or type is unimplemented. */
/* Supported Types are: */
/* TextDescriptionType */
static int icc_compare_ttype(
	icc *p,
	icmBase *dst,
	icmBase *src
) {
	if (dst->cmp == NULL) {
		icm_err(p,ICM_ERR_UNIMP_TTYPE_CMP,"icc_compare_ttype: unimplemented for %s",
		                                               icmTypeSig2str(dst->ttype));
		return 1;
	}

	return dst->cmp(dst, src);
}

/* Set (or) the given compatibility flag */
static void icc_set_cflag(icc *p, icmCompatFlags flags) {
	p->cflags |= flags;
}

/* Unset (and ~) the given compatibility flag */
static void icc_unset_cflag(icc  *p, icmCompatFlags flags) {
	p->cflags &= ~flags;
}

/* Return the state of the compatibility flags */
static icmCompatFlags icc_get_cflags(icc *p) {
	return p->cflags;
}

/* Set icmCFlagAllowWrVersion and tag  tagtype range over which it is enabled. */
/* Use ICMTVRANGE_NONE to disable */
static void icc_set_vcrange(icc *p, const icmTVRange *tvr) {
	if (tvr->min == ICMTV_MAX && tvr->max == ICMTV_MIN) {
		p->cflags &= ~icmCFlagAllowWrVersion;
		p->vcrange = *tvr;		/* Struct copy */
	} else {
		p->cflags |= icmCFlagAllowWrVersion;
		p->vcrange = *tvr;		/* Struct copy */
	}
}

static double icc_get_tac(icc *p, double *chmax,
                          void (*calfunc)(void *cntx, double *out, double *in), void *cntx);
static int icc_get_wb_points(icc *p, int *wpassumed, icmXYZNumber *wp,
	int *bpassumed, icmXYZNumber *bp, double toAbs[3][3], double fromAbs[3][3]);
static void icc_set_illum(struct _icc *p, double ill_wp[3]);
static void icc_chromAdaptMatrix(icc *p, int flags, double imat[3][3], double mat[3][3],
                                 icmXYZNumber d_wp, icmXYZNumber s_wp);


/* Set any version dependent defaults */
static void icc_set_defaults(icc *p) {

	/* Defaults: should we create a V4 style Display profile with D50 media white point */
	/* tag and 'chad' tag ? */
	if (p->header->vers.majv >= 4)
		p->wrDChad = 1;		/* For Display profile mark media WP as D50 and put */
							/* absolute to relative transform matrix in 'chad' tag. */
	else
		p->wrDChad = 0;		/* No by default - use Bradford and store real Media WP */

	/* Environment overides */
	if (getenv("ARGYLL_CREATE_DISPLAY_PROFILE_WITH_CHAD") != NULL)
		p->wrDChad = 1;

	if (getenv("ARGYLL_CREATE_DISPLAY_PROFILE_WITHOUT_CHAD") != NULL)
		p->wrDChad = 0;

	/* Should we use ICC standard Wrong Von Kries for */
	/* white point chromatic adapation for output class ? */
	if (getenv("ARGYLL_CREATE_WRONG_VON_KRIES_OUTPUT_CLASS_REL_WP") != NULL)
		p->useLinWpchtmx = 1;				/* Use Wrong Von Kries */
	else
		p->useLinWpchtmx = 0;				/* Use Bradford by default */
	p->wpchtmx_class = icMaxEnumClass;		/* Not set yet - auto set on create. */

	/* Default to saving ArgyllCMS private 'arts' tag (if appropriate type of */
	/* profile) to make white point chromatic adapation explicit. */
	p->useArts = 1;

	/* Should we create an Output profile using a 'chad' tag if it uses */
	/* a non-standard illuminant ? */
	if (getenv("ARGYLL_CREATE_OUTPUT_PROFILE_WITH_CHAD") != NULL)
		p->wrOChad = 1;		/* For Output profile, put illuminant to D50 Bradford */
							/* matrix in 'chad' tag, and transform real WP by it. */
	else
		p->wrOChad = 0;		/* No by default - Media WP inclues effect of illuminant. */

	/* Set a default wpchtmx in case the profile being read or written */
	/* doesn't use a white point (i.e., it's a device link) */
	/* This will be reset if the wpchtmx_class gets changed. */
	if (!p->useLinWpchtmx) {
		icmCpy3x3(p->wpchtmx, icmBradford);
		icmInverse3x3(p->iwpchtmx, p->wpchtmx);
	} else {
		icmCpy3x3(p->wpchtmx, icmWrongVonKries);
		icmCpy3x3(p->iwpchtmx, icmWrongVonKries);
	}

	/* 'chad' tag requires at least V2.4 */
	if ((p->wrDChad || p->wrOChad)
	  && p->get_version(p) < ICMTV_24) {
		p->set_version(p, ICMTV_24);
	}
}

/* Create an empty object. Return NULL on error */
icc *new_icc_a(
icmErr *e,				/* Return error (may be NULL) */
icmAlloc *al			/* Memory allocator to take reference of */
) {
	unsigned int i;
	icc *p = NULL;	

	if (e != NULL && e->c != ICM_ERR_OK)
		return NULL;

	if ((p = (icc *) al->calloc(al, 1,sizeof(icc))) == NULL) {
		if (e != NULL)
			icm_err_e(e, ICM_ERR_MALLOC, "Allocating icc failed");
		return NULL;
	}

	p->get_rfp            = icc_get_rfp;
	p->get_version   	  = icc_get_version;
	p->set_version   	  = icc_set_version;
	p->set_cflag          = icc_set_cflag;
	p->unset_cflag        = icc_unset_cflag;
	p->get_cflags         = icc_get_cflags;
	p->set_vcrange        = icc_set_vcrange;
	p->clear_err          = icm_err_clear;
	p->get_size           = icc_get_size;
	p->read               = icc_read;
	p->write              = icc_write;
	p->dump               = icc_dump;
	p->del                = icc_del;
	p->find_tag           = icc_find_tag;
	p->read_tag           = icc_read_tag;
	p->read_tag_any       = icc_read_tag_any;
	p->add_tag            = icc_add_tag;
	p->rename_tag         = icc_rename_tag;
	p->link_tag           = icc_link_tag;
	p->unread_tag         = icc_unread_tag;
	p->read_all_tags      = icc_read_all_tags;
	p->delete_tag         = icc_delete_tag;
	p->delete_tag_quiet   = icc_delete_tag_quiet;
	p->new_ttype          = icc_new_ttype;
	p->new_pe             = icc_new_pe;
	p->copy_ttype         = icc_copy_ttype;
	p->check_id      	  = icc_check_id;
	p->write_check        = icc_write_check;
	p->get_tac            = icc_get_tac;
	p->get_wb_points      = icc_get_wb_points;
	p->get_tag_lut_purpose  = icc_get_tag_lut_purpose;
	p->set_illum          = icc_set_illum;
	p->chromAdaptMatrix   = icc_chromAdaptMatrix;

	p->get_luobj          = icc_get_lu4obj;
	p->create_mono_xforms    = icc_create_mono_xforms;
	p->create_matrix_xforms  = icc_create_matrix_xforms;
	p->create_lut_xforms     = icc_create_lut_xforms;


	p->al                 = al->reference(al);
	p->tagtypetable  = icmTagTypeTable; 
	p->tagsigtable   = icmTagSigTable; 
	p->classtagtable = icmClassTagTable; 
	p->transtagtable = icmTransTagTable; 

	/* By default be leanient in what we read, and strict in what we write. */
	/* Just warn if we write tags read from an existing file though. */
	p->cflags |= icmCFlagRdFormatWarn;
	p->cflags |= icmCFlagRdVersionWarn;
	p->cflags |= icmCFlagRdAllowUnknown;
	p->cflags |= icmCFlagAllowExtensions;
	p->cflags |= icmCFlagAllowQuirks;
	p->cflags |= icmCFlagWrFileRdWarn;
	p->vcrange = icmtvrange_none;

	p->align = icAlignSize;		/* Everything is 32 bit aligned */

	/* Allocate a header object and set default values */
	if ((p->header = new_icmHeader(p)) == NULL) {
		if (e != NULL)
			*e = p->e;		/* Copy error out */
		p->del(p);
		return NULL;
	}

	/* Set default values */
	icc_set_defaults(p);

	/* Return any error */
	if (p->e.c != ICM_ERR_OK) {
		*e = p->e;		/* Copy error out */
		p->del(p);
		return NULL;
	}

	return p;
}

/* ================================================== */
/* Lut Color normalizing and de-normalizing functions */

/* As a rule, I am representing Lut in memory as values in machine form as real */
/* numbers in the range 0.0 - 1.0. For many color spaces (ie. RGB, Gray, */
/* hsv, hls, cmyk and other device coords), this is entirely appropriate, */
/* and is what the transform machinery expects between different stages. */
/* At the ends of the transform for CIE based spaces though, this is not correct, */
/* since the binary representation should be consistent with the encoding in Annex A, */
/* page 74 of the standard. (Note that the standard doesn't specify the encoding of */
/* many color spaces ie. Yuv, Yxy etc., and is unclear about PCS.) */

/* The following functions convert to and from the CIE base spaces */
/* and the real Lut input/output values. These are used to convert real color */
/* space values into/out of the raw lut 0.0-1.0 representation (which subsequently */
/* get converted to ICC integer values in the obvious way as a mapping to 0 .. 2^n-1). */

/* This is used internally to support the Lut->lookup() function, */
/* and can also be used by someone writing a Lut based profile to determine */
/* the colorspace range that the input lut indexes cover, as well */
/* as processing the output luts values into normalized form ready */
/* for writing. */

/*
	The V4 specificatins are misleading on device space encoding,
	since they assume that all devices spaces however labeled,
	have no defined ICC encoding. The end result is simple enough though:

	V2 Lab encoding:

	ICC V2 Lab encoding should be used in all PCS encodings in
	the following tags, even in V4 profiles:

		(icSigLut8Type encoding hasn't changed for V4)
		icSigLut16Type
		icSigNamedColorType
		icSigNamedColor2Type

	and can/should be used for _device_ space Lab encoding for these tags.

	ICC V4 Lab encoding should be used for all PCS encodings in
	all other situations, and can/should be used for device space Lab encoding
	for all other situations.

	V4 Tags that seem to use the ICC V4 Lab encoding:

		icSigColorantTableType
		icSigLutAToBType	
		icSigLutBToAType

	If we get an V4 tag in a V2 profile (like icSigColorantTableType) then the handling
	is undefined. We choose to use the corresponding profile version (i.e. mixed versions)
	for compatibility with icclibv2. 

	[ Since the ICC spec. doesn't cover device spaces labeled as Lab,
      these are ripe for mis-matches between different implementations.]

	The MPE based transforms encode PCS in 32 bit float directly,
	i.e. there is no scaling. This implies that the processing elements need to
	do the scaling needed to map PCS values into things like curves, cLUTs etc.

	Implicitly normal device encoding is the range 0.0 - 1.0, although
	MPE allows greater than that. The fallback transforms can't match
	this though.
*/


/* Setup the wpchtmx appropriately for creating profile. */
/* This is called if the deviceClass has changed on a call */
/* to ->chromAdaptMatrix(), ->get_size() or ->write(). */
static void icc_setup_wpchtmx(icc *p) {
	int useBradford = 1;		/* Default use Bradford */

	/* If set by reading profile or already set appropriately */
	if (p->wpchtmx_class == p->header->deviceClass)
		return;

	/* If we should use ICC standard Wrong Von Kries for white point chromatic adapation */
	if (p->header->deviceClass == icSigOutputClass
	 && p->useLinWpchtmx) {
		useBradford = 0;
	} 

	if (useBradford) {
		icmCpy3x3(p->wpchtmx, icmBradford);
		icmInverse3x3(p->iwpchtmx, p->wpchtmx);
	} else {
		icmCpy3x3(p->wpchtmx, icmWrongVonKries);
		icmCpy3x3(p->iwpchtmx, icmWrongVonKries);
	}

	/* This is set for this profile class now */
	p->wpchtmx_class = p->header->deviceClass;
}

/* Clear any existing 'chad' matrix, and if Output type profile */
/* and ARGYLL_CREATE_OUTPUT_PROFILE_WITH_CHAD set and */
/* ill_wp != NULL, create a 'chad' matrix. */
static void icc_set_illum(struct _icc *p, double ill_wp[3]) {

	p->chadmxValid = 0;		/* Calling set_illum signals profile creation, */
							/* so discard any previous (i.e. read) chad matrix */

	if (ill_wp != NULL) {
		icmCpy3(p->illwp, ill_wp);
		p->illwpValid = 1;
	}

	/* Is illuminant chromatic adapation chad matrix needed ? */
 	if (p->header->deviceClass == icSigOutputClass
	 && p->illwpValid
	 && p->wrOChad) {
		double wp[3];
		icmXYZNumber iwp;

		/* Create Output illuminant 'chad' matrix */
		icmAry2XYZ(iwp, p->illwp);
		icmChromAdaptMatrix(ICM_CAM_BRADFORD, icmD50, iwp, p->chadmx);  

		/* Optimally quantize chad matrix to preserver white point */
		icmQuantize3x3S15Fixed16(icmD50_ary3, p->chadmx, p->illwp);

		p->chadmxValid = 1;
	}
}

/* Return an overall Chromatic Adaptation Matrix for the given source and */
/* destination white points. This will depend on the icc profiles current setup */
/* for Abs->Rel conversion (wpchtmx[][] set to wrong Von Kries or not, whether */
/* 'arts' tag has been read), and whether an Output profile 'chad' tag has bean read */
/* or will be created. (i.e. on creation, assumes icc->set_illum() called). */
/* Use icmMulBy3x3(dst, mat, src) */
/* NOTE that to transform primaries they must be mat[XYZ][RGB] format! */
static void icc_chromAdaptMatrix(
	icc *p,
	int flags,				/* ICM_CAM_NONE or ICM_CAM_MULMATRIX to mult by mat */
	double imat[3][3],		/* Optional inverse CAT matrix result */
	double mat[3][3],		/* CAT optional input if ICM_CAM_MULMATRIX & result matrix */
	icmXYZNumber d_wp,		/* Destination white point (Usually PCS D50) */
	icmXYZNumber s_wp		/* Source media absolute white point */
) {
	double dst[3], src[3];			/* Source & destination white points */
	double vkmat[3][3];				/* Von Kries matrix */
	double omat[3][3];				/* Output matrix */

	if (p->header->deviceClass == icMaxEnumClass) {
		fprintf(stderr,"icc_chromAdaptMatrix called with no deviceClass!\n");
	}

	/* Take a copy of src/dst */
	icmXYZ2Ary(src, s_wp);
	icmXYZ2Ary(dst, d_wp);

	/* See if the profile type has changed, re-evaluate wpchtmx */
	if (p->wpchtmx_class != p->header->deviceClass) {
		icc_setup_wpchtmx(p);
	}

	/* Set initial matrix to unity if creating from scratch */
	if (flags & ICM_CAM_MULMATRIX)
		icmCpy3x3(omat, mat);
	else
		icmSetUnity3x3(omat);

	/* Incorporate Output chad matrix if we will be creating one */
	if (p->header->deviceClass == icSigOutputClass
	 && p->chadmxValid) {
		icmMulBy3x3(src, p->chadmx, src);
		icmMul3x3(omat, p->chadmx);
	}

	/* Transform src/dst to cone space */
	icmMulBy3x3(src, p->wpchtmx, src);
	icmMulBy3x3(dst, p->wpchtmx, dst);

	/* Transform incoming matrix to cone space */
	icmMul3x3(omat, p->wpchtmx);

	/* Setup the Von Kries white point adaptation matrix */
	vkmat[0][0] = dst[0]/src[0];
	vkmat[1][1] = dst[1]/src[1];
	vkmat[2][2] = dst[2]/src[2];
	vkmat[0][1] = vkmat[0][2] = 0.0;
	vkmat[1][0] = vkmat[1][2] = 0.0;
	vkmat[2][0] = vkmat[2][1] = 0.0;

	/* Apply chromatic adaptation */
	icmMul3x3(omat, vkmat);

	/* Transform from cone space */
	icmMul3x3(omat, p->iwpchtmx);

	if (mat != NULL)
		icmCpy3x3(mat, omat);

	if (imat != NULL)
		icmInverse3x3(imat, omat);
}

/* ============================================== */
/* MD5 checksum support */

/* Object for computing RFC 1321 MD5 checksums. */
/* Derived from Colin Plumb's 1993 public domain code. */

/* Reset the checksum */
static void icmMD5_reset(icmMD5 *p) {
	p->tlen = 0;

	p->sum[0] = 0x67452301;
	p->sum[1] = 0xefcdab89;
	p->sum[2] = 0x98badcfe;
	p->sum[3] = 0x10325476;

	p->fin = 0;
}

#define F1(x, y, z) (z ^ (x & (y ^ z)))
#define F2(x, y, z) F1(z, x, y)
#define F3(x, y, z) (x ^ y ^ z)
#define F4(x, y, z) (y ^ (x | ~z))

#define MD5STEP(f, w, x, y, z, pp, xtra, s) \
		data = (pp)[0] + ((pp)[3] << 24) + ((pp)[2] << 16) + ((pp)[1] << 8); \
		w += f(x, y, z) + data + xtra; \
		w = (w << s) | (w >> (32-s)); \
		w += x;

/* Add another 64 bytes to the checksum */
static void icmMD5_accume(icmMD5 *p, ORD8 *in) {
	ORD32 data, a, b, c, d;

	a = p->sum[0];
	b = p->sum[1];
	c = p->sum[2];
	d = p->sum[3];

	MD5STEP(F1, a, b, c, d, in + (4 * 0),  0xd76aa478, 7);
	MD5STEP(F1, d, a, b, c, in + (4 * 1),  0xe8c7b756, 12);
	MD5STEP(F1, c, d, a, b, in + (4 * 2),  0x242070db, 17);
	MD5STEP(F1, b, c, d, a, in + (4 * 3),  0xc1bdceee, 22);
	MD5STEP(F1, a, b, c, d, in + (4 * 4),  0xf57c0faf, 7);
	MD5STEP(F1, d, a, b, c, in + (4 * 5),  0x4787c62a, 12);
	MD5STEP(F1, c, d, a, b, in + (4 * 6),  0xa8304613, 17);
	MD5STEP(F1, b, c, d, a, in + (4 * 7),  0xfd469501, 22);
	MD5STEP(F1, a, b, c, d, in + (4 * 8),  0x698098d8, 7);
	MD5STEP(F1, d, a, b, c, in + (4 * 9),  0x8b44f7af, 12);
	MD5STEP(F1, c, d, a, b, in + (4 * 10), 0xffff5bb1, 17);
	MD5STEP(F1, b, c, d, a, in + (4 * 11), 0x895cd7be, 22);
	MD5STEP(F1, a, b, c, d, in + (4 * 12), 0x6b901122, 7);
	MD5STEP(F1, d, a, b, c, in + (4 * 13), 0xfd987193, 12);
	MD5STEP(F1, c, d, a, b, in + (4 * 14), 0xa679438e, 17);
	MD5STEP(F1, b, c, d, a, in + (4 * 15), 0x49b40821, 22);

	MD5STEP(F2, a, b, c, d, in + (4 * 1),  0xf61e2562, 5);
	MD5STEP(F2, d, a, b, c, in + (4 * 6),  0xc040b340, 9);
	MD5STEP(F2, c, d, a, b, in + (4 * 11), 0x265e5a51, 14);
	MD5STEP(F2, b, c, d, a, in + (4 * 0),  0xe9b6c7aa, 20);
	MD5STEP(F2, a, b, c, d, in + (4 * 5),  0xd62f105d, 5);
	MD5STEP(F2, d, a, b, c, in + (4 * 10), 0x02441453, 9);
	MD5STEP(F2, c, d, a, b, in + (4 * 15), 0xd8a1e681, 14);
	MD5STEP(F2, b, c, d, a, in + (4 * 4),  0xe7d3fbc8, 20);
	MD5STEP(F2, a, b, c, d, in + (4 * 9),  0x21e1cde6, 5);
	MD5STEP(F2, d, a, b, c, in + (4 * 14), 0xc33707d6, 9);
	MD5STEP(F2, c, d, a, b, in + (4 * 3),  0xf4d50d87, 14);
	MD5STEP(F2, b, c, d, a, in + (4 * 8),  0x455a14ed, 20);
	MD5STEP(F2, a, b, c, d, in + (4 * 13), 0xa9e3e905, 5);
	MD5STEP(F2, d, a, b, c, in + (4 * 2),  0xfcefa3f8, 9);
	MD5STEP(F2, c, d, a, b, in + (4 * 7),  0x676f02d9, 14);
	MD5STEP(F2, b, c, d, a, in + (4 * 12), 0x8d2a4c8a, 20);

	MD5STEP(F3, a, b, c, d, in + (4 * 5),  0xfffa3942, 4);
	MD5STEP(F3, d, a, b, c, in + (4 * 8),  0x8771f681, 11);
	MD5STEP(F3, c, d, a, b, in + (4 * 11), 0x6d9d6122, 16);
	MD5STEP(F3, b, c, d, a, in + (4 * 14), 0xfde5380c, 23);
	MD5STEP(F3, a, b, c, d, in + (4 * 1),  0xa4beea44, 4);
	MD5STEP(F3, d, a, b, c, in + (4 * 4),  0x4bdecfa9, 11);
	MD5STEP(F3, c, d, a, b, in + (4 * 7),  0xf6bb4b60, 16);
	MD5STEP(F3, b, c, d, a, in + (4 * 10), 0xbebfbc70, 23);
	MD5STEP(F3, a, b, c, d, in + (4 * 13), 0x289b7ec6, 4);
	MD5STEP(F3, d, a, b, c, in + (4 * 0),  0xeaa127fa, 11);
	MD5STEP(F3, c, d, a, b, in + (4 * 3),  0xd4ef3085, 16);
	MD5STEP(F3, b, c, d, a, in + (4 * 6),  0x04881d05, 23);
	MD5STEP(F3, a, b, c, d, in + (4 * 9),  0xd9d4d039, 4);
	MD5STEP(F3, d, a, b, c, in + (4 * 12), 0xe6db99e5, 11);
	MD5STEP(F3, c, d, a, b, in + (4 * 15), 0x1fa27cf8, 16);
	MD5STEP(F3, b, c, d, a, in + (4 * 2),  0xc4ac5665, 23);

	MD5STEP(F4, a, b, c, d, in + (4 * 0),  0xf4292244, 6);
	MD5STEP(F4, d, a, b, c, in + (4 * 7),  0x432aff97, 10);
	MD5STEP(F4, c, d, a, b, in + (4 * 14), 0xab9423a7, 15);
	MD5STEP(F4, b, c, d, a, in + (4 * 5),  0xfc93a039, 21);
	MD5STEP(F4, a, b, c, d, in + (4 * 12), 0x655b59c3, 6);
	MD5STEP(F4, d, a, b, c, in + (4 * 3),  0x8f0ccc92, 10);
	MD5STEP(F4, c, d, a, b, in + (4 * 10), 0xffeff47d, 15);
	MD5STEP(F4, b, c, d, a, in + (4 * 1),  0x85845dd1, 21);
	MD5STEP(F4, a, b, c, d, in + (4 * 8),  0x6fa87e4f, 6);
	MD5STEP(F4, d, a, b, c, in + (4 * 15), 0xfe2ce6e0, 10);
	MD5STEP(F4, c, d, a, b, in + (4 * 6),  0xa3014314, 15);
	MD5STEP(F4, b, c, d, a, in + (4 * 13), 0x4e0811a1, 21);
	MD5STEP(F4, a, b, c, d, in + (4 * 4),  0xf7537e82, 6);
	MD5STEP(F4, d, a, b, c, in + (4 * 11), 0xbd3af235, 10);
	MD5STEP(F4, c, d, a, b, in + (4 * 2),  0x2ad7d2bb, 15);
	MD5STEP(F4, b, c, d, a, in + (4 * 9),  0xeb86d391, 21);

	p->sum[0] += a;
	p->sum[1] += b;
	p->sum[2] += c;
	p->sum[3] += d;
}

#undef F1
#undef F2
#undef F3
#undef F4
#undef MD5STEP

/* Add some bytes */
static void icmMD5_add(icmMD5 *p, ORD8 *ibuf, unsigned int len) {
	unsigned int bs;

	if (p->fin)
		return;				/* This is actually an error */

	bs = p->tlen;			/* Current total bytes added */
	p->tlen = bs + len;		/* Update length after adding this buffer */
	bs &= 0x3f;				/* Bytes already in buffer */

	/* Deal with any existing partial bytes in p->buf */
	if (bs) {
		ORD8 *np = (ORD8 *)p->buf + bs;	/* Next free location in partial buffer */

		bs = 64 - bs;		/* Free space in partial buffer */

		if (len < bs) {		/* Not enought new to make a full buffer */
			memmove(np, ibuf, len);
			return;
		}

		memmove(np, ibuf, bs);	/* Now got one full buffer */
		icmMD5_accume(p, p->buf);
		ibuf += bs;
		len -= bs;
	}

	/* Deal directly with input data 64 bytes at a time */
	while (len >= 64) {
		icmMD5_accume(p, ibuf);
		ibuf += 64;
		len -= 64;
	}

	/* Copy any remaining bytes to our partial buffer */
	memmove(p->buf, ibuf, len);
}

/* Finalise the checksum and return the result. */
static void icmMD5_get(icmMD5 *p, ORD8 chsum[16]) {
	int i;
	unsigned count;
	ORD32 bits1, bits0;
	ORD8 *pp;

	if (p->fin == 0) {
	
		/* Compute number of bytes processed mod 64 */
		count = p->tlen & 0x3f;

		/* Set the first char of padding to 0x80.  This is safe since there is
		   always at least one byte free */
		pp = p->buf + count;
		*pp++ = 0x80;

		/* Bytes of padding needed to make 64 bytes */
		count = 64 - 1 - count;

		/* Pad out to 56 mod 64, allowing 8 bytes for length in bits. */
		if (count < 8) {	/* Not enough space for padding and length */

			memset(pp, 0, count);
			icmMD5_accume(p, p->buf);

			/* Now fill the next block with 56 bytes */
			memset(p->buf, 0, 56);
		} else {
			/* Pad block to 56 bytes */
			memset(pp, 0, count - 8);
		}

		/* Compute number of bits */
		bits1 = 0x7 & (p->tlen >> (32 - 3)); 
		bits0 = p->tlen << 3;

		/* Append number of bits */
		p->buf[64 - 8] = bits0 & 0xff; 
		p->buf[64 - 7] = (bits0 >> 8) & 0xff; 
		p->buf[64 - 6] = (bits0 >> 16) & 0xff; 
		p->buf[64 - 5] = (bits0 >> 24) & 0xff; 
		p->buf[64 - 4] = bits1 & 0xff; 
		p->buf[64 - 3] = (bits1 >> 8) & 0xff; 
		p->buf[64 - 2] = (bits1 >> 16) & 0xff; 
		p->buf[64 - 1] = (bits1 >> 24) & 0xff; 

		icmMD5_accume(p, p->buf);

		p->fin = 1;
	}

	/* Return the result, lsb to msb */
	pp = chsum;
	for (i = 0; i < 4; i++) {
		*pp++ = p->sum[i] & 0xff; 
		*pp++ = (p->sum[i] >> 8) & 0xff; 
		*pp++ = (p->sum[i] >> 16) & 0xff; 
		*pp++ = (p->sum[i] >> 24) & 0xff; 
	}
}

/* Take a reference */
static icmMD5 *icmMD5_reference(icmMD5 *p) {
	p->refcount++;
	return p;
}

/* Delete the instance */
static void icmMD5_del(icmMD5 *p) {
	if (p == NULL || --p->refcount > 0)
		return;
	{
		icmAlloc *al = p->al;

		al->free(al, p);
		al->del(al);		/* delete if last reference to al */
	}
}

/* Create a new MD5 checksumming object, with a reset checksum value */
/* Return it or NULL if malloc failes */
/* Note that this will fail if e has an error already set */
icmMD5 *new_icmMD5_a(icmErr *e, icmAlloc *al) {
	icmMD5 *p;

	if (e != NULL && e->c != ICM_ERR_OK)
		return NULL;		/* Pre-existing error */

	if ((p = (icmMD5 *)al->calloc(al,1,sizeof(icmMD5))) == NULL) {
		icm_err_e(e, ICM_ERR_MALLOC, "Allocating icmMD5 object failed");	
		return NULL;
	}

	p->refcount  = 1;
	p->al        = al->reference(al);
	p->reset     = icmMD5_reset;
	p->add       = icmMD5_add;
	p->get       = icmMD5_get;
	p->reference = icmMD5_reference;
	p->del       = icmMD5_del;

	p->reset(p);

	return p;
}

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

/* Dumy icmFile used to compute MD5 checksum on write */

/* Get the size of the file (Only valid for reading file). */
static size_t icmFileMD5_get_size(icmFile *pp) {
	icmFileMD5 *p = (icmFileMD5 *)pp;

	return p->size;
}

/* Set current position to offset. Return 0 on success, nz on failure. */
/* Seek can't be supported for MD5, so and seek must be to current location. */
static int icmFileMD5_seek(
icmFile *pp,
unsigned int offset
) {
	icmFileMD5 *p = (icmFileMD5 *)pp;

	if (p->of != offset) {
		icm_err_e(&p->e, ICM_ERR_INTERNAL, "icmFileMD5_seek: discontinuous write breaks MD5 calculation (seek %d expect %d)",offset,p->of);	
		return 1;
	}
	if (p->of > p->size)
		p->size = p->of;
	return 0;
}

/* Read count items of size length. Return number of items successfully read. */
/* Read is not implemented */
static size_t icmFileMD5_read(
icmFile *pp,
void *buffer,
size_t size,
size_t count
) {
	icmFileMD5 *p = (icmFileMD5 *)pp;
	icm_err_e(&p->e, ICM_ERR_INTERNAL, "icmFileMD5_read: not implemented");	
	return 0;
}

/* write count items of size length. Return number of items successfully written. */
/* Simply pass to MD5 to compute checksum */
static size_t icmFileMD5_write(
icmFile *pp,
void *buffer,
size_t size,
size_t count
) {
	icmFileMD5 *p = (icmFileMD5 *)pp;
	size_t len = size * count;

	p->md5->add(p->md5, (ORD8 *)buffer, len);
	p->of += len;
	if (p->of > p->size)
		p->size = p->of;
	return count;
}

/* do a printf */
/* Not implemented */
static int icmFileMD5_printf(
icmFile *pp,
const char *format,
...
) {
	icmFileMD5 *p = (icmFileMD5 *)pp;
	icm_err_e(&p->e, ICM_ERR_INTERNAL, "icmFileMD5_printf: not implemented");	
	return 0;
}

/* flush all write data out to secondary storage. Return nz on failure. */
static int icmFileMD5_flush(
icmFile *pp
) {
	return 0;
}

/* Take a reference of the icmFileMD5 */
static icmFile *icmFileMD5_reference(
icmFile *pp
) {
	pp->refcount++;
	return pp;
}

/* we're done with the file object, return nz on failure */
static int icmFileMD5_delete(
icmFile *pp
) {
	if (pp == NULL || --pp->refcount > 0)
		return 0;
	{
		icmFileMD5 *p = (icmFileMD5 *)pp;
		icmAlloc *al = p->al;
	
		p->md5->del(p->md5);	/* Free reference to md5 */
		al->free(al, p);		/* Free this object */
		al->del(al);			/* Free allocator if this is the last reference */
		return 0;
	}
}

/* Create a checksum dump file access class with allocator */
icmFile *new_icmFileMD5_a(
icmMD5 *md5,		/* MD5 object to use */
icmAlloc *al		/* heap allocator to reference */
) {
	icmFileMD5 *p;

	if ((p = (icmFileMD5 *) al->calloc(al, 1, sizeof(icmFileMD5))) == NULL) {
		return NULL;
	}
	p->refcount  = 1;
	p->md5       = md5->reference(md5);		/* MD5 compute object */
	p->al        = al->reference(al);		/* Heap allocator */
	p->get_size  = icmFileMD5_get_size;
	p->seek      = icmFileMD5_seek;
	p->read      = icmFileMD5_read;
	p->write     = icmFileMD5_write;
	p->printf    = icmFileMD5_printf;
	p->flush     = icmFileMD5_flush;
	p->reference = icmFileMD5_reference;
	p->del       = icmFileMD5_delete;
	p->of = 0;

	return (icmFile *)p;
}

/* ============================================= */
/* Implementation of color transform lookups.    */


/* ============================================= */

/* Returns total ink limit and channel maximums. */
/* Returns -1.0 if not applicable for this type of profile. */
/* Returns -1.0 for grey, additive, or any profiles < 4 channels. */
/* This is a place holder that uses a heuristic, */
/* until there is a private or standard tag for this information */
static double icc_get_tac(		/* return TAC */
icc *p,
double *chmax,					/* device return channel sums. May be NULL */
void (*calfunc)(void *cntx, double *out, double *in),	/* Optional calibration func. */
void *cntx
) {
	icmHeader *rh = p->header;
	icColorSpaceSignature colorSpace;
	icmLuSpace *luo;
	double tac = 0.0;
	double max[MAX_CHAN];			/* Channel maximums */

	/* If not something that can really have a TAC */
	if (rh->deviceClass != icSigDisplayClass
	 && rh->deviceClass != icSigOutputClass
	 && rh->deviceClass != icSigLinkClass) {
		return -1.0;
	}

	if (rh->deviceClass == icSigLinkClass)
		colorSpace = rh->pcs;
	else
		colorSpace = rh->colorSpace;

	/* If not a suitable color space */
	switch (colorSpace) {
		/* Not applicable */
    	case icSigXYZData:
    	case icSigLabData:
    	case icSigLuvData:
    	case icSigYCbCrData:
    	case icSigYxyData:
    	case icSigHsvData:
    	case icSigHlsData: {
			return -1.0;
		}

		/* Assume no limit */
    	case icSigGrayData:
    	case icSig2colorData:
    	case icSig3colorData:
    	case icSigRgbData: {
			return -1.0;
		}

		default:
			break;
	}

	if (rh->deviceClass == icSigLinkClass) {
		/* Get an input->output lookup */
		if ((luo = (icmLuSpace *)p->get_luobj(p, icmFwd, icmDefaultIntent, icmSigDefaultData, icmLuOrdNorm)) == NULL)
			return -1.0;
	} else {
		/* Get a PCS->device colorimetric lookup */
		if ((luo = (icmLuSpace *)p->get_luobj(p, icmBwd, icRelativeColorimetric, icmSigDefaultData, icmLuOrdNorm)) == NULL) {
			if ((luo = (icmLuSpace *)p->get_luobj(p, icmBwd, icmDefaultIntent, icmSigDefaultData, icmLuOrdNorm)) == NULL) {
				return -1.0;
			}
		}
	}


	tac = luo->get_tac(luo, chmax, calfunc, cntx);
	
	luo->del(luo);

	return tac;
	
}

/* ============================================= */

/* Return the white and black points from the ICC tags, */
/* and the corresponding absolute<->relative conversion matrices. */
/* Any argument may be NULL */
/* This method undoes any V4 style 'chrm' adapation so that the */
/* returned white & black represent the actual media color. */
/* The wpassumed flag returns 1 if the white point tag */
/* is missing because this type of profile doesn't have a wp tag. */
/* The bpassumed flag returns 1 if the black point tag */
/* is missing. */
/* Depends on: icSigMediaWhitePointTag, icSigMediaBlackPointTag, */
/* icc->chadmx, icc->wpchtmx which depends on 'arts' tag etc. */
/* return nz on error and sets error codes. */
static int icc_get_wb_points(
	icc *p,
	int *wpassumed, icmXYZNumber *wp,
	int *bpassumed, icmXYZNumber *bp,
	double toAbs[3][3],
	double fromAbs[3][3]
) {
	icmXYZArray *whitePointTag, *blackPointTag;
	int _wpassumed, _bpassumed;
	icmXYZNumber _wp, _bp;
	double _toAbs[3][3];
	double _fromAbs[3][3];

	/* Substitute local variables if arguments are NULL */
	if (wpassumed == NULL)
		wpassumed = &_wpassumed;
	if (wp == NULL)
		wp = &_wp;
	if (bpassumed == NULL)
		bpassumed = &_bpassumed;
	if (bp == NULL)
		bp = &_bp;
	if (toAbs == NULL)
		toAbs = _toAbs;
	if (fromAbs == NULL)
		fromAbs = _fromAbs;

	*wpassumed = *bpassumed = 0;

	if ((whitePointTag = (icmXYZArray *)p->read_tag(p, icSigMediaWhitePointTag)) == NULL
        || whitePointTag->ttype != icSigXYZType || whitePointTag->count < 1) {
		if (p->header->deviceClass != icSigLinkClass) { 
			return icm_err(p, 1,"icc_lookup: Profile is missing Media White Point Tag");
		}
		*wp = icmD50;					/* safe value */
		*wpassumed = 1;
	} else {
		*wp = whitePointTag->data[0];	/* Copy structure */
	}

	if ((blackPointTag = (icmXYZArray *)p->read_tag(p, icSigMediaBlackPointTag)) == NULL
        || blackPointTag->ttype != icSigXYZType || blackPointTag->count < 1) {
		*bp = icmBlack;					/* default */
		*bpassumed = 1;
	} else  {
		*bp = blackPointTag->data[0];	/* Copy structure */
	}

	/* If this is a Display profile, check if there is a 'chad' tag, then */
	/* setup the white point and toAbs/fromAbs matricies from that, so as to implement an */
	/* effective Absolute Colorimetric intent for such profiles. */
 	if (p->header->deviceClass == icSigDisplayClass
	 && p->naturalChad && p->chadmxValid) {
		double twp[3];
		double tbp[3];
		double ichad[3][3];

		/* Conversion matrix is chad matrix. */
		icmCpy3x3(fromAbs, p->chadmx);
		icmInverse3x3(toAbs, fromAbs);

		/* Compute absolute white point. We deliberately ignore what's in the white point tag */
		/* and assume D50, since dealing with a non-D50 white point tag is contrary to ICCV4 */
		/* and full of ambiguity (i.e. is it a separate "media" white different to the */
		/* display white and not D50, or has the profile creator mistakenly put the display */
		/* white in the white point tag ?) */
		icmMulBy3x3(twp, toAbs, icmD50_ary3); 
		icmAry2XYZ(*wp, twp);

		/* Track black point too */
		icmInverse3x3(ichad, p->chadmx);
		icmXYZ2Ary(tbp, *bp);
		icmMulBy3x3(tbp, ichad, tbp); 
		icmAry2XYZ(*bp, tbp);

		DBLLL(("toAbs and fromAbs created from 'chad' tag\n"));
		DBLLL(("computed wp %.8f %.8f %.8f\n", wp->X, wp->Y, wp->Z));

	/* If this is an Output profile, check if there is a 'chad' tag, and */
	/* setup the toAbs/fromAbs matricies so that they include it, so as to implement an */
	/* effective Absolute Colorimetric intent for such profiles. */
 	} else if (p->header->deviceClass == icSigOutputClass
	 && p->naturalChad && p->chadmxValid) {
		double twp[3];
		double tbp[3];
		double ichad[3][3];

		/* Convert the white point tag value backwards through the 'chad' */
		icmInverse3x3(ichad, p->chadmx);
		icmXYZ2Ary(twp, *wp);
		icmMulBy3x3(twp, ichad, twp); 
		icmAry2XYZ(*wp, twp);

		/* Track black point too */
		icmXYZ2Ary(tbp, *bp);
		icmMulBy3x3(tbp, ichad, tbp); 
		icmAry2XYZ(*bp, tbp);

		/* Create absolute <-> relative conversion matricies */
		p->chromAdaptMatrix(p, ICM_CAM_NONE, toAbs, fromAbs, icmD50, *wp);
		DBLLL(("toAbs and fromAbs created from 'chad' tag & WP tag\n"));
		DBLLL(("toAbs and fromAbs created from wp %f %f %f and D50 %f %f %f\n", wp->X, wp->Y, wp->Z,
		                                                           icmD50.X, icmD50.Y, icmD50.Z));
	} else {
		/* Create absolute <-> relative conversion matricies */
		p->chromAdaptMatrix(p, ICM_CAM_NONE, toAbs, fromAbs, icmD50, *wp);
		DBLLL(("toAbs and fromAbs created from wp %f %f %f and D50 %f %f %f\n", wp->X, wp->Y, wp->Z,
		                                                           icmD50.X, icmD50.Y, icmD50.Z));
	}

	DBLLL(("toAbs   = %f %f %f\n          %f %f %f\n          %f %f %f\n",
	        toAbs[0][0], toAbs[0][1], toAbs[0][2],
	        toAbs[1][0], toAbs[1][1], toAbs[1][2],
	        toAbs[2][0], toAbs[2][1], toAbs[2][2]));
	DBLLL(("fromAbs = %f %f %f\n          %f %f %f\n          %f %f %f\n",
	        fromAbs[0][0], fromAbs[0][1], fromAbs[0][2],
	        fromAbs[1][0], fromAbs[1][1], fromAbs[1][2],
	        fromAbs[2][0], fromAbs[2][1], fromAbs[2][2]));

	return ICM_ERR_OK;
}

/* ========================================================== */
/* Utility function declarations are in their own file */

#include "icc_util.c"


