/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.sanselan.color;

import org.apache.sanselan.util.Debug;

public abstract class ColorConversions
{
	public final static void main(String args[])
	{
		try
		{
			{
				for (int C = 0; C <= 256; C += 64)
					for (int M = 0; M <= 256; M += 64)
						for (int Y = 0; Y <= 256; Y += 64)
							for (int K = 0; K <= 256; K += 64)
							{

								int rgb1 = ColorConversions.convertCMYKtoRGB(
										Math.min(255, C), Math.min(255, M),
										Math.min(255, Y), Math.min(255, K));
								int rgb2 = ColorConversions
										.convertCMYKtoRGB_old(Math.min(255, C),
												Math.min(255, M), Math.min(255,
														Y), Math.min(255, K));

								if (rgb1 != rgb2)
								{
									Debug.debug();
									Debug.debug("C", C);
									Debug.debug("M", M);
									Debug.debug("Y", Y);
									Debug.debug("K", K);
									Debug.debug("rgb1", rgb1 + " ("
											+ Integer.toHexString(rgb1) + ")");
									Debug.debug("rgb2", rgb2 + " ("
											+ Integer.toHexString(rgb2) + ")");
								}
							}
			}
			int sample_rgbs[] = {
					0xffffffff, //	
					0xff000000, //	
					0xffff0000, //	
					0xff00ff00, //	
					0xff0000ff, //	
					0xffff00ff, //	
					0xfff0ff00, //	
					0xff00ffff, //	
					0x00000000, //	
					0xff7f7f7f, //	
			};
			for (int i = 0; i < sample_rgbs.length; i++)
			{
				int rgb = sample_rgbs[i];

				ColorXYZ xyz;
				{
					xyz = ColorConversions.convertRGBtoXYZ(rgb);
					int xyz_rgb = ColorConversions.convertXYZtoRGB(xyz);

					Debug.debug();
					Debug.debug("rgb", rgb + " (" + Integer.toHexString(rgb)
							+ ")");
					Debug.debug("xyz", xyz);
					Debug.debug("xyz_rgb", xyz_rgb + " ("
							+ Integer.toHexString(xyz_rgb) + ")");
					if ((0xffffff & xyz_rgb) != (0xffffff & rgb))
						Debug.debug("!!!!!!!!!!!!!!!!!!!!!!!");
				}
				ColorCIELab cielab;
				{
					cielab = ColorConversions.convertXYZtoCIELab(xyz);
					ColorXYZ cielab_xyz = ColorConversions
							.convertCIELabtoXYZ(cielab);
					int cielab_xyz_rgb = ColorConversions
							.convertXYZtoRGB(cielab_xyz);

					Debug.debug("cielab", cielab);
					Debug.debug("cielab_xyz", cielab_xyz);
					Debug.debug("cielab_xyz_rgb", cielab_xyz_rgb + " ("
							+ Integer.toHexString(cielab_xyz_rgb) + ")");
					if ((0xffffff & cielab_xyz_rgb) != (0xffffff & rgb))
						Debug.debug("!!!!!!!!!!!!!!!!!!!!!!!");
				}

				{
					ColorHunterLab hunterlab = ColorConversions
							.convertXYZtoHunterLab(xyz);
					ColorXYZ hunterlab_xyz = ColorConversions
							.convertHunterLabtoXYZ(hunterlab);
					int hunterlab_xyz_rgb = ColorConversions
							.convertXYZtoRGB(hunterlab_xyz);

					Debug.debug("hunterlab", hunterlab);
					Debug.debug("hunterlab_xyz", hunterlab_xyz);
					Debug.debug("hunterlab_xyz_rgb", hunterlab_xyz_rgb + " ("
							+ Integer.toHexString(hunterlab_xyz_rgb) + ")");
					if ((0xffffff & hunterlab_xyz_rgb) != (0xffffff & rgb))
						Debug.debug("!!!!!!!!!!!!!!!!!!!!!!!");
				}

				{
					ColorCMY cmy = ColorConversions.convertRGBtoCMY(rgb);
					ColorCMYK cmyk = ColorConversions.convertCMYtoCMYK(cmy);
					ColorCMY cmyk_cmy = ColorConversions.convertCMYKtoCMY(cmyk);
					int cmyk_cmy_rgb = ColorConversions
							.convertCMYtoRGB(cmyk_cmy);

					Debug.debug("cmy", cmy);
					Debug.debug("cmyk", cmyk);
					Debug.debug("cmyk_cmy", cmyk_cmy);
					Debug.debug("cmyk_cmy_rgb", cmyk_cmy_rgb + " ("
							+ Integer.toHexString(cmyk_cmy_rgb) + ")");
					if ((0xffffff & cmyk_cmy_rgb) != (0xffffff & rgb))
						Debug.debug("!!!!!!!!!!!!!!!!!!!!!!!");
				}

				{
					ColorHSL hsl = ColorConversions.convertRGBtoHSL(rgb);
					int hsl_rgb = ColorConversions.convertHSLtoRGB(hsl);

					Debug.debug("hsl", hsl);
					Debug.debug("hsl_rgb", hsl_rgb + " ("
							+ Integer.toHexString(hsl_rgb) + ")");
					if ((0xffffff & hsl_rgb) != (0xffffff & rgb))
						Debug.debug("!!!!!!!!!!!!!!!!!!!!!!!");
				}
				{
					ColorHSV hsv = ColorConversions.convertRGBtoHSV(rgb);
					int hsv_rgb = ColorConversions.convertHSVtoRGB(hsv);

					Debug.debug("hsv", hsv);
					Debug.debug("hsv_rgb", hsv_rgb + " ("
							+ Integer.toHexString(hsv_rgb) + ")");
					if ((0xffffff & hsv_rgb) != (0xffffff & rgb))
						Debug.debug("!!!!!!!!!!!!!!!!!!!!!!!");
				}

				{
					ColorCIELCH cielch = ColorConversions
							.convertCIELabtoCIELCH(cielab);
					ColorCIELab cielch_cielab = ColorConversions
							.convertCIELCHtoCIELab(cielch);

					Debug.debug("cielch", cielch);
					Debug.debug("cielch_cielab", cielch_cielab);
				}

				{
					ColorCIELuv cieluv = ColorConversions
							.convertXYZtoCIELuv(xyz);
					ColorXYZ cieluv_xyz = ColorConversions
							.convertCIELuvtoXYZ(cieluv);

					Debug.debug("cieluv", cieluv);
					Debug.debug("cieluv_xyz", cieluv_xyz);
				}

			}
		}
		catch (Throwable e)
		{
			Debug.debug(e);
		}
	}

