/* Convert colours in various ways.
 *
 * Written: January 1990
 * Modified .. innumerable times
 * Code by: DS, JC, J-Ph.L.
 * 18/7/93 JC
 *	- final tidies before v7 release
 *	- ANSIfied
 *	- code for samples removed
 * 5/5/94 JC
 *	- nint() -> rint() to make ANSI easier
 * 14/3/96 JC
 *	- new display characterisation
 *	- speed-up to im_col_XYZ2rgb() and im_col_rgb2XYZ()
 * 4/3/98 JC
 *	- new display profile for ultra2
 *	- new sRGB profile
 * 17/8/98 JC
 *	- error_exit() removed, now clips
 * 26/11/03 Andrey Kiselev
 * 	- tiny clean-up for calcul_tables()
 * 	- some reformatting
 * 23/7/07
 * 	- tiny cleanup for make_hI() prevents cond jump on ui in valgrind
 * 14/3/08
 * 	- more tiny cond jump valgrind fixes
 * 23/10/09
 * 	- gtkdoc comments
 */

/*

    This file is part of VIPS.
    
    VIPS is free software; you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

 */

/*

    These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk

 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /*HAVE_CONFIG_H*/
#include <vips/intl.h>

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <math.h>

#include <vips/vips.h>
#include <vips/internal.h>

#ifdef WITH_DMALLOC
#include <dmalloc.h>
#endif /*WITH_DMALLOC*/

/* Have the tables been made?
 */
static int made_ucs_tables = 0;

/* Arrays for lookup tables.
 */
static float LI[ 1001 ];
static float CI[ 3001 ];
static float hI[ 101 ][ 361 ];

/**
 * im_col_ab2Ch:
 * @a: CIE a* value
 * @b: CIE b* value
 * @C: return Chroma
 * @h: return Hue angle (degrees)
 *
 * Calculate Ch from ab, h in degrees.
 */
void
im_col_ab2Ch( float a, float b, float *C, float *h )
{	
	float in[3], out[3];

	in[1] = a;
	in[2] = b;
	imb_Lab2LCh( in, out, 1 );
	*C = out[1];
	*h = out[2];
}

/**
 * im_col_Ch2ab:
 * @C: Chroma
 * @h: Hue angle (degrees)
 * @a: return CIE a* value
 * @b: return CIE b* value
 *
 * Calculate ab from Ch, h in degrees.
 */
void
im_col_Ch2ab( float C, float h, float *a, float *b )
{
	float in[3], out[3];

	in[1] = C;
	in[2] = h;
	imb_LCh2Lab( in, out, 1 );
	*a = out[1];
	*b = out[2];
}

/**
 * im_col_XYZ2Lab:
 * @X: Input CIE XYZ colour
 * @Y: 
 * @Z: 
 * @L: return CIE Lab value
 * @a: 
 * @b: 
 *
 * Calculate Lab from XYZ, D65.
 * 
 * See also: im_XYZ2Lab_temp().
 */
void
im_col_XYZ2Lab( float X, float Y, float Z, float *L, float *a, float *b )
{	
	float in[3], out[3];
	im_colour_temperature temp;

	in[0] = X;
	in[1] = Y;
	in[2] = Z;
	temp.X0 = IM_D65_X0;
	temp.Y0 = IM_D65_Y0;
	temp.Z0 = IM_D65_Z0;
	imb_XYZ2Lab( in, out, 1, &temp );
	*L = out[0];
	*a = out[1];
	*b = out[2];
}

/**
 * im_col_Lab2XYZ:
 * @L: Input CIE Lab value
 * @a: 
 * @b: 
 * @X: Return CIE XYZ colour
 * @Y: 
 * @Z: 
 *
 * Calculate XYZ from Lab, D65.
 * 
 * See also: im_Lab2XYZ_temp().
 */
void
im_col_Lab2XYZ( float L, float a, float b, float *X, float *Y, float *Z )
{	
	float in[3], out[3];
	im_colour_temperature temp;

	in[0] = L;
	in[1] = a;
	in[2] = b;
	temp.X0 = IM_D65_X0;
	temp.Y0 = IM_D65_Y0;
	temp.Z0 = IM_D65_Z0;
	imb_Lab2XYZ( in, out, 1, &temp );
	*X = out[0];
	*Y = out[1];
	*Z = out[2];
}

