package net.returnvoid.color;

import java.util.ArrayList;

/*
 * This code is copyright (c) Diana Lange 2017
 *
 * The library is published under the Creative Commons license NonCommercial 4.0.
 * Please check https://creativecommons.org/licenses/by-nc/4.0/ for more information.
 * 
 * This program is distributed in the hope that it will be useful, but without any warranty.
 */

/**
 * A collection of functions to calculate the difference / distance or the
 * similarity between colors.
 * 
 * @author Diana Lange
 *
 */
public class ColorDifference {

	/**
	 * Weighting of the luminance for Lab / LCH difference measures
	 */
	static public float weightingLuminance = 1;

	/**
	 * Weighting of the chroma for Lab / LCH difference measures
	 */
	static public float weightingChroma = 1;

	/**
	 * Weighting of the hue for Lab / LCH difference measures
	 */
	static public float weightingHue = 1;

	private ColorDifference() {
	}

	/*
	 * ------------------------------------------------------------------------
	 * | Similarity |
	 * ------------------------------------------------------------------------
	 */

	/**
	 * Calculates the most similar color of all targets to the base color. Uses
	 * measure to determinate which similarity measure to use. If base is a
	 * member of targets, base will be the most similar color.
	 * 
	 * @param base
	 *            One color (not null).
	 * @param targets
	 *            Colors from which to chose the similar color from (not null,
	 *            but the values might be null).
	 * @param measure
	 *            A measure for the similarity (not null).
	 * @return The index of the most similar color or -1 if no color found.
	 */
	static public Integer getMostSimilarColor(RColor base, RColor[] targets, ColorDifferenceMeasure measure) {

		return ColorDifference.getMostSimilarColor(base, targets, null, false, measure);
	}

	/**
	 * Calculates the most similar color of all targets to the base color. Uses
	 * measure to determinate which similarity measure to use. When ignoreSelf
	 * is false and base is a member of targets, base will be the most similar
	 * color. Otherwise base will be ignored.
	 * 
	 * @param base
	 *            - One color (not null).
	 * @param targets
	 *            Colors from which to chose the similar color from (not null,
	 *            but the values might be null).
	 * @param ignoreSelf
	 *            When true, base will be ignored when it is member of targets.
	 * @param measure
	 *            A measure for the similarity (not null).
	 * @return The index of the most similar color or -1 if no color found.
	 */
	static public Integer getMostSimilarColor(RColor base, RColor[] targets, boolean ignoreSelf,
			ColorDifferenceMeasure measure) {
		return ColorDifference.getMostSimilarColor(base, targets, null, ignoreSelf, measure);
	}

	/**
	 * Calculates the most similar color of all targets to the base color. Uses
	 * measure to determinate which similarity measure to use. If base is a
	 * member of targets, base will be the most similar color.
	 * 
	 * @param base
	 *            One color (not null).
	 * @param targets
	 *            Colors from which to chose the similar color from (not null,
	 *            but the values might be null).
	 * @param measure
	 *            A measure for the similarity (not null).
	 * @return The index of the most similar color or -1 if no color found.
	 */
	static public Integer getMostSimilarColor(RColor base, ArrayList<RColor> targets,
			ColorDifferenceMeasure measure) {
		return ColorDifference.getMostSimilarColor(base, null, targets, false, measure);
	}

	/**
	 * Calculates the most similar color of all targets to the base color. Uses
	 * measure to determinate which similarity measure to use. When ignoreSelf
	 * is false and base is a member of targets, base will be the most similar
	 * color. Otherwise base will be ignored for the similarity.
	 * 
	 * @param base
	 *            One color (not null).
	 * @param targets
	 *            Colors from which to chose the similar color from (not null,
	 *            but the values might be null).
	 * @param ignoreSelf
	 *            When true, base will be ignored when it is member of targets.
	 * @param measure
	 *            A measure for the similarity (not null).
	 * @return The index of the most similar color or -1 if no color found.
	 */
	static public Integer getMostSimilarColor(RColor base, ArrayList<RColor> targets, boolean ignoreSelf,
			ColorDifferenceMeasure measure) {
		return ColorDifference.getMostSimilarColor(base, null, targets, ignoreSelf, measure);
	}