	public static final ColorCIELab convertXYZtoCIELab(ColorXYZ xyz)
	{
		return convertXYZtoCIELab(xyz.X, xyz.Y, xyz.Z);
	}

	private static final double ref_X = 95.047;
	private static final double ref_Y = 100.000;
	private static final double ref_Z = 108.883;

	public static final ColorCIELab convertXYZtoCIELab(double X, double Y,
			double Z)
	{

		double var_X = X / ref_X; //ref_X =  95.047  Observer= 2°, Illuminant= D65
		double var_Y = Y / ref_Y; //ref_Y = 100.000
		double var_Z = Z / ref_Z; //ref_Z = 108.883

		if (var_X > 0.008856)
			var_X = Math.pow(var_X, (1 / 3.0));
		else
			var_X = (7.787 * var_X) + (16 / 116.0);
		if (var_Y > 0.008856)
			var_Y = Math.pow(var_Y, 1 / 3.0);
		else
			var_Y = (7.787 * var_Y) + (16 / 116.0);
		if (var_Z > 0.008856)
			var_Z = Math.pow(var_Z, 1 / 3.0);
		else
			var_Z = (7.787 * var_Z) + (16 / 116.0);

		double L = (116 * var_Y) - 16;
		double a = 500 * (var_X - var_Y);
		double b = 200 * (var_Y - var_Z);
		return new ColorCIELab(L, a, b);
	}

