package net.returnvoid.color;

import java.awt.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 RGB color space. A RGBColor can be converted to any
 * other color model. To use this in Processing use the getColor() Method:<br>
 * 
 * <pre>
 * fill(myRGBObject.getColor());
 * </pre>
 * 
 * The difference between multiple color objects can be calculated with the
 * methods of ColorDifference class. <br>
 * 
 * R = Red [0, 255]<br>
 * G = Green [0, 255]<br>
 * B = Blue [0, 255] <br>
 * A = alpha = transparency [0, 255] <br>
 * <br>
 * 
 * @author Diana Lange
 *
 */
public class RGBColor implements RColor {

	/**
	 * The values of R - G - B - A
	 */
	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.RGBColor;

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

	/**
	 * Creates a new opaque RGBColor object. The input values will be
	 * constrained to the valid range of RGB.
	 * 
	 * @param r
	 *            The red value [0, 255]
	 * @param g
	 *            The green value [0, 255]
	 * @param b
	 *            The blue value [0, 255]
	 */
	public RGBColor(Float r, Float g, Float b) {
		this();
		this.setRed(r).setGreen(g).setBlue(b).setAlpha(255f);
	}

	/**
	 * Creates a new RGBColor object. The input values will be constrained to
	 * the valid range of RGB.
	 * 
	 * @param r
	 *            The red value [0, 255]
	 * @param g
	 *            The green value [0, 255]
	 * @param b
	 *            The blue value [0, 255]
	 * @param alpha
	 *            THe alpha value [0, 255]
	 */
	public RGBColor(Float r, Float g, Float b, Float alpha) {
		wasUpdated = true;
		hex = 0;
		channels = new Float[4];
		this.setRed(r).setGreen(g).setBlue(b).setAlpha(alpha);
	}

	/**
	 * Creates a new RGBColor object. The input values will be constrained to
	 * the valid range of RGBColor.
	 * 
	 * @param channels
	 *            Either [gray], [gray, alpha] or [r, g, b] or [r, g, b, alpha].
	 */
	public RGBColor(float... channels) {
		this();
		Float r;
		Float g;
		Float b;
		Float a;

		if (channels.length == 0) {
			r = g = b = 0f;
			a = 255f;
		} else if (channels.length == 1) {
			r = g = b = channels[0];
			a = 255f;
		} else if (channels.length == 2) {
			r = g = b = channels[0];
			a = channels[1];
		} else if (channels.length == 3) {
			r = channels[0];
			g = channels[1];
			b = channels[2];
			a = 255f;
		} else {
			r = channels[0];
			g = channels[1];
			b = channels[2];
			a = channels[3];
		}

		this.setRed(r).setGreen(g).setBlue(b).setAlpha(a);
	}

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

		if (wasUpdated) {
			RGBColor rgb = this.toRGB();

			hex = channels[3].intValue() << 24 | rgb.channels[0].intValue() << 16 | rgb.channels[1].intValue() << 8
					| rgb.channels[2].intValue();

			wasUpdated = false;
		}

		return hex;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.returnvoid.color.RVColor#setAlpha(float)
	 */
	@Override
	public RGBColor setAlpha(Float alpha) {

		wasUpdated = true;
		channels[3] = alpha >= 0 && alpha <= 255 ? alpha : (alpha < 0 ? 0 : 255);
		return this;
	}

	/**
	 * Sets the value of alpha / transparency. The range of alpha is [0, 255].
	 * 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 RGBColor 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 red value. The input value will be constrained to the valid
	 * range of RGB.
	 * 
	 * @param r
	 *            The new red value [0, 255].
	 * @return The current RGBColor object.
	 */
	public RGBColor setRed(Float r) {
		wasUpdated = true;
		channels[0] = r >= 0 && r <= 255 ? r : (r < 0 ? 0 : 255);

		return this;
	}

	/**
	 * Sets the channel values by the given color.
	 * 
	 * @param hex
	 *            The new color of this object. Should be in range of
	 *            000000-FFFFFF.
	 * @return The current RGBColor object.
	 */
	public RGBColor set(int hex) {
		this.channels[0] = (float) ((hex >> 16) & 255);
		this.channels[1] = (float) ((hex >> 8) & 255);
		this.channels[2] = (float) (hex & 255);

		return this;
	}

	/**
	 * Sets the red value. The input value will be constrained to the valid
	 * range of RGB.
	 * 
	 * @param r
	 *            The new red value [0, 255].
	 * @return The current RGBColor object.
	 */
	public RGBColor setRed(int r) {
		return setRed((float) r);
	}