	/**
	 * Calculates the most similar color of all targets to the base color. Uses
	 * measure to determinate which similarity measure to use. When ignoreSelf
	 * is false and base is a member of targets, base will be the most similar
	 * color. Otherwise base will be ignored for the similarity. Uses just
	 * aTargets OR lTargets. Not both. One of aTargets / lTargets should be
	 * null, the other one should be not null.
	 * 
	 * @param base
	 *            One color (not null).
	 * @param aTargets
	 *            Colors from which to chose the similar color from (values
	 *            might be null).
	 * @param lTargets
	 *            Colors from which to chose the similar color from (values
	 *            might be null).
	 * @param ignoreSelf
	 *            When true, base will be ignored when it is member of targets.
	 * @param measure
	 *            A measure for the similarity.
	 * @return The index of the most similar color or -1 if no color found.
	 */
	static private Integer getMostSimilarColor(RColor base, RColor[] aTargets, ArrayList<RColor> lTargets,
			boolean ignoreSelf, ColorDifferenceMeasure measure) {
		if ((aTargets != null && aTargets.length == 0) || (lTargets != null && lTargets.size() == 0)) {

			return -1;
		}

		int index = -1;
		float minDiff = 10000000;

		// get array size depending on which of the inputs is filled
		int max = aTargets != null ? aTargets.length : lTargets.size();

		for (int i = 0; i < max; i++) {

			RColor target = aTargets != null ? aTargets[i] : lTargets.get(i);

			if (target == null || ignoreSelf && base.equals(target)) {
				continue;
			}

			// calculate the distance
			float diff = ColorDifference.difference(base, target, measure);

			// keep track of the shortest distance
			if (diff < minDiff) {
				index = i;
				minDiff = diff;
			}
		}
		return index;
	}

	/*
	 * ------------------------------------------------------------------------
	 * | Difference |
	 * ------------------------------------------------------------------------
	 */

	/**
	 * Calculates the most different color of all targets to the base color.
	 * Uses measure to determinate which distance measure to use.
	 * 
	 * @param base
	 *            One color (not null).
	 * @param targets
	 *            Colors from which to chose the different color from (not null,
	 *            but values might be null)
	 * @param measure
	 *            A measure for the similarity.
	 * @return The index of the most different color or -1 if no color found.
	 */
	static public Integer getMostDifferentColor(RColor base, RColor[] targets, ColorDifferenceMeasure measure) {
		return ColorDifference.getMostDifferentColor(new RColor[] { base }, targets, measure);
	}

	/**
	 * Calculates the most different color of all targets to the base color.
	 * Uses measure to determinate which distance measure to use.
	 * 
	 * @param base
	 *            - One color (not null).
	 * @param targets
	 *            - Colors from which to chose the different color from (not
	 *            null, but values might be null)
	 * @param measure
	 *            - A measure for the similarity.
	 * @return The index of the most different color or -1 if no color found.
	 */
	static public Integer getMostDifferentColor(RColor base, ArrayList<RColor> targets,
			ColorDifferenceMeasure measure) {
		return ColorDifference.getMostDifferentColor(new RColor[] { base }, targets, measure);
	}

	/**
	 * Calculates the most different color of all targets to the base colors.
	 * Uses measure to determinate which distance measure to use.
	 * 
	 * @param bases
	 *            - Colors (not null).
	 * @param targets
	 *            - Colors from which to chose the different color from (not
	 *            null, but values might be null)
	 * @param measure
	 *            - A measure for the similarity.
	 * @return The index of the most different color or -1 if no color found.
	 */
	static public Integer getMostDifferentColor(RColor[] bases, ArrayList<RColor> targets,
			ColorDifferenceMeasure measure) {
		return ColorDifference.getMostDifferentColor(bases, null, targets, measure);
	}

	/**
	 * Calculates the most different color of all targets to the base colors.
	 * Uses measure to determinate which distance measure to use.
	 * 
	 * @param bases
	 *            - Colors (not null).
	 * @param targets
	 *            - Colors from which to chose the different color from (not
	 *            null, but values might be null)
	 * @param measure
	 *            - A measure for the similarity.
	 * @return The index of the most different color or -1 if no color found.
	 */
	static public Integer getMostDifferentColor(RColor[] bases, RColor[] targets, ColorDifferenceMeasure measure) {
		return ColorDifference.getMostDifferentColor(bases, targets, null, measure);
	}