	public static final ColorXYZ convertCIELabtoXYZ(ColorCIELab cielab)
	{
		return convertCIELabtoXYZ(cielab.L, cielab.a, cielab.b);
	}

	public static final ColorXYZ convertCIELabtoXYZ(double L, double a, double b)
	{
		double var_Y = (L + 16) / 116.0;
		double var_X = a / 500 + var_Y;
		double var_Z = var_Y - b / 200.0;

		if (Math.pow(var_Y, 3) > 0.008856)
			var_Y = Math.pow(var_Y, 3);
		else
			var_Y = (var_Y - 16 / 116.0) / 7.787;
		if (Math.pow(var_X, 3) > 0.008856)
			var_X = Math.pow(var_X, 3);
		else
			var_X = (var_X - 16 / 116.0) / 7.787;
		if (Math.pow(var_Z, 3) > 0.008856)
			var_Z = Math.pow(var_Z, 3);
		else
			var_Z = (var_Z - 16 / 116.0) / 7.787;

		double X = ref_X * var_X; //ref_X =  95.047  Observer= 2°, Illuminant= D65
		double Y = ref_Y * var_Y; //ref_Y = 100.000
		double Z = ref_Z * var_Z; //ref_Z = 108.883

		return new ColorXYZ(X, Y, Z);
	}

	public static final ColorHunterLab convertXYZtoHunterLab(ColorXYZ xyz)
	{
		return convertXYZtoHunterLab(xyz.X, xyz.Y, xyz.Z);
	}

	public static final ColorHunterLab convertXYZtoHunterLab(double X,
			double Y, double Z)
	{
		double L = 10 * Math.sqrt(Y);
		double a = 17.5 * (((1.02 * X) - Y) / Math.sqrt(Y));
		double b = 7 * ((Y - (0.847 * Z)) / Math.sqrt(Y));

		return new ColorHunterLab(L, a, b);
	}

	public static final ColorXYZ convertHunterLabtoXYZ(ColorHunterLab cielab)
	{
		return convertHunterLabtoXYZ(cielab.L, cielab.a, cielab.b);
	}

	public static final ColorXYZ convertHunterLabtoXYZ(double L, double a,
			double b)
	{
		double var_Y = L / 10;
		double var_X = a / 17.5 * L / 10;
		double var_Z = b / 7 * L / 10;

		double Y = Math.pow(var_Y, 2);
		double X = (var_X + Y) / 1.02;
		double Z = -(var_Z - Y) / 0.847;

		return new ColorXYZ(X, Y, Z);
	}

	public static final int convertXYZtoRGB(ColorXYZ xyz)
	{
		return convertXYZtoRGB(xyz.X, xyz.Y, xyz.Z);
	}

	public static final int convertXYZtoRGB(double X, double Y, double Z)
	{
		//Observer = 2°, Illuminant = D65
		double var_X = X / 100.0; //Where X = 0 ÷  95.047
		double var_Y = Y / 100.0; //Where Y = 0 ÷ 100.000
		double var_Z = Z / 100.0; //Where Z = 0 ÷ 108.883

		double var_R = var_X * 3.2406 + var_Y * -1.5372 + var_Z * -0.4986;
		double var_G = var_X * -0.9689 + var_Y * 1.8758 + var_Z * 0.0415;
		double var_B = var_X * 0.0557 + var_Y * -0.2040 + var_Z * 1.0570;

		if (var_R > 0.0031308)
			var_R = 1.055 * Math.pow(var_R, (1 / 2.4)) - 0.055;
		else
			var_R = 12.92 * var_R;
		if (var_G > 0.0031308)
			var_G = 1.055 * Math.pow(var_G, (1 / 2.4)) - 0.055;
		else
			var_G = 12.92 * var_G;
		if (var_B > 0.0031308)
			var_B = 1.055 * Math.pow(var_B, (1 / 2.4)) - 0.055;
		else
			var_B = 12.92 * var_B;

		double R = (var_R * 255);
		double G = (var_G * 255);
		double B = (var_B * 255);

		return convertRGBtoRGB(R, G, B);
	}

