package org.geogebra.common.awt;
import java.util.HashMap;
/**
* @author michael
*
* Thin class to just contain the color (as a single int)
*
* a HashMap is used to recycle colors (to avoid extra "new GColor()"s)
*
*/
public final class GColor implements GPaint {
// MUST be first in class
private static HashMap<Integer, GColor> map = new HashMap<Integer, GColor>();
/** WHITE */
public static final GColor WHITE = newColor(255, 255, 255);
/** BLACK */
public static final GColor BLACK = newColor(0, 0, 0);
/** RED */
public static final GColor RED = newColor(255, 0, 0);
/** ORANGE */
public static final GColor ORANGE = newColor(255, 127, 0);
/** YELLOW */
public static final GColor YELLOW = newColor(255, 255, 0);
/** GREEN */
public static final GColor GREEN = newColor(0, 255, 0);
/** BLUE */
public static final GColor BLUE = newColor(0, 0, 255);
/** CYAN */
public static final GColor CYAN = newColor(0, 255, 255);
/** DARK_CYAN */
public static final GColor DARK_CYAN = newColor(99, 219, 219);
/** DARK_GREEN */
public static final GColor DARK_GREEN = newColor(0, 127, 0);
/** MAGENTA */
public static final GColor MAGENTA = newColor(255, 0, 255);
/** LIGHTEST_GRAY */
public static final GColor LIGHTEST_GRAY = newColor(230, 230, 230);
/** LIGHT_GRAY */
public static final GColor LIGHT_GRAY = newColor(192, 192, 192);
/** GEOGEBRA_GRAY */
public static final GColor GEOGEBRA_GRAY = newColor(102, 102, 102);
/** GRAY */
public static final GColor GRAY = newColor(128, 128, 128);
/** DARK_GRAY */
public static final GColor DARK_GRAY = newColor(68, 68, 68);
/** PURPLE */
public static final GColor PURPLE = newColor(102, 102, 255);
/** GEOGEBRA_BLUE */
public static final GColor GEOGEBRA_BLUE = newColor(153, 153, 255);
/** MOW PURPLE */
public static final GColor MOW_PURPLE = newColor(163, 136, 212);
private final int value;
/**
* @param r
* red (0-255)
* @param g
* green (0-255)
* @param b
* blue (0-255)
* @param a
* alpha (0-255)
*/
private GColor(int r, int g, int b, int a) {
this.value = hashRGBA(r & 0xFF, g & 0xFF, b & 0xFF, a & 0xFF);
}
/**
* Creates an opaque sRGB color with the specified combined RGB value
* consisting of the red component in bits 16-23, the green component in
* bits 8-15, and the blue component in bits 0-7. Alpha is defaulted to 255.
*
* @param rgb
* RGB
* @return new color
*/
public static GColor newColorRGB(int rgb) {
return newColor(getRed(rgb), getGreen(rgb), getBlue(rgb), 255);
}
// private void log() {
// Log.debug("storing " + getColorString(this));
// Log.error("map length = " + map.size());
//
// // Log.printStacktrace("");
// }
private static int getRed(int rgba) {
return (rgba >> 16) & 0xFF;
}
private static int getGreen(int rgba) {
return (rgba >> 8) & 0xFF;
}
private static int getBlue(int rgba) {
return (rgba >> 0) & 0xFF;
}
private static int getAlpha(int rgba) {
return (rgba >> 24) & 0xff;
}
/**
* @return red (0 - 255)
*/
public int getRed() {
return getRed(value);
}
/**
* @return green (0 - 255)
*/
public int getGreen() {
return getGreen(value);
}
/**
* @return blue (0 - 255)
*/
public int getBlue() {
return getBlue(value);
}
/**
* @return alpha (0 - 255)
*/
public int getAlpha() {
return getAlpha(value);
}
/**
* @param r
* red (0-255)
* @param g
* green (0-255)
* @param b
* blue (0-255)
* @return new color
*/
public static GColor newColor(int r, int g, int b) {
return newColor(r, g, b, 255);
}
/**
* @param r
* red (0-255)
* @param g
* green (0-255)
* @param b
* blue (0-255)
* @param a
* alpha (0-255)
* @return new color
*/
public static GColor newColor(int r, int g, int b, int a) {
GColor ret;
int hash = hashRGBA(r, g, b, a);
synchronized (map) {
ret = map.get(hash);
if (ret == null) {
ret = new GColor(r, g, b, a);
map.put(hash, ret);
}
}
return ret;
}
/**
* Create a more readable (=darker) version of a color, to make it readable
* on white background. Does not change the color, if it already fulfills
* the requirements.
*
* Uses the W3C standard for contrast and brightness.
*
* @param color
* the base color
* @return a darker version of the input color that can be read on white
* background
*/
public static GColor updateForWhiteBackground(GColor color) {
int fgRed = color.getRed();
int fgGreen = color.getGreen();
int fgBlue = color.getBlue();
// prevent endless loop
int loopCounter = 0;
int difference = 5;
while (!checkColorRatioWhite(fgRed, fgGreen, fgBlue)
&& loopCounter < 50) {
// create a slightly darker version of the color
fgRed = Math.max(fgRed - difference, 0);
fgGreen = Math.max(fgGreen - difference, 0);
fgBlue = Math.max(fgBlue - difference, 0);
loopCounter++;
}
if (!checkColorRatioWhite(fgRed, fgGreen, fgBlue)) {
// If the color could not be set correctly, the font color is set to
// black.
return GColor.BLACK;
}
return GColor.newColor(fgRed, fgGreen, fgBlue);
}
/**
* uses the color contrast ratio of the W3C, which can be found at:
* http://www.w3.org/TR/WCAG20-TECHS/G18.html
* http://web.mst.edu/~rhall/web_design/color_readability.html
*
* @param foreground
* the text color
* @param background
* the background color
* @return if the contrast ration sufficient (true) or not (false)
*/
private static boolean checkColorRatioWhite(int fgRed, int fgGreen,
int fgBlue) {
int diff_hue = 3 * 255 - fgRed - fgBlue - fgGreen;
double diff_brightness = 255
- GColor.getGrayScale(fgGreen, fgRed, fgBlue);
return diff_brightness > 125 && diff_hue > 500;
}
// public int compareTo(GColor c) {
// if (getRed() < c.getRed()) {
// return -1;
// }
// if (getRed() > c.getRed()) {
// return 1;
// }
// if (getGreen() < c.getGreen()) {
// return -1;
// }
// if (getGreen() > c.getGreen()) {
// return 1;
// }
// if (getBlue() < c.getBlue()) {
// return -1;
// }
// if (getBlue() > c.getBlue()) {
// return 1;
// }
// if (getAlpha() < c.getAlpha()) {
// return -1;
// }
// if (getAlpha() > c.getAlpha()) {
// return 1;
// }
//
// return 0;
// }
/**
*
* @return gray scale value corresponding to this color (0 to 255)
*/
public double getGrayScale() {
return getGrayScale(getRed(), getGreen(), getBlue());
}
private static double getGrayScale(int red2, int green2, int blue2) {
return 0.2989 * red2 + 0.5870 * green2 + 0.1140 * blue2;
}
/**
*
* @return gray scale GColor corresponding to this color
*/
public GColor createGrayScale() {
int gray = (int) getGrayScale();
return GColor.newColor(gray, gray, gray, getAlpha());
}
/**
* This method could return Long, but it returns Integer for
* backwards-compatibility, even if it's negative
*
* @return int ARBG
*/
public int getRGB() {
return value;
}
/**
* @param r
* red (0-255)
* @param g
* green (0-255)
* @param b
* blue (0-255)
* @param a
* alpha (0-255)
* @return new color
*/
public static GColor newColor(double r, double g, double b, double a) {
return newColor((int) (r * 255), (int) (g * 255), (int) (b * 255),
(int) (a * 255));
}
/**
* @param r
* red (0-1)
* @param g
* green (0-1)
* @param b
* blue (0-1)
* @return new color
*/
public static GColor newColor(double r, double g, double b) {
return newColor((int) (r * 255), (int) (g * 255), (int) (b * 255), 255);
}
/**
* @param hue
* (0-1)
* @param saturation
* (0-1)
* @param brightness
* (0-1)
* @return new color as ARGB
*/
public static int HSBtoRGB(double hue, double saturation,
double brightness) {
int r = 0, g = 0, b = 0;
if (saturation == 0) {
r = g = b = (int) (brightness * 255.0f + 0.5f);
} else {
double h = (hue - Math.floor(hue)) * 6.0f;
double f = h - java.lang.Math.floor(h);
double p = brightness * (1.0f - saturation);
double q = brightness * (1.0f - saturation * f);
double t = brightness * (1.0f - (saturation * (1.0f - f)));
switch ((int) h) {
default:
case 0:
r = (int) (brightness * 255.0f + 0.5f);
g = (int) (t * 255.0f + 0.5f);
b = (int) (p * 255.0f + 0.5f);
break;
case 1:
r = (int) (q * 255.0f + 0.5f);
g = (int) (brightness * 255.0f + 0.5f);
b = (int) (p * 255.0f + 0.5f);
break;
case 2:
r = (int) (p * 255.0f + 0.5f);
g = (int) (brightness * 255.0f + 0.5f);
b = (int) (t * 255.0f + 0.5f);
break;
case 3:
r = (int) (p * 255.0f + 0.5f);
g = (int) (q * 255.0f + 0.5f);
b = (int) (brightness * 255.0f + 0.5f);
break;
case 4:
r = (int) (t * 255.0f + 0.5f);
g = (int) (p * 255.0f + 0.5f);
b = (int) (brightness * 255.0f + 0.5f);
break;
case 5:
r = (int) (brightness * 255.0f + 0.5f);
g = (int) (p * 255.0f + 0.5f);
b = (int) (q * 255.0f + 0.5f);
break;
}
}
return 0xff000000 | (r << 16) | (g << 8) | (b << 0);
}
/**
* @param color
* color
* @return HTML5 color string eg rgba(255,0,0,0.5)
*/
public static String getColorString(GColor color) {
String ret = "rgba(" + color.getRed() + "," + color.getGreen() + ","
+ color.getBlue() + "," + (color.getAlpha() / 255d) + ")";
return ret;
}
private static final double FACTOR = 0.7;
/**
* @return darker color
*/
public GColor darker() {
return GColor.newColor(Math.max((int) (getRed() * FACTOR), 0),
Math.max((int) (getGreen() * FACTOR), 0),
Math.max((int) (getBlue() * FACTOR), 0));
}
/**
* @return brighter color
*/
public GColor brighter() {
return GColor.newColor(Math.min((int) (getRed() / FACTOR), 255),
Math.min((int) (getGreen() / FACTOR), 255),
Math.min((int) (getBlue() / FACTOR), 255));
}
@Override
public boolean equals(Object object) {
if (!(object instanceof GColor)) {
return false;
}
GColor other = (GColor) object;
return other.value == this.value;
}
@Override
public int hashCode() {
return value;
}
/**
* @param r
* red (0-255)
* @param g
* green (0-255)
* @param b
* blue (0-255)
* @param a
* alpha (0-255)
* @return ARGB as an int
*/
public static int hashRGBA(int r, int g, int b, int a) {
return ((a & 0xFF) << 24) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8)
| ((b & 0xFF) << 0);
}
@Override
public String toString() {
return getColorString(this);
}
/**
* @param alpha
* 0 - 255
* @return new derived color with alpha set to new value
*/
public GColor deriveWithAlpha(int alpha) {
return newColor(getRed(), getGreen(), getBlue(), alpha);
}
/**
*
* @param color1
* first color
* @param color2
* second color
* @param mix
* ratio of second color, from [0,1]
* @param alpha
* output opacity
* @return color1 * (1-mix) + color2 * mix and force alpha
*/
public static GColor mixColors(GColor color1, GColor color2,
double mix, int alpha) {
int r = (int) (color1.getRed() * (1 - mix) + color2.getRed() * mix);
int g = (int) (color1.getGreen() * (1 - mix) + color2.getGreen() * mix);
int b = (int) (color1.getBlue() * (1 - mix) + color2.getBlue() * mix);
return newColor(r, g, b, alpha);
}
}