	/**
	 * Calculates the most different color of all targets to the base colors.
	 * Uses measure to determinate which distance measure to use. Uses just
	 * aTargets OR lTargets. Not both. One of aTargets / lTargets should be
	 * null, the other one should be not null.
	 * 
	 * @param bases
	 *            - Colors (not null, but values might be null).
	 * @param aTargets
	 *            - Colors from which to chose the different color from (values
	 *            might be null)
	 * @param lTargets
	 *            - Colors from which to chose the different color from (values
	 *            might be null)
	 * @param measure
	 *            - A measure for the similarity.
	 * @return The index of the most different color or -1 if no color found.
	 */
	static private Integer getMostDifferentColor(RColor[] bases, RColor[] aTargets, ArrayList<RColor> lTargets,
			ColorDifferenceMeasure measure) {

		// both inputs are empty...
		if ((aTargets != null && aTargets.length == 0) || (lTargets != null && lTargets.size() == 0)) {
			return -1;
		}

		int index = -1;
		float maxDiff = -1;

		// get array size depending on which of the inputs is filled
		int max = aTargets != null ? aTargets.length : lTargets.size();

		for (int i = 0; i < max; i++) {
			float summedDiff = 0;
			RColor target = aTargets != null ? aTargets[i] : lTargets.get(i);

			if (target == null) {
				continue;
			}

			for (int j = 0; j < bases.length; j++) {

				if (bases[j] == null) {
					continue;
				}

				// if they are equal, the difference is 0 - no need to calculate
				// that
				if (!bases[j].equals(target)) {
					summedDiff += ColorDifference.difference(bases[j], target, measure);
				}
			}

			// keep track of the highest distance.
			// the found color (target) should not be in bases. if it would that
			// would mean that one color of bases is the most different color
			// to itself (would not make sense, so avoid that case)
			if (summedDiff > maxDiff && !ColorHelper.contains(target, bases)) {
				index = i;
				maxDiff = summedDiff;
			}

		}
		return index;
	}

	/*
	 * ------------------------------------------------------------------------
	 * | Calculation |
	 * | Implementation based on: http://www.easyrgb.com/en/math.php
	 * ------------------------------------------------------------------------
	 */

	/**
	 * Calculates the difference / distance between two colors. Uses RGB
	 * Euclidean distance.
	 * 
	 * @param color1
	 *            One color.
	 * @param color2
	 *            One color .
	 * @return The value of the difference / distance.
	 */
	static public Float difference(int color1, int color2) {
		return ColorDifference.difference(RGBColor.toRGB(color1), RGBColor.toRGB(color2),
				ColorDifferenceMeasure.RGBEuclidean);
	}

	/**
	 * Calculates the difference / distance between two colors. Uses the
	 * Euclidean distance of the first colors color space. E.G. if color1 is a
	 * RGBColor then the RGBEuclidean distance will be used.
	 * 
	 * @param color1
	 *            One color.
	 * @param color2
	 *            One color .
	 * @return The value of the difference / distance.
	 */
	static public Float difference(RColor color1, RColor color2) {
		return ColorDifference.difference(color1, color2, color1.getColorSpace().toColorDifferenceMeasure());
	}

	/**
	 * Calculates the difference / distance between two colors. How the
	 * difference is calculated and which color model to use is defined by the
	 * measure.
	 * 
	 * @param color1
	 *            One color (not null).
	 * @param color2
	 *            One color (not null).
	 * @param measure
	 *            A ColorDifferenceMeasure, e.g.
	 *            ColorDifferenceMeasure.RGBEuclidean
	 * @return The value of the difference / distance.
	 */
	static public Float difference(RColor color1, RColor color2, ColorDifferenceMeasure measure) {
		Float diff = 0f;
		switch (measure) {
		case RGB2R4G3B:
			diff = ColorDifference.rgbWeighted(color1, color2);
			break;

		case RGBMeanRed:
			diff = ColorDifference.rgbMeanRed(color1, color2);
			break;

		case HSBEuclidean:
			diff = ColorDifference.hsbEuclidean(color1, color2);
			break;

		case HSBCylinder:
			diff = ColorDifference.hsbCylinder(color1, color2);
			break;

		case HSBCone:
			diff = ColorDifference.hsbCone(color1, color2);
			break;

		case LabDeltaE:
			diff = ColorDifference.labDeltaE(color1, color2);
			break;

		case LabDeltaE1994:
			diff = ColorDifference.labDeltaE1994(color1, color2);
			break;

		case LabDeltaE2000:
			diff = ColorDifference.labDeltaE2000(color1, color2);
			break;

		case LCHEuclidean:
			diff = ColorDifference.lchEuclidean(color1, color2);
			break;

		case LCHCMC:
			diff = ColorDifference.lchCMC(color1, color2);
			break;

		case XYZEuclidean:
			diff = ColorDifference.xyzEuclidean(color1, color2);
			break;

		case RGBEuclidean:
		default:
			diff = ColorDifference.rgbEuclidean(color1, color2);
			break;
		}

		return diff;
	}