	public static final ColorXYZ convertRGBtoXYZ(int rgb)
	{
		int r = 0xff & (rgb >> 16);
		int g = 0xff & (rgb >> 8);
		int b = 0xff & (rgb >> 0);

		double var_R = r / 255.0; //Where R = 0 ÷ 255
		double var_G = g / 255.0; //Where G = 0 ÷ 255
		double var_B = b / 255.0; //Where B = 0 ÷ 255

		if (var_R > 0.04045)
			var_R = Math.pow((var_R + 0.055) / 1.055, 2.4);
		else
			var_R = var_R / 12.92;
		if (var_G > 0.04045)
			var_G = Math.pow((var_G + 0.055) / 1.055, 2.4);
		else
			var_G = var_G / 12.92;
		if (var_B > 0.04045)
			var_B = Math.pow((var_B + 0.055) / 1.055, 2.4);
		else
			var_B = var_B / 12.92;

		var_R = var_R * 100;
		var_G = var_G * 100;
		var_B = var_B * 100;

		//		Observer. = 2°, Illuminant = D65
		double X = var_R * 0.4124 + var_G * 0.3576 + var_B * 0.1805;
		double Y = var_R * 0.2126 + var_G * 0.7152 + var_B * 0.0722;
		double Z = var_R * 0.0193 + var_G * 0.1192 + var_B * 0.9505;

		return new ColorXYZ(X, Y, Z);
	}

	public static final ColorCMY convertRGBtoCMY(int rgb)
	{
		int R = 0xff & (rgb >> 16);
		int G = 0xff & (rgb >> 8);
		int B = 0xff & (rgb >> 0);

		//		RGB values = 0 ÷ 255
		//		CMY values = 0 ÷ 1

		double C = 1 - (R / 255.0);
		double M = 1 - (G / 255.0);
		double Y = 1 - (B / 255.0);

		return new ColorCMY(C, M, Y);
	}

	public static final int convertCMYtoRGB(ColorCMY cmy)
	{
		//		CMY values = 0 ÷ 1
		//		RGB values = 0 ÷ 255

		double R = (1 - cmy.C) * 255.0;
		double G = (1 - cmy.M) * 255.0;
		double B = (1 - cmy.Y) * 255.0;

		return convertRGBtoRGB(R, G, B);
	}

	public static final ColorCMYK convertCMYtoCMYK(ColorCMY cmy)
	{
		//		Where CMYK and CMY values = 0 ÷ 1

		double C = cmy.C;
		double M = cmy.M;
		double Y = cmy.Y;

		double var_K = 1.0;

		if (C < var_K)
			var_K = C;
		if (M < var_K)
			var_K = M;
		if (Y < var_K)
			var_K = Y;
		if (var_K == 1)
		{ //Black
			C = 0;
			M = 0;
			Y = 0;
		}
		else
		{
			C = (C - var_K) / (1 - var_K);
			M = (M - var_K) / (1 - var_K);
			Y = (Y - var_K) / (1 - var_K);
		}
		return new ColorCMYK(C, M, Y, var_K);
	}

	public static final ColorCMY convertCMYKtoCMY(ColorCMYK cmyk)
	{
		return convertCMYKtoCMY(cmyk.C, cmyk.M, cmyk.Y, cmyk.K);
	}

