zeichenmaschine/src/main/java/schule/ngb/zm/Color.java

633 lines
16 KiB
Java

package schule.ngb.zm;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.image.ColorModel;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
/**
* Repräsentiert eine Farbe in der Zeichenmaschine.
* <p>
* Farben bestehen entweder aus einem Grauwert (zwischen 0 und 255) oder einem
* Rot-, Grün- und Blauanteil (jeweils zwischen 0 und 255).
* <p>
* Eine Farbe hat außerdem einen Transparenzwert zwischen 0 (unsichtbar) und 255
* (deckend).
*/
@SuppressWarnings( "unused" )
public class Color implements Paint {
//@formatter:off
/**
* Die Farbe Schwarz (Grauwert 0).
*/
public static final Color BLACK = new Color(0);
/**
* Die Farbe Weiß (Grauwert 255).
*/
public static final Color WHITE = new Color(255);
/**
* Die Farbe Grau (Grauwert 128).
*/
public static final Color GRAY = new Color(128);
/**
* Die Farbe Dunkelgrau (Grauwert 64).
*/
public static final Color DARKGRAY = new Color(64);
/**
* Die Farbe Hellgrau (Grauwert 192).
*/
public static final Color LIGHTGRAY = new Color(192);
/**
* Die Farbe Zeichenmaschinen-Rot.
*/
public static final Color RED = new Color(240, 80, 37);
/**
* Die Farbe Zeichenmaschinen-Grün.
*/
public static final Color GREEN = new Color(98, 199, 119);
/**
* Die Farbe Zeichenmaschinen-Blau.
*/
public static final Color BLUE = new Color(49, 197, 244);
/**
* Die Farbe Zeichenmaschinen-Gelb.
*/
public static final Color YELLOW = new Color(248, 239, 34);
/**
* Die Farbe Zeichenmaschinen-Orange.
*/
public static final Color ORANGE = new Color(248, 158, 80);
/**
* Die Farbe Zeichenmaschinen-Türkis.
*/
public static final Color CYAN = new Color(java.awt.Color.CYAN);
/**
* Die Farbe Zeichenmaschinen-Magenta.
*/
public static final Color MAGENTA = new Color(java.awt.Color.MAGENTA);
/**
* Die Farbe Zeichenmaschinen-Rosa.
*/
public static final Color PINK = new Color(240, 99, 164);
/**
* Die Farbe Zeichenmaschinen-Lila.
*/
public static final Color PURPLE = new Color(101, 0, 191);
/**
* Die Farbe Zeichenmaschinen-Braun.
*/
public static final Color BROWN = new Color(148, 82, 0);
/**
* Die Farbe Helmholtz-Grün.
*/
public static final Color HGGREEN = new Color(0, 165, 81);
/**
* Die Farbe Helmholtz-Rot.
*/
public static final Color HGRED = new Color(151, 54, 60);
//@formatter:on
/**
* RGBA Wert der Farbe als Integer kodiert.
*/
private final int rgba;
/**
* Erstellt eine leere (schwarze) Farbe.
*/
public Color() {
rgba = 0xFF000000;
}
/**
* Erstellt eine graue Farbe entsprechend des Grauwertes <var>gray</var>.
*
* @param gray Ein Grauwert zwischen 0 und 255.
*/
public Color( int gray ) {
this(gray, gray, gray, 255);
}
/**
* Erstellt eine graue Farbe entsprechend des Grauwertes <var>gray</var> und
* des Transparentwertes <var>alpha</var>.
*
* @param gray Ein Grauwert zwischen 0 und 255.
*/
public Color( int gray, int alpha ) {
this(gray, gray, gray, alpha);
}
/**
* Erstellt eine Farbe. Die Parameter <var>red</var>, <var>green</var> und
* <var>blue</var> geben die Rot-, Grün- und Blauanteile der Farbe. Die
* Werte liegen zwischen 0 und 255.
*
* @param red Rotwert zwischen 0 und 255.
* @param green Grünwert zwischen 0 und 255.
* @param blue Blauwert zwischen 0 und 255.
*/
public Color( int red, int green, int blue ) {
this(red, green, blue, 255);
}
/**
* Erstellt eine Farbe. Die Parameter <var>red</var>, <var>green</var> und
* <var>blue</var> geben die Rot-, Grün- und Blauanteile der Farbe. Die
* Werte liegen zwischen 0 und 255.
* <var>alpha</var> gibt den den Transparentwert an (auch zwischen
* 0 und 255), wobei 0 komplett durchsichtig ist und 255 komplett deckend.
*
* @param red Rotwert zwischen 0 und 255.
* @param green Grünwert zwischen 0 und 255.
* @param blue Blauwert zwischen 0 und 255.
* @param alpha Transparentwert zwischen 0 und 255.
*/
public Color( int red, int green, int blue, int alpha ) {
rgba = ((alpha & 0xFF) << 24) | ((red & 0xFF) << 16) | ((green & 0xFF) << 8) | (blue & 0xFF);
}
/**
* Erstellt eine Farbe als Kopie von <var>color</var>.
*
* @param color
*/
public Color( Color color ) {
this(color.getRGBA(), true);
}
/**
* Erstellt eine Farbe als Kopie von <var>color</var> und ersetzt den
* Transparentwert durch <var>alpha</var>.
*
* @param color
* @param alpha
*/
public Color( Color color, int alpha ) {
this(color.getRed(), color.getGreen(), color.getBlue(), alpha);
}
public Color( java.awt.Color color ) {
this(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha());
}
public Color( java.awt.Color color, int alpha ) {
this(color.getRed(), color.getGreen(), color.getBlue(), alpha);
}
/**
* Interner Konstruktor für die Initialisierung einer Farbe mit einem
* RGBA-Wert.
* <p>
* Da der Konstruktor {@link #Color(int)} schon besetzt ist, muss hier der
* Parameter {@code isRGBA} auf {@code true} gesetzt werden, damit
* {@code rgba} korrekt interpretiert wird.
*
* @param rgba RGBA-wert der Farbe.
* @param isRGBA Sollte immer {@code true} sein.
*/
Color( int rgba, boolean isRGBA ) {
if( !isRGBA ) {
this.rgba = (255 << 24) | (rgba << 16) | (rgba << 8) | rgba;
} else {
this.rgba = rgba;
}
}
/**
* Erzeugt eine Farbe aus einem kodierten RGBA Integer-Wert.
*
* @param rgba
* @return
*/
public static Color getRGBColor( int rgba ) {
return new Color(rgba, true);
}
public static Color getHSBColor( double h, double s, double b ) {
return new Color(java.awt.Color.getHSBColor((float) h, (float) s, (float) b));
}
public static Color getHSLColor( double h, double s, double l ) {
int rgb = Color.HSLtoRGB(new float[]{(float) h, (float) s, (float) l});
return Color.getRGBColor(rgb);
}
/**
* Erstellt aus einem Farbnamen ein Farbobjekt.
* <p>
* Die gültigen Farbnamen können unter <a
* href="https://htmlcolors.com/color-names">https://htmlcolors.com/color-names</a>
* nachgeschlagen werden.
*
* @param color Der Name einer Farbe.
* @return Ein Farbobjekt.
*/
public static Color parseString( String color ) {
color = color.toLowerCase().strip();
// Parse colornames file and return first match
try(
InputStream in = Color.class.getResourceAsStream("colornames.csv");
BufferedReader reader = new BufferedReader(new InputStreamReader(in))
) {
String line;
while( (line = reader.readLine()) != null ) {
String[] parts = line.split(",");
if( parts.length == 2 ) {
if( parts[0].equals(color) ) {
return Color.parseHexcode(parts[1]);
}
}
}
} catch( IOException ex ) {
// LOG?
}
return new Color();
}
/**
* Erzeugt eine Farbe aus einem hexadezimalen Code. Der Hexcode kann sechs-
* oder achtstellig sein (wenn ein Transparentwert vorhanden ist). Dem Code
* kann ein {@code #} Zeichen vorangestellt sein.
*
* @param hexcode
* @return
*/
public static Color parseHexcode( String hexcode ) {
if( hexcode.startsWith("#") ) {
hexcode = hexcode.substring(1);
}
if( hexcode.length() != 3 && hexcode.length() != 6 && hexcode.length() != 8 ) {
throw new IllegalArgumentException("color hexcodes have to be 3, 6 or 8 digits long, got " + hexcode.length());
}
// normalize hexcode to 8 digits
int alpha = 255;
if( hexcode.length() == 3 ) {
hexcode = "" + hexcode.charAt(0) + hexcode.charAt(0) + hexcode.charAt(1) + hexcode.charAt(1) + hexcode.charAt(2) + hexcode.charAt(2);
} else if( hexcode.length() == 8 ) {
alpha = Integer.valueOf(hexcode.substring(6, 8), 16);
hexcode = hexcode.substring(0, 6);
}
return Color.getRGBColor((alpha << 24) | Integer.valueOf(hexcode, 16));
}
public static Color interpolate( java.awt.Color color1, java.awt.Color color2, double t ) {
if( t < 0.0 || color2 == null ) {
return new Color(color1);
}
if( t > 1.0 || color1 == null ) {
return new Color(color2);
}
double pFactorInv = 1 - t;
return new Color((int) (pFactorInv * color1.getRed() + t * color2.getRed()), (int) (pFactorInv * color1.getGreen() + t * color2.getGreen()), (int) (pFactorInv * color1.getBlue() + t * color2.getBlue()), (int) (pFactorInv * color1.getAlpha() + t * color2.getAlpha()));
}
public static Color interpolate( Color color1, Color color2, double t ) {
if( color1 == null && color2 == null ) {
throw new IllegalArgumentException("Color.interpolate() needs at least one color to be not null.");
}
if( t < 0.0 || color2 == null ) {
return color1.copy();
}
if( t > 1.0 || color1 == null ) {
return color2.copy();
}
double pFactorInv = 1 - t;
return new Color((int) (pFactorInv * color1.getRed() + t * color2.getRed()), (int) (pFactorInv * color1.getGreen() + t * color2.getGreen()), (int) (pFactorInv * color1.getBlue() + t * color2.getBlue()), (int) (pFactorInv * color1.getAlpha() + t * color2.getAlpha()));
}
public static float[] RGBtoHSL( int rgb, float[] hsl ) {
float r = ((rgb >> 16) & 255) / 255.0f;
float g = ((rgb >> 8) & 255) / 255.0f;
float b = (rgb & 255) / 255.0f;
float max = Math.max(Math.max(r, g), b);
float min = Math.min(Math.min(r, g), b);
float c = max - min;
if( hsl == null ) {
hsl = new float[3];
}
float h_ = 0.f;
if( c == 0 ) {
h_ = 0;
} else if( max == r ) {
h_ = (g - b) / c;
if( h_ < 0 ) h_ += 6.f;
} else if( max == g ) {
h_ = (b - r) / c + 2.f;
} else if( max == b ) {
h_ = (r - g) / c + 4.f;
}
float h = 60.f * h_;
float l = (max + min) * 0.5f;
float s;
if( c == 0 ) {
s = 0.f;
} else {
s = c / (1 - Math.abs(2.f * l - 1.f));
}
hsl[0] = h;
hsl[1] = s;
hsl[2] = l;
return hsl;
}
public static int HSLtoRGB( float[] hsl ) {
return HSLtoRGB(hsl, 255);
}
/**
* Konvertiert eine Farbe mit Komponenten im HSL-Farbraum in den
* RGB-Farbraum.
* <p>
* Die Farbkomponenten werden als Float-Array übergeben. Im Index 0 steht
* der h-Wert im Bereich 0 bis 360, Index 1 und 2 enthalten den s- und
* l-Wert im Bereich von 0 bis 1.
*
* @param hsl Die Farbkomponenten im HSL-Farbraum.
* @param alpha Ein Transparenzwert im Bereich 0 bis 255.
* @return Der RGBA-Wert der Farbe.
*/
public static int HSLtoRGB( float[] hsl, int alpha ) {
float h = hsl[0];
float s = hsl[1];
float l = hsl[2];
float c = (1 - Math.abs(2.f * l - 1.f)) * s;
float h_ = h / 60.f;
float h_mod2 = h_;
if( h_mod2 >= 4.f ) h_mod2 -= 4.f;
else if( h_mod2 >= 2.f ) h_mod2 -= 2.f;
float x = c * (1 - Math.abs(h_mod2 - 1));
float r_, g_, b_;
if( h_ < 1 ) {
r_ = c;
g_ = x;
b_ = 0;
} else if( h_ < 2 ) {
r_ = x;
g_ = c;
b_ = 0;
} else if( h_ < 3 ) {
r_ = 0;
g_ = c;
b_ = x;
} else if( h_ < 4 ) {
r_ = 0;
g_ = x;
b_ = c;
} else if( h_ < 5 ) {
r_ = x;
g_ = 0;
b_ = c;
} else {
r_ = c;
g_ = 0;
b_ = x;
}
float m = l - (0.5f * c);
int r = (int) ((r_ + m) * (255.f) + 0.5f);
int g = (int) ((g_ + m) * (255.f) + 0.5f);
int b = (int) ((b_ + m) * (255.f) + 0.5f);
return (alpha & 255) << 24 | r << 16 | g << 8 | b;
}
/**
* Erzeugt eine Kopie dieser Farbe.
*
* @return Ein neues Farbobjekt.
*/
public Color copy() {
return new Color(this);
}
/**
* Gibt den RGBA-Wert dieser Farbe zurück.
* <p>
* Eine Farbe wird als 32-Bit Integer gespeichert. Bits 24-31 enthalten den
* Transparenzwert, 16-23 den Rotwert, 8-15 den Grünwert und 0-7 den
* Blauwert der Farbe.
*
* @return Der RGBA-Wert der Farbe.
* @see #getRed()
* @see #getGreen()
* @see #getBlue()
* @see #getAlpha()
*/
public int getRGBA() {
return rgba;
}
/**
* Gibt den Rotwert dieser Farbe zurück.
*
* @return Der Rotwert der Farbe zwischen 0 und 255.
*/
public int getRed() {
return (rgba >> 16) & 255;
}
/**
* Gibt den Grünwert dieser Farbe zurück.
*
* @return Der Grünwert der Farbe zwischen 0 und 255.
*/
public int getGreen() {
return (rgba >> 8) & 255;
}
/**
* Gibt den Blauwert dieser Farbe zurück.
*
* @return Der Blauwert der Farbe zwischen 0 und 255.
*/
public int getBlue() {
return rgba & 255;
}
/**
* Gibt den Transparenzwert dieser Farbe zurück.
*
* @return Der Transparenzwert der Farbe zwischen 0 und 255.
*/
public int getAlpha() {
return (rgba >> 24) & 255;
}
/**
* Erzeugt ein {@link java.awt.Color}-Objekt aus dieser Farbe.
* <p>
* Das erzeugte Farbobjekt hat dieselben Rot-, Grün-, Blau- und
* Transparenzwerte wie diese Farbe.
*
* @return Ein Java-Farbobjekt.
*/
public java.awt.Color getJavaColor() {
return new java.awt.Color(rgba, true);
}
@Override
public PaintContext createContext( ColorModel cm, Rectangle deviceBounds, Rectangle2D userBounds, AffineTransform xform, RenderingHints hints ) {
return getJavaColor().createContext(cm, deviceBounds, userBounds, xform, hints);
}
@Override
public int getTransparency() {
int alpha = getAlpha();
if( alpha == 0xff ) {
return Transparency.OPAQUE;
} else if( alpha == 0 ) {
return Transparency.BITMASK;
} else {
return Transparency.TRANSLUCENT;
}
}
/**
* Prüft, ob ein anderes Objekt zu diesem gleich ist.
*
* Die Methode gibt genau dann {@code true} zurück, wenn das andere
* Objekt nicht {@code null} ist, vom Typ {@code Color} ist und es
* dieselben Rot-, Grün-, Blau- und Transparenzwerte hat.
* @param obj Das zu vergleichende Objekt.
* @return {@code true}, wenn die Objekte gleich sind, sonst {@code false}.
*/
@Override
public boolean equals( Object obj ) {
if( obj == null ) {
return false;
}
if( obj instanceof Color ) {
return ((Color) obj).getRGBA() == this.rgba;
} else if( obj instanceof java.awt.Color ) {
return ((java.awt.Color) obj).getRGB() == this.rgba;
}
return false;
}
/**
* Erzeugt einen Text-String, der diese Farbe beschreibt.
*
* @return Eine Textversion der Farbe.
*/
@Override
public String toString() {
return getClass().getCanonicalName() + '[' + "r=" + getRed() + ',' + "g=" + getGreen() + ',' + "b=" + getBlue() + ',' + "a=" + getAlpha() + ']';
}
/**
* Berechnet einen Hashcode für dieses Farbobjekt.
*
* @return Ein Hashcode für diese Rabe.
*/
@Override
public int hashCode() {
return rgba;
}
/**
* Erzeugt eine um 30% hellere Version dieser Farbe.
*
* @return Ein Farbobjekt mit einer helleren Farbe.
*/
public Color brighter() {
return brighter(30);
}
/**
* Erzeugt eine um {@code percent} hellere Version dieser Farbe.
*
* @param percent Eine Prozentzahl zwischen 0 und 100.
* @return Ein Farbobjekt mit einer helleren Farbe.
*/
public Color brighter( int percent ) {
float[] hsl = RGBtoHSL(rgba, null);
hsl[2] = Math.min(Math.max(hsl[2] * (1.0f + percent / 100.0f), 0.0f), 1.0f);
return Color.getRGBColor(HSLtoRGB(hsl, getAlpha()));
}
/**
* Erzeugt eine um 30% dunklere Version dieser Farbe.
*
* @return Ein Farbobjekt mit einer dunkleren Farbe.
*/
public Color darker() {
return darker(30);
}
/**
* Erzeugt eine um {@code percent} dunklere Version dieser Farbe.
*
* @param percent Eine Prozentzahl zwischen 0 und 100.
* @return Ein Farbobjekt mit einer dunkleren Farbe.
*/
public Color darker( int percent ) {
float[] hsl = RGBtoHSL(rgba, null);
hsl[2] = Math.min(Math.max(hsl[2] * (1.0f - percent / 100.0f), 0.0f), 1.0f);
return Color.getRGBColor(HSLtoRGB(hsl, getAlpha()));
}
public Color greyscale() {
return new Color((int) (getRed() * .299 + getGreen() * .587 + getBlue() * 0.114));
}
/**
* Erzeugt eine zu dieser invertierte Farbe.
*
* @return Ein Farbobjekt mit der invertierten Farbe.
*/
public Color inverted() {
//return new Color((0xFFFFFFFF - (0x00FFFFFF & rgba)) | (0xFF000000 & rgba));
return new Color(rgba ^ 0x00FFFFFF);
}
/**
* Erzeugt die Komplementärfarbe zu dieser.
*
* @return Ein Farbobjekt mit der Komplementärfarbe.
*/
public Color complement() {
float[] hsl = RGBtoHSL(rgba, null);
hsl[0] = 360 - hsl[0];
return new Color(HSLtoRGB(hsl, getAlpha()));
}
/**
* Wählt entweder {@link #WHITE weiß} oder {@link #BLACK schwarz} aus, je
* nachdem, welche der Farben besser als Textfarbe mit dieser Farbe als
* Hintergrund funktioniert (besser lesbar ist).
*
* @return Schwarz oder weiß.
*/
public Color textcolor() {
// Basiert auf https://stackoverflow.com/questions/946544/good-text-foreground-color-for-a-given-background-color
if( (getRed() * .299 + getGreen() * .587 + getBlue() * 0.114) < 186 ) {
return WHITE;
} else {
return BLACK;
}
}
}