ColorUtility.java
package swingtree;
import org.jspecify.annotations.Nullable;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
/*
* Named colors moved to nested class to initialize them only when they
* are needed.
*/
final class ColorUtility {
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ColorUtility.class);
static UI.Color deriveColor(
final double hueShift,
final double saturationFactor,
final double brightnessFactor,
final double opacityFactor,
final double red,
final double green,
final double blue,
final double opacity
) {
double[] hsb = ColorUtility.RGBtoHSB(red, green, blue);
/* Allow brightness increase of black color */
double b = hsb[2];
if (b == 0 && brightnessFactor > 1.0) {
b = 0.05;
}
/* the tail "+ 360) % 360" solves shifts into negative numbers */
double h = (((hsb[0] + hueShift) % 360) + 360) % 360;
double s = Math.max(Math.min(hsb[1] * saturationFactor, 1.0), 0.0);
b = Math.max(Math.min(b * brightnessFactor, 1.0), 0.0);
double a = Math.max(Math.min(opacity * opacityFactor, 1.0), 0.0);
return UI.Color.ofHsb(h, s, b, a);
}
static double[] HSBtoRGB(double hue, double saturation, double brightness) {
// normalize the hue
double normalizedHue = ((hue % 360) + 360) % 360;
hue = normalizedHue / 360;
double r = 0, g = 0, b = 0;
if (saturation == 0) {
r = g = b = brightness;
} else {
double h = (hue - Math.floor(hue)) * 6.0;
double f = h - Math.floor(h);
double p = brightness * (1.0 - saturation);
double q = brightness * (1.0 - saturation * f);
double t = brightness * (1.0 - (saturation * (1.0 - f)));
switch ((int) h) {
case 0:
r = brightness;
g = t;
b = p;
break;
case 1:
r = q;
g = brightness;
b = p;
break;
case 2:
r = p;
g = brightness;
b = t;
break;
case 3:
r = p;
g = q;
b = brightness;
break;
case 4:
r = t;
g = p;
b = brightness;
break;
case 5:
r = brightness;
g = p;
b = q;
break;
}
}
double[] f = new double[3];
f[0] = r;
f[1] = g;
f[2] = b;
return f;
}
static double[] RGBtoHSB(double r, double g, double b) {
double hue, saturation, brightness;
double[] hsbvals = new double[3];
double cmax = (r > g) ? r : g;
if (b > cmax) cmax = b;
double cmin = (r < g) ? r : g;
if (b < cmin) cmin = b;
brightness = cmax;
if (cmax != 0)
saturation = (cmax - cmin) / cmax;
else
saturation = 0;
if (saturation == 0) {
hue = 0;
} else {
double redc = (cmax - r) / (cmax - cmin);
double greenc = (cmax - g) / (cmax - cmin);
double bluec = (cmax - b) / (cmax - cmin);
if (r == cmax)
hue = bluec - greenc;
else if (g == cmax)
hue = 2.0 + redc - bluec;
else
hue = 4.0 + greenc - redc;
hue = hue / 6.0;
if (hue < 0)
hue = hue + 1.0;
}
hsbvals[0] = hue * 360;
hsbvals[1] = saturation;
hsbvals[2] = brightness;
return hsbvals;
}
static UI.Color parseColor(final String colorAsString) {
// First some cleanup
final String colorString = colorAsString.trim();
if (colorAsString.isEmpty())
return UI.Color.UNDEFINED;
if (colorString.startsWith("#"))
return UI.Color.of(java.awt.Color.decode(colorString));
if (colorString.startsWith("0x"))
return UI.Color.of(java.awt.Color.decode(colorString));
if (colorString.startsWith("rgb")) {
// We have an rgb() or rgba() color
int start = colorString.indexOf('(');
int end = colorString.indexOf(')');
if (start < 0 || end < 0 || end < start) {
log.error("Invalid rgb() or rgba() color: " + colorString, new Throwable());
return UI.Color.UNDEFINED;
}
String[] parts = colorString.substring(start + 1, end).split(",", -1);
if (parts.length < 3 || parts.length > 4) {
log.error("Invalid rgb() or rgba() color: " + colorString, new Throwable());
return UI.Color.UNDEFINED;
}
for (int i = 0; i < parts.length; i++)
parts[i] = parts[i].trim();
int[] values = new int[parts.length];
for (int i = 0; i < parts.length; i++) {
String part = parts[i];
if (part.endsWith("%")) {
part = part.substring(0, part.length() - 1);
values[i] = Integer.parseInt(part);
if (values[i] < 0 || values[i] > 100) {
log.error("Invalid rgb() or rgba() color: " + colorString, new Throwable());
return UI.Color.UNDEFINED;
}
values[i] = (int) Math.ceil(values[i] * 2.55);
} else if (part.matches("[0-9]+((\\.[0-9]+[fF]?)|[fF])"))
values[i] = (int) (Float.parseFloat(part) * 255);
else
values[i] = Integer.parseInt(part);
}
int r = values[0];
int g = values[1];
int b = values[2];
int a = values.length == 4 ? values[3] : 255;
return UI.Color.ofRgba(r, g, b, a);
}
if (colorString.startsWith("hsb")) {
// We have an hsb() or hsba() color
int start = colorString.indexOf('(');
int end = colorString.indexOf(')');
if (start < 0 || end < 0 || end < start) {
log.error("Invalid hsb() or hsba() color: " + colorString, new Throwable());
return UI.Color.UNDEFINED;
}
String[] parts = colorString.substring(start + 1, end).split(",", -1);
if (parts.length < 3 || parts.length > 4) {
log.error("Invalid hsb() or hsba() color: " + colorString, new Throwable());
return UI.Color.UNDEFINED;
}
for (int i = 0; i < parts.length; i++)
parts[i] = parts[i].trim();
float[] values = new float[parts.length];
for (int i = 0; i < parts.length; i++) {
String part = parts[i];
if (part.endsWith("%")) {
part = part.substring(0, part.length() - 1);
values[i] = Float.parseFloat(part);
if (values[i] < 0 || values[i] > 100) {
log.error(
"Invalid hsb() or hsba() string '" + colorString + "', " +
"value '" + part + "' out of range.",
new Throwable()
);
return UI.Color.UNDEFINED;
}
values[i] = values[i] / 100.0f;
} else if (part.endsWith("°")) {
if (i > 0) {
log.error(
"Invalid hsb() or hsba() string '" + colorString + "', " +
"unexpected degree symbol in '" + part + "' (only allowed for hue)",
new Throwable()
);
return UI.Color.UNDEFINED;
}
part = part.substring(0, part.length() - 1);
values[i] = Float.parseFloat(part);
if (values[i] < 0 || values[i] > 360) {
log.error(
"Invalid hsb() or hsba() string '" + colorString + "', " +
"hue value '" + part + "' out of range.",
new Throwable()
);
return UI.Color.UNDEFINED;
}
values[i] = values[i] / 360.0f;
} else if (part.matches("[0-9]+((\\.[0-9]+[fF]?)|[fF])"))
values[i] = Float.parseFloat(part);
else
values[i] = Integer.parseInt(part);
}
float h = values[0];
float s = values[1];
float b = values[2];
float a = values.length == 4 ? values[3] : 1.0f;
java.awt.Color c = java.awt.Color.getHSBColor(h, s, b);
return UI.Color.ofRgba(c.getRed(), c.getGreen(), c.getBlue(), (int) (a * 255));
}
{
String maybeWord = colorString.toLowerCase(Locale.ENGLISH);
boolean transparent = false;
if (maybeWord.startsWith("transparent")) {
transparent = true;
maybeWord = maybeWord.substring(11).trim();
}
// Let's try a few common color names
UI.Color color = _tryFromName(maybeWord);
if (color == null && maybeWord.startsWith("darker")) {
color = _tryFromName(maybeWord.substring(6).trim());
if (color != null)
color = color.darker();
}
if (color == null && maybeWord.startsWith("dark")) {
color = _tryFromName(maybeWord.substring(4).trim());
if (color != null)
color = color.darker();
}
if (color == null && maybeWord.startsWith("lighter")) {
color = _tryFromName(maybeWord.substring(7).trim());
if (color != null)
color = color.brighter();
}
if (color == null && maybeWord.startsWith("light")) {
color = _tryFromName(maybeWord.substring(5).trim());
if (color != null)
color = color.brighter();
}
if (color == null && maybeWord.startsWith("brighter")) {
color = _tryFromName(maybeWord.substring(8).trim());
if (color != null)
color = color.brighter();
}
if (color == null && maybeWord.startsWith("bright")) {
color = _tryFromName(maybeWord.substring(6).trim());
if (color != null)
color = color.brighter();
}
if (color != null) {
if (transparent)
return UI.Color.ofRgba(color.getRed(), color.getGreen(), color.getBlue(), 255 / 2);
else
return color;
} else if (transparent)
return UI.Color.TRANSPARENT;
}
// Let's try to find it as a system property
UI.Color foundInSystemProperties = null;
try {
java.awt.Color found = java.awt.Color.getColor(colorString);
if (found != null && !(found instanceof UI.Color))
foundInSystemProperties = UI.Color.of(found);
} catch (IllegalArgumentException e) {
// Ignore
}
if (foundInSystemProperties != null)
return foundInSystemProperties;
return UI.Color.UNDEFINED;
}
private static UI.@Nullable Color _tryFromName(String maybeColorName) {
try {
String lowerCaseName = maybeColorName.toLowerCase(Locale.ENGLISH);
return ColorUtility.get(lowerCaseName);
} catch (IllegalArgumentException e) {
return null;
}
}
static UI.@Nullable Color get(String name) {
return NAMED_COLOURS.get(name);
}
private static final Map<String, UI.Color> NAMED_COLOURS = new HashMap<>();
static {
NAMED_COLOURS.put("aliceblue", UI.Color.ALICEBLUE);
NAMED_COLOURS.put("antiquewhite", UI.Color.ANTIQUEWHITE);
NAMED_COLOURS.put("aqua", UI.Color.AQUA);
NAMED_COLOURS.put("aquamarine", UI.Color.AQUAMARINE);
NAMED_COLOURS.put("azure", UI.Color.AZURE);
NAMED_COLOURS.put("beige", UI.Color.BEIGE);
NAMED_COLOURS.put("bisque", UI.Color.BISQUE);
NAMED_COLOURS.put("black", UI.Color.BLACK);
NAMED_COLOURS.put("blanchedalmond", UI.Color.BLANCHEDALMOND);
NAMED_COLOURS.put("blue", UI.Color.BLUE);
NAMED_COLOURS.put("blueviolet", UI.Color.BLUEVIOLET);
NAMED_COLOURS.put("brown", UI.Color.BROWN);
NAMED_COLOURS.put("burlywood", UI.Color.BURLYWOOD);
NAMED_COLOURS.put("cadetblue", UI.Color.CADETBLUE);
NAMED_COLOURS.put("chartreuse", UI.Color.CHARTREUSE);
NAMED_COLOURS.put("chocolate", UI.Color.CHOCOLATE);
NAMED_COLOURS.put("coral", UI.Color.CORAL);
NAMED_COLOURS.put("cornflowerblue", UI.Color.CORNFLOWERBLUE);
NAMED_COLOURS.put("cornsilk", UI.Color.CORNSILK);
NAMED_COLOURS.put("crimson", UI.Color.CRIMSON);
NAMED_COLOURS.put("cyan", UI.Color.CYAN);
NAMED_COLOURS.put("darkblue", UI.Color.DARKBLUE);
NAMED_COLOURS.put("darkcyan", UI.Color.DARKCYAN);
NAMED_COLOURS.put("darkgoldenrod", UI.Color.DARKGOLDENROD);
NAMED_COLOURS.put("darkgray", UI.Color.DARKGRAY);
NAMED_COLOURS.put("darkgreen", UI.Color.DARKGREEN);
NAMED_COLOURS.put("darkgrey", UI.Color.DARKGREY);
NAMED_COLOURS.put("darkkhaki", UI.Color.DARKKHAKI);
NAMED_COLOURS.put("darkmagenta", UI.Color.DARKMAGENTA);
NAMED_COLOURS.put("darkolivegreen", UI.Color.DARKOLIVEGREEN);
NAMED_COLOURS.put("darkorange", UI.Color.DARKORANGE);
NAMED_COLOURS.put("darkorchid", UI.Color.DARKORCHID);
NAMED_COLOURS.put("darkred", UI.Color.DARKRED);
NAMED_COLOURS.put("darksalmon", UI.Color.DARKSALMON);
NAMED_COLOURS.put("darkseagreen", UI.Color.DARKSEAGREEN);
NAMED_COLOURS.put("darkslateblue", UI.Color.DARKSLATEBLUE);
NAMED_COLOURS.put("darkslategray", UI.Color.DARKSLATEGRAY);
NAMED_COLOURS.put("darkslategrey", UI.Color.DARKSLATEGREY);
NAMED_COLOURS.put("darkturquoise", UI.Color.DARKTURQUOISE);
NAMED_COLOURS.put("darkviolet", UI.Color.DARKVIOLET);
NAMED_COLOURS.put("deeppink", UI.Color.DEEPPINK);
NAMED_COLOURS.put("deepskyblue", UI.Color.DEEPSKYBLUE);
NAMED_COLOURS.put("dimgray", UI.Color.DIMGRAY);
NAMED_COLOURS.put("dimgrey", UI.Color.DIMGREY);
NAMED_COLOURS.put("dodgerblue", UI.Color.DODGERBLUE);
NAMED_COLOURS.put("firebrick", UI.Color.FIREBRICK);
NAMED_COLOURS.put("floralwhite", UI.Color.FLORALWHITE);
NAMED_COLOURS.put("forestgreen", UI.Color.FORESTGREEN);
NAMED_COLOURS.put("fuchsia", UI.Color.FUCHSIA);
NAMED_COLOURS.put("gainsboro", UI.Color.GAINSBORO);
NAMED_COLOURS.put("ghostwhite", UI.Color.GHOSTWHITE);
NAMED_COLOURS.put("gold", UI.Color.GOLD);
NAMED_COLOURS.put("goldenrod", UI.Color.GOLDENROD);
NAMED_COLOURS.put("gray", UI.Color.GRAY);
NAMED_COLOURS.put("green", UI.Color.GREEN);
NAMED_COLOURS.put("greenyellow", UI.Color.GREENYELLOW);
NAMED_COLOURS.put("grey", UI.Color.GREY);
NAMED_COLOURS.put("honeydew", UI.Color.HONEYDEW);
NAMED_COLOURS.put("hotpink", UI.Color.HOTPINK);
NAMED_COLOURS.put("indianred", UI.Color.INDIANRED);
NAMED_COLOURS.put("indigo", UI.Color.INDIGO);
NAMED_COLOURS.put("ivory", UI.Color.IVORY);
NAMED_COLOURS.put("khaki", UI.Color.KHAKI);
NAMED_COLOURS.put("lavender", UI.Color.LAVENDER);
NAMED_COLOURS.put("lavenderblush", UI.Color.LAVENDERBLUSH);
NAMED_COLOURS.put("lawngreen", UI.Color.LAWNGREEN);
NAMED_COLOURS.put("lemonchiffon", UI.Color.LEMONCHIFFON);
NAMED_COLOURS.put("lightblue", UI.Color.LIGHTBLUE);
NAMED_COLOURS.put("lightcoral", UI.Color.LIGHTCORAL);
NAMED_COLOURS.put("lightcyan", UI.Color.LIGHTCYAN);
NAMED_COLOURS.put("lightgoldenrodyellow", UI.Color.LIGHTGOLDENRODYELLOW);
NAMED_COLOURS.put("lightgray", UI.Color.LIGHTGRAY);
NAMED_COLOURS.put("lightgreen", UI.Color.LIGHTGREEN);
NAMED_COLOURS.put("lightgrey", UI.Color.LIGHTGREY);
NAMED_COLOURS.put("lightpink", UI.Color.LIGHTPINK);
NAMED_COLOURS.put("lightsalmon", UI.Color.LIGHTSALMON);
NAMED_COLOURS.put("lightseagreen", UI.Color.LIGHTSEAGREEN);
NAMED_COLOURS.put("lightskyblue", UI.Color.LIGHTSKYBLUE);
NAMED_COLOURS.put("lightslategray", UI.Color.LIGHTSLATEGRAY);
NAMED_COLOURS.put("lightslategrey", UI.Color.LIGHTSLATEGREY);
NAMED_COLOURS.put("lightsteelblue", UI.Color.LIGHTSTEELBLUE);
NAMED_COLOURS.put("lightyellow", UI.Color.LIGHTYELLOW);
NAMED_COLOURS.put("lime", UI.Color.LIME);
NAMED_COLOURS.put("limegreen", UI.Color.LIMEGREEN);
NAMED_COLOURS.put("linen", UI.Color.LINEN);
NAMED_COLOURS.put("magenta", UI.Color.MAGENTA);
NAMED_COLOURS.put("maroon", UI.Color.MAROON);
NAMED_COLOURS.put("mediumaquamarine", UI.Color.MEDIUMAQUAMARINE);
NAMED_COLOURS.put("mediumblue", UI.Color.MEDIUMBLUE);
NAMED_COLOURS.put("mediumorchid", UI.Color.MEDIUMORCHID);
NAMED_COLOURS.put("mediumpurple", UI.Color.MEDIUMPURPLE);
NAMED_COLOURS.put("mediumseagreen", UI.Color.MEDIUMSEAGREEN);
NAMED_COLOURS.put("mediumslateblue", UI.Color.MEDIUMSLATEBLUE);
NAMED_COLOURS.put("mediumspringgreen", UI.Color.MEDIUMSPRINGGREEN);
NAMED_COLOURS.put("mediumturquoise", UI.Color.MEDIUMTURQUOISE);
NAMED_COLOURS.put("mediumvioletred", UI.Color.MEDIUMVIOLETRED);
NAMED_COLOURS.put("midnightblue", UI.Color.MIDNIGHTBLUE);
NAMED_COLOURS.put("mintcream", UI.Color.MINTCREAM);
NAMED_COLOURS.put("mistyrose", UI.Color.MISTYROSE);
NAMED_COLOURS.put("moccasin", UI.Color.MOCCASIN);
NAMED_COLOURS.put("navajowhite", UI.Color.NAVAJOWHITE);
NAMED_COLOURS.put("navy", UI.Color.NAVY);
NAMED_COLOURS.put("oak", UI.Color.OAK);
NAMED_COLOURS.put("oldlace", UI.Color.OLDLACE);
NAMED_COLOURS.put("olive", UI.Color.OLIVE);
NAMED_COLOURS.put("olivedrab", UI.Color.OLIVEDRAB);
NAMED_COLOURS.put("orange", UI.Color.ORANGE);
NAMED_COLOURS.put("orangered", UI.Color.ORANGERED);
NAMED_COLOURS.put("orchid", UI.Color.ORCHID);
NAMED_COLOURS.put("palegoldenrod", UI.Color.PALEGOLDENROD);
NAMED_COLOURS.put("palegreen", UI.Color.PALEGREEN);
NAMED_COLOURS.put("paleturquoise", UI.Color.PALETURQUOISE);
NAMED_COLOURS.put("palevioletred", UI.Color.PALEVIOLETRED);
NAMED_COLOURS.put("papayawhip", UI.Color.PAPAYAWHIP);
NAMED_COLOURS.put("peachpuff", UI.Color.PEACHPUFF);
NAMED_COLOURS.put("peru", UI.Color.PERU);
NAMED_COLOURS.put("pink", UI.Color.PINK);
NAMED_COLOURS.put("plum", UI.Color.PLUM);
NAMED_COLOURS.put("powderblue", UI.Color.POWDERBLUE);
NAMED_COLOURS.put("purple", UI.Color.PURPLE);
NAMED_COLOURS.put("red", UI.Color.RED);
NAMED_COLOURS.put("rosybrown", UI.Color.ROSYBROWN);
NAMED_COLOURS.put("royalblue", UI.Color.ROYALBLUE);
NAMED_COLOURS.put("saddlebrown", UI.Color.SADDLEBROWN);
NAMED_COLOURS.put("salmon", UI.Color.SALMON);
NAMED_COLOURS.put("sandybrown", UI.Color.SANDYBROWN);
NAMED_COLOURS.put("seagreen", UI.Color.SEAGREEN);
NAMED_COLOURS.put("seashell", UI.Color.SEASHELL);
NAMED_COLOURS.put("sienna", UI.Color.SIENNA);
NAMED_COLOURS.put("silver", UI.Color.SILVER);
NAMED_COLOURS.put("skyblue", UI.Color.SKYBLUE);
NAMED_COLOURS.put("slateblue", UI.Color.SLATEBLUE);
NAMED_COLOURS.put("slategray", UI.Color.SLATEGRAY);
NAMED_COLOURS.put("slategrey", UI.Color.SLATEGREY);
NAMED_COLOURS.put("snow", UI.Color.SNOW);
NAMED_COLOURS.put("springgreen", UI.Color.SPRINGGREEN);
NAMED_COLOURS.put("steelblue", UI.Color.STEELBLUE);
NAMED_COLOURS.put("tan", UI.Color.TAN);
NAMED_COLOURS.put("teal", UI.Color.TEAL);
NAMED_COLOURS.put("thistle", UI.Color.THISTLE);
NAMED_COLOURS.put("tomato", UI.Color.TOMATO);
NAMED_COLOURS.put("transparent", UI.Color.TRANSPARENT);
NAMED_COLOURS.put("turquoise", UI.Color.TURQUOISE);
NAMED_COLOURS.put("violet", UI.Color.VIOLET);
NAMED_COLOURS.put("wheat", UI.Color.WHEAT);
NAMED_COLOURS.put("white", UI.Color.WHITE);
NAMED_COLOURS.put("whitesmoke", UI.Color.WHITESMOKE);
NAMED_COLOURS.put("yellow", UI.Color.YELLOW);
NAMED_COLOURS.put("yellowgreen", UI.Color.YELLOWGREEN);
}
}