	public static final ColorCMY convertCMYKtoCMY(double C, double M, double Y,
			double K)
	{
		//	Where CMYK and CMY values = 0 ÷ 1

		C = (C * (1 - K) + K);
		M = (M * (1 - K) + K);
		Y = (Y * (1 - K) + K);

		return new ColorCMY(C, M, Y);
	}

	public static final int convertCMYKtoRGB(int c, int m, int y, int k)
	//	throws ImageReadException, IOException
	{
		double C = c / 255.0;
		double M = m / 255.0;
		double Y = y / 255.0;
		double K = k / 255.0;

		return convertCMYtoRGB(convertCMYKtoCMY(C, M, Y, K));
	}

	public static final ColorHSL convertRGBtoHSL(int rgb)
	{

		int R = 0xff & (rgb >> 16);
		int G = 0xff & (rgb >> 8);
		int B = 0xff & (rgb >> 0);

		double var_R = (R / 255.0); //Where RGB values = 0 ÷ 255
		double var_G = (G / 255.0);
		double var_B = (B / 255.0);

		double var_Min = Math.min(var_R, Math.min(var_G, var_B)); //Min. value of RGB
		double var_Max = Math.max(var_R, Math.max(var_G, var_B)); //Max. value of RGB
		double del_Max = var_Max - var_Min; //Delta RGB value

		double L = (var_Max + var_Min) / 2.0;

		double H, S;
		//		Debug.debug("del_Max", del_Max);
		if (del_Max == 0) //This is a gray, no chroma...
		{
			H = 0; //HSL results = 0 ÷ 1
			S = 0;
		}
		else
		//Chromatic data...
		{
			//			Debug.debug("L", L);

			if (L < 0.5)
				S = del_Max / (var_Max + var_Min);
			else
				S = del_Max / (2 - var_Max - var_Min);

			//			Debug.debug("S", S);

			double del_R = (((var_Max - var_R) / 6) + (del_Max / 2)) / del_Max;
			double del_G = (((var_Max - var_G) / 6) + (del_Max / 2)) / del_Max;
			double del_B = (((var_Max - var_B) / 6) + (del_Max / 2)) / del_Max;

			if (var_R == var_Max)
				H = del_B - del_G;
			else if (var_G == var_Max)
				H = (1 / 3.0) + del_R - del_B;
			else if (var_B == var_Max)
				H = (2 / 3.0) + del_G - del_R;
			else
			{
				Debug.debug("uh oh");
				H = 0; // cmc
			}

			//			Debug.debug("H1", H);

			if (H < 0)
				H += 1;
			if (H > 1)
				H -= 1;

			//			Debug.debug("H2", H);
		}

		return new ColorHSL(H, S, L);
	}

	public static int convertHSLtoRGB(ColorHSL hsl)
	{
		return convertHSLtoRGB(hsl.H, hsl.S, hsl.L);
	}

	public static int convertHSLtoRGB(double H, double S, double L)
	{
		double R, G, B;

		if (S == 0) //HSL values = 0 ÷ 1
		{
			R = L * 255; //RGB results = 0 ÷ 255
			G = L * 255;
			B = L * 255;
		}
		else
		{
			double var_2;

			if (L < 0.5)
				var_2 = L * (1 + S);
			else
				var_2 = (L + S) - (S * L);

			double var_1 = 2 * L - var_2;

			R = 255 * convertHuetoRGB(var_1, var_2, H + (1 / 3.0));
			G = 255 * convertHuetoRGB(var_1, var_2, H);
			B = 255 * convertHuetoRGB(var_1, var_2, H - (1 / 3.0));
		}

		return convertRGBtoRGB(R, G, B);
	}

	private static double convertHuetoRGB(double v1, double v2, double vH) //Function Hue_2_RGB
	{
		if (vH < 0)
			vH += 1;
		if (vH > 1)
			vH -= 1;
		if ((6 * vH) < 1)
			return (v1 + (v2 - v1) * 6 * vH);
		if ((2 * vH) < 1)
			return (v2);
		if ((3 * vH) < 2)
			return (v1 + (v2 - v1) * ((2 / 3.0) - vH) * 6);
		return (v1);
	}