	/**
	 * Calculates the difference / distance between two colors using RGB
	 * Euclidean distance.
	 * 
	 * @param color1
	 *            One color (not null).
	 * @param color2
	 *            One color (not null).
	 * @return The value of the difference / distance.
	 */
	static public Float rgbEuclidean(RColor color1, RColor color2) {
		RGBColor rgb1 = color1.toRGB();
		RGBColor rgb2 = color2.toRGB();

		return (float) Math.pow(Math.pow(rgb1.getRed() - rgb2.getRed(), 2)
				+ Math.pow(rgb1.getGreen() - rgb2.getGreen(), 2) + Math.pow(rgb1.getBlue() - rgb2.getBlue(), 2), 0.5);
	}

	/**
	 * Calculates the difference / distance between two colors using weighted
	 * RGB Euclidean distance. Red is weighted with 2, Green is weighted with 3
	 * and Blue is weighted with 3.
	 * 
	 * @param color1
	 *            - One color (not null).
	 * @param color2
	 *            - One color (not null).
	 * @return The value of the difference / distance.
	 */
	static public Float rgbWeighted(RColor color1, RColor color2) {
		RGBColor rgb1 = color1.toRGB();
		RGBColor rgb2 = color2.toRGB();

		return (float) Math.pow(2 * Math.pow(rgb1.getRed() - rgb2.getRed(), 2)
				+ 4 * Math.pow(rgb1.getGreen() - rgb2.getGreen(), 2) + 3 * Math.pow(rgb1.getBlue() - rgb2.getBlue(), 2),
				0.5);
	}

	/**
	 * Calculates the difference / distance between two colors using RGB
	 * Euclidean distance weighted with a mean red value.
	 * 
	 * @param color1
	 *            One color (not null).
	 * @param color2
	 *            One color (not null).
	 * @return The value of the difference / distance.
	 */
	static public Float rgbMeanRed(RColor color1, RColor color2) {
		RGBColor rgb1 = color1.toRGB();
		RGBColor rgb2 = color2.toRGB();

		float meanRed = 0.5f * (rgb1.getRed() + rgb2.getRed());
		float deltaR = rgb1.getRed() - rgb2.getRed();
		float deltaG = rgb1.getGreen() - rgb2.getGreen();
		float deltaB = rgb1.getBlue() - rgb2.getBlue();

		return (float) Math.pow((2 + meanRed / 256) * Math.pow(deltaR, 2) + 4 * Math.pow(deltaG, 2)
				+ (2 + (255 - meanRed) / 256) * Math.pow(deltaB, 2), 0.5);
	}

	/**
	 * Calculates the difference / distance between two colors using HSB
	 * Euclidean distance.
	 * 
	 * @param color1
	 *            One color (not null).
	 * @param color2
	 *            One color (not null).
	 * @return The value of the difference / distance.
	 */
	static public Float hsbEuclidean(RColor color1, RColor color2) {
		HSBColor hsb1 = color1.toHSB();
		HSBColor hsb2 = color2.toHSB();

		float deltaH = hsb1.getHue() - hsb2.getHue();

		if (Math.abs(deltaH) > 180) {
			if (hsb1.getHue() < hsb2.getHue()) {
				deltaH = 360 + deltaH;
			} else {
				deltaH = 360 - deltaH;
			}
		} else {
			deltaH = (float) Math.abs(deltaH);
		}

		return (float) Math.pow(Math.pow(deltaH / 1.8, 2) + Math.pow(hsb1.getSaturation() - hsb2.getSaturation(), 2)
				+ Math.pow(hsb1.getBrightness() - hsb2.getBrightness(), 2), 0.5);
	}