/**
 * im_col_pythagoras:
 * @L1: Input coordinate 1
 * @a1: 
 * @b1: 
 * @L2: Input coordinate 2
 * @a2: 
 * @b2: 
 *
 * Pythagorean distance between two points in colour space. Lab/XYZ/UCS etc.
 */
float
im_col_pythagoras( float L1, float a1, float b1, float L2, float a2, float b2 )
{
	float dL = L1 - L2;
	float da = a1 - a2;
	float db = b1 - b2;

	return( sqrt( dL*dL + da*da + db*db ) );
}

/* Functions to convert from Lab to uniform colour space and back.  
 */

/* Constants for Lucs.
 */
#define c1 21.75
#define c2 0.3838
#define c3 38.54

/**
 * im_col_L2Lucs:
 * @L: CIE L*
 *
 * Calculate Lucs from L.
 *
 * Returns: Lucs
 */
float
im_col_L2Lucs( float L )
{	
	float Lucs;

	if( L >= 16.0 )
		Lucs = (c1 * log( L ) + c2 * L - c3);
	else
		Lucs = 1.744 * L;

	return( Lucs );
}

/* Generate Ll and LI (inverse) tables. Don't call the above for speed.
 */
static void
make_LI( void )
{	
	int i, j=0;
	float L, Ll[ 1001 ];

	for( i = 0; i < 1001; i++ )
	{
		L = i / 10.0;
		if( L >= 16.0 )
			Ll[ i ] = (c1 * log( L ) + c2 * L - c3);
		else
			Ll[ i ] = 1.744 * L;
	}

	for( i = 0; i < 1001; i++ )
	{
		while ( (Ll[j]<=i/10.0) && ( j<1001) ) j++;
		LI[i] = (j-1)/10.0 + (i/10.0-Ll[j-1]) / ((Ll[j]-Ll[j-1])*10.0);
	}
}

/**
 * im_col_Lucs2L:
 * @L: L ucs
 *
 * Calculate L from Lucs using a table. Call im_col_make_tables_UCS() at
 * least once before using this function.
 *
 * Returns: L*
 */
float
im_col_Lucs2L( float Lucs )
{	
	int known;	/* nearest input value in the table, <= Lucs */

	known = floor(Lucs*10.0);
	if( known < 0 )
		known = 0;
	if( known > 1000 )
		known = 1000;

	return( LI[known] + (LI[known+1]-LI[known])*(Lucs*10.0-known) );
}

/* Constants for Cucs.
 */
#define c4 0.162
#define c5 10.92
#define c6 0.638
#define c7 0.07216
#define c8 4.907

/**
 * im_col_C2Cucs:
 * @C: Chroma
 *
 * Calculate Cucs from C.
 *
 * Returns: Cucs.
 */
float
im_col_C2Cucs( float C )
{	
	float Cucs;

	Cucs = (c4 * C + c5 * (log( c6 + c7 * C )) + c8);
	if ( Cucs<0 ) Cucs = 0;

	return( Cucs );
}

/* Generate Cucs table. Again, inline the code above.
 */
static void
make_CI( void )
{	
	int i;
	float C;
	float Cl[3001];

	for( i = 0; i < 3001; i++ ) {
		C = i / 10.0;
		Cl[i] = (c4 * C + c5 * (log( c6 + c7 * C )) + c8);
	}

	for( i = 0; i < 3001; i++ ) {
		int j;

		for( j = 0; j < 3001 && Cl[j] <= i / 10.0; j++ )
			;
		CI[i] = (j - 1) / 10.0 + 
			(i / 10.0 - Cl[j - 1]) / ((Cl[j] - Cl[j - 1]) * 10.0);
	}

}

/**
 * im_col_Cucs2C:
 * @Cucs: Cucs
 *
 * Calculate C from Cucs using a table. 
 * Call im_col_make_tables_UCS() at
 * least once before using this function.
 *
 * Returns: C.
 */
