package ij.process;
import java.awt.Color;
/**
* ColorSpaceConverter
*
* From ImageJ 1.5.1f ImageJ is in the public domain.
*
* @author dvs, hlp Created Jan 15, 2004 Version 3 posted on ImageJ Mar 12, 2006
* by Duane Schwartzwald vonschwartzwalder at mac.com Version 4 created Feb. 27,
* 2007 by Harry Parker, harrylparker at yahoo dot com, corrects RGB to XYZ (and
* LAB) conversion. Modified by Bernie Jenny, Sep. 20, 2016.
*/
public class ColorSpaceConverter {
public final static double LAB_MAX_L = 100;
public final static double LAB_MIN_L = 0;
public final static double LAB_MAX_A = 98.26;
public final static double LAB_MIN_A = -86.17;
public final static double LAB_MAX_B = 94.48;
public final static double LAB_MIN_B = -107.85;
/*
static {
ColorSpaceConverter converter = new ColorSpaceConverter();
double maxL = -Double.MAX_VALUE;
double maxA = -Double.MAX_VALUE;
double maxB = -Double.MAX_VALUE;
double minL = Double.MAX_VALUE;
double minA = Double.MAX_VALUE;
double minB = Double.MAX_VALUE;
for (int r = 0; r < 256; ++r) {
for (int g = 0; g < 256; ++g) {
for (int b = 0; b < 256; ++b) {
double[] lab = converter.RGBtoLAB(new int[]{r, g, b});
maxL = Math.max(maxL, lab[0]);
maxA = Math.max(maxA, lab[1]);
maxB = Math.max(maxB, lab[2]);
minL = Math.min(minL, lab[0]);
minA = Math.min(minA, lab[1]);
minB = Math.min(minB, lab[2]);
}
}
}
System.out.println(minL);
System.out.println(maxL);
System.out.println(minA);
System.out.println(maxA);
System.out.println(minB);
System.out.println(maxB);
}
*/
/**
* reference white in XYZ coordinates
*/
public double[] D50 = {96.4212, 100.0, 82.5188};
public double[] D55 = {95.6797, 100.0, 92.1481};
public double[] D65 = {95.0429, 100.0, 108.8900};
public double[] D75 = {94.9722, 100.0, 122.6394};
public double[] whitePoint = D65;
/**
* reference white in xyY coordinates
*/
public double[] chromaD50 = {0.3457, 0.3585, 100.0};
public double[] chromaD55 = {0.3324, 0.3474, 100.0};
public double[] chromaD65 = {0.3127, 0.3290, 100.0};
public double[] chromaD75 = {0.2990, 0.3149, 100.0};
public double[] chromaWhitePoint = chromaD65;
/**
* sRGB to XYZ conversion matrix
*/
public double[][] M = {{0.4124, 0.3576, 0.1805},
{0.2126, 0.7152, 0.0722},
{0.0193, 0.1192, 0.9505}};
/**
* XYZ to sRGB conversion matrix
*/
public double[][] Mi = {{3.2406, -1.5372, -0.4986},
{-0.9689, 1.8758, 0.0415},
{0.0557, -0.2040, 1.0570}};
/**
* Default constructor; uses D65 for the white point
*/
public ColorSpaceConverter() {
whitePoint = D65;
chromaWhitePoint = chromaD65;
}
/**
* Constructor for setting a non-default white point
*
* @param white "d50", "d55", "d65" or "d75"
*/
public ColorSpaceConverter(String white) {
whitePoint = D65;
chromaWhitePoint = chromaD65;
if (white.equalsIgnoreCase("d50")) {
whitePoint = D50;
chromaWhitePoint = chromaD50;
} else if (white.equalsIgnoreCase("d55")) {
whitePoint = D55;
chromaWhitePoint = chromaD55;
} else if (white.equalsIgnoreCase("d65")) {
whitePoint = D65;
chromaWhitePoint = chromaD65;
} else if (white.equalsIgnoreCase("d75")) {
whitePoint = D75;
chromaWhitePoint = chromaD75;
} else {
throw new IllegalArgumentException("Invalid white point");
}
}
/**
* @param H Hue angle/360 (0..1)
* @param S Saturation (0..1)
* @param B Value (0..1)
* @return RGB values
*/
public int[] HSBtoRGB(double H, double S, double B) {
int[] result = new int[3];
int rgb = Color.HSBtoRGB((float) H, (float) S, (float) B);
result[0] = (rgb >> 16) & 0xff;
result[1] = (rgb >> 8) & 0xff;
result[2] = (rgb >> 0) & 0xff;
return result;
}
public int[] HSBtoRGB(double[] HSB) {
return HSBtoRGB(HSB[0], HSB[1], HSB[2]);
}
/**
* Convert LAB to RGB.
*
* @param L
* @param a
* @param b
* @return RGB values
*/
public int[] LABtoRGB(double L, double a, double b) {
return XYZtoRGB(LABtoXYZ(L, a, b));
}
/**
* @param Lab
* @return RGB values
*/
public int[] LABtoRGB(double[] Lab) {
return XYZtoRGB(LABtoXYZ(Lab));
}
/**
* Convert LAB to XYZ.
*
* @param L
* @param a
* @param b
* @return XYZ values
*/
public double[] LABtoXYZ(double L, double a, double b) {
double[] result = new double[3];
double y = (L + 16.0) / 116.0;
double y3 = Math.pow(y, 3.0);
double x = (a / 500.0) + y;
double x3 = Math.pow(x, 3.0);
double z = y - (b / 200.0);
double z3 = Math.pow(z, 3.0);
if (y3 > 0.008856) {
y = y3;
} else {
y = (y - (16.0 / 116.0)) / 7.787;
}
if (x3 > 0.008856) {
x = x3;
} else {
x = (x - (16.0 / 116.0)) / 7.787;
}
if (z3 > 0.008856) {
z = z3;
} else {
z = (z - (16.0 / 116.0)) / 7.787;
}
result[0] = x * whitePoint[0];
result[1] = y * whitePoint[1];
result[2] = z * whitePoint[2];
return result;
}
/**
* Convert LAB to XYZ.
*
* @param Lab
* @return XYZ values
*/
public double[] LABtoXYZ(double[] Lab) {
return LABtoXYZ(Lab[0], Lab[1], Lab[2]);
}
/**
* @param R Red in range 0..255
* @param G Green in range 0..255
* @param B Blue in range 0..255
* @return HSB values: H is 0..360 degrees / 360 (0..1), S is 0..1, B is
* 0..1
*/
public double[] RGBtoHSB(int R, int G, int B) {
double[] result = new double[3];
float[] hsb = new float[3];
Color.RGBtoHSB(R, G, B, hsb);
result[0] = hsb[0];
result[1] = hsb[1];
result[2] = hsb[2];
return result;
}
public double[] RGBtoHSB(int[] RGB) {
return RGBtoHSB(RGB[0], RGB[1], RGB[2]);
}
/**
* @param rgb RGB value
* @return Lab values
*/
public double[] RGBtoLAB(int rgb) {
int r = (rgb & 0xff0000) >> 16;
int g = (rgb & 0xff00) >> 8;
int b = rgb & 0xff;
return XYZtoLAB(RGBtoXYZ(r, g, b));
}
/**
* @param RGB
* @return Lab values
*/
public double[] RGBtoLAB(int[] RGB) {
return XYZtoLAB(RGBtoXYZ(RGB));
}
/**
* Convert RGB to XYZ
*
* @param R
* @param G
* @param B
* @return XYZ in double array.
*/
public double[] RGBtoXYZ(int R, int G, int B) {
double[] result = new double[3];
// convert 0..255 into 0..1
double r = R / 255.0;
double g = G / 255.0;
double b = B / 255.0;
// assume sRGB
if (r <= 0.04045) {
r = r / 12.92;
} else {
r = Math.pow(((r + 0.055) / 1.055), 2.4);
}
if (g <= 0.04045) {
g = g / 12.92;
} else {
g = Math.pow(((g + 0.055) / 1.055), 2.4);
}
if (b <= 0.04045) {
b = b / 12.92;
} else {
b = Math.pow(((b + 0.055) / 1.055), 2.4);
}
r *= 100.0;
g *= 100.0;
b *= 100.0;
// [X Y Z] = [r g b][M]
result[0] = (r * M[0][0]) + (g * M[0][1]) + (b * M[0][2]);
result[1] = (r * M[1][0]) + (g * M[1][1]) + (b * M[1][2]);
result[2] = (r * M[2][0]) + (g * M[2][1]) + (b * M[2][2]);
return result;
}
/**
* Convert RGB to XYZ
*
* @param RGB
* @return XYZ in double array.
*/
public double[] RGBtoXYZ(int[] RGB) {
return RGBtoXYZ(RGB[0], RGB[1], RGB[2]);
}
/**
* @param x
* @param y
* @param Y
* @return XYZ values
*/
public double[] xyYtoXYZ(double x, double y, double Y) {
double[] result = new double[3];
if (y == 0) {
result[0] = 0;
result[1] = 0;
result[2] = 0;
} else {
result[0] = (x * Y) / y;
result[1] = Y;
result[2] = ((1 - x - y) * Y) / y;
}
return result;
}
/**
* @param xyY
* @return XYZ values
*/
public double[] xyYtoXYZ(double[] xyY) {
return xyYtoXYZ(xyY[0], xyY[1], xyY[2]);
}
/**
* Convert XYZ to LAB.
*
* @param X
* @param Y
* @param Z
* @return Lab values
*/
public double[] XYZtoLAB(double X, double Y, double Z) {
double x = X / whitePoint[0];
double y = Y / whitePoint[1];
double z = Z / whitePoint[2];
if (x > 0.008856) {
x = Math.pow(x, 1.0 / 3.0);
} else {
x = (7.787 * x) + (16.0 / 116.0);
}
if (y > 0.008856) {
y = Math.pow(y, 1.0 / 3.0);
} else {
y = (7.787 * y) + (16.0 / 116.0);
}
if (z > 0.008856) {
z = Math.pow(z, 1.0 / 3.0);
} else {
z = (7.787 * z) + (16.0 / 116.0);
}
double[] result = new double[3];
result[0] = (116.0 * y) - 16.0;
result[1] = 500.0 * (x - y);
result[2] = 200.0 * (y - z);
return result;
}
/**
* Convert XYZ to LAB.
*
* @param XYZ
* @return Lab values
*/
public double[] XYZtoLAB(double[] XYZ) {
return XYZtoLAB(XYZ[0], XYZ[1], XYZ[2]);
}
/**
* Convert XYZ to RGB.
*
* @param X
* @param Y
* @param Z
* @return RGB in int array.
*/
public int[] XYZtoRGB(double X, double Y, double Z) {
int[] result = new int[3];
double x = X / 100.0;
double y = Y / 100.0;
double z = Z / 100.0;
// [r g b] = [X Y Z][Mi]
double r = (x * Mi[0][0]) + (y * Mi[0][1]) + (z * Mi[0][2]);
double g = (x * Mi[1][0]) + (y * Mi[1][1]) + (z * Mi[1][2]);
double b = (x * Mi[2][0]) + (y * Mi[2][1]) + (z * Mi[2][2]);
// assume sRGB
if (r > 0.0031308) {
r = ((1.055 * Math.pow(r, 1.0 / 2.4)) - 0.055);
} else {
r = (r * 12.92);
}
if (g > 0.0031308) {
g = ((1.055 * Math.pow(g, 1.0 / 2.4)) - 0.055);
} else {
g = (g * 12.92);
}
if (b > 0.0031308) {
b = ((1.055 * Math.pow(b, 1.0 / 2.4)) - 0.055);
} else {
b = (b * 12.92);
}
r = (r < 0) ? 0 : r;
g = (g < 0) ? 0 : g;
b = (b < 0) ? 0 : b;
// convert 0..1 into 0..255
result[0] = (int) Math.round(r * 255);
result[1] = (int) Math.round(g * 255);
result[2] = (int) Math.round(b * 255);
return result;
}
/**
* Convert XYZ to RGB
*
* @param XYZ in a double array.
* @return RGB in int array.
*/
public int[] XYZtoRGB(double[] XYZ) {
return XYZtoRGB(XYZ[0], XYZ[1], XYZ[2]);
}
/**
* @param X
* @param Y
* @param Z
* @return xyY values
*/
public double[] XYZtoxyY(double X, double Y, double Z) {
double[] result = new double[3];
if ((X + Y + Z) == 0) {
result[0] = chromaWhitePoint[0];
result[1] = chromaWhitePoint[1];
result[2] = chromaWhitePoint[2];
} else {
result[0] = X / (X + Y + Z);
result[1] = Y / (X + Y + Z);
result[2] = Y;
}
return result;
}
/**
* @param XYZ
* @return xyY values
*/
public double[] XYZtoxyY(double[] XYZ) {
return XYZtoxyY(XYZ[0], XYZ[1], XYZ[2]);
}
}