/*
* (c) Copyright 2010-2011 AgileBirds
*
* This file is part of OpenFlexo.
*
* OpenFlexo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* OpenFlexo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OpenFlexo. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.openflexo.toolbox;
import java.awt.Color;
import java.util.HashMap;
public class ColorUtils {
public static class RGB {
public int r = 0;
public int g = 0;
public int b = 0;
public RGB() {
}
public RGB(Color color) {
r = color.getRed();
g = color.getGreen();
b = color.getBlue();
}
public double getRelativeLuminance() {
double rsRGB = (double) r / 255;
double gsRGB = (double) g / 255;
double bsRGB = (double) g / 255;
double r, g, b;
if (rsRGB <= 0.03928) {
r = rsRGB / 12.92;
} else {
r = Math.pow((rsRGB + 0.055) / 1.055, 2.4);
}
if (gsRGB <= 0.03928) {
g = gsRGB / 12.92;
} else {
g = Math.pow((gsRGB + 0.055) / 1.055, 2.4);
}
if (bsRGB <= 0.03928) {
b = bsRGB / 12.92;
} else {
b = Math.pow((bsRGB + 0.055) / 1.055, 2.4);
}
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}
@Override
public String toString() {
return "r=" + r + ",g=" + g + ",b=" + b;
}
public XYZ toXYZ() {
XYZ xyz = new XYZ();
double red, green, blue;
red = (double) r / 255;
green = (double) g / 255;
blue = (double) b / 255;
// adjusting values
if (red > 0.04045) {
red = (red + 0.055) / 1.055;
red = Math.pow(red, 2.4);
} else {
red = red / 12.92;
}
if (green > 0.04045) {
green = (green + 0.055) / 1.055;
green = Math.pow(green, 2.4);
} else {
green = green / 12.92;
}
if (blue > 0.04045) {
blue = (blue + 0.055) / 1.055;
blue = Math.pow(blue, 2.4);
} else {
blue = blue / 12.92;
}
red *= 100;
green *= 100;
blue *= 100;
// applying the matrix
xyz.x = red * 0.4124 + green * 0.3576 + blue * 0.1805;
xyz.y = red * 0.2126 + green * 0.7152 + blue * 0.0722;
xyz.z = red * 0.0193 + green * 0.1192 + blue * 0.9505;
return xyz;
}
public LAB toLAB() {
return toXYZ().toLAB();
}
}
public static class XYZ {
public double x = 0;
public double y = 0;
public double z = 0;
public LAB toLAB() {
LAB lab = new LAB();
double _x, _y, _z;
_x = x / 95.047;
_y = y / 100;
_z = z / 108.883;
// adjusting the values
if (_x > 0.008856) {
_x = Math.pow(_x, 1.0 / 3);
} else {
_x = 7.787 * _x + 16 / 116;
}
if (_y > 0.008856) {
_y = Math.pow(_y, 1.0 / 3);
} else {
_y = 7.787 * _y + 16 / 116;
}
if (_z > 0.008856) {
_z = Math.pow(_z, 1.0 / 3);
} else {
_z = 7.787 * _z + 16 / 116;
}
lab.l = 116 * _y - 16;
lab.a = 500 * (_x - _y);
lab.b = 200 * (_y - _z);
return lab;
}
@Override
public String toString() {
return "x=" + x + ",y=" + y + ",z=" + z;
}
}
public static class LAB {
public double l = 0;
public double a = 0;
public double b = 0;
public double cStarAB() {
return Math.sqrt(a * a + b * b);
}
// Computes the difference between two colors according to the algorithm CIEDE2000
public double computeDiff(LAB lab) {
double kh = 1, kl = 1, kc = 1;
double cDistance = (cStarAB() + lab.cStarAB()) / 2;
double cDistancePower7 = Math.pow(cDistance, 7);
double g = (1 - Math.sqrt(cDistancePower7 / (cDistancePower7 + Math.pow(25, 7)))) / 2;
double aPrime1 = (1 + g) * a;
double aPrime2 = (1 + g) * lab.a;
double cPrime1 = Math.sqrt(aPrime1 * aPrime1 + b * b);
double cPrime2 = Math.sqrt(aPrime2 * aPrime2 + lab.b * lab.b);
double hPrime1, hPrime2;
hPrime1 = computeHPrime(aPrime1, b);
hPrime2 = computeHPrime(aPrime2, lab.b);
double deltaLPrime, deltaCPrime, deltahPrime, deltaHPrime;
deltaLPrime = lab.l - l;
deltaCPrime = cPrime2 - cPrime1;
deltahPrime = 0;
if (cPrime1 != 0 && cPrime2 != 0) {
double hPrimeDiff = hPrime2 - hPrime1;
if (Math.abs(hPrimeDiff) < 180) {
deltahPrime = hPrimeDiff;
} else if (hPrimeDiff > 180) {
deltahPrime = hPrimeDiff - 360;
} else {
deltahPrime = hPrimeDiff + 360;
}
}
deltaHPrime = 2 * Math.sqrt(cPrime2 * cPrime1) * Math.sin(Math.toRadians(deltahPrime) / 2);
double lPrime, cPrime, hPrime, t, deltaTheta, rc, sl, sc, sh, rt;
lPrime = (l + lab.l) / 2;
cPrime = (cPrime1 + cPrime2) / 2;
hPrime = hPrime1 + hPrime2;
if (cPrime1 != 0 && cPrime2 != 0) {
double d = Math.abs(hPrime1 - hPrime2);
if (d <= 180) {
hPrime /= 2;
} else {
if (hPrime < 360) {
hPrime += 360;
hPrime /= 2;
} else {
hPrime -= 360;
hPrime /= 2;
}
}
}
t = 1 - 0.17 * Math.cos(Math.toRadians(hPrime - 30)) + 0.24 * Math.cos(Math.toRadians(2 * hPrime)) + 0.32
* Math.cos(Math.toRadians(3 * hPrime + 6)) - 0.2 * Math.cos(Math.toRadians(4 * hPrime - 63));
deltaTheta = 30 * Math.exp(-Math.pow((hPrime - 275) / 25, 2));
double cPrimePower7 = Math.pow(cPrime, 7);
rc = 2 * Math.sqrt(cPrimePower7 / (cPrimePower7 + Math.pow(25, 7)));
double pow = Math.pow(lPrime - 50, 2);
sl = 1 + 0.015 * pow / Math.sqrt(20 + pow);
sc = 1 + 0.045 * cPrime;
sh = 1 + 0.015 * cPrime * t;
rt = -Math.sin(2 * deltaTheta) * rc;
return Math.sqrt(Math.pow(deltaLPrime / (kl * sl), 2) + Math.pow(deltaCPrime / (kc * sc), 2)
+ Math.pow(deltaHPrime / (kh * sh), 2) + rt * deltaCPrime * deltaHPrime / (kc * sc * kh * sh));
}
private double computeHPrime(double aPrime1, double b) {
double hPrime = 0;
if (aPrime1 != 0 || b != 0) {
hPrime = Math.atan2(b, aPrime1);
if (hPrime < 0) {
hPrime += Math.PI * 2;
}
}
return Math.toDegrees(hPrime);
}
@Override
public String toString() {
return "L=" + l + ",a=" + a + ",b=" + b;
}
}
public static void main(String[] args) {
LAB lab1 = new LAB(), lab2 = new LAB();
lab1.l = 50;
lab1.a = 2.6772;
lab1.b = -79.7751;
lab2.l = 50;
lab2.a = 0;
lab2.b = -82.7485;
System.err.println(lab1.computeDiff(lab2));
}
// This may create a memory leak (although it will be limited to the 16.7M colors to the square ;-)) but according to
// the use we have of it, it should not be too much (only a few dozens of colors) and it should improve a lot the performance.
public static final HashMap<Color, HashMap<Color, Double>> constrastRatiosCache = new HashMap<Color, HashMap<Color, Double>>();
/**
* Return true if the difference between two colors matches the W3C recommendations for readability See
* http://www.wat-c.org/tools/CCA/1.1/
*/
public static boolean respectColorConstrastRecommandations(Color c1, Color c2) {
int colorConstrastDiff = getColorConstrastDiff(c1, c2);
int colorBrightnessDiff = getColorBrightnessDiff(c1, c2);
return colorConstrastDiff > 500 && colorBrightnessDiff > 125;
}
private static int getColorConstrastDiff(Color c1, Color c2) {
return Math.abs(c1.getRed() - c2.getRed()) + Math.abs(c1.getGreen() - c2.getGreen()) + Math.abs(c1.getBlue() - c2.getBlue());
}
private static int getColorBrightnessDiff(Color c1, Color c2) {
int bright1 = (c1.getRed() * 299 + c1.getGreen() * 587 + c1.getBlue() * 114) / 1000;
int bright2 = (c2.getRed() * 299 + c2.getGreen() * 587 + c2.getBlue() * 114) / 1000;
return Math.abs(bright1 - bright2);
}
public static double getContrastRatio(Color c1, Color c2) {
if (constrastRatiosCache.get(c1) != null && constrastRatiosCache.get(c1).get(c2) != null) {
return constrastRatiosCache.get(c1).get(c2);
}
double l1 = getRelativeLuminance(c1);
double l2 = getRelativeLuminance(c2);
double ratio;
if (l1 > l2) {
ratio = (l1 + 0.05) / (l2 + 0.05);
} else {
ratio = (l2 + 0.05) / (l1 + 0.05);
}
if (constrastRatiosCache.get(c1) == null) {
constrastRatiosCache.put(c1, new HashMap<Color, Double>());
}
constrastRatiosCache.get(c1).put(c2, ratio);
return ratio;
}
public static double getRelativeLuminance(Color c) {
return new RGB(c).getRelativeLuminance();
}
}