Update material-color-utilities
This commit is contained in:
parent
44ea0acf9b
commit
c5091ee4ca
@ -37,58 +37,58 @@ import utils.ColorUtils;
|
||||
*/
|
||||
public final class Cam16 {
|
||||
// Transforms XYZ color space coordinates to 'cone'/'RGB' responses in CAM16.
|
||||
static final float[][] XYZ_TO_CAM16RGB = {
|
||||
{0.401288f, 0.650173f, -0.051461f},
|
||||
{-0.250268f, 1.204414f, 0.045854f},
|
||||
{-0.002079f, 0.048952f, 0.953127f}
|
||||
static final double[][] XYZ_TO_CAM16RGB = {
|
||||
{0.401288, 0.650173, -0.051461},
|
||||
{-0.250268, 1.204414, 0.045854},
|
||||
{-0.002079, 0.048952, 0.953127}
|
||||
};
|
||||
|
||||
// Transforms 'cone'/'RGB' responses in CAM16 to XYZ color space coordinates.
|
||||
static final float[][] CAM16RGB_TO_XYZ = {
|
||||
{1.8620678f, -1.0112547f, 0.14918678f},
|
||||
{0.38752654f, 0.62144744f, -0.00897398f},
|
||||
{-0.01584150f, -0.03412294f, 1.0499644f}
|
||||
static final double[][] CAM16RGB_TO_XYZ = {
|
||||
{1.8620678, -1.0112547, 0.14918678},
|
||||
{0.38752654, 0.62144744, -0.00897398},
|
||||
{-0.01584150, -0.03412294, 1.0499644}
|
||||
};
|
||||
|
||||
// CAM16 color dimensions, see getters for documentation.
|
||||
private final float hue;
|
||||
private final float chroma;
|
||||
private final float j;
|
||||
private final float q;
|
||||
private final float m;
|
||||
private final float s;
|
||||
private final double hue;
|
||||
private final double chroma;
|
||||
private final double j;
|
||||
private final double q;
|
||||
private final double m;
|
||||
private final double s;
|
||||
|
||||
// Coordinates in UCS space. Used to determine color distance, like delta E equations in L*a*b*.
|
||||
private final float jstar;
|
||||
private final float astar;
|
||||
private final float bstar;
|
||||
private final double jstar;
|
||||
private final double astar;
|
||||
private final double bstar;
|
||||
|
||||
/**
|
||||
* CAM16 instances also have coordinates in the CAM16-UCS space, called J*, a*, b*, or jstar,
|
||||
* astar, bstar in code. CAM16-UCS is included in the CAM16 specification, and is used to measure
|
||||
* distances between colors.
|
||||
*/
|
||||
float distance(Cam16 other) {
|
||||
float dJ = getJStar() - other.getJStar();
|
||||
float dA = getAStar() - other.getAStar();
|
||||
float dB = getBStar() - other.getBStar();
|
||||
double distance(Cam16 other) {
|
||||
double dJ = getJstar() - other.getJstar();
|
||||
double dA = getAstar() - other.getAstar();
|
||||
double dB = getBstar() - other.getBstar();
|
||||
double dEPrime = Math.sqrt(dJ * dJ + dA * dA + dB * dB);
|
||||
double dE = 1.41 * Math.pow(dEPrime, 0.63);
|
||||
return (float) dE;
|
||||
return dE;
|
||||
}
|
||||
|
||||
/** Hue in CAM16 */
|
||||
public float getHue() {
|
||||
public double getHue() {
|
||||
return hue;
|
||||
}
|
||||
|
||||
/** Chroma in CAM16 */
|
||||
public float getChroma() {
|
||||
public double getChroma() {
|
||||
return chroma;
|
||||
}
|
||||
|
||||
/** Lightness in CAM16 */
|
||||
public float getJ() {
|
||||
public double getJ() {
|
||||
return j;
|
||||
}
|
||||
|
||||
@ -99,7 +99,7 @@ public final class Cam16 {
|
||||
* much brighter viewed in sunlight than in indoor light, but it is the lightest object under any
|
||||
* lighting.
|
||||
*/
|
||||
public float getQ() {
|
||||
public double getQ() {
|
||||
return q;
|
||||
}
|
||||
|
||||
@ -109,7 +109,7 @@ public final class Cam16 {
|
||||
* <p>Prefer chroma, colorfulness is an absolute quantity. For example, a yellow toy car is much
|
||||
* more colorful outside than inside, but it has the same chroma in both environments.
|
||||
*/
|
||||
public float getM() {
|
||||
public double getM() {
|
||||
return m;
|
||||
}
|
||||
|
||||
@ -119,22 +119,22 @@ public final class Cam16 {
|
||||
* <p>Colorfulness in proportion to brightness. Prefer chroma, saturation measures colorfulness
|
||||
* relative to the color's own brightness, where chroma is colorfulness relative to white.
|
||||
*/
|
||||
public float getS() {
|
||||
public double getS() {
|
||||
return s;
|
||||
}
|
||||
|
||||
/** Lightness coordinate in CAM16-UCS */
|
||||
public float getJStar() {
|
||||
public double getJstar() {
|
||||
return jstar;
|
||||
}
|
||||
|
||||
/** a* coordinate in CAM16-UCS */
|
||||
public float getAStar() {
|
||||
public double getAstar() {
|
||||
return astar;
|
||||
}
|
||||
|
||||
/** b* coordinate in CAM16-UCS */
|
||||
public float getBStar() {
|
||||
public double getBstar() {
|
||||
return bstar;
|
||||
}
|
||||
|
||||
@ -156,15 +156,15 @@ public final class Cam16 {
|
||||
* @param bstar CAM16-UCS b coordinate
|
||||
*/
|
||||
private Cam16(
|
||||
float hue,
|
||||
float chroma,
|
||||
float j,
|
||||
float q,
|
||||
float m,
|
||||
float s,
|
||||
float jstar,
|
||||
float astar,
|
||||
float bstar) {
|
||||
double hue,
|
||||
double chroma,
|
||||
double j,
|
||||
double q,
|
||||
double m,
|
||||
double s,
|
||||
double jstar,
|
||||
double astar,
|
||||
double bstar) {
|
||||
this.hue = hue;
|
||||
this.chroma = chroma;
|
||||
this.j = j;
|
||||
@ -200,88 +200,84 @@ public final class Cam16 {
|
||||
int red = (argb & 0x00ff0000) >> 16;
|
||||
int green = (argb & 0x0000ff00) >> 8;
|
||||
int blue = (argb & 0x000000ff);
|
||||
float redL = (float) ColorUtils.linearized(red);
|
||||
float greenL = (float) ColorUtils.linearized(green);
|
||||
float blueL = (float) ColorUtils.linearized(blue);
|
||||
float x = 0.41233895f * redL + 0.35762064f * greenL + 0.18051042f * blueL;
|
||||
float y = 0.2126f * redL + 0.7152f * greenL + 0.0722f * blueL;
|
||||
float z = 0.01932141f * redL + 0.11916382f * greenL + 0.95034478f * blueL;
|
||||
double redL = ColorUtils.linearized(red);
|
||||
double greenL = ColorUtils.linearized(green);
|
||||
double blueL = ColorUtils.linearized(blue);
|
||||
double x = 0.41233895 * redL + 0.35762064 * greenL + 0.18051042 * blueL;
|
||||
double y = 0.2126 * redL + 0.7152 * greenL + 0.0722 * blueL;
|
||||
double z = 0.01932141 * redL + 0.11916382 * greenL + 0.95034478 * blueL;
|
||||
|
||||
// Transform XYZ to 'cone'/'rgb' responses
|
||||
float[][] matrix = XYZ_TO_CAM16RGB;
|
||||
float rT = (x * matrix[0][0]) + (y * matrix[0][1]) + (z * matrix[0][2]);
|
||||
float gT = (x * matrix[1][0]) + (y * matrix[1][1]) + (z * matrix[1][2]);
|
||||
float bT = (x * matrix[2][0]) + (y * matrix[2][1]) + (z * matrix[2][2]);
|
||||
double[][] matrix = XYZ_TO_CAM16RGB;
|
||||
double rT = (x * matrix[0][0]) + (y * matrix[0][1]) + (z * matrix[0][2]);
|
||||
double gT = (x * matrix[1][0]) + (y * matrix[1][1]) + (z * matrix[1][2]);
|
||||
double bT = (x * matrix[2][0]) + (y * matrix[2][1]) + (z * matrix[2][2]);
|
||||
|
||||
// Discount illuminant
|
||||
float rD = viewingConditions.getRgbD()[0] * rT;
|
||||
float gD = viewingConditions.getRgbD()[1] * gT;
|
||||
float bD = viewingConditions.getRgbD()[2] * bT;
|
||||
double rD = viewingConditions.getRgbD()[0] * rT;
|
||||
double gD = viewingConditions.getRgbD()[1] * gT;
|
||||
double bD = viewingConditions.getRgbD()[2] * bT;
|
||||
|
||||
// Chromatic adaptation
|
||||
float rAF = (float) Math.pow(viewingConditions.getFl() * Math.abs(rD) / 100.0, 0.42);
|
||||
float gAF = (float) Math.pow(viewingConditions.getFl() * Math.abs(gD) / 100.0, 0.42);
|
||||
float bAF = (float) Math.pow(viewingConditions.getFl() * Math.abs(bD) / 100.0, 0.42);
|
||||
float rA = Math.signum(rD) * 400.0f * rAF / (rAF + 27.13f);
|
||||
float gA = Math.signum(gD) * 400.0f * gAF / (gAF + 27.13f);
|
||||
float bA = Math.signum(bD) * 400.0f * bAF / (bAF + 27.13f);
|
||||
double rAF = Math.pow(viewingConditions.getFl() * Math.abs(rD) / 100.0, 0.42);
|
||||
double gAF = Math.pow(viewingConditions.getFl() * Math.abs(gD) / 100.0, 0.42);
|
||||
double bAF = Math.pow(viewingConditions.getFl() * Math.abs(bD) / 100.0, 0.42);
|
||||
double rA = Math.signum(rD) * 400.0 * rAF / (rAF + 27.13);
|
||||
double gA = Math.signum(gD) * 400.0 * gAF / (gAF + 27.13);
|
||||
double bA = Math.signum(bD) * 400.0 * bAF / (bAF + 27.13);
|
||||
|
||||
// redness-greenness
|
||||
float a = (float) (11.0 * rA + -12.0 * gA + bA) / 11.0f;
|
||||
double a = (11.0 * rA + -12.0 * gA + bA) / 11.0;
|
||||
// yellowness-blueness
|
||||
float b = (float) (rA + gA - 2.0 * bA) / 9.0f;
|
||||
double b = (rA + gA - 2.0 * bA) / 9.0;
|
||||
|
||||
// auxiliary components
|
||||
float u = (20.0f * rA + 20.0f * gA + 21.0f * bA) / 20.0f;
|
||||
float p2 = (40.0f * rA + 20.0f * gA + bA) / 20.0f;
|
||||
double u = (20.0 * rA + 20.0 * gA + 21.0 * bA) / 20.0;
|
||||
double p2 = (40.0 * rA + 20.0 * gA + bA) / 20.0;
|
||||
|
||||
// hue
|
||||
float atan2 = (float) Math.atan2(b, a);
|
||||
float atanDegrees = atan2 * 180.0f / (float) Math.PI;
|
||||
float hue =
|
||||
double atan2 = Math.atan2(b, a);
|
||||
double atanDegrees = Math.toDegrees(atan2);
|
||||
double hue =
|
||||
atanDegrees < 0
|
||||
? atanDegrees + 360.0f
|
||||
: atanDegrees >= 360 ? atanDegrees - 360.0f : atanDegrees;
|
||||
float hueRadians = hue * (float) Math.PI / 180.0f;
|
||||
? atanDegrees + 360.0
|
||||
: atanDegrees >= 360 ? atanDegrees - 360.0 : atanDegrees;
|
||||
double hueRadians = Math.toRadians(hue);
|
||||
|
||||
// achromatic response to color
|
||||
float ac = p2 * viewingConditions.getNbb();
|
||||
double ac = p2 * viewingConditions.getNbb();
|
||||
|
||||
// CAM16 lightness and brightness
|
||||
float j =
|
||||
100.0f
|
||||
* (float)
|
||||
Math.pow(
|
||||
ac / viewingConditions.getAw(),
|
||||
viewingConditions.getC() * viewingConditions.getZ());
|
||||
float q =
|
||||
4.0f
|
||||
double j =
|
||||
100.0
|
||||
* Math.pow(
|
||||
ac / viewingConditions.getAw(),
|
||||
viewingConditions.getC() * viewingConditions.getZ());
|
||||
double q =
|
||||
4.0
|
||||
/ viewingConditions.getC()
|
||||
* (float) Math.sqrt(j / 100.0f)
|
||||
* (viewingConditions.getAw() + 4.0f)
|
||||
* Math.sqrt(j / 100.0)
|
||||
* (viewingConditions.getAw() + 4.0)
|
||||
* viewingConditions.getFlRoot();
|
||||
|
||||
// CAM16 chroma, colorfulness, and saturation.
|
||||
float huePrime = (hue < 20.14) ? hue + 360 : hue;
|
||||
float eHue = 0.25f * (float) (Math.cos(Math.toRadians(huePrime) + 2.0) + 3.8);
|
||||
float p1 = 50000.0f / 13.0f * eHue * viewingConditions.getNc() * viewingConditions.getNcb();
|
||||
float t = p1 * (float) Math.hypot(a, b) / (u + 0.305f);
|
||||
float alpha =
|
||||
(float) Math.pow(1.64 - Math.pow(0.29, viewingConditions.getN()), 0.73)
|
||||
* (float) Math.pow(t, 0.9);
|
||||
double huePrime = (hue < 20.14) ? hue + 360 : hue;
|
||||
double eHue = 0.25 * (Math.cos(Math.toRadians(huePrime) + 2.0) + 3.8);
|
||||
double p1 = 50000.0 / 13.0 * eHue * viewingConditions.getNc() * viewingConditions.getNcb();
|
||||
double t = p1 * Math.hypot(a, b) / (u + 0.305);
|
||||
double alpha =
|
||||
Math.pow(1.64 - Math.pow(0.29, viewingConditions.getN()), 0.73) * Math.pow(t, 0.9);
|
||||
// CAM16 chroma, colorfulness, saturation
|
||||
float c = alpha * (float) Math.sqrt(j / 100.0);
|
||||
float m = c * viewingConditions.getFlRoot();
|
||||
float s =
|
||||
50.0f
|
||||
* (float)
|
||||
Math.sqrt((alpha * viewingConditions.getC()) / (viewingConditions.getAw() + 4.0f));
|
||||
double c = alpha * Math.sqrt(j / 100.0);
|
||||
double m = c * viewingConditions.getFlRoot();
|
||||
double s =
|
||||
50.0 * Math.sqrt((alpha * viewingConditions.getC()) / (viewingConditions.getAw() + 4.0));
|
||||
|
||||
// CAM16-UCS components
|
||||
float jstar = (1.0f + 100.0f * 0.007f) * j / (1.0f + 0.007f * j);
|
||||
float mstar = 1.0f / 0.0228f * (float) Math.log1p(0.0228f * m);
|
||||
float astar = mstar * (float) Math.cos(hueRadians);
|
||||
float bstar = mstar * (float) Math.sin(hueRadians);
|
||||
double jstar = (1.0 + 100.0 * 0.007) * j / (1.0 + 0.007 * j);
|
||||
double mstar = 1.0 / 0.0228 * Math.log1p(0.0228 * m);
|
||||
double astar = mstar * Math.cos(hueRadians);
|
||||
double bstar = mstar * Math.sin(hueRadians);
|
||||
|
||||
return new Cam16(hue, c, j, q, m, s, jstar, astar, bstar);
|
||||
}
|
||||
@ -291,7 +287,7 @@ public final class Cam16 {
|
||||
* @param c CAM16 chroma
|
||||
* @param h CAM16 hue
|
||||
*/
|
||||
static Cam16 fromJch(float j, float c, float h) {
|
||||
static Cam16 fromJch(double j, double c, double h) {
|
||||
return fromJchInViewingConditions(j, c, h, ViewingConditions.DEFAULT);
|
||||
}
|
||||
|
||||
@ -302,25 +298,23 @@ public final class Cam16 {
|
||||
* @param viewingConditions Information about the environment where the color was observed.
|
||||
*/
|
||||
private static Cam16 fromJchInViewingConditions(
|
||||
float j, float c, float h, ViewingConditions viewingConditions) {
|
||||
float q =
|
||||
4.0f
|
||||
double j, double c, double h, ViewingConditions viewingConditions) {
|
||||
double q =
|
||||
4.0
|
||||
/ viewingConditions.getC()
|
||||
* (float) Math.sqrt(j / 100.0)
|
||||
* (viewingConditions.getAw() + 4.0f)
|
||||
* Math.sqrt(j / 100.0)
|
||||
* (viewingConditions.getAw() + 4.0)
|
||||
* viewingConditions.getFlRoot();
|
||||
float m = c * viewingConditions.getFlRoot();
|
||||
float alpha = c / (float) Math.sqrt(j / 100.0);
|
||||
float s =
|
||||
50.0f
|
||||
* (float)
|
||||
Math.sqrt((alpha * viewingConditions.getC()) / (viewingConditions.getAw() + 4.0f));
|
||||
double m = c * viewingConditions.getFlRoot();
|
||||
double alpha = c / Math.sqrt(j / 100.0);
|
||||
double s =
|
||||
50.0 * Math.sqrt((alpha * viewingConditions.getC()) / (viewingConditions.getAw() + 4.0));
|
||||
|
||||
float hueRadians = h * (float) Math.PI / 180.0f;
|
||||
float jstar = (1.0f + 100.0f * 0.007f) * j / (1.0f + 0.007f * j);
|
||||
float mstar = 1.0f / 0.0228f * (float) Math.log1p(0.0228 * m);
|
||||
float astar = mstar * (float) Math.cos(hueRadians);
|
||||
float bstar = mstar * (float) Math.sin(hueRadians);
|
||||
double hueRadians = Math.toRadians(h);
|
||||
double jstar = (1.0 + 100.0 * 0.007) * j / (1.0 + 0.007 * j);
|
||||
double mstar = 1.0 / 0.0228 * Math.log1p(0.0228 * m);
|
||||
double astar = mstar * Math.cos(hueRadians);
|
||||
double bstar = mstar * Math.sin(hueRadians);
|
||||
return new Cam16(h, c, j, q, m, s, jstar, astar, bstar);
|
||||
}
|
||||
|
||||
@ -333,7 +327,7 @@ public final class Cam16 {
|
||||
* @param bstar CAM16-UCS b dimension. Like a* in L*a*b*, it is a Cartesian coordinate on the X
|
||||
* axis.
|
||||
*/
|
||||
public static Cam16 fromUcs(float jstar, float astar, float bstar) {
|
||||
public static Cam16 fromUcs(double jstar, double astar, double bstar) {
|
||||
|
||||
return fromUcsInViewingConditions(jstar, astar, bstar, ViewingConditions.DEFAULT);
|
||||
}
|
||||
@ -349,24 +343,24 @@ public final class Cam16 {
|
||||
* @param viewingConditions Information about the environment where the color was observed.
|
||||
*/
|
||||
public static Cam16 fromUcsInViewingConditions(
|
||||
float jstar, float astar, float bstar, ViewingConditions viewingConditions) {
|
||||
double jstar, double astar, double bstar, ViewingConditions viewingConditions) {
|
||||
|
||||
double m = Math.hypot(astar, bstar);
|
||||
double m2 = Math.expm1(m * 0.0228f) / 0.0228f;
|
||||
double m2 = Math.expm1(m * 0.0228) / 0.0228;
|
||||
double c = m2 / viewingConditions.getFlRoot();
|
||||
double h = Math.atan2(bstar, astar) * (180.0f / Math.PI);
|
||||
double h = Math.atan2(bstar, astar) * (180.0 / Math.PI);
|
||||
if (h < 0.0) {
|
||||
h += 360.0f;
|
||||
h += 360.0;
|
||||
}
|
||||
float j = jstar / (1f - (jstar - 100f) * 0.007f);
|
||||
return fromJchInViewingConditions(j, (float) c, (float) h, viewingConditions);
|
||||
double j = jstar / (1. - (jstar - 100.) * 0.007);
|
||||
return fromJchInViewingConditions(j, c, h, viewingConditions);
|
||||
}
|
||||
|
||||
/**
|
||||
* ARGB representation of the color. Assumes the color was viewed in default viewing conditions,
|
||||
* which are near-identical to the default viewing conditions for sRGB.
|
||||
*/
|
||||
public int getInt() {
|
||||
public int toInt() {
|
||||
return viewed(ViewingConditions.DEFAULT);
|
||||
}
|
||||
|
||||
@ -377,58 +371,48 @@ public final class Cam16 {
|
||||
* @return ARGB representation of color
|
||||
*/
|
||||
int viewed(ViewingConditions viewingConditions) {
|
||||
float alpha =
|
||||
(getChroma() == 0.0 || getJ() == 0.0)
|
||||
? 0.0f
|
||||
: getChroma() / (float) Math.sqrt(getJ() / 100.0);
|
||||
double alpha =
|
||||
(getChroma() == 0.0 || getJ() == 0.0) ? 0.0 : getChroma() / Math.sqrt(getJ() / 100.0);
|
||||
|
||||
float t =
|
||||
(float)
|
||||
Math.pow(
|
||||
alpha / Math.pow(1.64 - Math.pow(0.29, viewingConditions.getN()), 0.73), 1.0 / 0.9);
|
||||
float hRad = getHue() * (float) Math.PI / 180.0f;
|
||||
double t =
|
||||
Math.pow(
|
||||
alpha / Math.pow(1.64 - Math.pow(0.29, viewingConditions.getN()), 0.73), 1.0 / 0.9);
|
||||
double hRad = Math.toRadians(getHue());
|
||||
|
||||
float eHue = 0.25f * (float) (Math.cos(hRad + 2.0) + 3.8);
|
||||
float ac =
|
||||
double eHue = 0.25 * (Math.cos(hRad + 2.0) + 3.8);
|
||||
double ac =
|
||||
viewingConditions.getAw()
|
||||
* (float)
|
||||
Math.pow(getJ() / 100.0, 1.0 / viewingConditions.getC() / viewingConditions.getZ());
|
||||
float p1 = eHue * (50000.0f / 13.0f) * viewingConditions.getNc() * viewingConditions.getNcb();
|
||||
float p2 = (ac / viewingConditions.getNbb());
|
||||
* Math.pow(getJ() / 100.0, 1.0 / viewingConditions.getC() / viewingConditions.getZ());
|
||||
double p1 = eHue * (50000.0 / 13.0) * viewingConditions.getNc() * viewingConditions.getNcb();
|
||||
double p2 = (ac / viewingConditions.getNbb());
|
||||
|
||||
float hSin = (float) Math.sin(hRad);
|
||||
float hCos = (float) Math.cos(hRad);
|
||||
double hSin = Math.sin(hRad);
|
||||
double hCos = Math.cos(hRad);
|
||||
|
||||
float gamma = 23.0f * (p2 + 0.305f) * t / (23.0f * p1 + 11.0f * t * hCos + 108.0f * t * hSin);
|
||||
float a = gamma * hCos;
|
||||
float b = gamma * hSin;
|
||||
float rA = (460.0f * p2 + 451.0f * a + 288.0f * b) / 1403.0f;
|
||||
float gA = (460.0f * p2 - 891.0f * a - 261.0f * b) / 1403.0f;
|
||||
float bA = (460.0f * p2 - 220.0f * a - 6300.0f * b) / 1403.0f;
|
||||
double gamma = 23.0 * (p2 + 0.305) * t / (23.0 * p1 + 11.0 * t * hCos + 108.0 * t * hSin);
|
||||
double a = gamma * hCos;
|
||||
double b = gamma * hSin;
|
||||
double rA = (460.0 * p2 + 451.0 * a + 288.0 * b) / 1403.0;
|
||||
double gA = (460.0 * p2 - 891.0 * a - 261.0 * b) / 1403.0;
|
||||
double bA = (460.0 * p2 - 220.0 * a - 6300.0 * b) / 1403.0;
|
||||
|
||||
float rCBase = (float) max(0, (27.13 * Math.abs(rA)) / (400.0 - Math.abs(rA)));
|
||||
float rC =
|
||||
Math.signum(rA)
|
||||
* (100.0f / viewingConditions.getFl())
|
||||
* (float) Math.pow(rCBase, 1.0 / 0.42);
|
||||
float gCBase = (float) max(0, (27.13 * Math.abs(gA)) / (400.0 - Math.abs(gA)));
|
||||
float gC =
|
||||
Math.signum(gA)
|
||||
* (100.0f / viewingConditions.getFl())
|
||||
* (float) Math.pow(gCBase, 1.0 / 0.42);
|
||||
float bCBase = (float) max(0, (27.13 * Math.abs(bA)) / (400.0 - Math.abs(bA)));
|
||||
float bC =
|
||||
Math.signum(bA)
|
||||
* (100.0f / viewingConditions.getFl())
|
||||
* (float) Math.pow(bCBase, 1.0 / 0.42);
|
||||
float rF = rC / viewingConditions.getRgbD()[0];
|
||||
float gF = gC / viewingConditions.getRgbD()[1];
|
||||
float bF = bC / viewingConditions.getRgbD()[2];
|
||||
double rCBase = max(0, (27.13 * Math.abs(rA)) / (400.0 - Math.abs(rA)));
|
||||
double rC =
|
||||
Math.signum(rA) * (100.0 / viewingConditions.getFl()) * Math.pow(rCBase, 1.0 / 0.42);
|
||||
double gCBase = max(0, (27.13 * Math.abs(gA)) / (400.0 - Math.abs(gA)));
|
||||
double gC =
|
||||
Math.signum(gA) * (100.0 / viewingConditions.getFl()) * Math.pow(gCBase, 1.0 / 0.42);
|
||||
double bCBase = max(0, (27.13 * Math.abs(bA)) / (400.0 - Math.abs(bA)));
|
||||
double bC =
|
||||
Math.signum(bA) * (100.0 / viewingConditions.getFl()) * Math.pow(bCBase, 1.0 / 0.42);
|
||||
double rF = rC / viewingConditions.getRgbD()[0];
|
||||
double gF = gC / viewingConditions.getRgbD()[1];
|
||||
double bF = bC / viewingConditions.getRgbD()[2];
|
||||
|
||||
float[][] matrix = CAM16RGB_TO_XYZ;
|
||||
float x = (rF * matrix[0][0]) + (gF * matrix[0][1]) + (bF * matrix[0][2]);
|
||||
float y = (rF * matrix[1][0]) + (gF * matrix[1][1]) + (bF * matrix[1][2]);
|
||||
float z = (rF * matrix[2][0]) + (gF * matrix[2][1]) + (bF * matrix[2][2]);
|
||||
double[][] matrix = CAM16RGB_TO_XYZ;
|
||||
double x = (rF * matrix[0][0]) + (gF * matrix[0][1]) + (bF * matrix[0][2]);
|
||||
double y = (rF * matrix[1][0]) + (gF * matrix[1][1]) + (bF * matrix[1][2]);
|
||||
double z = (rF * matrix[2][0]) + (gF * matrix[2][1]) + (bF * matrix[2][2]);
|
||||
|
||||
return ColorUtils.argbFromXyz(x, y, z);
|
||||
}
|
||||
|
||||
@ -17,7 +17,6 @@
|
||||
package hct;
|
||||
|
||||
import utils.ColorUtils;
|
||||
import utils.MathUtils;
|
||||
|
||||
/**
|
||||
* A color system built using CAM16 hue and chroma, and L* from L*a*b*.
|
||||
@ -39,9 +38,10 @@ import utils.MathUtils;
|
||||
* lighting environments.
|
||||
*/
|
||||
public final class Hct {
|
||||
private float hue;
|
||||
private float chroma;
|
||||
private float tone;
|
||||
private double hue;
|
||||
private double chroma;
|
||||
private double tone;
|
||||
private int argb;
|
||||
|
||||
/**
|
||||
* Create an HCT color from hue, chroma, and tone.
|
||||
@ -52,8 +52,9 @@ public final class Hct {
|
||||
* @param tone 0 <= tone <= 100; invalid values are corrected.
|
||||
* @return HCT representation of a color in default viewing conditions.
|
||||
*/
|
||||
public static Hct from(float hue, float chroma, float tone) {
|
||||
return new Hct(hue, chroma, tone);
|
||||
public static Hct from(double hue, double chroma, double tone) {
|
||||
int argb = HctSolver.solveToInt(hue, chroma, tone);
|
||||
return new Hct(argb);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -63,28 +64,27 @@ public final class Hct {
|
||||
* @return HCT representation of a color in default viewing conditions
|
||||
*/
|
||||
public static Hct fromInt(int argb) {
|
||||
Cam16 cam = Cam16.fromInt(argb);
|
||||
return new Hct(cam.getHue(), cam.getChroma(), (float) ColorUtils.lstarFromArgb(argb));
|
||||
return new Hct(argb);
|
||||
}
|
||||
|
||||
private Hct(float hue, float chroma, float tone) {
|
||||
setInternalState(gamutMap(hue, chroma, tone));
|
||||
private Hct(int argb) {
|
||||
setInternalState(argb);
|
||||
}
|
||||
|
||||
public float getHue() {
|
||||
public double getHue() {
|
||||
return hue;
|
||||
}
|
||||
|
||||
public float getChroma() {
|
||||
public double getChroma() {
|
||||
return chroma;
|
||||
}
|
||||
|
||||
public float getTone() {
|
||||
public double getTone() {
|
||||
return tone;
|
||||
}
|
||||
|
||||
public int toInt() {
|
||||
return gamutMap(hue, chroma, tone);
|
||||
return argb;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -93,8 +93,8 @@ public final class Hct {
|
||||
*
|
||||
* @param newHue 0 <= newHue < 360; invalid values are corrected.
|
||||
*/
|
||||
public void setHue(float newHue) {
|
||||
setInternalState(gamutMap((float) MathUtils.sanitizeDegreesDouble(newHue), chroma, tone));
|
||||
public void setHue(double newHue) {
|
||||
setInternalState(HctSolver.solveToInt(newHue, chroma, tone));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -103,8 +103,8 @@ public final class Hct {
|
||||
*
|
||||
* @param newChroma 0 <= newChroma < ?
|
||||
*/
|
||||
public void setChroma(float newChroma) {
|
||||
setInternalState(gamutMap(hue, newChroma, tone));
|
||||
public void setChroma(double newChroma) {
|
||||
setInternalState(HctSolver.solveToInt(hue, newChroma, tone));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -113,150 +113,15 @@ public final class Hct {
|
||||
*
|
||||
* @param newTone 0 <= newTone <= 100; invalid valids are corrected.
|
||||
*/
|
||||
public void setTone(float newTone) {
|
||||
setInternalState(gamutMap(hue, chroma, newTone));
|
||||
public void setTone(double newTone) {
|
||||
setInternalState(HctSolver.solveToInt(hue, chroma, newTone));
|
||||
}
|
||||
|
||||
private void setInternalState(int argb) {
|
||||
this.argb = argb;
|
||||
Cam16 cam = Cam16.fromInt(argb);
|
||||
float tone = (float) ColorUtils.lstarFromArgb(argb);
|
||||
hue = cam.getHue();
|
||||
chroma = cam.getChroma();
|
||||
this.tone = tone;
|
||||
}
|
||||
|
||||
/**
|
||||
* When the delta between the floor & ceiling of a binary search for maximum chroma at a hue and
|
||||
* tone is less than this, the binary search terminates.
|
||||
*/
|
||||
private static final float CHROMA_SEARCH_ENDPOINT = 0.4f;
|
||||
|
||||
/** The maximum color distance, in CAM16-UCS, between a requested color and the color returned. */
|
||||
private static final float DE_MAX = 1.0f;
|
||||
|
||||
/** The maximum difference between the requested L* and the L* returned. */
|
||||
private static final float DL_MAX = 0.2f;
|
||||
|
||||
/**
|
||||
* The minimum color distance, in CAM16-UCS, between a requested color and an 'exact' match. This
|
||||
* allows the binary search during gamut mapping to terminate much earlier when the error is
|
||||
* infinitesimal.
|
||||
*/
|
||||
private static final float DE_MAX_ERROR = 0.000000001f;
|
||||
|
||||
/**
|
||||
* When the delta between the floor & ceiling of a binary search for J, lightness in CAM16, is
|
||||
* less than this, the binary search terminates.
|
||||
*/
|
||||
private static final float LIGHTNESS_SEARCH_ENDPOINT = 0.01f;
|
||||
|
||||
/**
|
||||
* @param hue a number, in degrees, representing ex. red, orange, yellow, etc. Ranges from 0 <=
|
||||
* hue < 360.
|
||||
* @param chroma Informally, colorfulness. Ranges from 0 to roughly 150. Like all perceptually
|
||||
* accurate color systems, chroma has a different maximum for any given hue and tone, so the
|
||||
* color returned may be lower than the requested chroma.
|
||||
* @param tone Lightness. Ranges from 0 to 100.
|
||||
* @return ARGB representation of a color in default viewing conditions
|
||||
*/
|
||||
private static int gamutMap(float hue, float chroma, float tone) {
|
||||
return gamutMapInViewingConditions(hue, chroma, tone, ViewingConditions.DEFAULT);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param hue CAM16 hue.
|
||||
* @param chroma CAM16 chroma.
|
||||
* @param tone L*a*b* lightness.
|
||||
* @param viewingConditions Information about the environment where the color was observed.
|
||||
*/
|
||||
static int gamutMapInViewingConditions(
|
||||
float hue, float chroma, float tone, ViewingConditions viewingConditions) {
|
||||
|
||||
if (chroma < 1.0 || Math.round(tone) <= 0.0 || Math.round(tone) >= 100.0) {
|
||||
return ColorUtils.argbFromLstar(tone);
|
||||
}
|
||||
|
||||
hue = (float) MathUtils.sanitizeDegreesDouble(hue);
|
||||
|
||||
float high = chroma;
|
||||
float mid = chroma;
|
||||
float low = 0.0f;
|
||||
boolean isFirstLoop = true;
|
||||
|
||||
Cam16 answer = null;
|
||||
while (Math.abs(low - high) >= CHROMA_SEARCH_ENDPOINT) {
|
||||
Cam16 possibleAnswer = findCamByJ(hue, mid, tone);
|
||||
|
||||
if (isFirstLoop) {
|
||||
if (possibleAnswer != null) {
|
||||
return possibleAnswer.viewed(viewingConditions);
|
||||
} else {
|
||||
isFirstLoop = false;
|
||||
mid = low + (high - low) / 2.0f;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (possibleAnswer == null) {
|
||||
high = mid;
|
||||
} else {
|
||||
answer = possibleAnswer;
|
||||
low = mid;
|
||||
}
|
||||
|
||||
mid = low + (high - low) / 2.0f;
|
||||
}
|
||||
|
||||
if (answer == null) {
|
||||
return ColorUtils.argbFromLstar(tone);
|
||||
}
|
||||
|
||||
return answer.viewed(viewingConditions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param hue CAM16 hue
|
||||
* @param chroma CAM16 chroma
|
||||
* @param tone L*a*b* lightness
|
||||
* @return CAM16 instance within error tolerance of the provided dimensions, or null.
|
||||
*/
|
||||
private static Cam16 findCamByJ(float hue, float chroma, float tone) {
|
||||
float low = 0.0f;
|
||||
float high = 100.0f;
|
||||
float mid = 0.0f;
|
||||
float bestdL = 1000.0f;
|
||||
float bestdE = 1000.0f;
|
||||
|
||||
Cam16 bestCam = null;
|
||||
while (Math.abs(low - high) > LIGHTNESS_SEARCH_ENDPOINT) {
|
||||
mid = low + (high - low) / 2;
|
||||
Cam16 camBeforeClip = Cam16.fromJch(mid, chroma, hue);
|
||||
int clipped = camBeforeClip.getInt();
|
||||
float clippedLstar = (float) ColorUtils.lstarFromArgb(clipped);
|
||||
float dL = Math.abs(tone - clippedLstar);
|
||||
|
||||
if (dL < DL_MAX) {
|
||||
Cam16 camClipped = Cam16.fromInt(clipped);
|
||||
float dE =
|
||||
camClipped.distance(Cam16.fromJch(camClipped.getJ(), camClipped.getChroma(), hue));
|
||||
if (dE <= DE_MAX && dE <= bestdE) {
|
||||
bestdL = dL;
|
||||
bestdE = dE;
|
||||
bestCam = camClipped;
|
||||
}
|
||||
}
|
||||
|
||||
if (bestdL == 0 && bestdE < DE_MAX_ERROR) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (clippedLstar < tone) {
|
||||
low = mid;
|
||||
} else {
|
||||
high = mid;
|
||||
}
|
||||
}
|
||||
|
||||
return bestCam;
|
||||
this.tone = ColorUtils.lstarFromArgb(argb);
|
||||
}
|
||||
}
|
||||
|
||||
672
material-color-utilities/src/main/java/hct/HctSolver.java
Normal file
672
material-color-utilities/src/main/java/hct/HctSolver.java
Normal file
@ -0,0 +1,672 @@
|
||||
/*
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
// This file is automatically generated. Do not modify it.
|
||||
|
||||
package hct;
|
||||
|
||||
import utils.ColorUtils;
|
||||
import utils.MathUtils;
|
||||
|
||||
/** A class that solves the HCT equation. */
|
||||
public class HctSolver {
|
||||
private HctSolver() {}
|
||||
|
||||
static final double[][] SCALED_DISCOUNT_FROM_LINRGB =
|
||||
new double[][] {
|
||||
new double[] {
|
||||
0.001200833568784504, 0.002389694492170889, 0.0002795742885861124,
|
||||
},
|
||||
new double[] {
|
||||
0.0005891086651375999, 0.0029785502573438758, 0.0003270666104008398,
|
||||
},
|
||||
new double[] {
|
||||
0.00010146692491640572, 0.0005364214359186694, 0.0032979401770712076,
|
||||
},
|
||||
};
|
||||
|
||||
static final double[][] LINRGB_FROM_SCALED_DISCOUNT =
|
||||
new double[][] {
|
||||
new double[] {
|
||||
1373.2198709594231, -1100.4251190754821, -7.278681089101213,
|
||||
},
|
||||
new double[] {
|
||||
-271.815969077903, 559.6580465940733, -32.46047482791194,
|
||||
},
|
||||
new double[] {
|
||||
1.9622899599665666, -57.173814538844006, 308.7233197812385,
|
||||
},
|
||||
};
|
||||
|
||||
static final double[] Y_FROM_LINRGB = new double[] {0.2126, 0.7152, 0.0722};
|
||||
|
||||
static final double[] CRITICAL_PLANES =
|
||||
new double[] {
|
||||
0.015176349177441876,
|
||||
0.045529047532325624,
|
||||
0.07588174588720938,
|
||||
0.10623444424209313,
|
||||
0.13658714259697685,
|
||||
0.16693984095186062,
|
||||
0.19729253930674434,
|
||||
0.2276452376616281,
|
||||
0.2579979360165119,
|
||||
0.28835063437139563,
|
||||
0.3188300904430532,
|
||||
0.350925934958123,
|
||||
0.3848314933096426,
|
||||
0.42057480301049466,
|
||||
0.458183274052838,
|
||||
0.4976837250274023,
|
||||
0.5391024159806381,
|
||||
0.5824650784040898,
|
||||
0.6277969426914107,
|
||||
0.6751227633498623,
|
||||
0.7244668422128921,
|
||||
0.775853049866786,
|
||||
0.829304845476233,
|
||||
0.8848452951698498,
|
||||
0.942497089126609,
|
||||
1.0022825574869039,
|
||||
1.0642236851973577,
|
||||
1.1283421258858297,
|
||||
1.1946592148522128,
|
||||
1.2631959812511864,
|
||||
1.3339731595349034,
|
||||
1.407011200216447,
|
||||
1.4823302800086415,
|
||||
1.5599503113873272,
|
||||
1.6398909516233677,
|
||||
1.7221716113234105,
|
||||
1.8068114625156377,
|
||||
1.8938294463134073,
|
||||
1.9832442801866852,
|
||||
2.075074464868551,
|
||||
2.1693382909216234,
|
||||
2.2660538449872063,
|
||||
2.36523901573795,
|
||||
2.4669114995532007,
|
||||
2.5710888059345764,
|
||||
2.6777882626779785,
|
||||
2.7870270208169257,
|
||||
2.898822059350997,
|
||||
3.0131901897720907,
|
||||
3.1301480604002863,
|
||||
3.2497121605402226,
|
||||
3.3718988244681087,
|
||||
3.4967242352587946,
|
||||
3.624204428461639,
|
||||
3.754355295633311,
|
||||
3.887192587735158,
|
||||
4.022731918402185,
|
||||
4.160988767090289,
|
||||
4.301978482107941,
|
||||
4.445716283538092,
|
||||
4.592217266055746,
|
||||
4.741496401646282,
|
||||
4.893568542229298,
|
||||
5.048448422192488,
|
||||
5.20615066083972,
|
||||
5.3666897647573375,
|
||||
5.5300801301023865,
|
||||
5.696336044816294,
|
||||
5.865471690767354,
|
||||
6.037501145825082,
|
||||
6.212438385869475,
|
||||
6.390297286737924,
|
||||
6.571091626112461,
|
||||
6.7548350853498045,
|
||||
6.941541251256611,
|
||||
7.131223617812143,
|
||||
7.323895587840543,
|
||||
7.5195704746346665,
|
||||
7.7182615035334345,
|
||||
7.919981813454504,
|
||||
8.124744458384042,
|
||||
8.332562408825165,
|
||||
8.543448553206703,
|
||||
8.757415699253682,
|
||||
8.974476575321063,
|
||||
9.194643831691977,
|
||||
9.417930041841839,
|
||||
9.644347703669503,
|
||||
9.873909240696694,
|
||||
10.106627003236781,
|
||||
10.342513269534024,
|
||||
10.58158024687427,
|
||||
10.8238400726681,
|
||||
11.069304815507364,
|
||||
11.317986476196008,
|
||||
11.569896988756009,
|
||||
11.825048221409341,
|
||||
12.083451977536606,
|
||||
12.345119996613247,
|
||||
12.610063955123938,
|
||||
12.878295467455942,
|
||||
13.149826086772048,
|
||||
13.42466730586372,
|
||||
13.702830557985108,
|
||||
13.984327217668513,
|
||||
14.269168601521828,
|
||||
14.55736596900856,
|
||||
14.848930523210871,
|
||||
15.143873411576273,
|
||||
15.44220572664832,
|
||||
15.743938506781891,
|
||||
16.04908273684337,
|
||||
16.35764934889634,
|
||||
16.66964922287304,
|
||||
16.985093187232053,
|
||||
17.30399201960269,
|
||||
17.62635644741625,
|
||||
17.95219714852476,
|
||||
18.281524751807332,
|
||||
18.614349837764564,
|
||||
18.95068293910138,
|
||||
19.290534541298456,
|
||||
19.633915083172692,
|
||||
19.98083495742689,
|
||||
20.331304511189067,
|
||||
20.685334046541502,
|
||||
21.042933821039977,
|
||||
21.404114048223256,
|
||||
21.76888489811322,
|
||||
22.137256497705877,
|
||||
22.50923893145328,
|
||||
22.884842241736916,
|
||||
23.264076429332462,
|
||||
23.6469514538663,
|
||||
24.033477234264016,
|
||||
24.42366364919083,
|
||||
24.817520537484558,
|
||||
25.21505769858089,
|
||||
25.61628489293138,
|
||||
26.021211842414342,
|
||||
26.429848230738664,
|
||||
26.842203703840827,
|
||||
27.258287870275353,
|
||||
27.678110301598522,
|
||||
28.10168053274597,
|
||||
28.529008062403893,
|
||||
28.96010235337422,
|
||||
29.39497283293396,
|
||||
29.83362889318845,
|
||||
30.276079891419332,
|
||||
30.722335150426627,
|
||||
31.172403958865512,
|
||||
31.62629557157785,
|
||||
32.08401920991837,
|
||||
32.54558406207592,
|
||||
33.010999283389665,
|
||||
33.4802739966603,
|
||||
33.953417292456834,
|
||||
34.430438229418264,
|
||||
34.911345834551085,
|
||||
35.39614910352207,
|
||||
35.88485700094671,
|
||||
36.37747846067349,
|
||||
36.87402238606382,
|
||||
37.37449765026789,
|
||||
37.87891309649659,
|
||||
38.38727753828926,
|
||||
38.89959975977785,
|
||||
39.41588851594697,
|
||||
39.93615253289054,
|
||||
40.460400508064545,
|
||||
40.98864111053629,
|
||||
41.520882981230194,
|
||||
42.05713473317016,
|
||||
42.597404951718396,
|
||||
43.141702194811224,
|
||||
43.6900349931913,
|
||||
44.24241185063697,
|
||||
44.798841244188324,
|
||||
45.35933162437017,
|
||||
45.92389141541209,
|
||||
46.49252901546552,
|
||||
47.065252796817916,
|
||||
47.64207110610409,
|
||||
48.22299226451468,
|
||||
48.808024568002054,
|
||||
49.3971762874833,
|
||||
49.9904556690408,
|
||||
50.587870934119984,
|
||||
51.189430279724725,
|
||||
51.79514187861014,
|
||||
52.40501387947288,
|
||||
53.0190544071392,
|
||||
53.637271562750364,
|
||||
54.259673423945976,
|
||||
54.88626804504493,
|
||||
55.517063457223934,
|
||||
56.15206766869424,
|
||||
56.79128866487574,
|
||||
57.43473440856916,
|
||||
58.08241284012621,
|
||||
58.734331877617365,
|
||||
59.39049941699807,
|
||||
60.05092333227251,
|
||||
60.715611475655585,
|
||||
61.38457167773311,
|
||||
62.057811747619894,
|
||||
62.7353394731159,
|
||||
63.417162620860914,
|
||||
64.10328893648692,
|
||||
64.79372614476921,
|
||||
65.48848194977529,
|
||||
66.18756403501224,
|
||||
66.89098006357258,
|
||||
67.59873767827808,
|
||||
68.31084450182222,
|
||||
69.02730813691093,
|
||||
69.74813616640164,
|
||||
70.47333615344107,
|
||||
71.20291564160104,
|
||||
71.93688215501312,
|
||||
72.67524319850172,
|
||||
73.41800625771542,
|
||||
74.16517879925733,
|
||||
74.9167682708136,
|
||||
75.67278210128072,
|
||||
76.43322770089146,
|
||||
77.1981124613393,
|
||||
77.96744375590167,
|
||||
78.74122893956174,
|
||||
79.51947534912904,
|
||||
80.30219030335869,
|
||||
81.08938110306934,
|
||||
81.88105503125999,
|
||||
82.67721935322541,
|
||||
83.4778813166706,
|
||||
84.28304815182372,
|
||||
85.09272707154808,
|
||||
85.90692527145302,
|
||||
86.72564993000343,
|
||||
87.54890820862819,
|
||||
88.3767072518277,
|
||||
89.2090541872801,
|
||||
90.04595612594655,
|
||||
90.88742016217518,
|
||||
91.73345337380438,
|
||||
92.58406282226491,
|
||||
93.43925555268066,
|
||||
94.29903859396902,
|
||||
95.16341895893969,
|
||||
96.03240364439274,
|
||||
96.9059996312159,
|
||||
97.78421388448044,
|
||||
98.6670533535366,
|
||||
99.55452497210776,
|
||||
};
|
||||
|
||||
/**
|
||||
* Sanitizes a small enough angle in radians.
|
||||
*
|
||||
* @param angle An angle in radians; must not deviate too much from 0.
|
||||
* @return A coterminal angle between 0 and 2pi.
|
||||
*/
|
||||
static double sanitizeRadians(double angle) {
|
||||
return (angle + Math.PI * 8) % (Math.PI * 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delinearizes an RGB component, returning a floating-point number.
|
||||
*
|
||||
* @param rgbComponent 0.0 <= rgb_component <= 100.0, represents linear R/G/B channel
|
||||
* @return 0.0 <= output <= 255.0, color channel converted to regular RGB space
|
||||
*/
|
||||
static double trueDelinearized(double rgbComponent) {
|
||||
double normalized = rgbComponent / 100.0;
|
||||
double delinearized = 0.0;
|
||||
if (normalized <= 0.0031308) {
|
||||
delinearized = normalized * 12.92;
|
||||
} else {
|
||||
delinearized = 1.055 * Math.pow(normalized, 1.0 / 2.4) - 0.055;
|
||||
}
|
||||
return delinearized * 255.0;
|
||||
}
|
||||
|
||||
static double chromaticAdaptation(double component) {
|
||||
double af = Math.pow(Math.abs(component), 0.42);
|
||||
return MathUtils.signum(component) * 400.0 * af / (af + 27.13);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hue of a linear RGB color in CAM16.
|
||||
*
|
||||
* @param linrgb The linear RGB coordinates of a color.
|
||||
* @return The hue of the color in CAM16, in radians.
|
||||
*/
|
||||
static double hueOf(double[] linrgb) {
|
||||
double[] scaledDiscount = MathUtils.matrixMultiply(linrgb, SCALED_DISCOUNT_FROM_LINRGB);
|
||||
double rA = chromaticAdaptation(scaledDiscount[0]);
|
||||
double gA = chromaticAdaptation(scaledDiscount[1]);
|
||||
double bA = chromaticAdaptation(scaledDiscount[2]);
|
||||
// redness-greenness
|
||||
double a = (11.0 * rA + -12.0 * gA + bA) / 11.0;
|
||||
// yellowness-blueness
|
||||
double b = (rA + gA - 2.0 * bA) / 9.0;
|
||||
return Math.atan2(b, a);
|
||||
}
|
||||
|
||||
static boolean areInCyclicOrder(double a, double b, double c) {
|
||||
double deltaAB = sanitizeRadians(b - a);
|
||||
double deltaAC = sanitizeRadians(c - a);
|
||||
return deltaAB < deltaAC;
|
||||
}
|
||||
|
||||
/**
|
||||
* Solves the lerp equation.
|
||||
*
|
||||
* @param source The starting number.
|
||||
* @param mid The number in the middle.
|
||||
* @param target The ending number.
|
||||
* @return A number t such that lerp(source, target, t) = mid.
|
||||
*/
|
||||
static double intercept(double source, double mid, double target) {
|
||||
return (mid - source) / (target - source);
|
||||
}
|
||||
|
||||
static double[] lerpPoint(double[] source, double t, double[] target) {
|
||||
return new double[] {
|
||||
source[0] + (target[0] - source[0]) * t,
|
||||
source[1] + (target[1] - source[1]) * t,
|
||||
source[2] + (target[2] - source[2]) * t,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Intersects a segment with a plane.
|
||||
*
|
||||
* @param source The coordinates of point A.
|
||||
* @param coordinate The R-, G-, or B-coordinate of the plane.
|
||||
* @param target The coordinates of point B.
|
||||
* @param axis The axis the plane is perpendicular with. (0: R, 1: G, 2: B)
|
||||
* @return The intersection point of the segment AB with the plane R=coordinate, G=coordinate, or
|
||||
* B=coordinate
|
||||
*/
|
||||
static double[] setCoordinate(double[] source, double coordinate, double[] target, int axis) {
|
||||
double t = intercept(source[axis], coordinate, target[axis]);
|
||||
return lerpPoint(source, t, target);
|
||||
}
|
||||
|
||||
static boolean isBounded(double x) {
|
||||
return 0.0 <= x && x <= 100.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the nth possible vertex of the polygonal intersection.
|
||||
*
|
||||
* @param y The Y value of the plane.
|
||||
* @param n The zero-based index of the point. 0 <= n <= 11.
|
||||
* @return The nth possible vertex of the polygonal intersection of the y plane and the RGB cube,
|
||||
* in linear RGB coordinates, if it exists. If this possible vertex lies outside of the cube,
|
||||
* [-1.0, -1.0, -1.0] is returned.
|
||||
*/
|
||||
static double[] nthVertex(double y, int n) {
|
||||
double kR = Y_FROM_LINRGB[0];
|
||||
double kG = Y_FROM_LINRGB[1];
|
||||
double kB = Y_FROM_LINRGB[2];
|
||||
double coordA = n % 4 <= 1 ? 0.0 : 100.0;
|
||||
double coordB = n % 2 == 0 ? 0.0 : 100.0;
|
||||
if (n < 4) {
|
||||
double g = coordA;
|
||||
double b = coordB;
|
||||
double r = (y - g * kG - b * kB) / kR;
|
||||
if (isBounded(r)) {
|
||||
return new double[] {r, g, b};
|
||||
} else {
|
||||
return new double[] {-1.0, -1.0, -1.0};
|
||||
}
|
||||
} else if (n < 8) {
|
||||
double b = coordA;
|
||||
double r = coordB;
|
||||
double g = (y - r * kR - b * kB) / kG;
|
||||
if (isBounded(g)) {
|
||||
return new double[] {r, g, b};
|
||||
} else {
|
||||
return new double[] {-1.0, -1.0, -1.0};
|
||||
}
|
||||
} else {
|
||||
double r = coordA;
|
||||
double g = coordB;
|
||||
double b = (y - r * kR - g * kG) / kB;
|
||||
if (isBounded(b)) {
|
||||
return new double[] {r, g, b};
|
||||
} else {
|
||||
return new double[] {-1.0, -1.0, -1.0};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the segment containing the desired color.
|
||||
*
|
||||
* @param y The Y value of the color.
|
||||
* @param targetHue The hue of the color.
|
||||
* @return A list of two sets of linear RGB coordinates, each corresponding to an endpoint of the
|
||||
* segment containing the desired color.
|
||||
*/
|
||||
static double[][] bisectToSegment(double y, double targetHue) {
|
||||
double[] left = new double[] {-1.0, -1.0, -1.0};
|
||||
double[] right = left;
|
||||
double leftHue = 0.0;
|
||||
double rightHue = 0.0;
|
||||
boolean initialized = false;
|
||||
boolean uncut = true;
|
||||
for (int n = 0; n < 12; n++) {
|
||||
double[] mid = nthVertex(y, n);
|
||||
if (mid[0] < 0) {
|
||||
continue;
|
||||
}
|
||||
double midHue = hueOf(mid);
|
||||
if (!initialized) {
|
||||
left = mid;
|
||||
right = mid;
|
||||
leftHue = midHue;
|
||||
rightHue = midHue;
|
||||
initialized = true;
|
||||
continue;
|
||||
}
|
||||
if (uncut || areInCyclicOrder(leftHue, midHue, rightHue)) {
|
||||
uncut = false;
|
||||
if (areInCyclicOrder(leftHue, targetHue, midHue)) {
|
||||
right = mid;
|
||||
rightHue = midHue;
|
||||
} else {
|
||||
left = mid;
|
||||
leftHue = midHue;
|
||||
}
|
||||
}
|
||||
}
|
||||
return new double[][] {left, right};
|
||||
}
|
||||
|
||||
static double[] midpoint(double[] a, double[] b) {
|
||||
return new double[] {
|
||||
(a[0] + b[0]) / 2, (a[1] + b[1]) / 2, (a[2] + b[2]) / 2,
|
||||
};
|
||||
}
|
||||
|
||||
static int criticalPlaneBelow(double x) {
|
||||
return (int) Math.floor(x - 0.5);
|
||||
}
|
||||
|
||||
static int criticalPlaneAbove(double x) {
|
||||
return (int) Math.ceil(x - 0.5);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a color with the given Y and hue on the boundary of the cube.
|
||||
*
|
||||
* @param y The Y value of the color.
|
||||
* @param targetHue The hue of the color.
|
||||
* @return The desired color, in linear RGB coordinates.
|
||||
*/
|
||||
static double[] bisectToLimit(double y, double targetHue) {
|
||||
double[][] segment = bisectToSegment(y, targetHue);
|
||||
double[] left = segment[0];
|
||||
double leftHue = hueOf(left);
|
||||
double[] right = segment[1];
|
||||
for (int axis = 0; axis < 3; axis++) {
|
||||
if (left[axis] != right[axis]) {
|
||||
int lPlane = -1;
|
||||
int rPlane = 255;
|
||||
if (left[axis] < right[axis]) {
|
||||
lPlane = criticalPlaneBelow(trueDelinearized(left[axis]));
|
||||
rPlane = criticalPlaneAbove(trueDelinearized(right[axis]));
|
||||
} else {
|
||||
lPlane = criticalPlaneAbove(trueDelinearized(left[axis]));
|
||||
rPlane = criticalPlaneBelow(trueDelinearized(right[axis]));
|
||||
}
|
||||
for (int i = 0; i < 8; i++) {
|
||||
if (Math.abs(rPlane - lPlane) <= 1) {
|
||||
break;
|
||||
} else {
|
||||
int mPlane = (int) Math.floor((lPlane + rPlane) / 2.0);
|
||||
double midPlaneCoordinate = CRITICAL_PLANES[mPlane];
|
||||
double[] mid = setCoordinate(left, midPlaneCoordinate, right, axis);
|
||||
double midHue = hueOf(mid);
|
||||
if (areInCyclicOrder(leftHue, targetHue, midHue)) {
|
||||
right = mid;
|
||||
rPlane = mPlane;
|
||||
} else {
|
||||
left = mid;
|
||||
leftHue = midHue;
|
||||
lPlane = mPlane;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return midpoint(left, right);
|
||||
}
|
||||
|
||||
static double inverseChromaticAdaptation(double adapted) {
|
||||
double adaptedAbs = Math.abs(adapted);
|
||||
double base = Math.max(0, 27.13 * adaptedAbs / (400.0 - adaptedAbs));
|
||||
return MathUtils.signum(adapted) * Math.pow(base, 1.0 / 0.42);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a color with the given hue, chroma, and Y.
|
||||
*
|
||||
* @param hueRadians The desired hue in radians.
|
||||
* @param chroma The desired chroma.
|
||||
* @param y The desired Y.
|
||||
* @return The desired color as a hexadecimal integer, if found; 0 otherwise.
|
||||
*/
|
||||
static int findResultByJ(double hueRadians, double chroma, double y) {
|
||||
// Initial estimate of j.
|
||||
double j = Math.sqrt(y) * 11.0;
|
||||
// ===========================================================
|
||||
// Operations inlined from Cam16 to avoid repeated calculation
|
||||
// ===========================================================
|
||||
ViewingConditions viewingConditions = ViewingConditions.DEFAULT;
|
||||
double tInnerCoeff = 1 / Math.pow(1.64 - Math.pow(0.29, viewingConditions.getN()), 0.73);
|
||||
double eHue = 0.25 * (Math.cos(hueRadians + 2.0) + 3.8);
|
||||
double p1 = eHue * (50000.0 / 13.0) * viewingConditions.getNc() * viewingConditions.getNcb();
|
||||
double hSin = Math.sin(hueRadians);
|
||||
double hCos = Math.cos(hueRadians);
|
||||
for (int iterationRound = 0; iterationRound < 5; iterationRound++) {
|
||||
// ===========================================================
|
||||
// Operations inlined from Cam16 to avoid repeated calculation
|
||||
// ===========================================================
|
||||
double jNormalized = j / 100.0;
|
||||
double alpha = chroma == 0.0 || j == 0.0 ? 0.0 : chroma / Math.sqrt(jNormalized);
|
||||
double t = Math.pow(alpha * tInnerCoeff, 1.0 / 0.9);
|
||||
double ac =
|
||||
viewingConditions.getAw()
|
||||
* Math.pow(jNormalized, 1.0 / viewingConditions.getC() / viewingConditions.getZ());
|
||||
double p2 = ac / viewingConditions.getNbb();
|
||||
double gamma = 23.0 * (p2 + 0.305) * t / (23.0 * p1 + 11 * t * hCos + 108.0 * t * hSin);
|
||||
double a = gamma * hCos;
|
||||
double b = gamma * hSin;
|
||||
double rA = (460.0 * p2 + 451.0 * a + 288.0 * b) / 1403.0;
|
||||
double gA = (460.0 * p2 - 891.0 * a - 261.0 * b) / 1403.0;
|
||||
double bA = (460.0 * p2 - 220.0 * a - 6300.0 * b) / 1403.0;
|
||||
double rCScaled = inverseChromaticAdaptation(rA);
|
||||
double gCScaled = inverseChromaticAdaptation(gA);
|
||||
double bCScaled = inverseChromaticAdaptation(bA);
|
||||
double[] linrgb =
|
||||
MathUtils.matrixMultiply(
|
||||
new double[] {rCScaled, gCScaled, bCScaled}, LINRGB_FROM_SCALED_DISCOUNT);
|
||||
// ===========================================================
|
||||
// Operations inlined from Cam16 to avoid repeated calculation
|
||||
// ===========================================================
|
||||
if (linrgb[0] < 0 || linrgb[1] < 0 || linrgb[2] < 0) {
|
||||
return 0;
|
||||
}
|
||||
double kR = Y_FROM_LINRGB[0];
|
||||
double kG = Y_FROM_LINRGB[1];
|
||||
double kB = Y_FROM_LINRGB[2];
|
||||
double fnj = kR * linrgb[0] + kG * linrgb[1] + kB * linrgb[2];
|
||||
if (fnj <= 0) {
|
||||
return 0;
|
||||
}
|
||||
if (iterationRound == 4 || Math.abs(fnj - y) < 0.002) {
|
||||
if (linrgb[0] > 100.01 || linrgb[1] > 100.01 || linrgb[2] > 100.01) {
|
||||
return 0;
|
||||
}
|
||||
return ColorUtils.argbFromLinrgb(linrgb);
|
||||
}
|
||||
// Iterates with Newton method,
|
||||
// Using 2 * fn(j) / j as the approximation of fn'(j)
|
||||
j = j - (fnj - y) * j / (2 * fnj);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds an sRGB color with the given hue, chroma, and L*, if possible.
|
||||
*
|
||||
* @param hueDegrees The desired hue, in degrees.
|
||||
* @param chroma The desired chroma.
|
||||
* @param lstar The desired L*.
|
||||
* @return A hexadecimal representing the sRGB color. The color has sufficiently close hue,
|
||||
* chroma, and L* to the desired values, if possible; otherwise, the hue and L* will be
|
||||
* sufficiently close, and chroma will be maximized.
|
||||
*/
|
||||
public static int solveToInt(double hueDegrees, double chroma, double lstar) {
|
||||
if (chroma < 0.0001 || lstar < 0.0001 || lstar > 99.9999) {
|
||||
return ColorUtils.argbFromLstar(lstar);
|
||||
}
|
||||
hueDegrees = MathUtils.sanitizeDegreesDouble(hueDegrees);
|
||||
double hueRadians = hueDegrees / 180 * Math.PI;
|
||||
double y = ColorUtils.yFromLstar(lstar);
|
||||
int exactAnswer = findResultByJ(hueRadians, chroma, y);
|
||||
if (exactAnswer != 0) {
|
||||
return exactAnswer;
|
||||
}
|
||||
double[] linrgb = bisectToLimit(y, hueRadians);
|
||||
return ColorUtils.argbFromLinrgb(linrgb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds an sRGB color with the given hue, chroma, and L*, if possible.
|
||||
*
|
||||
* @param hueDegrees The desired hue, in degrees.
|
||||
* @param chroma The desired chroma.
|
||||
* @param lstar The desired L*.
|
||||
* @return An CAM16 object representing the sRGB color. The color has sufficiently close hue,
|
||||
* chroma, and L* to the desired values, if possible; otherwise, the hue and L* will be
|
||||
* sufficiently close, and chroma will be maximized.
|
||||
*/
|
||||
public static Cam16 solveToCam(double hueDegrees, double chroma, double lstar) {
|
||||
return Cam16.fromInt(solveToInt(hueDegrees, chroma, lstar));
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,64 +34,64 @@ public final class ViewingConditions {
|
||||
/** sRGB-like viewing conditions. */
|
||||
public static final ViewingConditions DEFAULT =
|
||||
ViewingConditions.make(
|
||||
new float[] {
|
||||
(float) ColorUtils.whitePointD65()[0],
|
||||
(float) ColorUtils.whitePointD65()[1],
|
||||
(float) ColorUtils.whitePointD65()[2]
|
||||
new double[] {
|
||||
ColorUtils.whitePointD65()[0],
|
||||
ColorUtils.whitePointD65()[1],
|
||||
ColorUtils.whitePointD65()[2]
|
||||
},
|
||||
(float) (200.0f / Math.PI * ColorUtils.yFromLstar(50.0f) / 100.f),
|
||||
50.0f,
|
||||
2.0f,
|
||||
(200.0 / Math.PI * ColorUtils.yFromLstar(50.0) / 100.f),
|
||||
50.0,
|
||||
2.0,
|
||||
false);
|
||||
|
||||
private final float aw;
|
||||
private final float nbb;
|
||||
private final float ncb;
|
||||
private final float c;
|
||||
private final float nc;
|
||||
private final float n;
|
||||
private final float[] rgbD;
|
||||
private final float fl;
|
||||
private final float flRoot;
|
||||
private final float z;
|
||||
private final double aw;
|
||||
private final double nbb;
|
||||
private final double ncb;
|
||||
private final double c;
|
||||
private final double nc;
|
||||
private final double n;
|
||||
private final double[] rgbD;
|
||||
private final double fl;
|
||||
private final double flRoot;
|
||||
private final double z;
|
||||
|
||||
public float getAw() {
|
||||
public double getAw() {
|
||||
return aw;
|
||||
}
|
||||
|
||||
public float getN() {
|
||||
public double getN() {
|
||||
return n;
|
||||
}
|
||||
|
||||
public float getNbb() {
|
||||
public double getNbb() {
|
||||
return nbb;
|
||||
}
|
||||
|
||||
float getNcb() {
|
||||
double getNcb() {
|
||||
return ncb;
|
||||
}
|
||||
|
||||
float getC() {
|
||||
double getC() {
|
||||
return c;
|
||||
}
|
||||
|
||||
float getNc() {
|
||||
double getNc() {
|
||||
return nc;
|
||||
}
|
||||
|
||||
public float[] getRgbD() {
|
||||
public double[] getRgbD() {
|
||||
return rgbD;
|
||||
}
|
||||
|
||||
float getFl() {
|
||||
double getFl() {
|
||||
return fl;
|
||||
}
|
||||
|
||||
public float getFlRoot() {
|
||||
public double getFlRoot() {
|
||||
return flRoot;
|
||||
}
|
||||
|
||||
float getZ() {
|
||||
double getZ() {
|
||||
return z;
|
||||
}
|
||||
|
||||
@ -114,57 +114,56 @@ public final class ViewingConditions {
|
||||
* perform this process on self-luminous objects like displays.
|
||||
*/
|
||||
static ViewingConditions make(
|
||||
float[] whitePoint,
|
||||
float adaptingLuminance,
|
||||
float backgroundLstar,
|
||||
float surround,
|
||||
double[] whitePoint,
|
||||
double adaptingLuminance,
|
||||
double backgroundLstar,
|
||||
double surround,
|
||||
boolean discountingIlluminant) {
|
||||
// Transform white point XYZ to 'cone'/'rgb' responses
|
||||
float[][] matrix = Cam16.XYZ_TO_CAM16RGB;
|
||||
float[] xyz = whitePoint;
|
||||
float rW = (xyz[0] * matrix[0][0]) + (xyz[1] * matrix[0][1]) + (xyz[2] * matrix[0][2]);
|
||||
float gW = (xyz[0] * matrix[1][0]) + (xyz[1] * matrix[1][1]) + (xyz[2] * matrix[1][2]);
|
||||
float bW = (xyz[0] * matrix[2][0]) + (xyz[1] * matrix[2][1]) + (xyz[2] * matrix[2][2]);
|
||||
float f = 0.8f + (surround / 10.0f);
|
||||
float c =
|
||||
double[][] matrix = Cam16.XYZ_TO_CAM16RGB;
|
||||
double[] xyz = whitePoint;
|
||||
double rW = (xyz[0] * matrix[0][0]) + (xyz[1] * matrix[0][1]) + (xyz[2] * matrix[0][2]);
|
||||
double gW = (xyz[0] * matrix[1][0]) + (xyz[1] * matrix[1][1]) + (xyz[2] * matrix[1][2]);
|
||||
double bW = (xyz[0] * matrix[2][0]) + (xyz[1] * matrix[2][1]) + (xyz[2] * matrix[2][2]);
|
||||
double f = 0.8 + (surround / 10.0);
|
||||
double c =
|
||||
(f >= 0.9)
|
||||
? (float) MathUtils.lerp(0.59f, 0.69f, ((f - 0.9f) * 10.0f))
|
||||
: (float) MathUtils.lerp(0.525f, 0.59f, ((f - 0.8f) * 10.0f));
|
||||
float d =
|
||||
? MathUtils.lerp(0.59, 0.69, ((f - 0.9) * 10.0))
|
||||
: MathUtils.lerp(0.525, 0.59, ((f - 0.8) * 10.0));
|
||||
double d =
|
||||
discountingIlluminant
|
||||
? 1.0f
|
||||
: f * (1.0f - ((1.0f / 3.6f) * (float) Math.exp((-adaptingLuminance - 42.0f) / 92.0f)));
|
||||
d = (d > 1.0) ? 1.0f : (d < 0.0) ? 0.0f : d;
|
||||
float nc = f;
|
||||
float[] rgbD =
|
||||
new float[] {
|
||||
d * (100.0f / rW) + 1.0f - d, d * (100.0f / gW) + 1.0f - d, d * (100.0f / bW) + 1.0f - d
|
||||
? 1.0
|
||||
: f * (1.0 - ((1.0 / 3.6) * Math.exp((-adaptingLuminance - 42.0) / 92.0)));
|
||||
d = MathUtils.clampDouble(0.0, 1.0, d);
|
||||
double nc = f;
|
||||
double[] rgbD =
|
||||
new double[] {
|
||||
d * (100.0 / rW) + 1.0 - d, d * (100.0 / gW) + 1.0 - d, d * (100.0 / bW) + 1.0 - d
|
||||
};
|
||||
float k = 1.0f / (5.0f * adaptingLuminance + 1.0f);
|
||||
float k4 = k * k * k * k;
|
||||
float k4F = 1.0f - k4;
|
||||
float fl =
|
||||
(k4 * adaptingLuminance) + (0.1f * k4F * k4F * (float) Math.cbrt(5.0 * adaptingLuminance));
|
||||
float n = (float) (ColorUtils.yFromLstar(backgroundLstar) / whitePoint[1]);
|
||||
float z = 1.48f + (float) Math.sqrt(n);
|
||||
float nbb = 0.725f / (float) Math.pow(n, 0.2);
|
||||
float ncb = nbb;
|
||||
float[] rgbAFactors =
|
||||
new float[] {
|
||||
(float) Math.pow(fl * rgbD[0] * rW / 100.0, 0.42),
|
||||
(float) Math.pow(fl * rgbD[1] * gW / 100.0, 0.42),
|
||||
(float) Math.pow(fl * rgbD[2] * bW / 100.0, 0.42)
|
||||
double k = 1.0 / (5.0 * adaptingLuminance + 1.0);
|
||||
double k4 = k * k * k * k;
|
||||
double k4F = 1.0 - k4;
|
||||
double fl = (k4 * adaptingLuminance) + (0.1 * k4F * k4F * Math.cbrt(5.0 * adaptingLuminance));
|
||||
double n = (ColorUtils.yFromLstar(backgroundLstar) / whitePoint[1]);
|
||||
double z = 1.48 + Math.sqrt(n);
|
||||
double nbb = 0.725 / Math.pow(n, 0.2);
|
||||
double ncb = nbb;
|
||||
double[] rgbAFactors =
|
||||
new double[] {
|
||||
Math.pow(fl * rgbD[0] * rW / 100.0, 0.42),
|
||||
Math.pow(fl * rgbD[1] * gW / 100.0, 0.42),
|
||||
Math.pow(fl * rgbD[2] * bW / 100.0, 0.42)
|
||||
};
|
||||
|
||||
float[] rgbA =
|
||||
new float[] {
|
||||
(400.0f * rgbAFactors[0]) / (rgbAFactors[0] + 27.13f),
|
||||
(400.0f * rgbAFactors[1]) / (rgbAFactors[1] + 27.13f),
|
||||
(400.0f * rgbAFactors[2]) / (rgbAFactors[2] + 27.13f)
|
||||
double[] rgbA =
|
||||
new double[] {
|
||||
(400.0 * rgbAFactors[0]) / (rgbAFactors[0] + 27.13),
|
||||
(400.0 * rgbAFactors[1]) / (rgbAFactors[1] + 27.13),
|
||||
(400.0 * rgbAFactors[2]) / (rgbAFactors[2] + 27.13)
|
||||
};
|
||||
|
||||
float aw = ((2.0f * rgbA[0]) + rgbA[1] + (0.05f * rgbA[2])) * nbb;
|
||||
return new ViewingConditions(n, aw, nbb, ncb, c, nc, rgbD, fl, (float) Math.pow(fl, 0.25), z);
|
||||
double aw = ((2.0 * rgbA[0]) + rgbA[1] + (0.05 * rgbA[2])) * nbb;
|
||||
return new ViewingConditions(n, aw, nbb, ncb, c, nc, rgbD, fl, Math.pow(fl, 0.25), z);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -174,16 +173,16 @@ public final class ViewingConditions {
|
||||
* requires a color science textbook, such as Fairchild's Color Appearance Models.
|
||||
*/
|
||||
private ViewingConditions(
|
||||
float n,
|
||||
float aw,
|
||||
float nbb,
|
||||
float ncb,
|
||||
float c,
|
||||
float nc,
|
||||
float[] rgbD,
|
||||
float fl,
|
||||
float flRoot,
|
||||
float z) {
|
||||
double n,
|
||||
double aw,
|
||||
double nbb,
|
||||
double ncb,
|
||||
double c,
|
||||
double nc,
|
||||
double[] rgbD,
|
||||
double fl,
|
||||
double flRoot,
|
||||
double z) {
|
||||
this.n = n;
|
||||
this.aw = aw;
|
||||
this.nbb = nbb;
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
package palettes;
|
||||
|
||||
import static java.lang.Math.max;
|
||||
import static java.lang.Math.min;
|
||||
|
||||
import hct.Hct;
|
||||
|
||||
@ -38,17 +39,35 @@ public final class CorePalette {
|
||||
* @param argb ARGB representation of a color
|
||||
*/
|
||||
public static CorePalette of(int argb) {
|
||||
return new CorePalette(argb);
|
||||
return new CorePalette(argb, false);
|
||||
}
|
||||
|
||||
private CorePalette(int argb) {
|
||||
/**
|
||||
* Create content key tones from a color.
|
||||
*
|
||||
* @param argb ARGB representation of a color
|
||||
*/
|
||||
public static CorePalette contentOf(int argb) {
|
||||
return new CorePalette(argb, true);
|
||||
}
|
||||
|
||||
private CorePalette(int argb, boolean isContent) {
|
||||
Hct hct = Hct.fromInt(argb);
|
||||
float hue = hct.getHue();
|
||||
this.a1 = TonalPalette.fromHueAndChroma(hue, max(48f, hct.getChroma()));
|
||||
this.a2 = TonalPalette.fromHueAndChroma(hue, 16f);
|
||||
this.a3 = TonalPalette.fromHueAndChroma(hue + 60f, 24f);
|
||||
this.n1 = TonalPalette.fromHueAndChroma(hue, 4f);
|
||||
this.n2 = TonalPalette.fromHueAndChroma(hue, 8f);
|
||||
this.error = TonalPalette.fromHueAndChroma(25, 84f);
|
||||
double hue = hct.getHue();
|
||||
double chroma = hct.getChroma();
|
||||
if (isContent) {
|
||||
this.a1 = TonalPalette.fromHueAndChroma(hue, chroma);
|
||||
this.a2 = TonalPalette.fromHueAndChroma(hue, chroma / 3.);
|
||||
this.a3 = TonalPalette.fromHueAndChroma(hue + 60., chroma / 2.);
|
||||
this.n1 = TonalPalette.fromHueAndChroma(hue, min(chroma / 12., 4.));
|
||||
this.n2 = TonalPalette.fromHueAndChroma(hue, min(chroma / 6., 8.));
|
||||
} else {
|
||||
this.a1 = TonalPalette.fromHueAndChroma(hue, max(48., chroma));
|
||||
this.a2 = TonalPalette.fromHueAndChroma(hue, 16.);
|
||||
this.a3 = TonalPalette.fromHueAndChroma(hue + 60., 24.);
|
||||
this.n1 = TonalPalette.fromHueAndChroma(hue, 4.);
|
||||
this.n2 = TonalPalette.fromHueAndChroma(hue, 8.);
|
||||
}
|
||||
this.error = TonalPalette.fromHueAndChroma(25, 84.);
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,8 +25,8 @@ import java.util.Map;
|
||||
*/
|
||||
public final class TonalPalette {
|
||||
Map<Integer, Integer> cache;
|
||||
float hue;
|
||||
float chroma;
|
||||
double hue;
|
||||
double chroma;
|
||||
|
||||
/**
|
||||
* Create tones using the HCT hue and chroma from a color.
|
||||
@ -46,11 +46,11 @@ public final class TonalPalette {
|
||||
* @param chroma HCT chroma
|
||||
* @return Tones matching hue and chroma.
|
||||
*/
|
||||
public static final TonalPalette fromHueAndChroma(float hue, float chroma) {
|
||||
public static final TonalPalette fromHueAndChroma(double hue, double chroma) {
|
||||
return new TonalPalette(hue, chroma);
|
||||
}
|
||||
|
||||
private TonalPalette(float hue, float chroma) {
|
||||
private TonalPalette(double hue, double chroma) {
|
||||
cache = new HashMap<>();
|
||||
this.hue = hue;
|
||||
this.chroma = chroma;
|
||||
|
||||
@ -14,6 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// This file is automatically generated. Do not modify it.
|
||||
|
||||
package scheme;
|
||||
|
||||
import palettes.CorePalette;
|
||||
@ -43,7 +45,9 @@ public class Scheme {
|
||||
private int surfaceVariant;
|
||||
private int onSurfaceVariant;
|
||||
private int outline;
|
||||
private int outlineVariant;
|
||||
private int shadow;
|
||||
private int scrim;
|
||||
private int inverseSurface;
|
||||
private int inverseOnSurface;
|
||||
private int inversePrimary;
|
||||
@ -74,7 +78,9 @@ public class Scheme {
|
||||
int surfaceVariant,
|
||||
int onSurfaceVariant,
|
||||
int outline,
|
||||
int outlineVariant,
|
||||
int shadow,
|
||||
int scrim,
|
||||
int inverseSurface,
|
||||
int inverseOnSurface,
|
||||
int inversePrimary) {
|
||||
@ -102,14 +108,31 @@ public class Scheme {
|
||||
this.surfaceVariant = surfaceVariant;
|
||||
this.onSurfaceVariant = onSurfaceVariant;
|
||||
this.outline = outline;
|
||||
this.outlineVariant = outlineVariant;
|
||||
this.shadow = shadow;
|
||||
this.scrim = scrim;
|
||||
this.inverseSurface = inverseSurface;
|
||||
this.inverseOnSurface = inverseOnSurface;
|
||||
this.inversePrimary = inversePrimary;
|
||||
}
|
||||
|
||||
public static Scheme light(int argb) {
|
||||
CorePalette core = CorePalette.of(argb);
|
||||
return lightFromCorePalette(CorePalette.of(argb));
|
||||
}
|
||||
|
||||
public static Scheme dark(int argb) {
|
||||
return darkFromCorePalette(CorePalette.of(argb));
|
||||
}
|
||||
|
||||
public static Scheme lightContent(int argb) {
|
||||
return lightFromCorePalette(CorePalette.contentOf(argb));
|
||||
}
|
||||
|
||||
public static Scheme darkContent(int argb) {
|
||||
return darkFromCorePalette(CorePalette.contentOf(argb));
|
||||
}
|
||||
|
||||
private static Scheme lightFromCorePalette(CorePalette core) {
|
||||
return new Scheme()
|
||||
.withPrimary(core.a1.tone(40))
|
||||
.withOnPrimary(core.a1.tone(100))
|
||||
@ -134,14 +157,15 @@ public class Scheme {
|
||||
.withSurfaceVariant(core.n2.tone(90))
|
||||
.withOnSurfaceVariant(core.n2.tone(30))
|
||||
.withOutline(core.n2.tone(50))
|
||||
.withOutlineVariant(core.n2.tone(80))
|
||||
.withShadow(core.n1.tone(0))
|
||||
.withScrim(core.n1.tone(0))
|
||||
.withInverseSurface(core.n1.tone(20))
|
||||
.withInverseOnSurface(core.n1.tone(95))
|
||||
.withInversePrimary(core.a1.tone(80));
|
||||
}
|
||||
|
||||
public static Scheme dark(int argb) {
|
||||
CorePalette core = CorePalette.of(argb);
|
||||
private static Scheme darkFromCorePalette(CorePalette core) {
|
||||
return new Scheme()
|
||||
.withPrimary(core.a1.tone(80))
|
||||
.withOnPrimary(core.a1.tone(20))
|
||||
@ -166,7 +190,9 @@ public class Scheme {
|
||||
.withSurfaceVariant(core.n2.tone(30))
|
||||
.withOnSurfaceVariant(core.n2.tone(80))
|
||||
.withOutline(core.n2.tone(60))
|
||||
.withOutlineVariant(core.n2.tone(30))
|
||||
.withShadow(core.n1.tone(0))
|
||||
.withScrim(core.n1.tone(0))
|
||||
.withInverseSurface(core.n1.tone(90))
|
||||
.withInverseOnSurface(core.n1.tone(20))
|
||||
.withInversePrimary(core.a1.tone(40));
|
||||
@ -471,6 +497,19 @@ public class Scheme {
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getOutlineVariant() {
|
||||
return outlineVariant;
|
||||
}
|
||||
|
||||
public void setOutlineVariant(int outlineVariant) {
|
||||
this.outlineVariant = outlineVariant;
|
||||
}
|
||||
|
||||
public Scheme withOutlineVariant(int outlineVariant) {
|
||||
this.outlineVariant = outlineVariant;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getShadow() {
|
||||
return shadow;
|
||||
}
|
||||
@ -484,6 +523,19 @@ public class Scheme {
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getScrim() {
|
||||
return scrim;
|
||||
}
|
||||
|
||||
public void setScrim(int scrim) {
|
||||
this.scrim = scrim;
|
||||
}
|
||||
|
||||
public Scheme withScrim(int scrim) {
|
||||
this.scrim = scrim;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getInverseSurface() {
|
||||
return inverseSurface;
|
||||
}
|
||||
@ -572,8 +624,12 @@ public class Scheme {
|
||||
+ onSurfaceVariant
|
||||
+ ", outline="
|
||||
+ outline
|
||||
+ ", outlineVariant="
|
||||
+ outlineVariant
|
||||
+ ", shadow="
|
||||
+ shadow
|
||||
+ ", scrim="
|
||||
+ scrim
|
||||
+ ", inverseSurface="
|
||||
+ inverseSurface
|
||||
+ ", inverseOnSurface="
|
||||
@ -666,9 +722,15 @@ public class Scheme {
|
||||
if (outline != scheme.outline) {
|
||||
return false;
|
||||
}
|
||||
if (outlineVariant != scheme.outlineVariant) {
|
||||
return false;
|
||||
}
|
||||
if (shadow != scheme.shadow) {
|
||||
return false;
|
||||
}
|
||||
if (scrim != scheme.scrim) {
|
||||
return false;
|
||||
}
|
||||
if (inverseSurface != scheme.inverseSurface) {
|
||||
return false;
|
||||
}
|
||||
@ -708,7 +770,9 @@ public class Scheme {
|
||||
result = 31 * result + surfaceVariant;
|
||||
result = 31 * result + onSurfaceVariant;
|
||||
result = 31 * result + outline;
|
||||
result = 31 * result + outlineVariant;
|
||||
result = 31 * result + shadow;
|
||||
result = 31 * result + scrim;
|
||||
result = 31 * result + inverseSurface;
|
||||
result = 31 * result + inverseOnSurface;
|
||||
result = 31 * result + inversePrimary;
|
||||
|
||||
@ -54,6 +54,14 @@ public class ColorUtils {
|
||||
return (255 << 24) | ((red & 255) << 16) | ((green & 255) << 8) | (blue & 255);
|
||||
}
|
||||
|
||||
/** Converts a color from linear RGB components to ARGB format. */
|
||||
public static int argbFromLinrgb(double[] linrgb) {
|
||||
int r = delinearized(linrgb[0]);
|
||||
int g = delinearized(linrgb[1]);
|
||||
int b = delinearized(linrgb[2]);
|
||||
return argbFromRgb(r, g, b);
|
||||
}
|
||||
|
||||
/** Returns the alpha component of a color in ARGB format. */
|
||||
public static int alphaFromArgb(int argb) {
|
||||
return (argb >> 24) & 255;
|
||||
@ -148,18 +156,9 @@ public class ColorUtils {
|
||||
* @return ARGB representation of grayscale color with lightness matching L*
|
||||
*/
|
||||
public static int argbFromLstar(double lstar) {
|
||||
double fy = (lstar + 16.0) / 116.0;
|
||||
double fz = fy;
|
||||
double fx = fy;
|
||||
double kappa = 24389.0 / 27.0;
|
||||
double epsilon = 216.0 / 24389.0;
|
||||
boolean lExceedsEpsilonKappa = lstar > 8.0;
|
||||
double y = lExceedsEpsilonKappa ? fy * fy * fy : lstar / kappa;
|
||||
boolean cubeExceedEpsilon = fy * fy * fy > epsilon;
|
||||
double x = cubeExceedEpsilon ? fx * fx * fx : lstar / kappa;
|
||||
double z = cubeExceedEpsilon ? fz * fz * fz : lstar / kappa;
|
||||
double[] whitePoint = WHITE_POINT_D65;
|
||||
return argbFromXyz(x * whitePoint[0], y * whitePoint[1], z * whitePoint[2]);
|
||||
double y = yFromLstar(lstar);
|
||||
int component = delinearized(y);
|
||||
return argbFromRgb(component, component, component);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -169,14 +168,8 @@ public class ColorUtils {
|
||||
* @return L*, from L*a*b*, coordinate of the color
|
||||
*/
|
||||
public static double lstarFromArgb(int argb) {
|
||||
double y = xyzFromArgb(argb)[1] / 100.0;
|
||||
double e = 216.0 / 24389.0;
|
||||
if (y <= e) {
|
||||
return 24389.0 / 27.0 * y;
|
||||
} else {
|
||||
double yIntermediate = Math.pow(y, 1.0 / 3.0);
|
||||
return 116.0 * yIntermediate - 16.0;
|
||||
}
|
||||
double y = xyzFromArgb(argb)[1];
|
||||
return 116.0 * labF(y / 100.0) - 16.0;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -191,12 +184,7 @@ public class ColorUtils {
|
||||
* @return Y in XYZ
|
||||
*/
|
||||
public static double yFromLstar(double lstar) {
|
||||
double ke = 8.0;
|
||||
if (lstar > ke) {
|
||||
return Math.pow((lstar + 16.0) / 116.0, 3.0) * 100.0;
|
||||
} else {
|
||||
return lstar / 24389.0 / 27.0 * 100.0;
|
||||
}
|
||||
return 100.0 * labInvf((lstar + 16.0) / 116.0);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -260,6 +248,5 @@ public class ColorUtils {
|
||||
return (116 * ft - 16) / kappa;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -102,6 +102,22 @@ public class MathUtils {
|
||||
return degrees;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign of direction change needed to travel from one angle to another.
|
||||
*
|
||||
* <p>For angles that are 180 degrees apart from each other, both directions have the same travel
|
||||
* distance, so either direction is shortest. The value 1.0 is returned in this case.
|
||||
*
|
||||
* @param from The angle travel starts from, in degrees.
|
||||
* @param to The angle travel ends at, in degrees.
|
||||
* @return -1 if decreasing from leads to the shortest travel distance, 1 if increasing from leads
|
||||
* to the shortest travel distance.
|
||||
*/
|
||||
public static double rotationDirection(double from, double to) {
|
||||
double increasingDifference = sanitizeDegreesDouble(to - from);
|
||||
return increasingDifference <= 180.0 ? 1.0 : -1.0;
|
||||
}
|
||||
|
||||
/** Distance of two points on a circle, represented using degrees. */
|
||||
public static double differenceDegrees(double a, double b) {
|
||||
return 180.0 - Math.abs(Math.abs(a - b) - 180.0);
|
||||
@ -114,6 +130,5 @@ public class MathUtils {
|
||||
double c = row[0] * matrix[2][0] + row[1] * matrix[2][1] + row[2] * matrix[2][2];
|
||||
return new double[] {a, b, c};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -1,34 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 Google LLC
|
||||
*
|
||||
* Licensed 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 utils;
|
||||
|
||||
/** Utility methods for string representations of colors. */
|
||||
final class StringUtils {
|
||||
private StringUtils() {}
|
||||
|
||||
/**
|
||||
* Hex string representing color, ex. #ff0000 for red.
|
||||
*
|
||||
* @param argb ARGB representation of a color.
|
||||
*/
|
||||
public static String hexFromArgb(int argb) {
|
||||
int red = ColorUtils.redFromArgb(argb);
|
||||
int blue = ColorUtils.blueFromArgb(argb);
|
||||
int green = ColorUtils.greenFromArgb(argb);
|
||||
return String.format("#%02x%02x%02x", red, green, blue);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user