/* dE00.c
 *
 * Modified:
 * 31/10/12
 * 	- from dE76.c
 */

/*

	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., 51 Franklin Street, Fifth Floor, Boston, MA
	02110-1301  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 <glib/gi18n-lib.h>

#include <math.h>

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

#include "pcolour.h"

typedef struct _VipsdE00 {
	VipsColourDifference parent_instance;

} VipsdE00;

typedef VipsColourDifferenceClass VipsdE00Class;

G_DEFINE_TYPE(VipsdE00, vips_dE00, VIPS_TYPE_COLOUR_DIFFERENCE);

/**
 * vips_col_dE00:
 * @L1: Input coordinate 1
 * @a1: Input coordinate 1
 * @b1: Input coordinate 1
 * @L2: Input coordinate 2
 * @a2: Input coordinate 2
 * @b2: Input coordinate 2
 *
 * 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
vips_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
		vips_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 = vips_col_ab2h(a1d, b1d);

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

	/* L' bar, C' bar, h' bar
	 */
	double Ldb = (L1d + L2d) / 2;
	double Cdb = (C1d + C2d) / 2;
	double hdb = VIPS_FABS(h1d - h2d) < 180
		? (h1d + h2d) / 2
		: VIPS_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(VIPS_RAD(2 * dtheta)) * RC;
	double T = 1 -
		0.17 * cos(VIPS_RAD(hdb - 30)) +
		0.24 * cos(VIPS_RAD(2 * hdb)) +
		0.32 * cos(VIPS_RAD(3 * hdb + 6)) -
		0.20 * cos(VIPS_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 = VIPS_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(VIPS_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;
}

/* Find the difference between two buffers of LAB data.
 */
static void
vips_dE00_line(VipsColour *colour,
	VipsPel *out, VipsPel **in, int width)
{
	float *p1 = (float *) in[0];
	float *p2 = (float *) in[1];
	float *q = (float *) out;

	int x;

	for (x = 0; x < width; x++) {
		q[x] = vips_col_dE00(p1[0], p1[1], p1[2],
			p2[0], p2[1], p2[2]);

		p1 += 3;
		p2 += 3;
	}
}

static void
vips_dE00_class_init(VipsdE00Class *class)
{
	VipsObjectClass *object_class = (VipsObjectClass *) class;
	VipsColourClass *colour_class = VIPS_COLOUR_CLASS(class);

	object_class->nickname = "dE00";
	object_class->description = _("calculate dE00");

	colour_class->process_line = vips_dE00_line;
}

static void
vips_dE00_init(VipsdE00 *dE00)
{
	VipsColourDifference *difference = VIPS_COLOUR_DIFFERENCE(dE00);

	difference->interpretation = VIPS_INTERPRETATION_LAB;
}

/**
 * vips_dE00:
 * @left: first input image
 * @right: second input image
 * @out: output image
 * @...: %NULL-terminated list of optional named arguments
 *
 * Calculate dE 00.
 *
 * Returns: 0 on success, -1 on error
 */
int
vips_dE00(VipsImage *left, VipsImage *right, VipsImage **out, ...)
{
	va_list ap;
	int result;

	va_start(ap, out);
	result = vips_call_split("dE00", ap, left, right, out);
	va_end(ap);

	return result;
}