float
im_col_Cucs2C( float Cucs )
{	
	int known;	/* nearest input value in the table, <= Cucs */

	known = floor(Cucs*10.0);
	if( known < 0 )
		known = 0;
	if( known > 3000 )
		known = 3000;

	return( CI[known] + (CI[known+1]-CI[known])*(Cucs*10.0-known) );
}

/**
 * im_col_Ch2hucs:
 * @C: Chroma
 * @h: Hue (degrees)
 *
 * Calculate hucs from C and h.
 *
 * Returns: hucs.
 */
float
im_col_Ch2hucs( float C, float h )
{	
	float P, D, f, g;
	float k4, k5, k6, k7, k8;
	float hucs;

	if( h < 49.1 ) {
		k4 = 133.87;
		k5 = -134.5;
		k6 = -.924;
		k7 = 1.727;
		k8 = 340.0;
	}
	else if( h < 110.1 ) {
		k4 = 11.78;
		k5 = -12.7;
		k6 = -.218;
		k7 = 2.12;
		k8 = 333.0;
	}
	else if( h < 269.6 ) {
		k4 = 13.87;
		k5 = 10.93;
		k6 = 0.14;
		k7 = 1.0;
		k8 = -83.0;
	}
	else {
		k4 = .14;
		k5 = 5.23;
		k6 = .17;
		k7 = 1.61;
		k8 = 233.0;
	}

	P = cos( IM_RAD( k8 + k7 * h ) );
	D = k4 + k5 * P * pow( fabs( P ), k6 );
	g = C * C * C * C;
	f = sqrt( g / (g + 1900.0) );
	hucs = h + D * f;

	return( hucs );
}

/* The difficult one: hucs. Again, inline.
 */
static void
make_hI( void )
{	
	int i, j, k;
	float P, D, C, f, hl[101][361];
	float k4, k5, k6, k7, k8;

	for( i = 0; i < 361; i++ ) {
		if( i < 49.1 ) {
			k4 = 133.87;
			k5 = -134.5;
			k6 = -.924;
			k7 = 1.727;
			k8 = 340.0;
		}
		else if( i < 110.1 ) {
			k4 = 11.78;
			k5 = -12.7;
			k6 = -.218;
			k7 = 2.12;
			k8 = 333.0;
		}
		else if( i < 269.6 ) {
			k4 = 13.87;
			k5 = 10.93;
			k6 = 0.14;
			k7 = 1.0;
			k8 = -83.0;
		}
		else {
			k4 = .14;
			k5 = 5.23;
			k6 = .17;
			k7 = 1.61;
			k8 = 233.0;
		}

		P = cos( IM_RAD( k8 + k7 * i ) );
		D = k4 + k5 * P * pow( fabs( P ), k6 );

		for( j = 0; j < 101; j++ ) {
			float g;

			C = j * 2.0;
			g = C * C * C * C;
			f = sqrt( g / (g + 1900.0) );

			hl[j][i] = i + D * f;
		}

	}

	for( j = 0; j < 101; j++ ) {
		k = 0;
		for( i = 0; i < 361; i++ ) {
			while( k < 361 && hl[j][k] <= i ) 
				k++;
			hI[j][i] = k - 1 + (i - hl[j][k - 1]) / 
				(hl[j][k] - hl[j][k - 1]);
		}
	}
}

/**
 * im_col_Chucs2h:
 * @C: Chroma
 * @hucs: Hue ucs (degrees)
 *
 * Calculate h from C and hucs, using a table.
 * Call im_col_make_tables_UCS() at
 * least once before using this function.
 *
 * Returns: h.
 */
float
im_col_Chucs2h( float C, float hucs )
{	
	int r, known;	/* nearest input value in the table, <= hucs */

	/* Which row of the table?
	 */
	r = (int) ((C + 1.0) / 2.0);
	if( r < 0 )
		r = 0;
	if( r > 100 )
		r = 100;

	known = floor( hucs );
	if( known < 0 )
		known = 0;
	if( known > 360 )
		known = 360;

	return( hI[r][known] + 
		(hI[r][(known + 1) % 360] - hI[r][known]) * (hucs - known) );
}

/**
 * im_col_make_tables_UCS:
 * 
 * Make the lookup tables for ucs.
 */
void
im_col_make_tables_UCS( void )
{	
	if( !made_ucs_tables ) {
		make_LI();
		make_CI();
		make_hI();
		made_ucs_tables = -1;
	}
}