	/**
	 * Calculates the difference / distance between two colors using HSB
	 * Euclidean distance using the 3D interpretation of the color model.
	 * 
	 * @param color1
	 *            One color (not null).
	 * @param color2
	 *            One color (not null).
	 * @return The value of the difference / distance.
	 */
	static public Float hsbCylinder(RColor color1, RColor color2) {
		HSBColor hsb1 = color1.toHSB();
		HSBColor hsb2 = color2.toHSB();

		double x1 = Math.cos(Math.PI * 2 * hsb1.getHue() / 360) * hsb1.getSaturation();
		double y1 = Math.sin(Math.PI * 2 * hsb1.getHue() / 360) * hsb1.getSaturation();

		double x2 = Math.cos(Math.PI * 2 * hsb2.getHue() / 360) * hsb2.getSaturation();
		double y2 = Math.sin(Math.PI * 2 * hsb2.getHue() / 360) * hsb2.getSaturation();

		return (float) Math.pow(
				Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2) + Math.pow(hsb1.getBrightness() - hsb2.getBrightness(), 2),
				0.5);
	}

	/**
	 * Calculates the difference / distance between two colors using HSB
	 * Euclidean distance using the 3D interpretation of the color model.
	 * 
	 * @param color1
	 *            One color (not null).
	 * @param color2
	 *            One color (not null).
	 * @return The value of the difference / distance.
	 */
	static public Float hsbCone(RColor color1, RColor color2) {
		HSBColor hsb1 = color1.toHSB();
		HSBColor hsb2 = color2.toHSB();

		
		float r1 = hsb1.getSaturation() * hsb1.getBrightness() / 100;
		float r2 = hsb2.getSaturation() * hsb2.getBrightness() / 100;

		double x1 = Math.cos(Math.PI * 2 * hsb1.getHue() / 360) * r1;
		double y1 = Math.sin(Math.PI * 2 * hsb1.getHue() / 360) * r1;

		double x2 = Math.cos(Math.PI * 2 * hsb2.getHue() / 360) * r2;
		double y2 = Math.sin(Math.PI * 2 * hsb2.getHue() / 360) * r2;

		return (float) Math.pow(
				Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2) + Math.pow(hsb1.getBrightness() - hsb2.getBrightness(), 2),
				0.5);
	}

	/**
	 * Calculates the difference / distance between two colors using XYZ
	 * Euclidean distance.
	 * 
	 * @param color1
	 *            One color (not null).
	 * @param color2
	 *            One color (not null).
	 * @return The value of the difference / distance.
	 */
	static public Float xyzEuclidean(RColor color1, RColor color2) {
		XYZColor xyz1 = color1.toXYZ();
		XYZColor xyz2 = color2.toXYZ();

		return (float) Math.pow(Math.pow(xyz1.getX() - xyz2.getX(), 2) + Math.pow(xyz1.getY() - xyz2.getY(), 2)
				+ Math.pow(xyz1.getZ() - xyz2.getZ(), 2), 0.5);
	}

	/**
	 * Calculates the chroma difference / distance between two colors.
	 * 
	 * @param color1
	 *            One color (not null).
	 * @param color2
	 *            One color (not null).
	 * @return The value of the difference / distance.
	 */
	static public Float labDeltaC(RColor color1, RColor color2) {
		LabColor lab1 = color1.toLab();
		LabColor lab2 = color2.toLab();

		return LabColor.getChroma(lab2.getA(), lab2.getB()) - LabColor.getChroma(lab1.getA(), lab1.getB());
	}

	/**
	 * Calculates the hue difference between two colors.
	 * 
	 * @param color1
	 *            One color (not null).
	 * @param color2
	 *            One color (not null).
	 * @return The value of the difference / distance.
	 */
	static public Float labDeltaH(RColor color1, RColor color2) {
		LabColor lab1 = color1.toLab();
		LabColor lab2 = color2.toLab();

		/*
		 * the value under the root should be always positive. But due the
		 * calculation using floats it might be that the value of underRoot
		 * might be near zero and negative. This causes the dist value to become
		 * NaN. To prevent any wrong calculations if the number is NaN, which
		 * only happens when underRoot is near zero, the dist is set to 0
		 */

		double underRoot = Math.pow(lab2.getA() - lab1.getA(), 2) + Math.pow(lab2.getB() - lab1.getB(), 2)
				- Math.pow(ColorDifference.labDeltaC(lab1, lab2), 2);
		float dist = (float) (Math.pow(underRoot, 0.5));

		return Float.isNaN(dist) ? 0 : dist;
	}

	/**
	 * Calculates the difference / distance between two colors using Lab
	 * Euclidean (Delta E) distance.
	 * 
	 * @param color1
	 *            One color (not null).
	 * @param color2
	 *            One color (not null).
	 * @return The value of the difference / distance.
	 */
	static public Float labDeltaE(RColor color1, RColor color2) {
		LabColor lab1 = color1.toLab();
		LabColor lab2 = color2.toLab();

		return (float) Math.pow(Math.pow(lab1.getLuminance() - lab2.getLuminance(), 2)
				+ Math.pow(lab1.getA() - lab2.getA(), 2) + Math.pow(lab1.getB() - lab2.getB(), 2), 0.5);
	}

	/**
	 * Calculates the difference / distance between two colors using Lab Delta E
	 * 1994 distance.
	 * 
	 * @param color1
	 *            One color (not null).
	 * @param color2
	 *            One color (not null).
	 * @return The value of the difference / distance.
	 */
	static public Float labDeltaE1994(RColor color1, RColor color2) {
		LabColor lab1 = color1.toLab();
		LabColor lab2 = color2.toLab();

		float c1 = lab1.getChroma();
		float c2 = lab2.getChroma();

		float deltaC = c2 - c1;
		float deltaL = lab2.getLuminance() - lab1.getLuminance();
		float deltaE = ColorDifference.labDeltaE(lab1, lab2);
		float deltaH = deltaE * deltaE - deltaL * deltaL - deltaC * deltaC;

		if (deltaH > 0) {
			deltaH = (float) Math.pow(deltaH, 0.5);
		} else {
			deltaH = 0;
		}

		float sL = 1;
		float sC = 1 + (0.045f * c1);
		float sH = 1 + (0.015f * c1);

		deltaL /= ColorDifference.weightingLuminance * sL;
		deltaC /= ColorDifference.weightingChroma * sC;
		deltaH /= ColorDifference.weightingHue * sH;

		return (float) Math.pow(deltaL * deltaL + deltaC * deltaC + deltaH * deltaH, 0.5);
	}

	/**
	 * Calculates the difference / distance between two colors using Lab Delta E
	 * 2000 distance.
	 * 
	 * @param color1
	 *            One color (not null).
	 * @param color2
	 *            One color (not null).
	 * @return The value of the difference / distance.
	 */
	static public Float labDeltaE2000(RColor color1, RColor color2) {
		LabColor lab1 = color1.toLab();
		LabColor lab2 = color2.toLab();

		double meanChroma = 0.5 * (lab1.getChroma() + lab2.getChroma());
		double gx = 0.5 * (1 - Math.pow(Math.pow(meanChroma, 7) / (Math.pow(meanChroma, 7) + Math.pow(25, 7)), 0.5));

		double a1 = (1 + gx) * lab1.getA();
		double a2 = (1 + gx) * lab2.getA();

		double c1 = LabColor.getChroma((float) a1, lab1.getB());
		double c2 = LabColor.getChroma((float) a2, lab2.getB());

		double h1 = LabColor.getHue(c1, lab1.getB());
		double h2 = LabColor.getHue(c2, lab2.getB());

		double deltaL = lab2.getLuminance() - lab1.getLuminance();
		double deltaC = c2 - c1;
		double deltaH = 0;

		if (c1 * c2 != 0) {
			if (Math.abs(h2 - h1) <= 180) {
				deltaH = h2 - h1;
			} else {
				if (h2 - h1 > 180) {
					deltaH = h2 - h1 - 360;
				} else {
					deltaH = h2 - h1 + 360;
				}
			}
		}

		deltaH = 2 * Math.pow(c1 * c2, 0.5) * Math.sin(Math.PI * deltaH / 360);

		float meanL = 0.5f * (lab1.getLuminance() + lab2.getLuminance());
		double meanC = 0.5 * (c1 + c2);
		double meanH = h1 + h2;

		if (c1 * c2 != 0) {
			if (Math.abs(h1 - h2) > 180) {
				if (h2 + h1 < 360) {
					meanH = h2 + h1 + 360;
				} else {
					meanH = h2 + h1 - 360;
				}
			}

			meanH *= 0.5;
		}

		double t = 1 - 0.17 * Math.cos(Math.PI * 2 * (meanH - 30) / 360) + 0.24 * Math.cos(Math.PI * 4 * meanH / 360)
				+ 0.32 * Math.cos(Math.PI * 2 * (3 * meanH + 6) / 360)
				- 0.20 * Math.cos(Math.PI * 2 * (4 * meanH - 63) / 360);

		double pH = 30 * Math.exp(-1 * (meanH - 275) / 25 * (meanH - 275) / 25);
		double rootC = 2 * Math.pow(Math.pow(meanC, 7) / (Math.pow(meanC, 7) + Math.pow(25, 7)), 0.5);

		double sL = 1 + (0.015 * (meanL - 50) * (meanL - 50)) / Math.pow(20 + (meanL - 50) * (meanL - 50), 0.5);
		double sC = 1 + 0.045 * meanC;
		double sH = 1 + 0.015 * meanC * t;

		double rt = -1 * Math.sin(Math.PI * 4 * pH / 360) * rootC;

		deltaL /= ColorDifference.weightingLuminance * sL;
		deltaC /= ColorDifference.weightingChroma * sC;
		deltaH /= ColorDifference.weightingHue * sH;

		float dist = (float) Math.pow(deltaL * deltaL + deltaC * deltaC + deltaH * deltaH + rt * deltaC * deltaH, 0.5);

		return dist;
	}

	/**
	 * Calculates the difference / distance between two colors using CMC
	 * distance.
	 * 
	 * @param color1
	 *            One color (not null).
	 * @param color2
	 *            One color (not null).
	 * @return The value of the difference / distance.
	 */
	static public Float lchCMC(RColor color1, RColor color2) {
		LabColor lab1 = color1.toLab();
		LabColor lab2 = color2.toLab();

		float c1 = lab1.getChroma();
		float c2 = lab2.getChroma();
		double f = Math.pow(Math.pow(c1, 4) / (Math.pow(c1, 4) + 1900), 0.5);
		float h1 = lab1.getHue();

		double t = 0;

		if (164 < h1 || h1 > 345) {
			t = 0.36 + Math.abs(0.4 * Math.cos(Math.PI * 2 * (h1 + 35) / 360));
		} else {
			t = 0.56 + Math.abs(0.2 * Math.cos(Math.PI * 2 * (h1 + 168) / 360));
		}

		double sL = 0.511;

		if (lab1.getLuminance() >= 16) {
			sL = 0.040975 * lab1.getLuminance() / (1 + 0.01765 * lab1.getLuminance());
		}

		double sC = 0.638 + 0.0638 * c1 / (1 + 0.0131 * c1);
		double sH = sC * (f * t + 1 - f);
		Float deltaH = ColorDifference.labDeltaH(lab1, lab2);

		sL = (lab2.getLuminance() - lab1.getLuminance()) / (sL * ColorDifference.weightingChroma);
		sC = (c2 - c1) / (sC * ColorDifference.weightingChroma);
		sH = deltaH / (sH);

		float dist = (float) Math.pow(sL * sL + sC * sC + sH * sH, 0.5);

		return dist;
	}

	/**
	 * Calculates the difference / distance between two colors using LCH
	 * Euclidean distance.
	 * 
	 * @param color1
	 *            One color (not null).
	 * @param color2
	 *            One color (not null).
	 * @return The value of the difference / distance.
	 */
	static public Float lchEuclidean(RColor color1, RColor color2) {
		LCHColor lch1 = color1.toLCH();
		LCHColor lch2 = color2.toLCH();
		
		// normalize the chroma calues to [0, 100]
		float c1 = 100 * lch1.getChroma() / LCHColor.MAX_C;
		float c2 = 100 * lch2.getChroma() / LCHColor.MAX_C;

		
		// create x / y coordinates from given hue / chroma
		// z equals to the luminance
		double x1 = Math.cos(Math.PI * lch1.getHue() / 180) * c1;
		double y1 = Math.sin(Math.PI * lch1.getHue() / 180) * c1;

		double x2 = Math.cos(Math.PI * lch2.getHue() / 180) * c2;
		double y2 = Math.sin(Math.PI * lch2.getHue() / 180) * c2;
		
	
		float meanWeightingCH = (ColorDifference.weightingHue + ColorDifference.weightingChroma) * 0.5f;

		
		return (float) Math.pow(
				Math.pow((x1 - x2) / meanWeightingCH, 2) + Math.pow((y1 - y2) / meanWeightingCH, 2)
						+ Math.pow((lch1.getLuminance() - lch2.getLuminance()) / ColorDifference.weightingLuminance, 2),
				0.5);
				
	}
}
