|
|
@@ -7,6 +7,7 @@
|
|
|
package net.ranides.assira.awt;
|
|
|
|
|
|
import java.awt.Color;
|
|
|
+import java.awt.color.ColorSpace;
|
|
|
import java.util.Locale;
|
|
|
import java.util.regex.Matcher;
|
|
|
import java.util.regex.Pattern;
|
|
|
@@ -57,13 +58,25 @@ public final class ColorUtils {
|
|
|
return String.format(Locale.ROOT, "rgb(%d,%d,%d)", color.getRed(), color.getGreen(), color.getBlue() );
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ public static Color parse(String value) {
|
|
|
+ if(6 == value.length() && value.matches("[0-9a-fA-F]{6}")) {
|
|
|
+ return fromCSS_S6(value);
|
|
|
+ }
|
|
|
+ if(3 == value.length() && value.matches("[0-9a-fA-F]{3}")) {
|
|
|
+ return fromCSS_S3(value);
|
|
|
+ }
|
|
|
+ return fromCSS(value);
|
|
|
+ }
|
|
|
|
|
|
public static Color fromCSS(String value) {
|
|
|
if(7 == value.length() && value.startsWith("#")) {
|
|
|
- return fromCSS_H6(value);
|
|
|
+ // NOPMD
|
|
|
+ return fromCSS_S6(value.substring(1));
|
|
|
}
|
|
|
if(4 == value.length() && value.startsWith("#")) {
|
|
|
- return fromCSS_H3(value);
|
|
|
+ // NOPMD
|
|
|
+ return fromCSS_S3(value.substring(1));
|
|
|
}
|
|
|
if(value.startsWith("rgb")) {
|
|
|
Matcher hit = RE_RGBA.matcher(value);
|
|
|
@@ -77,19 +90,18 @@ public final class ColorUtils {
|
|
|
return fromCSS_HSLA(hit);
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
throw new IllegalArgumentException("Invalid color: " + value);
|
|
|
}
|
|
|
|
|
|
|
|
|
- private static Color fromCSS_H6(String value) { // NOPMD
|
|
|
- return new Color(Integer.parseInt(value.substring(1),16));
|
|
|
+ private static Color fromCSS_S6(String value) { // NOPMD
|
|
|
+ return new Color(Integer.parseInt(value,16));
|
|
|
}
|
|
|
|
|
|
- private static Color fromCSS_H3(String value) { // NOPMD
|
|
|
- int r = 17*ord(value.charAt(1));
|
|
|
- int g = 17*ord(value.charAt(2));
|
|
|
- int b = 17*ord(value.charAt(3));
|
|
|
+ private static Color fromCSS_S3(String value) { // NOPMD
|
|
|
+ int r = 17*ord(value.charAt(0));
|
|
|
+ int g = 17*ord(value.charAt(1));
|
|
|
+ int b = 17*ord(value.charAt(2));
|
|
|
return new Color(r,g,b);
|
|
|
|
|
|
}
|
|
|
@@ -132,5 +144,126 @@ public final class ColorUtils {
|
|
|
}
|
|
|
throw new IllegalArgumentException("Invalid digit: " + c);
|
|
|
}
|
|
|
+
|
|
|
+ private static class CIELab extends ColorSpace {
|
|
|
+
|
|
|
+ private static final long serialVersionUID = 5027741380892134289L;
|
|
|
+
|
|
|
+ private static final ColorSpace CIEXYZ = ColorSpace.getInstance(ColorSpace.CS_CIEXYZ);
|
|
|
+
|
|
|
+ private static final double N = 4.0 / 29.0;
|
|
|
+
|
|
|
+ private static class Holder {
|
|
|
+ static final CIELab INSTANCE = new CIELab();
|
|
|
+ }
|
|
|
+
|
|
|
+ public static CIELab getInstance() {
|
|
|
+ return Holder.INSTANCE;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public float[] fromCIEXYZ(float[] colorvalue) {
|
|
|
+ double l = f(colorvalue[1]);
|
|
|
+ double L = 116.0 * l - 16.0;
|
|
|
+ double a = 500.0 * (f(colorvalue[0]) - l);
|
|
|
+ double b = 200.0 * (l - f(colorvalue[2]));
|
|
|
+ return new float[] {(float) L, (float) a, (float) b};
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public float[] fromRGB(float[] rgbvalue) {
|
|
|
+ float[] xyz = CIEXYZ.fromRGB(rgbvalue);
|
|
|
+ return fromCIEXYZ(xyz);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public float getMaxValue(int component) {
|
|
|
+ return 128f;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public float getMinValue(int component) {
|
|
|
+ return (component == 0)? 0f: -128f;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String getName(int idx) {
|
|
|
+ return String.valueOf("Lab".charAt(idx));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public float[] toCIEXYZ(float[] colorvalue) {
|
|
|
+ double i = (colorvalue[0] + 16.0) * (1.0 / 116.0);
|
|
|
+ double X = fInv(i + colorvalue[1] * (1.0 / 500.0));
|
|
|
+ double Y = fInv(i);
|
|
|
+ double Z = fInv(i - colorvalue[2] * (1.0 / 200.0));
|
|
|
+ return new float[] {(float) X, (float) Y, (float) Z};
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public float[] toRGB(float[] colorvalue) {
|
|
|
+ float[] xyz = toCIEXYZ(colorvalue);
|
|
|
+ return CIEXYZ.toRGB(xyz);
|
|
|
+ }
|
|
|
+
|
|
|
+ CIELab() {
|
|
|
+ super(ColorSpace.TYPE_Lab, 3);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static double f(double x) {
|
|
|
+ if (x > 216.0 / 24389.0) {
|
|
|
+ return Math.cbrt(x);
|
|
|
+ } else {
|
|
|
+ return (841.0 / 108.0) * x + N;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static double fInv(double x) {
|
|
|
+ if (x > 6.0 / 29.0) {
|
|
|
+ return x*x*x;
|
|
|
+ } else {
|
|
|
+ return (108.0 / 841.0) * (x - N);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private Object readResolve() {
|
|
|
+ return getInstance();
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ public static double distance(Color a, Color b) {
|
|
|
+ return distance(a.getRGBColorComponents(new float[3]), b.getRGBColorComponents(new float[3]));
|
|
|
+ }
|
|
|
+
|
|
|
+ public static double distance(LABColor a, LABColor b) {
|
|
|
+ return distance(a.components, b.components);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static double distance(float[] a, float[] b) {
|
|
|
+ double sum = 0;
|
|
|
+ for(int i=0, n= a.length; i<n; i++) {
|
|
|
+ sum += (a[i]-b[i])*(a[i]-b[i]);
|
|
|
+ }
|
|
|
+ return Math.sqrt(sum);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static class LABColor {
|
|
|
+
|
|
|
+ private float[] components;
|
|
|
+
|
|
|
+ public LABColor(float[] components) {
|
|
|
+ this.components = components;
|
|
|
+ }
|
|
|
+
|
|
|
+ public LABColor(Color color) {
|
|
|
+ components = color.getColorComponents(CIELab.getInstance(), new float[3]);
|
|
|
+ }
|
|
|
+
|
|
|
+ public LABColor(int r, int g, int b) {
|
|
|
+ components = new Color(r,g,b).getColorComponents(CIELab.getInstance(), new float[3]);
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
|
|
|
}
|