1*970e1046SAndroid Build Coastguard Worker /* 2*970e1046SAndroid Build Coastguard Worker * Copyright 2021 Google LLC 3*970e1046SAndroid Build Coastguard Worker * 4*970e1046SAndroid Build Coastguard Worker * Licensed under the Apache License, Version 2.0 (the "License"); 5*970e1046SAndroid Build Coastguard Worker * you may not use this file except in compliance with the License. 6*970e1046SAndroid Build Coastguard Worker * You may obtain a copy of the License at 7*970e1046SAndroid Build Coastguard Worker * 8*970e1046SAndroid Build Coastguard Worker * http://www.apache.org/licenses/LICENSE-2.0 9*970e1046SAndroid Build Coastguard Worker * 10*970e1046SAndroid Build Coastguard Worker * Unless required by applicable law or agreed to in writing, software 11*970e1046SAndroid Build Coastguard Worker * distributed under the License is distributed on an "AS IS" BASIS, 12*970e1046SAndroid Build Coastguard Worker * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13*970e1046SAndroid Build Coastguard Worker * See the License for the specific language governing permissions and 14*970e1046SAndroid Build Coastguard Worker * limitations under the License. 15*970e1046SAndroid Build Coastguard Worker */ 16*970e1046SAndroid Build Coastguard Worker 17*970e1046SAndroid Build Coastguard Worker package com.google.ux.material.libmonet.hct; 18*970e1046SAndroid Build Coastguard Worker 19*970e1046SAndroid Build Coastguard Worker import static java.lang.Math.max; 20*970e1046SAndroid Build Coastguard Worker 21*970e1046SAndroid Build Coastguard Worker import com.google.ux.material.libmonet.utils.ColorUtils; 22*970e1046SAndroid Build Coastguard Worker 23*970e1046SAndroid Build Coastguard Worker /** 24*970e1046SAndroid Build Coastguard Worker * CAM16, a color appearance model. Colors are not just defined by their hex code, but rather, a hex 25*970e1046SAndroid Build Coastguard Worker * code and viewing conditions. 26*970e1046SAndroid Build Coastguard Worker * 27*970e1046SAndroid Build Coastguard Worker * <p>CAM16 instances also have coordinates in the CAM16-UCS space, called J*, a*, b*, or jstar, 28*970e1046SAndroid Build Coastguard Worker * astar, bstar in code. CAM16-UCS is included in the CAM16 specification, and should be used when 29*970e1046SAndroid Build Coastguard Worker * measuring distances between colors. 30*970e1046SAndroid Build Coastguard Worker * 31*970e1046SAndroid Build Coastguard Worker * <p>In traditional color spaces, a color can be identified solely by the observer's measurement of 32*970e1046SAndroid Build Coastguard Worker * the color. Color appearance models such as CAM16 also use information about the environment where 33*970e1046SAndroid Build Coastguard Worker * the color was observed, known as the viewing conditions. 34*970e1046SAndroid Build Coastguard Worker * 35*970e1046SAndroid Build Coastguard Worker * <p>For example, white under the traditional assumption of a midday sun white point is accurately 36*970e1046SAndroid Build Coastguard Worker * measured as a slightly chromatic blue by CAM16. (roughly, hue 203, chroma 3, lightness 100) 37*970e1046SAndroid Build Coastguard Worker */ 38*970e1046SAndroid Build Coastguard Worker public final class Cam16 { 39*970e1046SAndroid Build Coastguard Worker // Transforms XYZ color space coordinates to 'cone'/'RGB' responses in CAM16. 40*970e1046SAndroid Build Coastguard Worker static final double[][] XYZ_TO_CAM16RGB = { 41*970e1046SAndroid Build Coastguard Worker {0.401288, 0.650173, -0.051461}, 42*970e1046SAndroid Build Coastguard Worker {-0.250268, 1.204414, 0.045854}, 43*970e1046SAndroid Build Coastguard Worker {-0.002079, 0.048952, 0.953127} 44*970e1046SAndroid Build Coastguard Worker }; 45*970e1046SAndroid Build Coastguard Worker 46*970e1046SAndroid Build Coastguard Worker // Transforms 'cone'/'RGB' responses in CAM16 to XYZ color space coordinates. 47*970e1046SAndroid Build Coastguard Worker static final double[][] CAM16RGB_TO_XYZ = { 48*970e1046SAndroid Build Coastguard Worker {1.8620678, -1.0112547, 0.14918678}, 49*970e1046SAndroid Build Coastguard Worker {0.38752654, 0.62144744, -0.00897398}, 50*970e1046SAndroid Build Coastguard Worker {-0.01584150, -0.03412294, 1.0499644} 51*970e1046SAndroid Build Coastguard Worker }; 52*970e1046SAndroid Build Coastguard Worker 53*970e1046SAndroid Build Coastguard Worker // CAM16 color dimensions, see getters for documentation. 54*970e1046SAndroid Build Coastguard Worker private final double hue; 55*970e1046SAndroid Build Coastguard Worker private final double chroma; 56*970e1046SAndroid Build Coastguard Worker private final double j; 57*970e1046SAndroid Build Coastguard Worker private final double q; 58*970e1046SAndroid Build Coastguard Worker private final double m; 59*970e1046SAndroid Build Coastguard Worker private final double s; 60*970e1046SAndroid Build Coastguard Worker 61*970e1046SAndroid Build Coastguard Worker // Coordinates in UCS space. Used to determine color distance, like delta E equations in L*a*b*. 62*970e1046SAndroid Build Coastguard Worker private final double jstar; 63*970e1046SAndroid Build Coastguard Worker private final double astar; 64*970e1046SAndroid Build Coastguard Worker private final double bstar; 65*970e1046SAndroid Build Coastguard Worker 66*970e1046SAndroid Build Coastguard Worker // Avoid allocations during conversion by pre-allocating an array. 67*970e1046SAndroid Build Coastguard Worker private final double[] tempArray = new double[] {0.0, 0.0, 0.0}; 68*970e1046SAndroid Build Coastguard Worker 69*970e1046SAndroid Build Coastguard Worker /** 70*970e1046SAndroid Build Coastguard Worker * CAM16 instances also have coordinates in the CAM16-UCS space, called J*, a*, b*, or jstar, 71*970e1046SAndroid Build Coastguard Worker * astar, bstar in code. CAM16-UCS is included in the CAM16 specification, and is used to measure 72*970e1046SAndroid Build Coastguard Worker * distances between colors. 73*970e1046SAndroid Build Coastguard Worker */ distance(Cam16 other)74*970e1046SAndroid Build Coastguard Worker public double distance(Cam16 other) { 75*970e1046SAndroid Build Coastguard Worker double dJ = getJstar() - other.getJstar(); 76*970e1046SAndroid Build Coastguard Worker double dA = getAstar() - other.getAstar(); 77*970e1046SAndroid Build Coastguard Worker double dB = getBstar() - other.getBstar(); 78*970e1046SAndroid Build Coastguard Worker double dEPrime = Math.sqrt(dJ * dJ + dA * dA + dB * dB); 79*970e1046SAndroid Build Coastguard Worker double dE = 1.41 * Math.pow(dEPrime, 0.63); 80*970e1046SAndroid Build Coastguard Worker return dE; 81*970e1046SAndroid Build Coastguard Worker } 82*970e1046SAndroid Build Coastguard Worker 83*970e1046SAndroid Build Coastguard Worker /** Hue in CAM16 */ getHue()84*970e1046SAndroid Build Coastguard Worker public double getHue() { 85*970e1046SAndroid Build Coastguard Worker return hue; 86*970e1046SAndroid Build Coastguard Worker } 87*970e1046SAndroid Build Coastguard Worker 88*970e1046SAndroid Build Coastguard Worker /** Chroma in CAM16 */ getChroma()89*970e1046SAndroid Build Coastguard Worker public double getChroma() { 90*970e1046SAndroid Build Coastguard Worker return chroma; 91*970e1046SAndroid Build Coastguard Worker } 92*970e1046SAndroid Build Coastguard Worker 93*970e1046SAndroid Build Coastguard Worker /** Lightness in CAM16 */ getJ()94*970e1046SAndroid Build Coastguard Worker public double getJ() { 95*970e1046SAndroid Build Coastguard Worker return j; 96*970e1046SAndroid Build Coastguard Worker } 97*970e1046SAndroid Build Coastguard Worker 98*970e1046SAndroid Build Coastguard Worker /** 99*970e1046SAndroid Build Coastguard Worker * Brightness in CAM16. 100*970e1046SAndroid Build Coastguard Worker * 101*970e1046SAndroid Build Coastguard Worker * <p>Prefer lightness, brightness is an absolute quantity. For example, a sheet of white paper is 102*970e1046SAndroid Build Coastguard Worker * much brighter viewed in sunlight than in indoor light, but it is the lightest object under any 103*970e1046SAndroid Build Coastguard Worker * lighting. 104*970e1046SAndroid Build Coastguard Worker */ getQ()105*970e1046SAndroid Build Coastguard Worker public double getQ() { 106*970e1046SAndroid Build Coastguard Worker return q; 107*970e1046SAndroid Build Coastguard Worker } 108*970e1046SAndroid Build Coastguard Worker 109*970e1046SAndroid Build Coastguard Worker /** 110*970e1046SAndroid Build Coastguard Worker * Colorfulness in CAM16. 111*970e1046SAndroid Build Coastguard Worker * 112*970e1046SAndroid Build Coastguard Worker * <p>Prefer chroma, colorfulness is an absolute quantity. For example, a yellow toy car is much 113*970e1046SAndroid Build Coastguard Worker * more colorful outside than inside, but it has the same chroma in both environments. 114*970e1046SAndroid Build Coastguard Worker */ getM()115*970e1046SAndroid Build Coastguard Worker public double getM() { 116*970e1046SAndroid Build Coastguard Worker return m; 117*970e1046SAndroid Build Coastguard Worker } 118*970e1046SAndroid Build Coastguard Worker 119*970e1046SAndroid Build Coastguard Worker /** 120*970e1046SAndroid Build Coastguard Worker * Saturation in CAM16. 121*970e1046SAndroid Build Coastguard Worker * 122*970e1046SAndroid Build Coastguard Worker * <p>Colorfulness in proportion to brightness. Prefer chroma, saturation measures colorfulness 123*970e1046SAndroid Build Coastguard Worker * relative to the color's own brightness, where chroma is colorfulness relative to white. 124*970e1046SAndroid Build Coastguard Worker */ getS()125*970e1046SAndroid Build Coastguard Worker public double getS() { 126*970e1046SAndroid Build Coastguard Worker return s; 127*970e1046SAndroid Build Coastguard Worker } 128*970e1046SAndroid Build Coastguard Worker 129*970e1046SAndroid Build Coastguard Worker /** Lightness coordinate in CAM16-UCS */ getJstar()130*970e1046SAndroid Build Coastguard Worker public double getJstar() { 131*970e1046SAndroid Build Coastguard Worker return jstar; 132*970e1046SAndroid Build Coastguard Worker } 133*970e1046SAndroid Build Coastguard Worker 134*970e1046SAndroid Build Coastguard Worker /** a* coordinate in CAM16-UCS */ getAstar()135*970e1046SAndroid Build Coastguard Worker public double getAstar() { 136*970e1046SAndroid Build Coastguard Worker return astar; 137*970e1046SAndroid Build Coastguard Worker } 138*970e1046SAndroid Build Coastguard Worker 139*970e1046SAndroid Build Coastguard Worker /** b* coordinate in CAM16-UCS */ getBstar()140*970e1046SAndroid Build Coastguard Worker public double getBstar() { 141*970e1046SAndroid Build Coastguard Worker return bstar; 142*970e1046SAndroid Build Coastguard Worker } 143*970e1046SAndroid Build Coastguard Worker 144*970e1046SAndroid Build Coastguard Worker /** 145*970e1046SAndroid Build Coastguard Worker * All of the CAM16 dimensions can be calculated from 3 of the dimensions, in the following 146*970e1046SAndroid Build Coastguard Worker * combinations: - {j or q} and {c, m, or s} and hue - jstar, astar, bstar Prefer using a static 147*970e1046SAndroid Build Coastguard Worker * method that constructs from 3 of those dimensions. This constructor is intended for those 148*970e1046SAndroid Build Coastguard Worker * methods to use to return all possible dimensions. 149*970e1046SAndroid Build Coastguard Worker * 150*970e1046SAndroid Build Coastguard Worker * @param hue for example, red, orange, yellow, green, etc. 151*970e1046SAndroid Build Coastguard Worker * @param chroma informally, colorfulness / color intensity. like saturation in HSL, except 152*970e1046SAndroid Build Coastguard Worker * perceptually accurate. 153*970e1046SAndroid Build Coastguard Worker * @param j lightness 154*970e1046SAndroid Build Coastguard Worker * @param q brightness; ratio of lightness to white point's lightness 155*970e1046SAndroid Build Coastguard Worker * @param m colorfulness 156*970e1046SAndroid Build Coastguard Worker * @param s saturation; ratio of chroma to white point's chroma 157*970e1046SAndroid Build Coastguard Worker * @param jstar CAM16-UCS J coordinate 158*970e1046SAndroid Build Coastguard Worker * @param astar CAM16-UCS a coordinate 159*970e1046SAndroid Build Coastguard Worker * @param bstar CAM16-UCS b coordinate 160*970e1046SAndroid Build Coastguard Worker */ Cam16( double hue, double chroma, double j, double q, double m, double s, double jstar, double astar, double bstar)161*970e1046SAndroid Build Coastguard Worker private Cam16( 162*970e1046SAndroid Build Coastguard Worker double hue, 163*970e1046SAndroid Build Coastguard Worker double chroma, 164*970e1046SAndroid Build Coastguard Worker double j, 165*970e1046SAndroid Build Coastguard Worker double q, 166*970e1046SAndroid Build Coastguard Worker double m, 167*970e1046SAndroid Build Coastguard Worker double s, 168*970e1046SAndroid Build Coastguard Worker double jstar, 169*970e1046SAndroid Build Coastguard Worker double astar, 170*970e1046SAndroid Build Coastguard Worker double bstar) { 171*970e1046SAndroid Build Coastguard Worker this.hue = hue; 172*970e1046SAndroid Build Coastguard Worker this.chroma = chroma; 173*970e1046SAndroid Build Coastguard Worker this.j = j; 174*970e1046SAndroid Build Coastguard Worker this.q = q; 175*970e1046SAndroid Build Coastguard Worker this.m = m; 176*970e1046SAndroid Build Coastguard Worker this.s = s; 177*970e1046SAndroid Build Coastguard Worker this.jstar = jstar; 178*970e1046SAndroid Build Coastguard Worker this.astar = astar; 179*970e1046SAndroid Build Coastguard Worker this.bstar = bstar; 180*970e1046SAndroid Build Coastguard Worker } 181*970e1046SAndroid Build Coastguard Worker 182*970e1046SAndroid Build Coastguard Worker /** 183*970e1046SAndroid Build Coastguard Worker * Create a CAM16 color from a color, assuming the color was viewed in default viewing conditions. 184*970e1046SAndroid Build Coastguard Worker * 185*970e1046SAndroid Build Coastguard Worker * @param argb ARGB representation of a color. 186*970e1046SAndroid Build Coastguard Worker */ fromInt(int argb)187*970e1046SAndroid Build Coastguard Worker public static Cam16 fromInt(int argb) { 188*970e1046SAndroid Build Coastguard Worker return fromIntInViewingConditions(argb, ViewingConditions.DEFAULT); 189*970e1046SAndroid Build Coastguard Worker } 190*970e1046SAndroid Build Coastguard Worker 191*970e1046SAndroid Build Coastguard Worker /** 192*970e1046SAndroid Build Coastguard Worker * Create a CAM16 color from a color in defined viewing conditions. 193*970e1046SAndroid Build Coastguard Worker * 194*970e1046SAndroid Build Coastguard Worker * @param argb ARGB representation of a color. 195*970e1046SAndroid Build Coastguard Worker * @param viewingConditions Information about the environment where the color was observed. 196*970e1046SAndroid Build Coastguard Worker */ 197*970e1046SAndroid Build Coastguard Worker // The RGB => XYZ conversion matrix elements are derived scientific constants. While the values 198*970e1046SAndroid Build Coastguard Worker // may differ at runtime due to floating point imprecision, keeping the values the same, and 199*970e1046SAndroid Build Coastguard Worker // accurate, across implementations takes precedence. 200*970e1046SAndroid Build Coastguard Worker @SuppressWarnings("FloatingPointLiteralPrecision") fromIntInViewingConditions(int argb, ViewingConditions viewingConditions)201*970e1046SAndroid Build Coastguard Worker static Cam16 fromIntInViewingConditions(int argb, ViewingConditions viewingConditions) { 202*970e1046SAndroid Build Coastguard Worker // Transform ARGB int to XYZ 203*970e1046SAndroid Build Coastguard Worker int red = (argb & 0x00ff0000) >> 16; 204*970e1046SAndroid Build Coastguard Worker int green = (argb & 0x0000ff00) >> 8; 205*970e1046SAndroid Build Coastguard Worker int blue = (argb & 0x000000ff); 206*970e1046SAndroid Build Coastguard Worker double redL = ColorUtils.linearized(red); 207*970e1046SAndroid Build Coastguard Worker double greenL = ColorUtils.linearized(green); 208*970e1046SAndroid Build Coastguard Worker double blueL = ColorUtils.linearized(blue); 209*970e1046SAndroid Build Coastguard Worker double x = 0.41233895 * redL + 0.35762064 * greenL + 0.18051042 * blueL; 210*970e1046SAndroid Build Coastguard Worker double y = 0.2126 * redL + 0.7152 * greenL + 0.0722 * blueL; 211*970e1046SAndroid Build Coastguard Worker double z = 0.01932141 * redL + 0.11916382 * greenL + 0.95034478 * blueL; 212*970e1046SAndroid Build Coastguard Worker 213*970e1046SAndroid Build Coastguard Worker return fromXyzInViewingConditions(x, y, z, viewingConditions); 214*970e1046SAndroid Build Coastguard Worker } 215*970e1046SAndroid Build Coastguard Worker fromXyzInViewingConditions( double x, double y, double z, ViewingConditions viewingConditions)216*970e1046SAndroid Build Coastguard Worker static Cam16 fromXyzInViewingConditions( 217*970e1046SAndroid Build Coastguard Worker double x, double y, double z, ViewingConditions viewingConditions) { 218*970e1046SAndroid Build Coastguard Worker // Transform XYZ to 'cone'/'rgb' responses 219*970e1046SAndroid Build Coastguard Worker double[][] matrix = XYZ_TO_CAM16RGB; 220*970e1046SAndroid Build Coastguard Worker double rT = (x * matrix[0][0]) + (y * matrix[0][1]) + (z * matrix[0][2]); 221*970e1046SAndroid Build Coastguard Worker double gT = (x * matrix[1][0]) + (y * matrix[1][1]) + (z * matrix[1][2]); 222*970e1046SAndroid Build Coastguard Worker double bT = (x * matrix[2][0]) + (y * matrix[2][1]) + (z * matrix[2][2]); 223*970e1046SAndroid Build Coastguard Worker 224*970e1046SAndroid Build Coastguard Worker // Discount illuminant 225*970e1046SAndroid Build Coastguard Worker double rD = viewingConditions.getRgbD()[0] * rT; 226*970e1046SAndroid Build Coastguard Worker double gD = viewingConditions.getRgbD()[1] * gT; 227*970e1046SAndroid Build Coastguard Worker double bD = viewingConditions.getRgbD()[2] * bT; 228*970e1046SAndroid Build Coastguard Worker 229*970e1046SAndroid Build Coastguard Worker // Chromatic adaptation 230*970e1046SAndroid Build Coastguard Worker double rAF = Math.pow(viewingConditions.getFl() * Math.abs(rD) / 100.0, 0.42); 231*970e1046SAndroid Build Coastguard Worker double gAF = Math.pow(viewingConditions.getFl() * Math.abs(gD) / 100.0, 0.42); 232*970e1046SAndroid Build Coastguard Worker double bAF = Math.pow(viewingConditions.getFl() * Math.abs(bD) / 100.0, 0.42); 233*970e1046SAndroid Build Coastguard Worker double rA = Math.signum(rD) * 400.0 * rAF / (rAF + 27.13); 234*970e1046SAndroid Build Coastguard Worker double gA = Math.signum(gD) * 400.0 * gAF / (gAF + 27.13); 235*970e1046SAndroid Build Coastguard Worker double bA = Math.signum(bD) * 400.0 * bAF / (bAF + 27.13); 236*970e1046SAndroid Build Coastguard Worker 237*970e1046SAndroid Build Coastguard Worker // redness-greenness 238*970e1046SAndroid Build Coastguard Worker double a = (11.0 * rA + -12.0 * gA + bA) / 11.0; 239*970e1046SAndroid Build Coastguard Worker // yellowness-blueness 240*970e1046SAndroid Build Coastguard Worker double b = (rA + gA - 2.0 * bA) / 9.0; 241*970e1046SAndroid Build Coastguard Worker 242*970e1046SAndroid Build Coastguard Worker // auxiliary components 243*970e1046SAndroid Build Coastguard Worker double u = (20.0 * rA + 20.0 * gA + 21.0 * bA) / 20.0; 244*970e1046SAndroid Build Coastguard Worker double p2 = (40.0 * rA + 20.0 * gA + bA) / 20.0; 245*970e1046SAndroid Build Coastguard Worker 246*970e1046SAndroid Build Coastguard Worker // hue 247*970e1046SAndroid Build Coastguard Worker double atan2 = Math.atan2(b, a); 248*970e1046SAndroid Build Coastguard Worker double atanDegrees = Math.toDegrees(atan2); 249*970e1046SAndroid Build Coastguard Worker double hue = 250*970e1046SAndroid Build Coastguard Worker atanDegrees < 0 251*970e1046SAndroid Build Coastguard Worker ? atanDegrees + 360.0 252*970e1046SAndroid Build Coastguard Worker : atanDegrees >= 360 ? atanDegrees - 360.0 : atanDegrees; 253*970e1046SAndroid Build Coastguard Worker double hueRadians = Math.toRadians(hue); 254*970e1046SAndroid Build Coastguard Worker 255*970e1046SAndroid Build Coastguard Worker // achromatic response to color 256*970e1046SAndroid Build Coastguard Worker double ac = p2 * viewingConditions.getNbb(); 257*970e1046SAndroid Build Coastguard Worker 258*970e1046SAndroid Build Coastguard Worker // CAM16 lightness and brightness 259*970e1046SAndroid Build Coastguard Worker double j = 260*970e1046SAndroid Build Coastguard Worker 100.0 261*970e1046SAndroid Build Coastguard Worker * Math.pow( 262*970e1046SAndroid Build Coastguard Worker ac / viewingConditions.getAw(), 263*970e1046SAndroid Build Coastguard Worker viewingConditions.getC() * viewingConditions.getZ()); 264*970e1046SAndroid Build Coastguard Worker double q = 265*970e1046SAndroid Build Coastguard Worker 4.0 266*970e1046SAndroid Build Coastguard Worker / viewingConditions.getC() 267*970e1046SAndroid Build Coastguard Worker * Math.sqrt(j / 100.0) 268*970e1046SAndroid Build Coastguard Worker * (viewingConditions.getAw() + 4.0) 269*970e1046SAndroid Build Coastguard Worker * viewingConditions.getFlRoot(); 270*970e1046SAndroid Build Coastguard Worker 271*970e1046SAndroid Build Coastguard Worker // CAM16 chroma, colorfulness, and saturation. 272*970e1046SAndroid Build Coastguard Worker double huePrime = (hue < 20.14) ? hue + 360 : hue; 273*970e1046SAndroid Build Coastguard Worker double eHue = 0.25 * (Math.cos(Math.toRadians(huePrime) + 2.0) + 3.8); 274*970e1046SAndroid Build Coastguard Worker double p1 = 50000.0 / 13.0 * eHue * viewingConditions.getNc() * viewingConditions.getNcb(); 275*970e1046SAndroid Build Coastguard Worker double t = p1 * Math.hypot(a, b) / (u + 0.305); 276*970e1046SAndroid Build Coastguard Worker double alpha = 277*970e1046SAndroid Build Coastguard Worker Math.pow(1.64 - Math.pow(0.29, viewingConditions.getN()), 0.73) * Math.pow(t, 0.9); 278*970e1046SAndroid Build Coastguard Worker // CAM16 chroma, colorfulness, saturation 279*970e1046SAndroid Build Coastguard Worker double c = alpha * Math.sqrt(j / 100.0); 280*970e1046SAndroid Build Coastguard Worker double m = c * viewingConditions.getFlRoot(); 281*970e1046SAndroid Build Coastguard Worker double s = 282*970e1046SAndroid Build Coastguard Worker 50.0 * Math.sqrt((alpha * viewingConditions.getC()) / (viewingConditions.getAw() + 4.0)); 283*970e1046SAndroid Build Coastguard Worker 284*970e1046SAndroid Build Coastguard Worker // CAM16-UCS components 285*970e1046SAndroid Build Coastguard Worker double jstar = (1.0 + 100.0 * 0.007) * j / (1.0 + 0.007 * j); 286*970e1046SAndroid Build Coastguard Worker double mstar = 1.0 / 0.0228 * Math.log1p(0.0228 * m); 287*970e1046SAndroid Build Coastguard Worker double astar = mstar * Math.cos(hueRadians); 288*970e1046SAndroid Build Coastguard Worker double bstar = mstar * Math.sin(hueRadians); 289*970e1046SAndroid Build Coastguard Worker 290*970e1046SAndroid Build Coastguard Worker return new Cam16(hue, c, j, q, m, s, jstar, astar, bstar); 291*970e1046SAndroid Build Coastguard Worker } 292*970e1046SAndroid Build Coastguard Worker 293*970e1046SAndroid Build Coastguard Worker /** 294*970e1046SAndroid Build Coastguard Worker * @param j CAM16 lightness 295*970e1046SAndroid Build Coastguard Worker * @param c CAM16 chroma 296*970e1046SAndroid Build Coastguard Worker * @param h CAM16 hue 297*970e1046SAndroid Build Coastguard Worker */ fromJch(double j, double c, double h)298*970e1046SAndroid Build Coastguard Worker static Cam16 fromJch(double j, double c, double h) { 299*970e1046SAndroid Build Coastguard Worker return fromJchInViewingConditions(j, c, h, ViewingConditions.DEFAULT); 300*970e1046SAndroid Build Coastguard Worker } 301*970e1046SAndroid Build Coastguard Worker 302*970e1046SAndroid Build Coastguard Worker /** 303*970e1046SAndroid Build Coastguard Worker * @param j CAM16 lightness 304*970e1046SAndroid Build Coastguard Worker * @param c CAM16 chroma 305*970e1046SAndroid Build Coastguard Worker * @param h CAM16 hue 306*970e1046SAndroid Build Coastguard Worker * @param viewingConditions Information about the environment where the color was observed. 307*970e1046SAndroid Build Coastguard Worker */ fromJchInViewingConditions( double j, double c, double h, ViewingConditions viewingConditions)308*970e1046SAndroid Build Coastguard Worker private static Cam16 fromJchInViewingConditions( 309*970e1046SAndroid Build Coastguard Worker double j, double c, double h, ViewingConditions viewingConditions) { 310*970e1046SAndroid Build Coastguard Worker double q = 311*970e1046SAndroid Build Coastguard Worker 4.0 312*970e1046SAndroid Build Coastguard Worker / viewingConditions.getC() 313*970e1046SAndroid Build Coastguard Worker * Math.sqrt(j / 100.0) 314*970e1046SAndroid Build Coastguard Worker * (viewingConditions.getAw() + 4.0) 315*970e1046SAndroid Build Coastguard Worker * viewingConditions.getFlRoot(); 316*970e1046SAndroid Build Coastguard Worker double m = c * viewingConditions.getFlRoot(); 317*970e1046SAndroid Build Coastguard Worker double alpha = c / Math.sqrt(j / 100.0); 318*970e1046SAndroid Build Coastguard Worker double s = 319*970e1046SAndroid Build Coastguard Worker 50.0 * Math.sqrt((alpha * viewingConditions.getC()) / (viewingConditions.getAw() + 4.0)); 320*970e1046SAndroid Build Coastguard Worker 321*970e1046SAndroid Build Coastguard Worker double hueRadians = Math.toRadians(h); 322*970e1046SAndroid Build Coastguard Worker double jstar = (1.0 + 100.0 * 0.007) * j / (1.0 + 0.007 * j); 323*970e1046SAndroid Build Coastguard Worker double mstar = 1.0 / 0.0228 * Math.log1p(0.0228 * m); 324*970e1046SAndroid Build Coastguard Worker double astar = mstar * Math.cos(hueRadians); 325*970e1046SAndroid Build Coastguard Worker double bstar = mstar * Math.sin(hueRadians); 326*970e1046SAndroid Build Coastguard Worker return new Cam16(h, c, j, q, m, s, jstar, astar, bstar); 327*970e1046SAndroid Build Coastguard Worker } 328*970e1046SAndroid Build Coastguard Worker 329*970e1046SAndroid Build Coastguard Worker /** 330*970e1046SAndroid Build Coastguard Worker * Create a CAM16 color from CAM16-UCS coordinates. 331*970e1046SAndroid Build Coastguard Worker * 332*970e1046SAndroid Build Coastguard Worker * @param jstar CAM16-UCS lightness. 333*970e1046SAndroid Build Coastguard Worker * @param astar CAM16-UCS a dimension. Like a* in L*a*b*, it is a Cartesian coordinate on the Y 334*970e1046SAndroid Build Coastguard Worker * axis. 335*970e1046SAndroid Build Coastguard Worker * @param bstar CAM16-UCS b dimension. Like a* in L*a*b*, it is a Cartesian coordinate on the X 336*970e1046SAndroid Build Coastguard Worker * axis. 337*970e1046SAndroid Build Coastguard Worker */ fromUcs(double jstar, double astar, double bstar)338*970e1046SAndroid Build Coastguard Worker public static Cam16 fromUcs(double jstar, double astar, double bstar) { 339*970e1046SAndroid Build Coastguard Worker 340*970e1046SAndroid Build Coastguard Worker return fromUcsInViewingConditions(jstar, astar, bstar, ViewingConditions.DEFAULT); 341*970e1046SAndroid Build Coastguard Worker } 342*970e1046SAndroid Build Coastguard Worker 343*970e1046SAndroid Build Coastguard Worker /** 344*970e1046SAndroid Build Coastguard Worker * Create a CAM16 color from CAM16-UCS coordinates in defined viewing conditions. 345*970e1046SAndroid Build Coastguard Worker * 346*970e1046SAndroid Build Coastguard Worker * @param jstar CAM16-UCS lightness. 347*970e1046SAndroid Build Coastguard Worker * @param astar CAM16-UCS a dimension. Like a* in L*a*b*, it is a Cartesian coordinate on the Y 348*970e1046SAndroid Build Coastguard Worker * axis. 349*970e1046SAndroid Build Coastguard Worker * @param bstar CAM16-UCS b dimension. Like a* in L*a*b*, it is a Cartesian coordinate on the X 350*970e1046SAndroid Build Coastguard Worker * axis. 351*970e1046SAndroid Build Coastguard Worker * @param viewingConditions Information about the environment where the color was observed. 352*970e1046SAndroid Build Coastguard Worker */ fromUcsInViewingConditions( double jstar, double astar, double bstar, ViewingConditions viewingConditions)353*970e1046SAndroid Build Coastguard Worker public static Cam16 fromUcsInViewingConditions( 354*970e1046SAndroid Build Coastguard Worker double jstar, double astar, double bstar, ViewingConditions viewingConditions) { 355*970e1046SAndroid Build Coastguard Worker 356*970e1046SAndroid Build Coastguard Worker double m = Math.hypot(astar, bstar); 357*970e1046SAndroid Build Coastguard Worker double m2 = Math.expm1(m * 0.0228) / 0.0228; 358*970e1046SAndroid Build Coastguard Worker double c = m2 / viewingConditions.getFlRoot(); 359*970e1046SAndroid Build Coastguard Worker double h = Math.atan2(bstar, astar) * (180.0 / Math.PI); 360*970e1046SAndroid Build Coastguard Worker if (h < 0.0) { 361*970e1046SAndroid Build Coastguard Worker h += 360.0; 362*970e1046SAndroid Build Coastguard Worker } 363*970e1046SAndroid Build Coastguard Worker double j = jstar / (1. - (jstar - 100.) * 0.007); 364*970e1046SAndroid Build Coastguard Worker return fromJchInViewingConditions(j, c, h, viewingConditions); 365*970e1046SAndroid Build Coastguard Worker } 366*970e1046SAndroid Build Coastguard Worker 367*970e1046SAndroid Build Coastguard Worker /** 368*970e1046SAndroid Build Coastguard Worker * ARGB representation of the color. Assumes the color was viewed in default viewing conditions, 369*970e1046SAndroid Build Coastguard Worker * which are near-identical to the default viewing conditions for sRGB. 370*970e1046SAndroid Build Coastguard Worker */ toInt()371*970e1046SAndroid Build Coastguard Worker public int toInt() { 372*970e1046SAndroid Build Coastguard Worker return viewed(ViewingConditions.DEFAULT); 373*970e1046SAndroid Build Coastguard Worker } 374*970e1046SAndroid Build Coastguard Worker 375*970e1046SAndroid Build Coastguard Worker /** 376*970e1046SAndroid Build Coastguard Worker * ARGB representation of the color, in defined viewing conditions. 377*970e1046SAndroid Build Coastguard Worker * 378*970e1046SAndroid Build Coastguard Worker * @param viewingConditions Information about the environment where the color will be viewed. 379*970e1046SAndroid Build Coastguard Worker * @return ARGB representation of color 380*970e1046SAndroid Build Coastguard Worker */ viewed(ViewingConditions viewingConditions)381*970e1046SAndroid Build Coastguard Worker int viewed(ViewingConditions viewingConditions) { 382*970e1046SAndroid Build Coastguard Worker double[] xyz = xyzInViewingConditions(viewingConditions, tempArray); 383*970e1046SAndroid Build Coastguard Worker return ColorUtils.argbFromXyz(xyz[0], xyz[1], xyz[2]); 384*970e1046SAndroid Build Coastguard Worker } 385*970e1046SAndroid Build Coastguard Worker xyzInViewingConditions(ViewingConditions viewingConditions, double[] returnArray)386*970e1046SAndroid Build Coastguard Worker double[] xyzInViewingConditions(ViewingConditions viewingConditions, double[] returnArray) { 387*970e1046SAndroid Build Coastguard Worker double alpha = 388*970e1046SAndroid Build Coastguard Worker (getChroma() == 0.0 || getJ() == 0.0) ? 0.0 : getChroma() / Math.sqrt(getJ() / 100.0); 389*970e1046SAndroid Build Coastguard Worker 390*970e1046SAndroid Build Coastguard Worker double t = 391*970e1046SAndroid Build Coastguard Worker Math.pow( 392*970e1046SAndroid Build Coastguard Worker alpha / Math.pow(1.64 - Math.pow(0.29, viewingConditions.getN()), 0.73), 1.0 / 0.9); 393*970e1046SAndroid Build Coastguard Worker double hRad = Math.toRadians(getHue()); 394*970e1046SAndroid Build Coastguard Worker 395*970e1046SAndroid Build Coastguard Worker double eHue = 0.25 * (Math.cos(hRad + 2.0) + 3.8); 396*970e1046SAndroid Build Coastguard Worker double ac = 397*970e1046SAndroid Build Coastguard Worker viewingConditions.getAw() 398*970e1046SAndroid Build Coastguard Worker * Math.pow(getJ() / 100.0, 1.0 / viewingConditions.getC() / viewingConditions.getZ()); 399*970e1046SAndroid Build Coastguard Worker double p1 = eHue * (50000.0 / 13.0) * viewingConditions.getNc() * viewingConditions.getNcb(); 400*970e1046SAndroid Build Coastguard Worker double p2 = (ac / viewingConditions.getNbb()); 401*970e1046SAndroid Build Coastguard Worker 402*970e1046SAndroid Build Coastguard Worker double hSin = Math.sin(hRad); 403*970e1046SAndroid Build Coastguard Worker double hCos = Math.cos(hRad); 404*970e1046SAndroid Build Coastguard Worker 405*970e1046SAndroid Build Coastguard Worker double gamma = 23.0 * (p2 + 0.305) * t / (23.0 * p1 + 11.0 * t * hCos + 108.0 * t * hSin); 406*970e1046SAndroid Build Coastguard Worker double a = gamma * hCos; 407*970e1046SAndroid Build Coastguard Worker double b = gamma * hSin; 408*970e1046SAndroid Build Coastguard Worker double rA = (460.0 * p2 + 451.0 * a + 288.0 * b) / 1403.0; 409*970e1046SAndroid Build Coastguard Worker double gA = (460.0 * p2 - 891.0 * a - 261.0 * b) / 1403.0; 410*970e1046SAndroid Build Coastguard Worker double bA = (460.0 * p2 - 220.0 * a - 6300.0 * b) / 1403.0; 411*970e1046SAndroid Build Coastguard Worker 412*970e1046SAndroid Build Coastguard Worker double rCBase = max(0, (27.13 * Math.abs(rA)) / (400.0 - Math.abs(rA))); 413*970e1046SAndroid Build Coastguard Worker double rC = 414*970e1046SAndroid Build Coastguard Worker Math.signum(rA) * (100.0 / viewingConditions.getFl()) * Math.pow(rCBase, 1.0 / 0.42); 415*970e1046SAndroid Build Coastguard Worker double gCBase = max(0, (27.13 * Math.abs(gA)) / (400.0 - Math.abs(gA))); 416*970e1046SAndroid Build Coastguard Worker double gC = 417*970e1046SAndroid Build Coastguard Worker Math.signum(gA) * (100.0 / viewingConditions.getFl()) * Math.pow(gCBase, 1.0 / 0.42); 418*970e1046SAndroid Build Coastguard Worker double bCBase = max(0, (27.13 * Math.abs(bA)) / (400.0 - Math.abs(bA))); 419*970e1046SAndroid Build Coastguard Worker double bC = 420*970e1046SAndroid Build Coastguard Worker Math.signum(bA) * (100.0 / viewingConditions.getFl()) * Math.pow(bCBase, 1.0 / 0.42); 421*970e1046SAndroid Build Coastguard Worker double rF = rC / viewingConditions.getRgbD()[0]; 422*970e1046SAndroid Build Coastguard Worker double gF = gC / viewingConditions.getRgbD()[1]; 423*970e1046SAndroid Build Coastguard Worker double bF = bC / viewingConditions.getRgbD()[2]; 424*970e1046SAndroid Build Coastguard Worker 425*970e1046SAndroid Build Coastguard Worker double[][] matrix = CAM16RGB_TO_XYZ; 426*970e1046SAndroid Build Coastguard Worker double x = (rF * matrix[0][0]) + (gF * matrix[0][1]) + (bF * matrix[0][2]); 427*970e1046SAndroid Build Coastguard Worker double y = (rF * matrix[1][0]) + (gF * matrix[1][1]) + (bF * matrix[1][2]); 428*970e1046SAndroid Build Coastguard Worker double z = (rF * matrix[2][0]) + (gF * matrix[2][1]) + (bF * matrix[2][2]); 429*970e1046SAndroid Build Coastguard Worker 430*970e1046SAndroid Build Coastguard Worker if (returnArray != null) { 431*970e1046SAndroid Build Coastguard Worker returnArray[0] = x; 432*970e1046SAndroid Build Coastguard Worker returnArray[1] = y; 433*970e1046SAndroid Build Coastguard Worker returnArray[2] = z; 434*970e1046SAndroid Build Coastguard Worker return returnArray; 435*970e1046SAndroid Build Coastguard Worker } else { 436*970e1046SAndroid Build Coastguard Worker return new double[] {x, y, z}; 437*970e1046SAndroid Build Coastguard Worker } 438*970e1046SAndroid Build Coastguard Worker } 439*970e1046SAndroid Build Coastguard Worker } 440