
/* 
 * International Color Consortium color transform expanded support
 *
 * Author:  Graeme W. Gill
 * Date:    1/5/01
 * Version: 1.20
 *
 * Copyright 2000-2004 Graeme W. Gill
 * All rights reserved.
 * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
 * see the License.txt file for licencing details.
 *
 */

/*
 * This is some test code to test the CIECAM97s3 functionality. 
 */


#include <stdio.h>
#include <math.h>
#include "xcam.h"
#include "cam97s3.h"

#define DIAG		/* Print internal value diagnostics for each conversion */
#define SPOTTEST	/* Test known spot colors */
#undef INVTEST		/* Lab cube to XYZ to Jab to XYZ */
#undef INVTEST2	/* Jab cube to XYZ to Jab */
#define TRES 33
#define USE_HK 0	/* Use Helmholtz-Kohlraush */
#define NOCAMCLIP 1	/* Don't clip before XYZ2Jab */

#ifndef _isnan
#define _isnan(x) ((x) != (x))
#define _finite(x) ((x) == (x))
#endif

static void
Lab2XYZ(double *out, double *in) {
	double L = in[0], a = in[1], b = in[2];
	double x,y,z,fx,fy,fz;

	if (L > 8.0) {
		fy = (L + 16.0)/116.0;
		y = pow(fy,3.0);
	} else {
		y = L/903.2963058;
		fy = 7.787036979 * y + 16.0/116.0;
	}

	fx = a/500.0 + fy;
	if (fx > 24.0/116.0)
		x = pow(fx,3.0);
	else
		x = (fx - 16.0/116.0)/7.787036979;

	fz = fy - b/200.0;
	if (fz > 24.0/116.0)
		z = pow(fz,3.0);
	else
		z = (fz - 16.0/116.0)/7.787036979;

	out[0] = x * 0.9642;
	out[1] = y * 1.0000;
	out[2] = z * 0.8249;
}

/* CIE XYZ to perceptual Lab */
static void
XYZ2Lab(double *out, double *in) {
	double X = in[0], Y = in[1], Z = in[2];
	double x,y,z,fx,fy,fz;

	x = X/0.9642;
	y = Y/1.0000;
	z = Z/0.8249;

	if (x > 0.008856451586)
		fx = pow(x,1.0/3.0);
	else
		fx = 7.787036979 * x + 16.0/116.0;

	if (y > 0.008856451586)
		fy = pow(y,1.0/3.0);
	else
		fy = 7.787036979 * y + 16.0/116.0;

	if (z > 0.008856451586)
		fz = pow(z,1.0/3.0);
	else
		fz = 7.787036979 * z + 16.0/116.0;

	out[0] = 116.0 * fy - 16.0;
	out[1] = 500.0 * (fx - fy);
	out[2] = 200.0 * (fy - fz);
}


/* Return maximum difference */
double maxdiff(double in1[3], double in2[3]) {
	int i;
	double tt, rv = 0.0;

	/* Deal with the possibility that we have nan's */
	for (i = 0; i < 3; i++) {
		tt = fabs(in1[i] - in2[i]);
		if (_isnan(tt))
			return tt;
		if (tt > rv)
			rv = tt;
	}
	return rv;
}

/* Return absolute difference */
double absdiff(double in1[3], double in2[3]) {
	double tt, rv = 0.0;
	tt = in1[0] - in2[0];
	rv += tt * tt;
	tt = in1[1] - in2[1];
	rv += tt * tt;
	tt = in1[2] - in2[2];
	rv += tt * tt;
	return sqrt(rv);
}

/* Return maximum Lab difference of XYZ */
double maxxyzdiff(double i1[3], double i2[3]) {
	int i;
	double tt, rv = 0.0;
	double in1[3], in2[3];

	XYZ2Lab(in1, i1);
	XYZ2Lab(in2, i2);

	/* Deal with the possibility that we have nan's */
	for (i = 0; i < 3; i++) {
		tt = fabs(in1[i] - in2[i]);
		if (_isnan(tt))
			return tt;
		if (tt > rv)
			rv = tt;
	}
	return rv;
}