	public static final ColorHSV convertRGBtoHSV(int rgb)
	{
		int R = 0xff & (rgb >> 16);
		int G = 0xff & (rgb >> 8);
		int B = 0xff & (rgb >> 0);

		double var_R = (R / 255.0); //RGB values = 0 ÷ 255
		double var_G = (G / 255.0);
		double var_B = (B / 255.0);

		double var_Min = Math.min(var_R, Math.min(var_G, var_B)); //Min. value of RGB
		double var_Max = Math.max(var_R, Math.max(var_G, var_B)); //Max. value of RGB
		double del_Max = var_Max - var_Min; //Delta RGB value

		double V = var_Max;

		double H, S;
		if (del_Max == 0) //This is a gray, no chroma...
		{
			H = 0; //HSV results = 0 ÷ 1
			S = 0;
		}
		else
		//Chromatic data...
		{
			S = del_Max / var_Max;

			double del_R = (((var_Max - var_R) / 6) + (del_Max / 2)) / del_Max;
			double del_G = (((var_Max - var_G) / 6) + (del_Max / 2)) / del_Max;
			double del_B = (((var_Max - var_B) / 6) + (del_Max / 2)) / del_Max;

			if (var_R == var_Max)
				H = del_B - del_G;
			else if (var_G == var_Max)
				H = (1 / 3.0) + del_R - del_B;
			else if (var_B == var_Max)
				H = (2 / 3.0) + del_G - del_R;
			else
			{
				Debug.debug("uh oh");
				H = 0; // cmc;
			}

			if (H < 0)
				H += 1;
			if (H > 1)
				H -= 1;
		}

		return new ColorHSV(H, S, V);
	}

	public static int convertHSVtoRGB(ColorHSV HSV)
	{
		return convertHSVtoRGB(HSV.H, HSV.S, HSV.V);
	}

	public static int convertHSVtoRGB(double H, double S, double V)
	{
		double R, G, B;

		if (S == 0) //HSV values = 0 ÷ 1
		{
			R = V * 255;
			G = V * 255;
			B = V * 255;
		}
		else
		{
			double var_h = H * 6;
			if (var_h == 6)
				var_h = 0; //H must be < 1
			double var_i = Math.floor(var_h); //Or ... var_i = floor( var_h )
			double var_1 = V * (1 - S);
			double var_2 = V * (1 - S * (var_h - var_i));
			double var_3 = V * (1 - S * (1 - (var_h - var_i)));

			double var_r, var_g, var_b;

			if (var_i == 0)
			{
				var_r = V;
				var_g = var_3;
				var_b = var_1;
			}
			else if (var_i == 1)
			{
				var_r = var_2;
				var_g = V;
				var_b = var_1;
			}
			else if (var_i == 2)
			{
				var_r = var_1;
				var_g = V;
				var_b = var_3;
			}
			else if (var_i == 3)
			{
				var_r = var_1;
				var_g = var_2;
				var_b = V;
			}
			else if (var_i == 4)
			{
				var_r = var_3;
				var_g = var_1;
				var_b = V;
			}
			else
			{
				var_r = V;
				var_g = var_1;
				var_b = var_2;
			}

			R = var_r * 255; //RGB results = 0 ÷ 255
			G = var_g * 255;
			B = var_b * 255;
		}

		return convertRGBtoRGB(R, G, B);
	}

	public static final int convertCMYKtoRGB_old(int sc, int sm, int sy, int sk)
	//	throws ImageReadException, IOException
	{
		int red = 255 - (sc + sk);
		int green = 255 - (sm + sk);
		int blue = 255 - (sy + sk);

		return convertRGBtoRGB(red, green, blue);
	}

	private static double cube(double f)
	{
		return f * f * f;
	}

	private static double square(double f)
	{
		return f * f;
	}

