package org.mapfish.print.map.style.json; import com.google.common.base.Optional; import org.geotools.styling.SLD; import java.awt.Color; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Parses colors from text strings. Supports formats: * <ul> * <li><code>#F00</code></li> * <li><code>#FFAABB</code></li> * <li><code>rgb(100,100,100)</code></li> * <li><code>rgba(100,100,100, 100)</code></li> * <li><code>hsl(100,100,100)</code></li> * <li><code>hsla(100,100,100, 100)</code></li> * <li><code>-65536</code></li> * <li><code>red</code></li> * </ul> * The numbers in the rgb(..) and hsl(..) can have the following formats: * <ul> * <li>0-255</li> * <li>0%-100%</li> * <li>0.0%-100.0%</li> * <li>0.0-1.0</li> * <li>0f-1f</li> * <li>0.0f-1.0f</li> * </ul> */ public final class ColorParser { private static final float MAX_INT_COLOR = 255f; private static final String NUMBER_PATTERN = "\\s*(\\d*\\.?\\d*[%f]?)\\s*"; private static final Pattern RGB_COLOR_EXTRACTOR = Pattern.compile( "rgb\\s*\\(" + NUMBER_PATTERN + "," + NUMBER_PATTERN + "," + NUMBER_PATTERN + "\\)"); private static final Pattern RGBA_COLOR_EXTRACTOR = Pattern.compile( "rgba\\s*\\(" + NUMBER_PATTERN + "," + NUMBER_PATTERN + "," + NUMBER_PATTERN + "," + NUMBER_PATTERN + "\\)"); private static final Pattern HSL_COLOR_EXTRACTOR = Pattern.compile( "hsl\\s*\\(" + NUMBER_PATTERN + "," + NUMBER_PATTERN + "," + NUMBER_PATTERN + "\\)"); private static final Pattern HSLA_COLOR_EXTRACTOR = Pattern.compile( "hsla\\s*\\(" + NUMBER_PATTERN + "," + NUMBER_PATTERN + "," + NUMBER_PATTERN + "," + NUMBER_PATTERN + "\\)"); private ColorParser() { // utility class so ignore } /** * Parse the string and convert it to a {@link java.awt.Color}. See class description for details on the supported * color formats. * * @param colorString the color string encoded. */ public static Color toColor(final String colorString) { String trimmedString = colorString.trim(); Color color = null; if (trimmedString.startsWith("#")) { final int shortHexCode = 4; if (trimmedString.length() == shortHexCode) { StringBuilder builder = new StringBuilder("#"); for (int i = 1; i < trimmedString.length(); i++) { builder.append(trimmedString.charAt(i)); builder.append(trimmedString.charAt(i)); } color = SLD.toColor(builder.toString()); } else { color = SLD.toColor(trimmedString); } } if (color == null) { color = parseRgbColor(trimmedString); } if (color == null) { color = parseRgbaColor(trimmedString); } if (color == null) { color = parseHslColor(trimmedString); } if (color == null) { color = parseHslaColor(trimmedString); } if (color == null) { final Field[] fields = Color.class.getFields(); for (Field field : fields) { if (field.getType() == Color.class && Modifier.isFinal(field.getModifiers()) && Modifier.isStatic(field.getModifiers()) && field.getName().equalsIgnoreCase(trimmedString)) { try { return (Color) field.get(null); } catch (IllegalAccessException e) { throw new Error(e); } } } color = Color.decode(trimmedString); } return color; } private static Color parseRgbColor(final String colorString) { final Matcher matcher = RGB_COLOR_EXTRACTOR.matcher(colorString); if (matcher.matches()) { String red = matcher.group(1); String green = matcher.group(2); String blue = matcher.group(3); return toColorRGBA(red, green, blue, "255"); } return null; } private static Color parseRgbaColor(final String colorString) { final Matcher matcher = RGBA_COLOR_EXTRACTOR.matcher(colorString); if (matcher.matches()) { String red = matcher.group(1); String green = matcher.group(2); String blue = matcher.group(3); String alpha = matcher.group(4); return toColorRGBA(red, green, blue, alpha); } return null; } private static Color parseHslColor(final String colorString) { final Matcher matcher = HSL_COLOR_EXTRACTOR.matcher(colorString); if (matcher.matches()) { String hue = matcher.group(1); String saturation = matcher.group(2); String luminance = matcher.group(3); String alpha = "255"; return toColorFromHSLA(hue, saturation, luminance, alpha); } return null; } private static Color parseHslaColor(final String colorString) { final Matcher matcher = HSLA_COLOR_EXTRACTOR.matcher(colorString); if (matcher.matches()) { String hue = matcher.group(1); String saturation = matcher.group(2); String luminance = matcher.group(3); String alpha = matcher.group(4); return toColorFromHSLA(hue, saturation, luminance, alpha); } return null; } private static Color toColorRGBA(final String red, final String green, final String blue, final String alpha) { float finalRed = parsePercent(red). or(parseInt(red)). or(parseFloat(red)). or(parseDouble(red)).get(); float finalGreen = parsePercent(green). or(parseInt(green)). or(parseFloat(green)). or(parseDouble(green)).get(); float finalBlue = parsePercent(blue). or(parseInt(blue)). or(parseFloat(blue)). or(parseDouble(blue)).get(); float finalAlpha = parsePercent(alpha). or(parseInt(alpha)). or(parseFloat(alpha)). or(parseDouble(alpha)).or(1.0f); return new Color(finalRed, finalGreen, finalBlue, finalAlpha); } private static Optional<Float> parsePercent(final String colorString) { if (colorString.endsWith("%")) { return Optional.of(parseDouble(colorString.substring(0, colorString.length() - 1)).get() / 100f); } return Optional.absent(); } private static Color toColorFromHSLA(final String hue, final String saturation, final String luminance, final String alpha) { float finalHue = parsePercent(hue). or(parseInt(hue)). or(parseFloat(hue)). or(parseDouble(hue)).get(); float finalSaturation = parsePercent(saturation). or(parseInt(saturation)). or(parseFloat(saturation)). or(parseDouble(saturation)).get(); float finalLuminance = parsePercent(luminance). or(parseInt(luminance)). or(parseFloat(luminance)). or(parseDouble(luminance)).get(); float finalAlpha = parsePercent(alpha). or(parseInt(alpha)). or(parseFloat(alpha)). or(parseDouble(alpha)).or(1.0f); if (finalSaturation < 0.0f || finalSaturation > 1.0f) { String message = "Color parameter outside of expected range - Saturation (" + saturation + ")"; throw new IllegalArgumentException(message); } if (finalLuminance < 0.0f || finalLuminance > 1.0f) { String message = "Color parameter outside of expected range - Luminance (" + luminance + ")"; throw new IllegalArgumentException(message); } if (finalAlpha < 0.0f || finalAlpha > 1.0f) { String message = "Color parameter outside of expected range - Alpha (" + alpha + ")"; throw new IllegalArgumentException(message); } float q; if (finalLuminance < 0.5) { q = finalLuminance * (1 + finalSaturation); } else { q = (finalLuminance + finalSaturation) - (finalSaturation * finalLuminance); } float p = 2 * finalLuminance - q; float red = hueToRGB(p, q, finalHue + (1.0f / 3.0f)); float green = hueToRGB(p, q, finalHue); float blue = hueToRGB(p, q, finalHue - (1.0f / 3.0f)); return new Color(red, green, blue, finalAlpha); } private static float hueToRGB(final float p, final float q, final float hue) { float finalHue = hue; if (finalHue < 0) { finalHue += 1; } if (finalHue > 1) { finalHue -= 1; } if (6 * finalHue < 1) { return p + ((q - p) * 6 * finalHue); } if (2 * finalHue < 1) { return q; } if (3 * finalHue < 2) { return p + ((q - p) * 6 * ((2.0f / 3.0f) - finalHue)); } return p; } private static Optional<Float> parseFloat(final String stringForm) { try { return Optional.of(Float.parseFloat(stringForm)); } catch (NumberFormatException e) { return Optional.absent(); } } private static Optional<Float> parseDouble(final String stringForm) { try { return Optional.of((float) Double.parseDouble(stringForm)); } catch (NumberFormatException e) { return Optional.absent(); } } private static Optional<Float> parseInt(final String stringForm) { try { final int i = Integer.parseInt(stringForm); if (i == 0) { return Optional.of(0f); } return Optional.of(i / MAX_INT_COLOR); } catch (NumberFormatException e) { return Optional.absent(); } } /** * Check if the given color string can be parsed. * @param colorString The color to parse. */ public static boolean canParseColor(final String colorString) { try { return ColorParser.toColor(colorString) != null; } catch (Exception exc) { return false; } } /** * Get the "rgb(...)" representation for a color. * @param color The color. */ public static String toRGB(final Color color) { return "rgb(" + color.getRed() + ", " + color.getGreen() + ", " + color.getBlue() + ")"; } }