/**
 * im_col_dECMC:
 * @L1: Input coordinate 1
 * @a1: 
 * @b1: 
 * @L2: Input coordinate 2
 * @a2: 
 * @b2: 
 * 
 * CMC colour difference from a pair of Lab values.
 *
 * Returns: CMC(1:1) colour difference
 */
float 
im_col_dECMC( float L1, float a1, float b1, 
	float L2, float a2, float b2 )
{
	float h1, C1;
	float h2, C2;
	float Lucs1, Cucs1, hucs1;
	float Lucs2, Cucs2, hucs2;
	float aucs1, bucs1;
	float aucs2, bucs2;

	/* Turn to LCh.
	 */
	im_col_ab2Ch( a1, b1, &C1, &h1 );
	im_col_ab2Ch( a2, b2, &C2, &h2 );

	/* Turn to LCh in CMC space.
	 */
	Lucs1 = im_col_L2Lucs( L1 );
	Cucs1 = im_col_C2Cucs( C1 );
	hucs1 = im_col_Ch2hucs( C1, h1 );

	Lucs2 = im_col_L2Lucs( L2 );
	Cucs2 = im_col_C2Cucs( C2 );
	hucs2 = im_col_Ch2hucs( C2, h2 );

	/* Turn to Lab in CMC space.
	 */
	im_col_Ch2ab( Cucs1, hucs1, &aucs1, &bucs1 );
	im_col_Ch2ab( Cucs2, hucs2, &aucs2, &bucs2 );

	/* Find difference.
	 */
	return( im_col_pythagoras( Lucs1, aucs1, bucs1, Lucs2, aucs2, bucs2 ) );
}

/**
 * im_col_ab2h:
 * @a: CIE a
 * @b: CIE b
 *
 * Returns: Hue (degrees) 
 */
double
im_col_ab2h( double a, double b )
{
	double h;

	/* We have to be careful we have the right quadrant!
	 */
	if( a == 0 ) {
		if( b < 0.0 )
			h = 270;
		else if( b == 0.0 )
			h = 0;
		else
			h = 90;
	}
	else {
		double t = atan( b / a );

		if( a > 0.0 )
			if( b < 0.0 )
				h = IM_DEG( t + IM_PI * 2.0 );
			else
				h = IM_DEG( t );
		else
			h = IM_DEG( t + IM_PI );
	}

	return( h );
}

/**
 * im_col_dE00:
 * @L1: Input coordinate 1
 * @a1: 
 * @b1: 
 * @L2: Input coordinate 2
 * @a2: 
 * @b2: 
 *
 * CIEDE2000, from: 
 * 
 * Luo, Cui, Rigg, "The Development of the CIE 2000 Colour-Difference 
 * Formula: CIEDE2000", COLOR research and application, pp 340
 *
 * Returns: CIE dE2000 colour difference.
 */
