From 79ce23b80a68a607413e034f9ebaf33c8890a292 Mon Sep 17 00:00:00 2001 From: BWS Systems Date: Wed, 25 Sep 2019 16:38:30 -0500 Subject: [PATCH] Continue color validation --- .../HABridge/hue/ColorConverter.java | 942 ++++++++++++++++++ .../bwssystems/HABridge/hue/ColorDecode.java | 52 +- .../bwssystems/HABridge/hue/XYColorSpace.java | 26 + .../color/test/ConvertCIEColorTestCase.java | 25 + 4 files changed, 1019 insertions(+), 26 deletions(-) create mode 100644 src/main/java/com/bwssystems/HABridge/hue/ColorConverter.java create mode 100644 src/main/java/com/bwssystems/HABridge/hue/XYColorSpace.java diff --git a/src/main/java/com/bwssystems/HABridge/hue/ColorConverter.java b/src/main/java/com/bwssystems/HABridge/hue/ColorConverter.java new file mode 100644 index 0000000..4d7c3c6 --- /dev/null +++ b/src/main/java/com/bwssystems/HABridge/hue/ColorConverter.java @@ -0,0 +1,942 @@ +package com.bwssystems.HABridge.hue; + +/** + * Convert between different color spaces supported. + * RGB -> CMYK -> RGB + * RGB -> YIQ -> RGB + * RGB -> YCbCr -> RGB + * RGB -> YUV -> RGB + * RGB -> RGChromaticity + * RGB -> HSV -> RGB + * RGB -> YCC -> RGB + * RGB -> YCoCg -> RGB + * RGB -> XYZ -> RGB + * RGB -> HunterLAB -> RGB + * RGB -> HLS -> RGB + * RGB -> CIE-LAB -> RGB + * XYZ -> HunterLAB -> XYZ + * XYZ -> CIE-LAB -> XYZ + * @author Diego Catalano + */ +public class ColorConverter { + + /** + * Don't let anyone instantiate this class. + */ + private ColorConverter() {} + + public static enum YCbCrColorSpace {ITU_BT_601,ITU_BT_709_HDTV}; + + // XYZ (Tristimulus) Reference values of a perfect reflecting diffuser + + //2o Observer (CIE 1931) + // X2, Y2, Z2 + public static float[] CIE2_A = {109.850f, 100f, 35.585f}; //Incandescent + public static float[] CIE2_C = {98.074f, 100f, 118.232f}; + public static float[] CIE2_D50 = {96.422f, 100f, 82.521f}; + public static float[] CIE2_D55 = {95.682f, 100f, 92.149f}; + public static float[] CIE2_D65 = {95.047f, 100f, 108.883f}; //Daylight + public static float[] CIE2_D75 = {94.972f, 100f, 122.638f}; + public static float[] CIE2_F2 = {99.187f, 100f, 67.395f}; //Fluorescent + public static float[] CIE2_F7 = {95.044f, 100f, 108.755f}; + public static float[] CIE2_F11 = {100.966f, 100f, 64.370f}; + + //10o Observer (CIE 1964) + // X2, Y2, Z2 + public static float[] CIE10_A = {111.144f, 100f, 35.200f}; //Incandescent + public static float[] CIE10_C = {97.285f, 100f, 116.145f}; + public static float[] CIE10_D50 = {96.720f, 100f, 81.427f}; + public static float[] CIE10_D55 = {95.799f, 100f, 90.926f}; + public static float[] CIE10_D65 = {94.811f, 100f, 107.304f}; //Daylight + public static float[] CIE10_D75 = {94.416f, 100f, 120.641f}; + public static float[] CIE10_F2 = {103.280f, 100f, 69.026f}; //Fluorescent + public static float[] CIE10_F7 = {95.792f, 100f, 107.687f}; + public static float[] CIE10_F11 = {103.866f, 100f, 65.627f}; + + /** + * RFB -> CMYK + * @param red Values in the range [0..255]. + * @param green Values in the range [0..255]. + * @param blue Values in the range [0..255]. + * @return CMYK color space. Normalized. + */ + public static float[] RGBtoCMYK(int red, int green, int blue){ + float[] cmyk = new float[4]; + + float r = red / 255f; + float g = green / 255f; + float b = blue / 255f; + + float k = 1.0f - Math.max(r, Math.max(g, b)); + float c = (1f-r-k) / (1f-k); + float m = (1f-g-k) / (1f-k); + float y = (1f-b-k) / (1f-k); + + cmyk[0] = c; + cmyk[1] = m; + cmyk[2] = y; + cmyk[3] = k; + + return cmyk; + } + + /** + * CMYK -> RGB + * @param c Cyan. + * @param m Magenta. + * @param y Yellow. + * @param k Black. + * @return RGB color space. + */ + public static int[] CMYKtoRGB(float c, float m, float y, float k){ + int[] rgb = new int[3]; + + rgb[0] = (int)(255 * (1-c) * (1-k)); + rgb[1] = (int)(255 * (1-m) * (1-k)); + rgb[2] = (int)(255 * (1-y) * (1-k)); + + return rgb; + } + + /** + * RGB -> YUV. + * Y in the range [0..1]. + * U in the range [-0.5..0.5]. + * V in the range [-0.5..0.5]. + * @param red Values in the range [0..255]. + * @param green Values in the range [0..255]. + * @param blue Values in the range [0..255]. + * @return YUV color space. + */ + public static float[] RGBtoYUV(int red, int green, int blue){ + + float r = (float)red / 255; + float g = (float)green / 255; + float b = (float)blue / 255; + + float[] yuv = new float[3]; + float y,u,v; + + y = (float)(0.299 * r + 0.587 * g + 0.114 * b); + u = (float)(-0.14713 * r - 0.28886 * g + 0.436 * b); + v = (float)(0.615 * r - 0.51499 * g - 0.10001 * b); + + yuv[0] = y; + yuv[1] = u; + yuv[2] = v; + + return yuv; + } + + /** + * YUV -> RGB. + * @param y Luma. In the range [0..1]. + * @param u Chrominance. In the range [-0.5..0.5]. + * @param v Chrominance. In the range [-0.5..0.5]. + * @return RGB color space. + */ + public static int[] YUVtoRGB(float y, float u, float v){ + int[] rgb = new int[3]; + float r,g,b; + + r = (float)((y + 0.000 * u + 1.140 * v) * 255); + g = (float)((y - 0.396 * u - 0.581 * v) * 255); + b = (float)((y + 2.029 * u + 0.000 * v) * 255); + + rgb[0] = (int)r; + rgb[1] = (int)g; + rgb[2] = (int)b; + + return rgb; + } + + /** + * RGB -> YIQ. + * @param red Values in the range [0..255]. + * @param green Values in the range [0..255]. + * @param blue Values in the range [0..255]. + * @return YIQ color space. + */ + public static float[] RGBtoYIQ(int red, int green, int blue){ + float[] yiq = new float[3]; + float y,i,q; + + float r = (float)red / 255; + float g = (float)green / 255; + float b = (float)blue / 255; + + y = (float)(0.299 * r + 0.587 * g + 0.114 * b); + i = (float)(0.596 * r - 0.275 * g - 0.322 * b); + q = (float)(0.212 * r - 0.523 * g + 0.311 * b); + + yiq[0] = y; + yiq[1] = i; + yiq[2] = q; + + return yiq; + } + + /** + * YIQ -> RGB. + * @param y Luma. Values in the range [0..1]. + * @param i In-phase. Values in the range [-0.5..0.5]. + * @param q Quadrature. Values in the range [-0.5..0.5]. + * @return RGB color space. + */ + public static int[] YIQtoRGB(double y, double i, double q){ + int[] rgb = new int[3]; + int r,g,b; + + r = (int)((y + 0.956 * i + 0.621 * q) * 255); + g = (int)((y - 0.272 * i - 0.647 * q) * 255); + b = (int)((y - 1.105 * i + 1.702 * q) * 255); + + r = Math.max(0,Math.min(255,r)); + g = Math.max(0,Math.min(255,g)); + b = Math.max(0,Math.min(255,b)); + + rgb[0] = r; + rgb[1] = g; + rgb[2] = b; + + return rgb; + } + + public static float[] RGBtoYCbCr(int red, int green, int blue, YCbCrColorSpace colorSpace){ + + float r = (float)red / 255; + float g = (float)green / 255; + float b = (float)blue / 255; + + float[] YCbCr = new float[3]; + float y,cb,cr; + + if (colorSpace == YCbCrColorSpace.ITU_BT_601) { + y = (float)(0.299 * r + 0.587 * g + 0.114 * b); + cb = (float)(-0.169 * r - 0.331 * g + 0.500 * b); + cr = (float)(0.500 * r - 0.419 * g - 0.081 * b); + } + else{ + y = (float)(0.2215 * r + 0.7154 * g + 0.0721 * b); + cb = (float)(-0.1145 * r - 0.3855 * g + 0.5000 * b); + cr = (float)(0.5016 * r - 0.4556 * g - 0.0459 * b); + } + + YCbCr[0] = (float)y; + YCbCr[1] = (float)cb; + YCbCr[2] = (float)cr; + + return YCbCr; + } + + public static int[] YCbCrtoRGB(float y, float cb, float cr, YCbCrColorSpace colorSpace){ + int[] rgb = new int[3]; + float r,g,b; + + if (colorSpace == YCbCrColorSpace.ITU_BT_601) { + r = (float)(y + 0.000 * cb + 1.403 * cr) * 255; + g = (float)(y - 0.344 * cb - 0.714 * cr) * 255; + b = (float)(y + 1.773 * cb + 0.000 * cr) * 255; + } + else{ + r = (float)(y + 0.000 * cb + 1.5701 * cr) * 255; + g = (float)(y - 0.1870 * cb - 0.4664 * cr) * 255; + b = (float)(y + 1.8556 * cb + 0.000 * cr) * 255; + } + + rgb[0] = (int)r; + rgb[1] = (int)g; + rgb[2] = (int)b; + + return rgb; + } + + /** + * Rg-Chromaticity space is already known to remove ambiguities due to illumination or surface pose. + * @see Neural Information Processing - Chi Sing Leung. p. 668 + * @param red Red coefficient. + * @param green Green coefficient. + * @param blue Blue coefficient. + * @return Normalized RGChromaticity. Range[0..1]. + */ + public static double[] RGChromaticity(int red, int green, int blue){ + double[] color = new double[5]; + + double sum = red + green + blue; + + //red + color[0] = red / sum; + + //green + color[1] = green / sum; + + //blue + color[2] = 1 - color[0] - color[1]; + + double rS = color[0] - 0.333; + double gS = color[1] - 0.333; + + //saturation + color[3] = Math.sqrt(rS * rS + gS * gS); + + //hue + color[4] = Math.atan(rS / gS); + + return color; + } + + /** + * RGB -> HSV. + * Adds (hue + 360) % 360 for represent hue in the range [0..359]. + * @param red Red coefficient. Values in the range [0..255]. + * @param green Green coefficient. Values in the range [0..255]. + * @param blue Blue coefficient. Values in the range [0..255]. + * @return HSV color space. + */ + public static float[] RGBtoHSV(int red, int green, int blue){ + float[] hsv = new float[3]; + float r = red / 255f; + float g = green / 255f; + float b = blue / 255f; + + float max = Math.max(r, Math.max(g, b)); + float min = Math.min(r, Math.min(g, b)); + float delta = max - min; + + // Hue + if (max == min){ + hsv[0] = 0; + } + else if (max == r){ + hsv[0] = ((g - b) / delta) * 60f; + } + else if (max == g){ + hsv[0] = ((b - r) / delta + 2f) * 60f; + } + else if (max == b){ + hsv[0] = ((r - g) / delta + 4f) * 60f; + } + + // Saturation + if (delta == 0) + hsv[1] = 0; + else + hsv[1] = delta / max; + + //Value + hsv[2] = max; + + return hsv; + } + + /** + * HSV -> RGB. + * @param hue Hue. + * @param saturation Saturation. In the range[0..1]. + * @param value Value. In the range[0..1]. + * @return RGB color space. In the range[0..255]. + */ + public static int[] HSVtoRGB(float hue, float saturation, float value){ + int[] rgb = new int[3]; + + float hi = (float)Math.floor(hue / 60.0) % 6; + float f = (float)((hue / 60.0) - Math.floor(hue / 60.0)); + float p = (float)(value * (1.0 - saturation)); + float q = (float)(value * (1.0 - (f * saturation))); + float t = (float)(value * (1.0 - ((1.0 - f) * saturation))); + + if (hi == 0){ + rgb[0] = (int)(value * 255); + rgb[1] = (int)(t * 255); + rgb[2] = (int)(p * 255); + } + else if (hi == 1){ + rgb[0] = (int)(q * 255); + rgb[1] = (int)(value * 255); + rgb[2] = (int)(p * 255); + } + else if (hi == 2){ + rgb[0] = (int)(p * 255); + rgb[1] = (int)(value * 255); + rgb[2] = (int)(t * 255); + } + else if (hi == 3){ + rgb[0] = (int)(p * 255); + rgb[1] = (int)(value * 255); + rgb[2] = (int)(q * 255); + } + else if (hi == 4){ + rgb[0] = (int)(t * 255); + rgb[1] = (int)(value * 255); + rgb[2] = (int)(p * 255); + } + else if (hi == 5){ + rgb[0] = (int)(value * 255); + rgb[1] = (int)(p * 255); + rgb[2] = (int)(q * 255); + } + + return rgb; + } + + /** + * RGB -> YCC. + * @param red Red coefficient. Values in the range [0..255]. + * @param green Green coefficient. Values in the range [0..255]. + * @param blue Blue coefficient. Values in the range [0..255]. + * @return YCC color space. In the range [0..1]. + */ + public static float[] RGBtoYCC(int red, int green, int blue){ + float[] ycc = new float[3]; + + float r = red / 255f; + float g = green / 255f; + float b = blue / 255f; + + float y = 0.213f * r + 0.419f * g + 0.081f * b; + float c1 = -0.131f * r - 0.256f * g + 0.387f * b + 0.612f; + float c2 = 0.373f * r - 0.312f * r - 0.061f * b + 0.537f; + + ycc[0] = y; + ycc[1] = c1; + ycc[2] = c2; + + return ycc; + } + + /** + * YCC -> RGB. + * @param y Y coefficient. + * @param c1 C coefficient. + * @param c2 C coefficient. + * @return RGB color space. + */ + public static int[] YCCtoRGB(float y, float c1, float c2){ + int[] rgb = new int[3]; + + float r = 0.981f * y + 1.315f * (c2 - 0.537f); + float g = 0.981f * y - 0.311f * (c1 - 0.612f)- 0.669f * (c2 - 0.537f); + float b = 0.981f * y + 1.601f * (c1 - 0.612f); + + rgb[0] = (int)(r * 255f); + rgb[1] = (int)(g * 255f); + rgb[2] = (int)(b * 255f); + + return rgb; + } + + /** + * RGB -> YCoCg. + * @param red Red coefficient. Values in the range [0..255]. + * @param green Green coefficient. Values in the range [0..255]. + * @param blue Blue coefficient. Values in the range [0..255]. + * @return YCoCg color space. + */ + public static float[] RGBtoYCoCg(int red, int green, int blue){ + float[] yCoCg = new float[3]; + + float r = red / 255f; + float g = green / 255f; + float b = blue / 255f; + + float y = r / 4f + g / 2f + b / 4f; + float co = r / 2f - b / 2f; + float cg = -r / 4f + g / 2f - b / 4f; + + yCoCg[0] = y; + yCoCg[1] = co; + yCoCg[2] = cg; + + return yCoCg; + } + + /** + * YCoCg -> RGB. + * @param y Pseudo luminance, or intensity. + * @param co Orange chrominance. + * @param cg Green chrominance. + * @return RGB color space. + */ + public static int[] YCoCgtoRGB(float y, float co, float cg){ + int[] rgb = new int[3]; + + float r = y + co - cg; + float g = y + cg; + float b = y - co - cg; + + rgb[0] = (int)(r * 255f); + rgb[1] = (int)(g * 255f); + rgb[2] = (int)(b * 255f); + + return rgb; + } + + /** + * RGB -> XYZ + * @param red Red coefficient. Values in the range [0..255]. + * @param green Green coefficient. Values in the range [0..255]. + * @param blue Blue coefficient. Values in the range [0..255]. + * @return XYZ color space. + */ + public static float[] RGBtoXYZ(int red, int green, int blue){ + float[] xyz = new float[3]; + + float r = red / 255f; + float g = green / 255f; + float b = blue / 255f; + + //R + if ( r > 0.04045) + r = (float)Math.pow(( ( r + 0.055f ) / 1.055f ), 2.4f); + else + r /= 12.92f; + + //G + if ( g > 0.04045) + g = (float)Math.pow(( ( g + 0.055f ) / 1.055f ), 2.4f); + else + g /= 12.92f; + + //B + if ( b > 0.04045) + b = (float)Math.pow(( ( b + 0.055f ) / 1.055f ), 2.4f); + else + b /= 12.92f; + + r *= 100; + g *= 100; + b *= 100; + + float x = 0.412453f * r + 0.35758f * g + 0.180423f * b; + float y = 0.212671f * r + 0.71516f * g + 0.072169f * b; + float z = 0.019334f * r + 0.119193f * g + 0.950227f * b; + + xyz[0] = x; + xyz[1] = y; + xyz[2] = z; + + return xyz; + } + + /** + * XYZ -> RGB + * @param x X coefficient. + * @param y Y coefficient. + * @param z Z coefficient. + * @return RGB color space. + */ + public static int[] XYZtoRGB(float x, float y, float z){ + int[] rgb = new int[3]; + + x /= 100; + y /= 100; + z /= 100; + + float r = 3.240479f * x - 1.53715f * y - 0.498535f * z; + float g = -0.969256f * x + 1.875991f * y + 0.041556f * z; + float b = 0.055648f * x - 0.204043f * y + 1.057311f * z; + + if ( r > 0.0031308 ) + r = 1.055f * ( (float)Math.pow(r, 0.4166f) ) - 0.055f; + else + r = 12.92f * r; + + if ( g > 0.0031308 ) + g = 1.055f * ( (float)Math.pow(g, 0.4166f) ) - 0.055f; + else + g = 12.92f * g; + + if ( b > 0.0031308 ) + b = 1.055f * ( (float)Math.pow(b, 0.4166f) ) - 0.055f; + else + b = 12.92f * b; + + rgb[0] = (int)(r * 255); + rgb[1] = (int)(g * 255); + rgb[2] = (int)(b * 255); + + return rgb; + } + + /** + * XYZ -> HunterLAB + * @param x X coefficient. + * @param y Y coefficient. + * @param z Z coefficient. + * @return HunterLab coefficient. + */ + public static float[] XYZtoHunterLAB(float x, float y, float z){ + float[] hunter = new float[3]; + + + float sqrt = (float)Math.sqrt(y); + + float l = 10 * sqrt; + float a = 17.5f * (((1.02f * x) - y) / sqrt); + float b = 7f * ((y - (0.847f * z)) / sqrt); + + hunter[0] = l; + hunter[1] = a; + hunter[2] = b; + + return hunter; + } + + /** + * HunterLAB -> XYZ + * @param l L coefficient. + * @param a A coefficient. + * @param b B coefficient. + * @return XYZ color space. + */ + public static float[] HunterLABtoXYZ(float l, float a, float b){ + float[] xyz = new float[3]; + + + float tempY = l / 10f; + float tempX = a / 17.5f * l / 10f; + float tempZ = b / 7f * l / 10f; + + float y = tempY * tempY; + float x = (tempX + y) / 1.02f; + float z = -(tempZ - y) / 0.847f; + + xyz[0] = x; + xyz[1] = y; + xyz[2] = z; + + return xyz; + } + + /** + * RGB -> HunterLAB. + * @param red Red coefficient. Values in the range [0..255]. + * @param green Green coefficient. Values in the range [0..255]. + * @param blue Blue coefficient. Values in the range [0..255]. + * @return HunterLAB color space. + */ + public static float[] RGBtoHunterLAB(int red, int green, int blue){ + float[] xyz = RGBtoXYZ(red, green, blue); + return XYZtoHunterLAB(xyz[0], xyz[1], xyz[2]); + } + + /** + * HunterLAB -> RGB. + * @param l L coefficient. + * @param a A coefficient. + * @param b B coefficient. + * @return RGB color space. + */ + public static int[] HunterLABtoRGB(float l, float a, float b){ + float[] xyz = HunterLABtoXYZ(l, a, b); + return XYZtoRGB(xyz[0], xyz[1], xyz[2]); + } + + /** + * RGB -> HLS. + * @param red Red coefficient. Values in the range [0..255]. + * @param green Green coefficient. Values in the range [0..255]. + * @param blue Blue coefficient. Values in the range [0..255]. + * @return HLS color space. + */ + public static float[] RGBtoHLS(int red, int green, int blue){ + float[] hsl = new float[3]; + + float r = red / 255f; + float g = green / 255f; + float b = blue / 255f; + + float max = Math.max(r,Math.max(r,b)); + float min = Math.min(r,Math.min(r,b)); + float delta = max - min; + + //HSK + float h = 0; + float s = 0; + float l = (max + min) / 2; + + if ( delta == 0 ){ + // gray color + h = 0; + s = 0.0f; + } + else + { + // get saturation value + s = ( l <= 0.5 ) ? ( delta / ( max + min ) ) : ( delta / ( 2 - max - min ) ); + + // get hue value + float hue; + + if ( r == max ) + { + hue = ( ( g - b ) / 6 ) / delta; + } + else if ( g == max ) + { + hue = ( 1.0f / 3 ) + ( ( b - r ) / 6 ) / delta; + } + else + { + hue = ( 2.0f / 3 ) + ( ( r - g ) / 6 ) / delta; + } + + // correct hue if needed + if ( hue < 0 ) + hue += 1; + if ( hue > 1 ) + hue -= 1; + + h = (int) ( hue * 360 ); + } + + hsl[0] = h; + hsl[1] = s; + hsl[2] = l; + + return hsl; + } + + /** + * HLS -> RGB. + * @param hue Hue. + * @param saturation Saturation. + * @param luminance Luminance. + * @return RGB color space. + */ + public static int[] HSLtoRGB(float hue, float saturation, float luminance){ + int[] rgb = new int[3]; + float r = 0, g = 0, b = 0; + + if ( saturation == 0 ) + { + // gray values + r = g = b = (int) ( luminance * 255 ); + } + else + { + float v1, v2; + float h = (float) hue / 360; + + v2 = ( luminance < 0.5 ) ? + ( luminance * ( 1 + saturation ) ) : + ( ( luminance + saturation ) - ( luminance * saturation ) ); + v1 = 2 * luminance - v2; + + r = (int) ( 255 * Hue_2_RGB( v1, v2, h + ( 1.0f / 3 ) ) ); + g = (int) ( 255 * Hue_2_RGB( v1, v2, h ) ); + b = (int) ( 255 * Hue_2_RGB( v1, v2, h - ( 1.0f / 3 ) ) ); + } + + rgb[0] = (int)r; + rgb[1] = (int)g; + rgb[2] = (int)b; + + return rgb; + } + + private static float Hue_2_RGB( float v1, float v2, float vH ){ + 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.0f / 3 ) - vH ) * 6 ); + return v1; + } + + /** + * RGB -> CIE-LAB. + * @param red Red coefficient. Values in the range [0..255]. + * @param green Green coefficient. Values in the range [0..255]. + * @param blue Blue coefficient. Values in the range [0..255]. + * @param tristimulus XYZ Tristimulus. + * @return CIE-LAB color space. + */ + public static float[] RGBtoLAB(int red, int green, int blue, float[] tristimulus){ + float[] xyz = RGBtoXYZ(red, green, blue); + float[] lab = XYZtoLAB(xyz[0], xyz[1], xyz[2], tristimulus); + + return lab; + } + + /** + * CIE-LAB -> RGB. + * @param l L coefficient. + * @param a A coefficient. + * @param b B coefficient. + * @param tristimulus XYZ Tristimulus. + * @return RGB color space. + */ + public static int[] LABtoRGB(float l, float a, float b, float[] tristimulus){ + float[] xyz = LABtoXYZ(l, a, b, tristimulus); + return XYZtoRGB(xyz[0], xyz[1], xyz[2]); + } + + /** + * XYZ -> CIE-LAB. + * @param x X coefficient. + * @param y Y coefficient. + * @param z Z coefficient. + * @param tristimulus XYZ Tristimulus. + * @return CIE-LAB color space. + */ + public static float[] XYZtoLAB(float x, float y, float z, float[] tristimulus){ + float[] lab = new float[3]; + + x /= tristimulus[0]; + y /= tristimulus[1]; + z /= tristimulus[2]; + + if (x > 0.008856) + x = (float)Math.pow(x,0.33f); + else + x = (7.787f * x) + ( 0.1379310344827586f ); + + if (y > 0.008856) + y = (float)Math.pow(y,0.33f); + else + y = (7.787f * y) + ( 0.1379310344827586f ); + + if (z > 0.008856) + z = (float)Math.pow(z,0.33f); + else + z = (7.787f * z) + ( 0.1379310344827586f ); + + lab[0] = ( 116 * y ) - 16; + lab[1] = 500 * ( x - y ); + lab[2] = 200 * ( y - z ); + + return lab; + } + + /** + * CIE-LAB -> XYZ. + * @param l L coefficient. + * @param a A coefficient. + * @param b B coefficient. + * @param tristimulus XYZ Tristimulus. + * @return XYZ color space. + */ + public static float[] LABtoXYZ(float l, float a, float b, float[] tristimulus){ + float[] xyz = new float[3]; + + float y = ( l + 16f ) / 116f; + float x = a / 500f + y; + float z = y - b / 200f; + + //Y + if ( Math.pow(y,3) > 0.008856 ) + y = (float)Math.pow(y,3); + else + y = (float)(( y - 16 / 116 ) / 7.787); + + //X + if ( Math.pow(x,3) > 0.008856 ) + x = (float)Math.pow(x,3); + else + x = (float)(( x - 16 / 116 ) / 7.787); + + // Z + if ( Math.pow(z,3) > 0.008856 ) + z = (float)Math.pow(z,3); + else + z = (float)(( z - 16 / 116 ) / 7.787); + + xyz[0] = x * tristimulus[0]; + xyz[1] = y * tristimulus[1]; + xyz[2] = z * tristimulus[2]; + + return xyz; + } + + /** + * RGB -> C1C2C3. + * @param r Red coefficient. Values in the range [0..255]. + * @param g Green coefficient. Values in the range [0..255]. + * @param b Blue coefficient. Values in the range [0..255]. + * @return C1C2C3 color space. + */ + public static float[] RGBtoC1C2C3(int r, int g, int b){ + + float[] c = new float[3]; + + c[0] = (float)Math.atan(r / Math.max(g, b)); + c[1] = (float)Math.atan(g / Math.max(r, b)); + c[2] = (float)Math.atan(b / Math.max(r, g)); + + return c; + + } + + /** + * RGB -> O1O2. + * @param r Red coefficient. Values in the range [0..255]. + * @param g Green coefficient. Values in the range [0..255]. + * @param b Blue coefficient. Values in the range [0..255]. + * @return O1O2 color space. + */ + public static float[] RGBtoO1O2(int r, int g, int b){ + + float[] o = new float[2]; + + o[0] = (r - g) / 2f; + o[1] = (r + g) / 4f - (b / 2f); + + return o; + + } + + /** + * RGB -> Grayscale. + * @param r Red coefficient. Values in the range [0..255]. + * @param g Green coefficient. Values in the range [0..255]. + * @param b Blue coefficient. Values in the range [0..255]. + * @return Grayscale color space. + */ + public static float RGBtoGrayscale(int r, int g, int b){ + + return r*0.2125f + g*0.7154f + b*0.0721f; + + } + + /** + * XYZ -> Philips Hue XY + * @param x X coefficient. + * @param y Y coefficient. + * @param z Z coefficient. + * @return Hue xy array + */ + public static XYColorSpace XYZtoXY(float x, float y, float z){ + float[] xy = new float[2]; + + xy[0] = x / (x + y + z); + xy[1] = y / (x + y + z); + + XYColorSpace xyColor = new XYColorSpace(); + xyColor.setBrightness((int)Math.round(y * 254.0f)); + xyColor.setXy(xy); + return xyColor; + } + + /** + * Philips Hue XY -> XYZ + * @param x X coefficient. + * @param y Y coefficient. + * @return XYZ array + */ + public static float[] XYtoXYZ(XYColorSpace xy){ + float[] xyz = new float[3]; + + xyz[0] = (xy.getBrightnessAdjusted() / xy.getXy()[1]) * xy.getXy()[0]; + xyz[1] = xy.getBrightnessAdjusted(); + xyz[2] = (xy.getBrightnessAdjusted() / xy.getXy()[1]) * (1.0f - xy.getXy()[0] - xy.getXy()[1]); + + return xyz; + } + +} \ No newline at end of file diff --git a/src/main/java/com/bwssystems/HABridge/hue/ColorDecode.java b/src/main/java/com/bwssystems/HABridge/hue/ColorDecode.java index 4cca226..27c1590 100644 --- a/src/main/java/com/bwssystems/HABridge/hue/ColorDecode.java +++ b/src/main/java/com/bwssystems/HABridge/hue/ColorDecode.java @@ -122,64 +122,64 @@ public class ColorDecode { List rgb; double x = xy.get(0); // the given x value double y = xy.get(1); // the given y value - double z = 1.0 - x - y; - double Y = (double) brightness / (double) 254.00; // The given brightness value + double z = 1.0f - x - y; + double Y = (double) brightness / (double) 254.00f; // The given brightness value double X = (Y / y) * x; double Z = (Y / y) * z; - double r = X * 1.656492 - Y * 0.354851 - Z * 0.255038; - double g = -X * 0.707196 + Y * 1.655397 + Z * 0.036152; - double b = X * 0.051713 - Y * 0.121364 + Z * 1.011530; + double r = X * 1.656492f - Y * 0.354851f - Z * 0.255038f; + double g = -X * 0.707196f + Y * 1.655397f + Z * 0.036152f; + double b = X * 0.051713f - Y * 0.121364f + Z * 1.011530f; - if (r > b && r > g && r > 1.0) { + if (r > b && r > g && r > 1.0f) { g = g / r; b = b / r; - r = 1.0; - } else if (g > b && g > r && g > 1.0) { + r = 1.0f; + } else if (g > b && g > r && g > 1.0f) { r = r / g; b = b / g; - g = 1.0; - } else if (b > r && b > g && b > 1.0) { + g = 1.0f; + } else if (b > r && b > g && b > 1.0f) { r = r / b; g = g / b; - b = 1.0; + b = 1.0f; } - r = r <= 0.0031308 ? 12.92 * r : (1.0 + 0.055) * Math.pow(r, (1.0 / 2.4)) - 0.055; - g = g <= 0.0031308 ? 12.92 * g : (1.0 + 0.055) * Math.pow(g, (1.0 / 2.4)) - 0.055; - b = b <= 0.0031308 ? 12.92 * b : (1.0 + 0.055) * Math.pow(b, (1.0 / 2.4)) - 0.055; + r = r <= 0.0031308f ? 12.92f * r : (1.0f + 0.055f) * Math.pow(r, (1.0f / 2.4f)) - 0.055f; + g = g <= 0.0031308f ? 12.92f * g : (1.0f + 0.055f) * Math.pow(g, (1.0f / 2.4f)) - 0.055f; + b = b <= 0.0031308f ? 12.92f * b : (1.0f + 0.055f) * Math.pow(b, (1.0f / 2.4f)) - 0.055f; if (r > b && r > g) { // red is biggest - if (r > 1.0) { + if (r > 1.0f) { g = g / r; b = b / r; - r = 1.0; + r = 1.0f; } } else if (g > b && g > r) { // green is biggest - if (g > 1.0) { + if (g > 1.0f) { r = r / g; b = b / g; - g = 1.0; + g = 1.0f; } } else if (b > r && b > g) { // blue is biggest - if (b > 1.0) { + if (b > 1.0f) { r = r / b; g = g / b; - b = 1.0; + b = 1.0f; } } - if (r < 0.0) - r = 0; - if (g < 0.0) - g = 0; - if (b < 0.0) - b = 0; + if (r < 0.0f) + r = 0.0f; + if (g < 0.0f) + g = 0.0f; + if (b < 0.0f) + b = 0.0f; rgb = new ArrayList(); rgb.add((int) Math.round(r * 255)); diff --git a/src/main/java/com/bwssystems/HABridge/hue/XYColorSpace.java b/src/main/java/com/bwssystems/HABridge/hue/XYColorSpace.java new file mode 100644 index 0000000..148bb91 --- /dev/null +++ b/src/main/java/com/bwssystems/HABridge/hue/XYColorSpace.java @@ -0,0 +1,26 @@ +package com.bwssystems.HABridge.hue; + +public class XYColorSpace { + float[] xy; + int brightness; + + public float[] getXy() { + return xy; + } + + public void setXy(float[] xy) { + this.xy = xy; + } + + public int getBrightness() { + return brightness; + } + + public float getBrightnessAdjusted() { + return ((float) brightness / 254.0f) * 100f; + } + + public void setBrightness(int brightness) { + this.brightness = brightness; + } +} \ No newline at end of file diff --git a/src/test/java/com/bwssystems/color/test/ConvertCIEColorTestCase.java b/src/test/java/com/bwssystems/color/test/ConvertCIEColorTestCase.java index 7e5323f..9ed5438 100644 --- a/src/test/java/com/bwssystems/color/test/ConvertCIEColorTestCase.java +++ b/src/test/java/com/bwssystems/color/test/ConvertCIEColorTestCase.java @@ -9,6 +9,8 @@ import org.junit.Test; import com.bwssystems.HABridge.hue.ColorData; import com.bwssystems.HABridge.hue.ColorDecode; +import com.bwssystems.HABridge.hue.XYColorSpace; +import com.bwssystems.HABridge.hue.ColorConverter; public class ConvertCIEColorTestCase { @@ -16,6 +18,29 @@ public class ConvertCIEColorTestCase { public void testColorConversion() { ArrayList xy = new ArrayList(Arrays.asList(Double.parseDouble("0.671254"), Double.parseDouble("0.303273"))); + XYColorSpace xyColor = new XYColorSpace(); + xyColor.setBrightness(254); + float[] xyFloat = new float[2]; + xyFloat[0] = xy.get(0).floatValue(); + xyFloat[1] = xy.get(1).floatValue(); + xyColor.setXy(xyFloat); + float[] xyz = ColorConverter.XYtoXYZ(xyColor); + int[] rgb = ColorConverter.XYZtoRGB(xyz[0], xyz[1], xyz[2]); + List rgbDecode = new ArrayList(); + rgbDecode.add(0, rgb[0]); + rgbDecode.add(1, rgb[1]); + rgbDecode.add(2, rgb[2]); + List assertDecode = new ArrayList(); + assertDecode.add(0, 255); + assertDecode.add(1, 47); + assertDecode.add(2, 43); + Assert.assertEquals(rgbDecode, assertDecode); + } + + @Test + public void testColorConversionRGB() { + ArrayList xy = new ArrayList(Arrays.asList(Double.parseDouble("0.671254"), Double.parseDouble("0.303273"))); + List colorDecode = ColorDecode.convertCIEtoRGB(xy, 254); List assertDecode = new ArrayList(); assertDecode.add(0, 255);