	public static final int convertCIELabtoARGBTest(int cieL, int cieA, int cieB)
	{
		double X, Y, Z;

		{

			double var_Y = (((double) cieL * 100.0 / 255.0) + 16.0) / 116.0;
			double var_X = cieA / 500.0 + var_Y;
			double var_Z = var_Y - cieB / 200.0;

			double var_x_cube = cube(var_X);
			double var_y_cube = cube(var_Y);
			double var_z_cube = cube(var_Z);

			if (var_y_cube > 0.008856)
				var_Y = var_y_cube;
			else
				var_Y = (var_Y - 16 / 116.0) / 7.787;

			if (var_x_cube > 0.008856)
				var_X = var_x_cube;
			else
				var_X = (var_X - 16 / 116.0) / 7.787;

			if (var_z_cube > 0.008856)
				var_Z = var_z_cube;
			else
				var_Z = (var_Z - 16 / 116.0) / 7.787;

			//			double ref_X = 95.047;
			//			double ref_Y = 100.000;
			//			double ref_Z = 108.883;

			X = ref_X * var_X; //ref_X =  95.047  Observer= 2°, Illuminant= D65
			Y = ref_Y * var_Y; //ref_Y = 100.000
			Z = ref_Z * var_Z; //ref_Z = 108.883

		}

		double R, G, B;
		{
			double var_X = X / 100; //X = From 0 to ref_X
			double var_Y = Y / 100; //Y = From 0 to ref_Y
			double var_Z = Z / 100; //Z = From 0 to ref_Y

			double var_R = var_X * 3.2406 + var_Y * -1.5372 + var_Z * -0.4986;
			double var_G = var_X * -0.9689 + var_Y * 1.8758 + var_Z * 0.0415;
			double var_B = var_X * 0.0557 + var_Y * -0.2040 + var_Z * 1.0570;

			if (var_R > 0.0031308)
				var_R = 1.055 * Math.pow(var_R, (1 / 2.4)) - 0.055;
			else
				var_R = 12.92 * var_R;
			if (var_G > 0.0031308)
				var_G = 1.055 * Math.pow(var_G, (1 / 2.4)) - 0.055;
			else
				var_G = 12.92 * var_G;

			if (var_B > 0.0031308)
				var_B = 1.055 * Math.pow(var_B, (1 / 2.4)) - 0.055;
			else
				var_B = 12.92 * var_B;

			R = (var_R * 255);
			G = (var_G * 255);
			B = (var_B * 255);
		}

		return convertRGBtoRGB(R, G, B);
	}

	private static final int convertRGBtoRGB(double R, double G, double B)
	{
		int red = (int) Math.round(R);
		int green = (int) Math.round(G);
		int blue = (int) Math.round(B);

		red = Math.min(255, Math.max(0, red));
		green = Math.min(255, Math.max(0, green));
		blue = Math.min(255, Math.max(0, blue));

		int alpha = 0xff;
		int rgb = (alpha << 24) | (red << 16) | (green << 8) | (blue << 0);

		return rgb;
	}

	private static final int convertRGBtoRGB(int red, int green, int blue)
	{
		red = Math.min(255, Math.max(0, red));
		green = Math.min(255, Math.max(0, green));
		blue = Math.min(255, Math.max(0, blue));

		int alpha = 0xff;
		int rgb = (alpha << 24) | (red << 16) | (green << 8) | (blue << 0);

		return rgb;
	}

	public static ColorCIELCH convertCIELabtoCIELCH(ColorCIELab cielab)
	{
		return convertCIELabtoCIELCH(cielab.L, cielab.a, cielab.b);
	}

	public static ColorCIELCH convertCIELabtoCIELCH(double L, double a, double b)
	{
		double var_H = Math.atan2(b, a); //Quadrant by signs

		if (var_H > 0)
			var_H = (var_H / Math.PI) * 180.0;
		else
			var_H = 360 - radian_2_degree(Math.abs(var_H));

		//		L = L;
		double C = Math.sqrt(square(a) + square(b));
		double H = var_H;

		return new ColorCIELCH(L, C, H);
	}

