package net.returnvoid.color;

import java.util.ArrayList;

import net.returnvoid.tools.RMath;
import net.returnvoid.tools.StringTools;

/*
 * 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 model for color in Lab color space. A LabColor can be converted to any
 * other color model. To use this in Processing use the getColor() Method:<br>
 * 
 * <pre>
 * fill(myLabObject.getColor());
 * </pre>
 * 
 * The difference between multiple color objects can be calculated with the
 * methods of ColorDifference class. <br>
 * 
 * The ranges of the values are not bounded. The following values just give an
 * approximation of the ranges.<br>
 * <br>
 * 
 * L = Luminance [0, 100] = [LabColor.MIN_L, LabColor.MAX_L]<br>
 * a ~ [-86, 98] = [LabColor.MIN_A, LabColor.MAX_A]<br>
 * b ~ [-107.6, 94.6] = [LabColor.MIN_B, LabColor.MAX_B] <br>
 * Alpha = transparency [0, 100] <br>
 * <br>
 * 
 * @author Diana Lange
 *
 */
public class LabColor implements RColor {

	/**
	 * The values of L - a - b - Alpha
	 */
	private Float[] channels;

	/**
	 * True if any channel has been updated (e.g. with one of the setters). The
	 * hex value will be recalculated if wasUpdated is true and the method for
	 * getting the hex value is called.
	 */
	private boolean wasUpdated;

	/**
	 * The hexadecimal value of the color object. This value can be used within
	 * the the color methods of processing to set the fill or stroke color. The
	 * range of hex is 0x00000000 to 0xFFFFFFFF. hex only will be calculated
	 * when wasUpdated is true and a method for getting the hex value is called.
	 */
	private int hex;

	/**
	 * The ColorSpace of RGB colors.
	 */
	public final static ColorSpace NAME = ColorSpace.LabColor;

	/**
	 * The minimal luminance value any Lab color can have.
	 */
	public final static float MIN_L = 0f;

	/**
	 * The maximal luminance value any Lab color can have.
	 */
	public final static float MAX_L = 100f;

	/**
	 * The minimal a value any Lab color can have (approximation).
	 */
	public final static float MIN_A = -86f;

	/**
	 * The maximal a value any Lab color can have (approximation).
	 */
	public final static float MAX_A = 98f;

	/**
	 * The minimal b value any Lab color can have (approximation).
	 */
	public final static float MIN_B = -107.6f;

	/**
	 * The maximal b value any Lab color can have (approximation).
	 */
	public final static float MAX_B = 94.6f;

	/**
	 * Builds an new LabColor (all values initialized, all color channels are
	 * zero).
	 */
	private LabColor() {
		wasUpdated = true;
		hex = 0;
		channels = new Float[4];
	}

	/**
	 * Creates a new opaque LabColor object. The input values will be
	 * constrained to the valid range of Lab.
	 * 
	 * @param l
	 *            The luminance value [0, 100]
	 * @param a
	 *            The a value [-86, 98]
	 * @param b
	 *            The b value [-107.6, 94.6]
	 */
	public LabColor(Float l, Float a, Float b) {
		this();
		this.setLuminance(l).setA(a).setB(b).setAlpha(100f);
	}

	/**
	 * Creates a new LabColor object. The input values will be constrained to
	 * the valid range of Lab.
	 * 
	 * @param l
	 *            The luminance value [0, 100]
	 * @param a
	 *            The a value [-86, 98]
	 * @param b
	 *            The b value [-107.6, 94.6]
	 * @param alpha
	 *            THe alpha value [0, 100]
	 */
	public LabColor(Float l, Float a, Float b, Float alpha) {
		this();
		this.setLuminance(l).setA(a).setB(b).setAlpha(alpha);
	}