	/**
	 * Sets the green value. The input value will be constrained to the valid
	 * range of RGB.
	 * 
	 * @param g
	 *            The new green value [0, 255].
	 * @return The current RGBColor object.
	 */
	public RGBColor setGreen(Float g) {
		wasUpdated = true;
		channels[1] = g >= 0 && g <= 255 ? g : (g < 0 ? 0 : 255);

		return this;
	}

	/**
	 * Sets the green value. The input value will be constrained to the valid
	 * range of RGB.
	 * 
	 * @param g
	 *            The new green value [0, 255].
	 * @return The current RGBColor object.
	 */
	public RGBColor setGreen(int g) {
		return setGreen((float) g);
	}

	/**
	 * Sets the blue value. The input value will be constrained to the valid
	 * range of RGB.
	 * 
	 * @param b
	 *            The new blue value [0, 255].
	 * @return The current RGBColor object.
	 */
	public RGBColor setBlue(Float b) {
		wasUpdated = true;
		channels[2] = b >= 0 && b <= 255 ? b : (b < 0 ? 0 : 255);

		return this;
	}

	/**
	 * Sets the blue value. The input value will be constrained to the valid
	 * range of RGB.
	 * 
	 * @param b
	 *            The new blue value [0, 255].
	 * @return The current RGBColor object.
	 */
	public RGBColor setBlue(int b) {
		return setBlue((float) b);
	}

	/**
	 * Returns the current value of red of this color.
	 * 
	 * @return The red of this color.
	 */
	public Float getRed() {
		return channels[0];
	}

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

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