	public static ColorCIELab convertCIELCHtoCIELab(ColorCIELCH cielch)
	{
		return convertCIELCHtoCIELab(cielch.L, cielch.C, cielch.H);
	}

	public static ColorCIELab convertCIELCHtoCIELab(double L, double C, double H)
	{
		//		Where CIE-H° = 0 ÷ 360°

		//		CIE-L* = CIE-L;
		double a = Math.cos(degree_2_radian(H)) * C;
		double b = Math.sin(degree_2_radian(H)) * C;

		return new ColorCIELab(L, a, b);
	}

	public static double degree_2_radian(double degree)
	{
		return degree * Math.PI / 180.0;
	}

	public static double radian_2_degree(double radian)
	{
		return radian * 180.0 / Math.PI;
	}

	public static ColorCIELuv convertXYZtoCIELuv(ColorXYZ xyz)
	{
		return convertXYZtoCIELuv(xyz.X, xyz.Y, xyz.Z);
	}

	public static ColorCIELuv convertXYZtoCIELuv(double X, double Y, double Z)
	{
		// problems here with div by zero

		double var_U = (4 * X) / (X + (15 * Y) + (3 * Z));
		double var_V = (9 * Y) / (X + (15 * Y) + (3 * Z));

		//		Debug.debug("var_U", var_U);
		//		Debug.debug("var_V", var_V);

		double var_Y = Y / 100.0;
		//		Debug.debug("var_Y", var_Y);

		if (var_Y > 0.008856)
			var_Y = Math.pow(var_Y, (1 / 3.0));
		else
			var_Y = (7.787 * var_Y) + (16 / 116.0);

		double ref_X = 95.047; //Observer= 2°, Illuminant= D65
		double ref_Y = 100.000;
		double ref_Z = 108.883;

		//		Debug.debug("var_Y", var_Y);

		double ref_U = (4 * ref_X) / (ref_X + (15 * ref_Y) + (3 * ref_Z));
		double ref_V = (9 * ref_Y) / (ref_X + (15 * ref_Y) + (3 * ref_Z));

		//		Debug.debug("ref_U", ref_U);
		//		Debug.debug("ref_V", ref_V);

		double L = (116 * var_Y) - 16;
		double u = 13 * L * (var_U - ref_U);
		double v = 13 * L * (var_V - ref_V);

		return new ColorCIELuv(L, u, v);
	}

	public static ColorXYZ convertCIELuvtoXYZ(ColorCIELuv cielch)
	{
		return convertCIELuvtoXYZ(cielch.L, cielch.u, cielch.v);
	}

	public static ColorXYZ convertCIELuvtoXYZ(double L, double u, double v)
	{
		// problems here with div by zero

		double var_Y = (L + 16) / 116;
		if (Math.pow(var_Y, 3) > 0.008856)
			var_Y = Math.pow(var_Y, 3);
		else
			var_Y = (var_Y - 16 / 116) / 7.787;

		double ref_X = 95.047; //Observer= 2°, Illuminant= D65
		double ref_Y = 100.000;
		double ref_Z = 108.883;

		double ref_U = (4 * ref_X) / (ref_X + (15 * ref_Y) + (3 * ref_Z));
		double ref_V = (9 * ref_Y) / (ref_X + (15 * ref_Y) + (3 * ref_Z));
		double var_U = u / (13 * L) + ref_U;
		double var_V = v / (13 * L) + ref_V;

		double Y = var_Y * 100;
		double X = -(9 * Y * var_U) / ((var_U - 4) * var_V - var_U * var_V);
		double Z = (9 * Y - (15 * var_V * Y) - (var_V * X)) / (3 * var_V);

		return new ColorXYZ(X, Y, Z);
	}
}