int
main(void) {
	int ok = 1;
	double white[6][3] = {
		{ 0.9505, 1.000, 1.0888 },
		{ 0.9505, 1.000, 1.0888 },
		{ 1.0985, 1.000, 0.3558 },
		{ 1.0985, 1.000, 0.3558 },
		{ 0.9505, 1.000, 1.0888 },
		{ 0.9642, 1.000, 0.8249 }	/* D50 for inversion tests */
	};
	double La[6] = { 318.31, 31.83, 318.31, 31.83, 318.31, 150.0 };
	double sample[5][3] = {
		{ 0.1901, 0.2000, 0.2178 },
		{ 0.5706, 0.4306, 0.3196 },
		{ 0.0353, 0.0656, 0.0214 },
		{ 0.1901, 0.2000, 0.2178 },
		{ 0.9505, 1.000, 1.0888 }		/* Check white */
	};

#ifdef CIECAM97S3_HK
#ifdef CIECAM97S3_SPLINE_E
	double correct[5][4] = {			/* Hue range is last two */
		{ 41.14,     0.06,     225.3, 251.9 },
		{ 69.04,     71.04,    19.4,  19.4 },
		{ 35.09,     87.15,   175.3, 175.3 },
		{ 55.64,     82.43,   252.5, 252.5 },
		{ 100.00000, 0.0,     0.0,   360.0 }
	};
#else
	double correct[5][4] = {			/* Hue range is last two */
		{ 41.14,     0.06,     225.3, 251.9 },
		{ 69.056048, 71.20763, 19.4,  19.4 },
		{ 35.365009, 88.65238, 175.3, 175.3 },
		{ 55.266184, 80.55128, 252.5, 252.5 },
		{ 100.00000, 0.0,      0.0,   360.0 }
	};
#endif
#else
	double correct[5][4] = {			/* Hue range is last two */
		{ 41.13,  0.05,   225.3, 251.9 },
		{ 64.14,  71.22,  19.4,  19.4 },
		{ 19.18,  88.64,  175.3, 175.3 },
		{ 39.11,  80.55,  252.5, 252.5 },
		{ 100.0,  0.00,   0.0,   360.0 }		/* Check white */
	};
#endif

	double Jab[3];
	double res[3];
	cam97s3 *cam;
	int c;

	cam = new_cam97s3();

#ifdef SPOTTEST
	for (c = 0; c < 5; c++) {

#ifdef DIAG
		printf("Case %d:\n",c);
#endif /* DIAG */
		cam->set_view(
			cam,
			vc_average,	/* Enumerated Viewing Condition */
			white[c],	/* Reference/Adapted White XYZ (Y range 0.0 .. 1.0) */
			La[c],		/* Adapting/Surround Luminance cd/m^2 */
			0.20,		/* Relative Luminance of Background to reference white */
			0.0,		/* Luminance of white in image - not used */
			0.00,		/* Flare as a fraction of the reference white (Y range 0.0 .. 1.0) */
			white[c],	/* The Flare color coordinates (typically the Ambient color) */
			USE_HK,
			NOCAMCLIP
		);
#ifdef DIAG
		printf("\n");
#endif /* DIAG */
	
		{
			double JCh[3], target[3];
			cam->XYZ_to_cam(cam, Jab, sample[c]);
	
			/* Convert to JCh for checking */
			JCh[0] = Jab[0];

			/* Compute hue angle */
    		JCh[2] = (180.0/3.14159265359) * atan2(Jab[2], Jab[1]);
			JCh[2] = (JCh[2] < 0.0) ? JCh[2] + 360.0 : JCh[2];
	
			/* Compute chroma value */
			JCh[1] = sqrt(Jab[1] * Jab[1] + Jab[2] * Jab[2]);

			target[0] = correct[c][0];
			target[1] = correct[c][1];
			if (JCh[2] >= correct[c][2] && JCh[2] <= correct[c][3]) {
				target[2] = JCh[2];
			} else {
				if (fabs(JCh[2] - correct[c][2]) < fabs(JCh[2] - correct[c][3]))
					target[2] = correct[c][2];
				else
					target[2] = correct[c][3];
			}

			cam->cam_to_XYZ(cam, res, Jab);

			if (maxdiff(JCh, target) > 0.05) {
				printf("Spottest: Excesive error in conversion to CAM %f\n",maxdiff(JCh, target));
				ok = 0;
			}
			if (maxdiff(sample[c], res) > 1e-5) {
				printf("Spottest: Excessive error in inversion %f\n",maxdiff(sample[c], res));
				ok = 0;
			}
#ifdef DIAG
			printf("Jab is %f %f %f, Jch is %f %f %f\n",
			        Jab[0], Jab[1], Jab[2],
			        JCh[0], JCh[1], JCh[2]);

			printf("Error to expected value = %f\n",maxdiff(JCh, target));
		
			printf("XYZ is %f %f %f\n",res[0], res[1], res[2]);
			printf("Inversion error = %f\n",maxdiff(sample[c], res));
#endif /* DIAG */
		}
	}
#endif /* SPOTTEST */

#ifdef INVTEST
	for (c = 5; c < 6; c++) {
		/* Get the range of Jab values */
		double min[3] = { 1e6, 1e6, 1e6 };
		double max[3] = { -1e6, -1e6, -1e6 };

		cam->set_view(
			cam,
			vc_average,	/* Enumerated Viewing Condition */
			white[c],	/* Reference/Adapted White XYZ (Y range 0.0 .. 1.0) = D50 */
			34.0,		/* Adapting/Surround Luminance cd/m^2 */
			0.20,		/* Relative Luminance of Background to reference white */
			0.0,		/* Luminance of white in image - not used */
			0.01,		/* Flare as a fraction of the reference white (Y range 0.0 .. 1.0) */
			white[c],	/* The Flare color coordinates (typically the Ambient color) */
			USE_HK,
			NOCAMCLIP
		);
	
		{
			int co0, co1, co2;		/* (using co[3] triggers compiler bug) */
			double merr = 0.0;
			double xyz[3], Lab[3], Jab[3], checkxyz[3];


#ifdef NEVER
			/* Test case */
			Lab[0] = 0.0;
			Lab[1] = -180.0;
			Lab[2] = -180.0;
			Lab2XYZ(xyz, Lab);
			cam->XYZ_to_cam(cam, Jab, xyz);
			cam->cam_to_XYZ(cam, checkxyz, Jab);

#else /* !NEVER */

			/* itterate through Lab space */
			for (co0 = 0; co0 < TRES; co0++) {
				Lab[0] = co0/(TRES-1.0);
				Lab[0] = Lab[0] * 100.0;
				for (co1 = 0; co1 < TRES; co1++) {
					Lab[1] = co1/(TRES-1.0);
					Lab[1] = (Lab[1] - 0.5) * 256.0;
					for (co2 = 0; co2 < TRES; co2++) {
						double mxd;
						int i;
						Lab[2] = co2/(TRES-1.0);
						Lab[2] = (Lab[2] - 0.5) * 256.0;
		
						Lab2XYZ(xyz, Lab);
						cam->XYZ_to_cam(cam, Jab, xyz);
						for (i = 0; i < 3; i++) {
							if (Jab[i] < min[i])
								min[i] = Jab[i];
							if (Jab[i] > max[i])
								max[i] = Jab[i];
						}
						cam->cam_to_XYZ(cam, checkxyz, Jab);

						/* Check the result */
						mxd = maxxyzdiff(checkxyz, xyz);
						if (_finite(merr) && (_isnan(mxd) || mxd > merr))
							merr = mxd;
#ifdef DIAG
						if (_isnan(mxd) || mxd > 0.1) {
							printf("\n");
							printf("Lab = %f %f %f\n",Lab[0], Lab[1], Lab[2]);
							printf("%f %f %f -> %f %f %f -> %f %f %f [%f]\n",
							xyz[0],xyz[1],xyz[2],Jab[0],Jab[1],Jab[2],
							checkxyz[0],checkxyz[1],checkxyz[2],
							maxxyzdiff(checkxyz, xyz));
						}
#endif /* DIAG */
					}
				}
			}
			if (_isnan(merr) || merr > 0.1) {
				printf("Excessive error in inversion check %f\n",merr);
				ok = 0;
			}
#ifdef DIAG
			printf("Inversion check complete, peak error = %f\n",merr);
#endif /* !NEVER */

#endif /* DIAG */
			printf("\n");
			printf("Range of Jab values was:\n");
			printf("J:  %f -> %f\n", min[0], max[0]);
			printf("a:  %f -> %f\n", min[1], max[1]);
			printf("b:  %f -> %f\n", min[2], max[2]);
		}
	}
#endif /* INVTEST */

#ifdef INVTEST2
	for (c = 5; c < 6; c++) {
		cam->set_view(
			cam,
			vc_average,	/* Enumerated Viewing Condition */
			white[c],	/* Reference/Adapted White XYZ (Y range 0.0 .. 1.0) = D50 */
			34.0,		/* Adapting/Surround Luminance cd/m^2 */
			0.20,		/* Relative Luminance of Background to reference white */
			0.0,		/* Luminance of white in image - not used */
			0.01,		/* Flare as a fraction of the reference white (Y range 0.0 .. 1.0) */
			white[c],	/* The Flare color coordinates (typically the Ambient color) */
			USE_HK,
			NOCAMCLIP
		);
	
		{
			int co0, co1, co2;		/* (using co[3] triggers compiler bug) */
			double merr = 0.0;
			double xyz[3], Jab[3], checkJab[3];

#ifdef NEVER
			/* Test case */
#ifdef NEVER
			Jab[0] = 0.0;
			Jab[1] = -180.0;
			Jab[2] = -180.0;
			cam->cam_to_XYZ(cam, xyz, Jab);
			cam->XYZ_to_cam(cam, checkJab, xyz);
#else
			xyz[0] = 555.365115;
			xyz[1] = -518.091908;
			xyz[2] = -1591.731701;
			cam->XYZ_to_cam(cam, Jab, xyz);
			cam->cam_to_XYZ(cam, xyz, Jab);
#endif

#else /* !NEVER */

			/* itterate through Jab space */
			for (co0 = 0; co0 < TRES; co0++) {
				Jab[0] = co0/(TRES-1.0);
				Jab[0] = 0.0 + Jab[0] * 100.0;	
				for (co1 = 0; co1 < TRES; co1++) {
					Jab[1] = co1/(TRES-1.0);
					Jab[1] = (Jab[1] - 0.5) * 256.0;
					for (co2 = 0; co2 < TRES; co2++) {
						double mxd;
						Jab[2] = co2/(TRES-1.0);
						Jab[2] = (Jab[2] - 0.5) * 256.0;
		
						cam->cam_to_XYZ(cam, xyz, Jab);
						cam->XYZ_to_cam(cam, checkJab, xyz);

						/* Check the result */
						mxd = maxdiff(checkJab, Jab);
						if (_finite(merr) && (_isnan(mxd) || mxd > merr))
							merr = mxd;
#ifdef DIAG
						if (_isnan(mxd) || mxd > 0.1) {
							printf("\n");
							printf("Jab = %f %f %f\n",Jab[0], Jab[1], Jab[2]);
							printf("%f %f %f -> %f %f %f -> %f %f %f [%f]\n",
							Jab[0],Jab[1],Jab[2], xyz[0],xyz[1],xyz[2],
							checkJab[0],checkJab[1],checkJab[2],
							maxdiff(checkJab, Jab));
						}
#endif /* DIAG */
					}
				}
			}
			if (_isnan(merr) || merr > 1.0) {
				printf("Excessive error in inversion check 2 %f\n",merr);
				ok = 0;
			}
#ifdef DIAG
			printf("Inversion check 2 complete, peak error = %f\n",merr);
#endif /* DIAG */
#endif /* !NEVER */
		}
	}
#endif /* INVTEST2 */

	printf("\n");
	if (ok == 0) {
		printf("Cam testing FAILED\n");
	} else {
		printf("Cam testing OK\n");
	}

	return 0;
}