	/**
	 * Creates a new LabColor object. The input values will be constrained to
	 * the valid range of LabColor.
	 * 
	 * @param channels
	 *            Either [l, a, b] or [l, a, b, alpha].
	 */
	public LabColor(float... channels) {
		this();
		this.setLuminance(channels.length >= 1 ? channels[0] : 0f).setA(channels.length >= 2 ? channels[1] : 0f)
				.setB(channels.length >= 3 ? channels[2] : 0f).setAlpha(channels.length >= 4 ? channels[3] : 100f);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.returnvoid.color.RVColor#getColor()
	 */
	@Override
	public int getColor() {

		if (wasUpdated) {

			hex = this.toRGB().getColor();

			wasUpdated = false;
		}

		return hex;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.returnvoid.color.RVColor#setAlpha(float)
	 */
	@Override
	public LabColor setAlpha(Float alpha) {
		wasUpdated = true;
		channels[3] = alpha >= 0 && alpha <= 100 ? alpha : (alpha < 0 ? 0 : 100);
		return this;
	}
	
	/**
	 * Sets the value of alpha / transparency. The range of alpha is [0, 100].
	 * Zero always means that the color is completely transparent. The high
	 * value means that the color is completely opaque.
	 * 
	 * @param alpha
	 *            The value of the colors transparency.
	 * 
	 * @return The current color object.
	 */
	public LabColor setAlpha(int alpha) {
		return this.setAlpha((float) alpha);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.returnvoid.color.RVColor#getAlpha()
	 */
	@Override
	public Float getAlpha() {
		return channels[3];
	}

	/**
	 * Sets the luminance value. The input value will be constrained to the
	 * valid range of Lab.
	 * 
	 * @param l
	 *            The new luminance value [0, 100].
	 * @return The current LabColor object.
	 */
	public LabColor setLuminance(Float l) {
		wasUpdated = true;
		channels[0] = l >= LabColor.MIN_L && l <= LabColor.MAX_L ? l
				: (l < LabColor.MIN_L ? LabColor.MIN_L : LabColor.MAX_L);

		return this;
	}

	/**
	 * Sets the luminance value. The input value will be constrained to the
	 * valid range of Lab.
	 * 
	 * @param l
	 *            The new luminance value [0, 100].
	 * @return The current LabColor object.
	 */
	public LabColor setLuminance(int l) {
		return setLuminance((float) l);
	}

	/**
	 * Sets the a value. The input value will be constrained to the valid range
	 * of Lab.
	 * 
	 * @param a
	 *            The new a value [-86, 98].
	 * @return The current LabColor object.
	 */
	public LabColor setA(Float a) {
		wasUpdated = true;
		channels[1] = a >= LabColor.MIN_A && a <= LabColor.MAX_A ? a
				: (a < LabColor.MIN_A ? LabColor.MIN_A : LabColor.MAX_A);

		return this;
	}

	/**
	 * Sets the a value. The input value will be constrained to the valid range
	 * of Lab.
	 * 
	 * @param a
	 *            The new a value [-86, 98].
	 * @return The current LabColor object.
	 */
	public LabColor setA(int a) {

		return setA((float) a);
	}

	/**
	 * Sets the b value. The input value will be constrained to the valid range
	 * of Lab.
	 * 
	 * @param b
	 *            The new a value [-107.6, 94.6].
	 * @return The current LabColor object.
	 */
	public LabColor setB(Float b) {
		wasUpdated = true;
		channels[2] = b >= LabColor.MIN_B && b <= LabColor.MAX_B ? b
				: (b < LabColor.MIN_B ? LabColor.MIN_B : LabColor.MAX_B);

		return this;
	}

	/**
	 * Sets the b value. The input value will be constrained to the valid range
	 * of Lab.
	 * 
	 * @param b
	 *            The new a value [-107.6, 94.6].
	 * @return The current LabColor object.
	 */
	public LabColor setB(int b) {
		return setB((float) b);
	}

	/**
	 * Returns the current value of luminance of this color.
	 * 
	 * @return The luminance of this color.
	 */
	public float getLuminance() {
		return channels[0];
	}

	/**
	 * Returns the current value of a of this color.
	 * 
	 * @return The a of this color.
	 */
	public Float getA() {
		return channels[1];
	}

	/**
	 * Returns the current value of b of this color.
	 * 
	 * @return The b of this color.
	 */
	public Float getB() {
		return channels[2];
	}

	/**
	 * Returns the current value of chroma of this color (equivalent to the
	 * chroma value of this color's LCH conversion).
	 * 
	 * @return The chroma of this color.
	 */
	public Float getChroma() {
		return LabColor.getChroma(getA(), getB());
	}

	/**
	 * Returns the current value of hue of this color (equivalent to the hue
	 * value of this color's LCH conversion).
	 * 
	 * @return The chroma of this color.
	 */
	public Float getHue() {

		return LabColor.getHue(getA(), getB());
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.returnvoid.color.RVColor#getAlpha()
	 */
	@Override
	public String getHexString() {
		return Integer.toHexString(this.getColor()).toUpperCase();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.returnvoid.color.RVColor#getColorIngnoringAlpha()
	 */
	@Override
	public int getOpColor() {

		int hex = this.getColor();
		return 255 << 24 | hex;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.returnvoid.color.RVColor#getHexStringIngnoringAlpha()
	 */
	@Override
	public String getOpHexString() {
		return Integer.toHexString(this.getOpColor()).toUpperCase().substring(2);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.returnvoid.color.RVColor#getValues()
	 */
	@Override
	public float[] getValues() {
		float[] copy = new float[4];
		copy[0] = channels[0];
		copy[1] = channels[1];
		copy[2] = channels[2];
		copy[3] = channels[3];
		return copy;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.returnvoid.color.RVColor#getValuesNormalized()
	 */
	@Override
	public float[] getValuesNormalized() {
		float[] vals = getValues();
		vals[0] = RMath.map(vals[0], LabColor.MIN_L, LabColor.MAX_L, 0, 1);
		vals[1] = RMath.map(vals[1], LabColor.MIN_A, LabColor.MAX_A, 0, 1);
		vals[2] = RMath.map(vals[2], LabColor.MIN_B, LabColor.MAX_B, 0, 1);
		vals[3] /= 100;

		return vals;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.returnvoid.color.RVColor#getName()
	 */
	@Override
	public String getName() {
		return LabColor.NAME.name();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.returnvoid.color.RVColor#getColorSpace()
	 */
	@Override
	public ColorSpace getColorSpace() {
		return LabColor.NAME;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.returnvoid.color.RVColor#copy()
	 */
	@Override
	public LabColor copy() {
		return new LabColor(new Float(getLuminance()), new Float(getA()), new Float(getB()), new Float(getAlpha()));
	}

	/**
	 * Checks if the input color is equal to this object. Two colors are equal
	 * if they are member of the same color space and all channel values are
	 * equal.
	 * 
	 * @param other
	 *            The other color
	 * @return True if both colors are considered the same;
	 */
	public boolean equals(RColor other) {

		if (this.getColorSpace() != other.getColorSpace()) {
			return false;
		}

		LabColor labOther = (LabColor) other;

		for (int i = 0; i < channels.length; i++) {
			if (channels[i] != labOther.channels[i]) {
				return false;
			}
		}

		return true;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return getName() + ": [" + StringTools.joinAndFormat(getValues(), ", ", 3, 2) + "] [#" + getOpHexString() + "]";
	}

	/*
	 * ------------------------------------------------------------------------
	 * | Static Functions |
	 * ------------------------------------------------------------------------
	 */

	/**
	 * Calculates the mean color of the input colors
	 * 
	 * @param colors
	 *            Colors (1 or more).
	 * @return The new LabColor object that represents the mean color.
	 */
	static public LabColor getMeanColor(RColor... colors) {
		return LabColor.getMeanColor(0, colors.length, colors);
	}

	/**
	 * Calculates the mean color of the input colors
	 * 
	 * @param colors
	 *            Colors.
	 * @return The new LabColor object that represents the mean color.
	 */
	static public LabColor getMeanColor(ArrayList<RColor> colors) {
		return LabColor.getMeanColor(0, colors.size(), colors);
	}

	/**
	 * Calculates the mean color of the input colors. With start and end a
	 * subset is defined. Just this subset will be used to calculate the mean.
	 * 
	 * @param start
	 *            Start index of the subset of the input colors (start < end).
	 *            start will be member of the subset.
	 * @param end
	 *            End index of the subset of the input colors (start < end). end
	 *            will not be member of the subset.
	 * @param colors
	 *            Colors (1 or more).
	 * @return The new LabColor object that represents the mean color.
	 */
	static public LabColor getMeanColor(int start, int end, RColor... colors) {

		return LabColor.getMeanColor(start, end, colors, null);
	}

	/**
	 * Calculates the mean color of the input colors. With start and end a
	 * subset is defined. Just this subset will be used to calculate the mean.
	 * 
	 * @param start
	 *            Start index of the subset of the input colors (start < end).
	 *            start will be member of the subset.
	 * @param end
	 *            End index of the subset of the input colors (start < end). end
	 *            will not be member of the subset.
	 * @param colors
	 *            Colors.
	 * @return The new LabColor object that represents the mean color.
	 */
	static public LabColor getMeanColor(int start, int end, ArrayList<RColor> colors) {

		return LabColor.getMeanColor(start, end, null, colors);
	}

	/**
	 * Calculates the mean color of the input colors of aColors / lColors. With
	 * start and end a subset is defined. Just this subset will be used to
	 * calculate the mean. start and end should be in the array range; otherwise
	 * this will produce an ArrayIndexOutOfBounds Exception. Uses just aColors
	 * OR lColors. Not both. One of aColors / lColors should be null, the other
	 * one should be not null.
	 * 
	 * @param start
	 *            Start index of the subset of the input colors (start < end).
	 *            start will be member of the subset.
	 * @param end
	 *            End index of the subset of the input colors (start < end). end
	 *            will not be member of the subset.
	 * @param aColors
	 *            Colors.
	 * @param lColors
	 *            Colors.
	 * @return The new LabColor object that represents the mean color.
	 */
	static private LabColor getMeanColor(int start, int end, RColor[] aColors, ArrayList<RColor> lColors) {

		float[] means = { 0, 0, 0, 0 };
		for (int i = start; i < end; i++) {
			LabColor lab = aColors != null ? aColors[i].toLab() : lColors.get(i).toLab();
			means[0] += lab.getLuminance();
			means[1] += lab.getA();
			means[2] += lab.getB();
			means[3] += lab.getAlpha();
		}

		means[0] /= (end - start);
		means[1] /= (end - start);
		means[2] /= (end - start);
		means[3] /= (end - start);

		return new LabColor(means[0], means[1], means[2], means[3]);
	}

	/**
	 * Calculates the loss - the mean distance of all members of colors to
	 * rvMean.
	 * 
	 * @param rvMean
	 *            A base color which will be used to calculate the distance to.
	 * @param colors
	 *            Colors.
	 * @param m
	 *            The measure which defines how the loss is calculated and which
	 *            color model will be used. This should be an Lab color
	 *            difference measure.
	 * @return The mean loss value.
	 */
	static public Float getLoss(RColor rvMean, RColor[] colors, ColorDifferenceMeasure m) {
		return LabColor.getLoss(rvMean, colors, null, m, 0, colors.length);
	}

	/**
	 * Calculates the loss - the mean distance of all members of colors to
	 * rvMean. With start and end a subset is defined. Just this subset will be
	 * used to calculate the loss. start and end should be in the array range;
	 * otherwise this will produce an ArrayIndexOutOfBounds Exception.
	 * 
	 * @param rvMean
	 *            A base color which will be used to calculate the distance to.
	 * @param colors
	 *            Colors.
	 * @param m
	 *            The measure which defines how the loss is calculated and which
	 *            color model will be used. This should be an Lab color
	 *            difference measure.
	 * @param start
	 *            Start index of the subset of the input colors (start < end).
	 *            start will be member of the subset.
	 * @param end
	 *            End index of the subset of the input colors (start < end). end
	 *            will not be member of the subset.
	 * @return The mean loss value.
	 */
	static public Float getLoss(RColor rvMean, RColor[] colors, ColorDifferenceMeasure m, int start, int end) {
		return LabColor.getLoss(rvMean, colors, null, m, start, end);
	}

	/**
	 * Calculates the loss - the mean distance of all members of colors to
	 * rvMean.
	 * 
	 * @param rvMean
	 *            A base color which will be used to calculate the distance to.
	 * @param lColors
	 *            Colors.
	 * @param m
	 *            The measure which defines how the loss is calculated and which
	 *            color model will be used. This should be an Lab color
	 *            difference measure.
	 * @return The mean loss value.
	 */
	static public Float getLoss(RColor rvMean, ArrayList<RColor> lColors, ColorDifferenceMeasure m) {
		return LabColor.getLoss(rvMean, null, lColors, m, 0, lColors.size());
	}

	/**
	 * Calculates the loss - the mean distance of all members of colors to
	 * rvMean. With start and end a subset is defined. Just this subset will be
	 * used to calculate the loss. start and end should be in the array range;
	 * otherwise this will produce an ArrayIndexOutOfBounds Exception.
	 * 
	 * @param rvMean
	 *            A base color which will be used to calculate the distance to.
	 * @param lColors
	 *            Colors.
	 * @param m
	 *            The measure which defines how the loss is calculated and which
	 *            color model will be used. This should be an Lab color
	 *            difference measure.
	 * @param start
	 *            Start index of the subset of the input colors (start < end).
	 *            start will be member of the subset.
	 * @param end
	 *            End index of the subset of the input colors (start < end). end
	 *            will not be member of the subset.
	 * @return The mean loss value.
	 */
	static public Float getLoss(RColor rvMean, ArrayList<RColor> lColors, ColorDifferenceMeasure m, int start,
			int end) {
		return LabColor.getLoss(rvMean, null, lColors, m, start, end);
	}

	/**
	 * Calculates the loss - the mean distance of all members of aColors /
	 * lColors to rvMean. With start and end a subset is defined. Just this
	 * subset will be used to calculate the loss. start and end should be in the
	 * array range; otherwise this will produce an ArrayIndexOutOfBounds
	 * Exception. Uses just aColors OR lColors. Not both. One of aColors /
	 * lColors should be null, the other one should be not null.
	 * 
	 * @param rvMean
	 *            A base color which will be used to calculate the distance to.
	 * @param aColors
	 *            Colors.
	 * @param lColors
	 *            Colors.
	 * @param m
	 *            The measure which defines how the loss is calculated and which
	 *            color model will be used. This should be an Lab color
	 *            difference measure.
	 * @param start
	 *            Start index of the subset of the input colors (start < end).
	 *            start will be member of the subset.
	 * @param end
	 *            End index of the subset of the input colors (start < end). end
	 *            will not be member of the subset.
	 * @return The mean loss value.
	 */
	static private float getLoss(RColor rvMean, RColor[] aColors, ArrayList<RColor> lColors, ColorDifferenceMeasure m,
			int start, int end) {
		float loss = 0f;

		LabColor mean = rvMean.toLab();

		for (int i = start; i < end; i++) {
			float diff = 0f;
			LabColor c = aColors != null ? aColors[i].toLab() : lColors.get(i).toLab();

			if (m == ColorDifferenceMeasure.LabDeltaE1994) {
				diff = ColorDifference.labDeltaE1994(mean, c);
			} else if (m == ColorDifferenceMeasure.LabDeltaE2000) {
				diff = ColorDifference.labDeltaE2000(mean, c);
			} else {
				diff = ColorDifference.labDeltaE(mean, c);
			}

			loss += diff;
		}

		return loss / (end - start);
	}

	/**
	 * Lerps the two input colors and creates a new color. The values of the
	 * colors will be interpolated linear. The interpolation is defined by the
	 * amt parameter. For amt=0 the outcome will be input1, for amt=1 the
	 * outcome will be input2.
	 * 
	 * @param input1
	 *            The first color.
	 * @param input2
	 *            The second color.
	 * @param amt
	 *            The lerping value [0, 1].
	 * @return A new 'mixed' color.
	 */
	static public LabColor lerpColors(RColor input1, RColor input2, float amt) {
		amt = RMath.constrain(amt, 0, 1);

		LabColor c1 = input1.toLab();
		LabColor c2 = input2.toLab();

		return new LabColor(RMath.map(amt, 0, 1, c1.getLuminance(), c2.getLuminance()),
				RMath.map(amt, 0, 1, c1.getA(), c2.getA()), RMath.map(amt, 0, 1, c1.getB(), c2.getB()),
				RMath.map(amt, 0, 1, c1.getAlpha(), c2.getAlpha()));
	}

	/**
	 * Creates a new opaque LabColor with random values.
	 * 
	 * @return The new color.
	 */
	static public LabColor getRandomLab() {

		// since the valid ranges for a/b varies (non uniform space) the random
		// color is converted to RGB and back to get true valid values
		LabColor lab = new LabColor(RMath.random(LabColor.MIN_L, LabColor.MAX_L),
				RMath.random(LabColor.MIN_A, LabColor.MAX_A), RMath.random(LabColor.MIN_B, LabColor.MAX_B));

		return lab.toRGB().toLab();
	}

	/**
	 * Calculates the chroma value of a given set of a and b values.
	 * 
	 * @param a
	 *            The a value (e.g. getA() of any Lab color)
	 * @param b
	 *            The b value (e.g. getB() of any Lab color)
	 * @return The chroma value (chroma of LCH color model)
	 */
	static public float getChroma(Float a, Float b) {
		return (float) Math.pow(Math.pow(a, 2) + Math.pow(b, 2), 0.5);
	}

	/**
	 * Calculates the hue value of a given set of a and b values.
	 * 
	 * @param a
	 *            The a value (e.g. getA() of any Lab color)
	 * @param b
	 *            The b value (e.g. getB() of any Lab color)
	 * @return The hue value (hue of LCH color model)
	 */
	static public float getHue(double a, double b) {

		int bias = 0;

		if (a >= 0 && b == 0) {
			return 0;
		} else if (a < 0 && b == 0) {
			return 180;
		} else if (a == 0 && b > 0) {
			return 90;
		} else if (a == 0 && b < 0) {
			return 270;
		} else if (a < 0) {
			bias = 180;
		} else if (a > 0 && b < 0) {
			bias = 360;
		}

		return (float) (bias + 180 * Math.atan(b / a) / (Math.PI));
	}

	/*
	 * ------------------------------------------------------------------------
	 * | Conversion |
	 * ------------------------------------------------------------------------
	 */

	// RGB -----------------------

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.returnvoid.color.RVColor#toRGB()
	 */
	@Override
	public RGBColor toRGB() {
		return this.toXYZ().toRGB();
	}

	// HSB -----------------------

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.returnvoid.color.RVColor#toHSB()
	 */
	@Override
	public HSBColor toHSB() {
		return this.toXYZ().toRGB().toHSB();
	}

	// XYZ -----------------------

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.returnvoid.color.RVColor#toXYZ()
	 */
	@Override
	public XYZColor toXYZ() {
		double y = (channels[0] + 16) / 116;
		double x = channels[1] / 500 + y;
		double z = y - channels[2] / 200;

		if (Math.pow(y, 3) > 0.008856) {
			y = Math.pow(y, 3);
		} else {
			y = (y - 16.0 / 116) / 7.787;
		}

		if (Math.pow(x, 3) > 0.008856) {
			x = Math.pow(x, 3);
		} else {
			x = (x - 16.0 / 116) / 7.787;
		}

		if (Math.pow(z, 3) > 0.008856) {
			z = Math.pow(z, 3);
		} else {
			z = (z - 16.0 / 116) / 7.787;
		}

		return new XYZColor((float) (x * XYZColor.REFERENCE_X), (float) (y * XYZColor.REFERENCE_Y),
				(float) (z * XYZColor.REFERENCE_Z), this.getAlpha());
	}

	// Lab -----------------------

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.returnvoid.color.RVColor#toLab()
	 */
	public LabColor toLab() {
		return this;
	}

	// LCH -----------------------

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.returnvoid.color.RVColor#toLCH()
	 */
	@Override
	public LCHColor toLCH() {
		/*
		 * float h = (float) Math.atan2(this.getB(), this.getA());
		 * 
		 * if (h > 0) { h = (float) (h / Math.PI * 180); } else { h = (float)
		 * (360 - Math.abs(h) / Math.PI * 180); }
		 * 
		 * return new LCHColor(this.getLuminance(), (float)
		 * Math.pow(Math.pow(this.getA(), 2) + Math.pow(this.getB(), 2), 0.5),
		 * h, this.getAlpha());
		 */

		return new LCHColor(this.getLuminance(), LabColor.getChroma(this.getA(), this.getB()),
				LabColor.getHue(this.getA(), this.getB()), this.getAlpha());
	}
}