	/*
	 * (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] /= 255;
		vals[1] /= 255;
		vals[2] /= 255;
		vals[3] /= 255;

		return vals;
	}

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

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

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.returnvoid.color.RVColor#copy()
	 */
	@Override
	public RGBColor copy() {
		return new RGBColor(new Float(getRed()), new Float(getGreen()), new Float(getBlue()), 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;
		}

		RGBColor rgbOther = (RGBColor) other;

		for (int i = 0; i < channels.length; i++) {
			if (channels[i] != rgbOther.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 RGBColor object that represents the mean color.
	 */
	static public RGBColor getMeanColor(RColor... colors) {
		return RGBColor.getMeanColor(0, colors.length, colors);
	}

	/**
	 * Calculates the mean color of the input colors
	 * 
	 * @param colors
	 *            Colors.
	 * @return The new RGBColor object that represents the mean color.
	 */
	static public RGBColor getMeanColor(ArrayList<RColor> colors) {
		return RGBColor.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 RGBColor object that represents the mean color.
	 */
	static public RGBColor getMeanColor(int start, int end, RColor... colors) {

		return RGBColor.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 RGBColor object that represents the mean color.
	 */
	static public RGBColor getMeanColor(int start, int end, ArrayList<RColor> colors) {

		return RGBColor.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 RGBColor object that represents the mean color.
	 */
	static private RGBColor getMeanColor(int start, int end, RColor[] aColors, ArrayList<RColor> lColors) {

		float[] means = { 0, 0, 0, 0 };
		for (int i = start; i < end; i++) {
			RGBColor rgb = aColors != null ? aColors[i].toRGB() : lColors.get(i).toRGB();
			means[0] += rgb.getRed();
			means[1] += rgb.getGreen();
			means[2] += rgb.getBlue();
			means[3] += rgb.getAlpha();
		}

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

		return new RGBColor(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 RGB color
	 *            difference measure.
	 * @return The mean loss value.
	 */
	static public Float getLoss(RColor rvMean, RColor[] colors, ColorDifferenceMeasure m) {
		return RGBColor.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 RGB 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 RGBColor.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 colors
	 *            Colors.
	 * @param m
	 *            The measure which defines how the loss is calculated and which
	 *            color model will be used. This should be an RGB color
	 *            difference measure.
	 * @return The mean loss value.
	 */
	static public Float getLoss(RColor rvMean, ArrayList<RColor> colors, ColorDifferenceMeasure m) {
		return RGBColor.getLoss(rvMean, null, colors, m, 0, colors.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 colors
	 *            Colors.
	 * @param m
	 *            The measure which defines how the loss is calculated and which
	 *            color model will be used. This should be an RGB 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> colors, ColorDifferenceMeasure m, int start, int end) {
		return RGBColor.getLoss(rvMean, null, colors, 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 RGB 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 = 0;

		RColor mean = rvMean.toRGB();

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

			if (m == ColorDifferenceMeasure.RGB2R4G3B) {
				diff = ColorDifference.rgbWeighted(mean, c);
			} else if (m == ColorDifferenceMeasure.RGBMeanRed) {
				diff = ColorDifference.rgbMeanRed(mean, c);
			} else {
				diff = ColorDifference.rgbEuclidean(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 RGBColor lerpColors(RColor input1, RColor input2, float amt) {
		amt = RMath.constrain(amt, 0, 1);

		RGBColor c1 = input1.toRGB();
		RGBColor c2 = input2.toRGB();

		return new RGBColor(RMath.map(amt, 0, 1, c1.getRed(), c2.getRed()),
				RMath.map(amt, 0, 1, c1.getGreen(), c2.getGreen()), RMath.map(amt, 0, 1, c1.getBlue(), c2.getBlue()),
				RMath.map(amt, 0, 1, c1.getAlpha(), c2.getAlpha()));
	}

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

		return new RGBColor(RMath.random(255.0f), RMath.random(255.0f), RMath.random(255.0f));
	}

	/**
	 * Creates an opaque RGBColor by the given input value. Usage:
	 * 
	 * <pre>
	 * color c1 = color(255, 0, 0);
	 * color c2 = #FF0000;
	 * RGBColor rgb1 = RGBColor.toRGB(c1);
	 * RGBColor rgb2 = RGBColor.toRGB(c2);
	 * </pre>
	 * 
	 * @param hex
	 *            The color value.
	 * @return The color object.
	 */
	static public RGBColor toRGB(Integer hex) {
		return new RGBColor((hex >> 16) & 255, (hex >> 8) & 255, hex & 255);
	}

	/**
	 * Creates RGBColor with transparency by the given input value. Usage:
	 * 
	 * <pre>
	 * color c1 = color(255, 0, 0, 120);
	 * color c2 = #FF0000;
	 * RGBColor rgb1 = RGBColor.toRGB(c1, alpha(c1));
	 * RGBColor rgb2 = RGBColor.toRGB(c2, 120);
	 * </pre>
	 * 
	 * @param hex
	 *            The color value.
	 * @param alpha
	 *            The transparency value.
	 * @return The color object.
	 */
	static public RGBColor toRGB(Integer hex, float alpha) {
		return new RGBColor((hex >> 16) & 255, (hex >> 8) & 255, hex & 255, alpha);
	}

	/**
	 * Creates HSBColor with given RGB values. Usage:
	 * 
	 * <pre>
	 * HSBColor hsb = RGBColor.toHSB(255.0, 0, 255.0);
	 * </pre>
	 * 
	 * @param paramters
	 *            Either [gray], [gray, alpha] or [r, g, b] or [r, g, b, alpha].
	 * @return The color object.
	 */
	static public HSBColor toHSB(float... paramters) {
		float r;
		float g;
		float b;
		float a;

		if (paramters.length == 0) {
			r = g = b = 0f;
			a = 255f;
		} else if (paramters.length == 1) {
			r = g = b = paramters[0];
			a = 255f;
		} else if (paramters.length == 2) {
			r = g = b = paramters[0];
			a = paramters[1];
		} else if (paramters.length == 3) {
			r = paramters[0];
			g = paramters[1];
			b = paramters[2];
			a = 255f;
		} else {
			r = paramters[0];
			g = paramters[1];
			b = paramters[2];
			a = paramters[3];
		}

		float[] vals = Color.RGBtoHSB((int) r, (int) g, (int) b, null);

		return new HSBColor(vals[0] * 360, vals[1] * 100, vals[2] * 100, 100 * a / 255);
	}

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

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

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

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

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.returnvoid.color.RVColor#toHSB()
	 */
	@Override
	public HSBColor toHSB() {
		return RGBColor.toHSB(channels[0], channels[1], channels[2], channels[3]);
	}

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

	/*
	 * (non-Javadoc)
	 * 
	 * @see de.returnvoid.color.RVColor#toXYZ()
	 */
	@Override
	public XYZColor toXYZ() {
		float r = channels[0] / 255;
		float g = channels[1] / 255;
		float b = channels[2] / 255;

		if (r > 0.04045) {
			r = (float) Math.pow((r + 0.055) / 1.055, 2.4);
		} else {
			r /= 12.92;
		}

		if (g > 0.04045) {
			g = (float) Math.pow((g + 0.055) / 1.055, 2.4);
		} else {
			g /= 12.92;
		}

		if (b > 0.04045) {
			b = (float) Math.pow((b + 0.055) / 1.055, 2.4);
		} else {
			b /= 12.92;
		}

		r *= 100;
		g *= 100;
		b *= 100;

		return new XYZColor(r * 0.4124f + g * 0.3576f + b * 0.1805f, r * 0.2126f + g * 0.7152f + b * 0.0722f,
				r * 0.0193f + g * 0.1192f + b * 0.9505f, 100 * channels[3] / 255);
	}

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

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

		return this.toXYZ().toLab();
	}

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

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

		return this.toLab().toLCH();
	}

}