float 
im_col_dE00( float L1, float a1, float b1, 
	float L2, float a2, float b2 )
{
/* Code if you want XYZ params and the colour temp used in the reference

	float 
	im_col_dE00( float X1, float Y1, float Z1, 
		float X2, float Y2, float Z2 )
	{
		const double X0 = 94.811;
		const double Y0 = 100.0;
		const double Z0 = 107.304;

#define f(I) ((I) > 0.008856 ? \
	cbrt( (I), 1.0 / 3.0 ) : 7.7871 * (I) + (16.0 / 116.0))

		double nX1 = f( X1 / X0 );
		double nY1 = f( Y1 / Y0 );
		double nZ1 = f( Z1 / Z0 );

		double L1 = 116 * nY1 - 16;
		double a1 = 500 * (nX1 - nY1);
		double b1 = 200 * (nY1 - nZ1);

		double nX2 = f( X2 / X0 );
		double nY2 = f( Y2 / Y0 );
		double nZ2 = f( Z2 / Z0 );

		double L2 = 116 * nY2 - 16;
		double a2 = 500 * (nX2 - nY2);
		double b2 = 200 * (nY2 - nZ2);
 */

	/* Chroma and mean chroma (C bar)
	 */
	double C1 = sqrt( a1 * a1 + b1 * b1 );
	double C2 = sqrt( a2 * a2 + b2 * b2 );
	double Cb = (C1 + C2) / 2;

	/* G
	 */
	double Cb7 = Cb * Cb * Cb * Cb * Cb * Cb * Cb;
	double G = 0.5 * (1 - sqrt( Cb7 / (Cb7 + pow( 25, 7 )) ));

	/* L', a', b', C', h'
	 */
	double L1d = L1;
	double a1d = (1 + G) * a1;
	double b1d = b1;
	double C1d = sqrt( a1d * a1d + b1d * b1d );
	double h1d = im_col_ab2h( a1d, b1d );

	double L2d = L2;
	double a2d = (1 + G) * a2;
	double b2d = b2;
	double C2d = sqrt( a2d * a2d + b2d * b2d );
	double h2d = im_col_ab2h( a2d, b2d );

	/* L' bar, C' bar, h' bar
	 */
	double Ldb = (L1d + L2d) / 2;
	double Cdb = (C1d + C2d) / 2;
	double hdb = fabs( h1d - h2d ) < 180 ?
	 	(h1d + h2d) / 2 :
	 	fabs( h1d + h2d - 360 ) / 2;

	/* dtheta, RC
	 */
	double hdbd = (hdb - 275) / 25;
	double dtheta = 30 * exp( -(hdbd * hdbd) );
	double Cdb7 = Cdb * Cdb * Cdb * Cdb * Cdb * Cdb * Cdb;
	double RC = 2 * sqrt( Cdb7 / (Cdb7 + pow( 25, 7 )) );

	/* RT, T.
	 */
	double RT = -sin( IM_RAD( 2 * dtheta ) ) * RC;
	double T = 1 - 
		0.17 * cos( IM_RAD( hdb - 30 ) ) +
		0.24 * cos( IM_RAD( 2 * hdb ) ) +
		0.32 * cos( IM_RAD( 3 * hdb + 6 ) ) -
		0.20 * cos( IM_RAD( 4 * hdb - 63 ) );

	/* SL, SC, SH
	 */
	double Ldb50 = Ldb - 50;
	double SL = 1 + (0.015 * Ldb50 * Ldb50) / sqrt( 20 + Ldb50 * Ldb50);
	double SC = 1 + 0.045 * Cdb;
	double SH = 1 + 0.015 * Cdb * T;

	/* hue difference ... careful!
	 */
	double dhd = fabs( h1d - h2d ) < 180 ?
		h1d - h2d :
		360 - (h1d - h2d);

	/* dLd, dCd dHd
	 */
	double dLd = L1d - L2d;
	double dCd = C1d - C2d;
	double dHd = 2 * sqrt( C1d * C2d ) * sin( IM_RAD( dhd / 2 ) );

	/* Parametric factors for viewing parameters.
	 */
	const double kL = 1.0;
	const double kC = 1.0;
	const double kH = 1.0;

	/* Normalised terms.
	 */
	double nL = dLd / (kL * SL);
	double nC = dCd / (kC * SC);
	double nH = dHd / (kH * SH);

	/* dE00!!
	 */
	double dE00 = sqrt( nL * nL + nC * nC + nH * nH + RT * nC * nH );

	/*
	printf( "X1 = %g, Y1 = %g, Z1 = %g\n", X1, Y1, Z1 );
	printf( "X2 = %g, Y2 = %g, Z2 = %g\n", X2, Y2, Z2 );
	printf( "L1 = %g, a1 = %g, b1 = %g\n", L1, a1, b1 );
	printf( "L2 = %g, a2 = %g, b2 = %g\n", L2, a2, b2 );
	printf( "L1d = %g, a1d = %g, b1d = %g, C1d = %g, h1d = %g\n",
		L1d, a1d, b1d, C1d, h1d );
	printf( "L2d = %g, a2d = %g, b2d = %g, C2d = %g, h2d = %g\n",
		L2d, a2d, b2d, C2d, h2d );
	printf( "G = %g, T = %g, SL = %g, SC = %g, SH = %g, RT = %g\n",
		G, T, SL, SC, SH, RT );
	printf( "dE00 = %g\n", dE00 );
	 */

	return( dE00 );
}

