001package jmri.util;
002
003import java.awt.Color;
004import javax.annotation.CheckForNull;
005import javax.annotation.CheckReturnValue;
006import javax.annotation.Nonnull;
007import org.slf4j.Logger;
008import org.slf4j.LoggerFactory;
009
010/**
011 * A collection of utilities related to colors.
012 *
013 * @author Dave Duchamp Copyright: (c) 2004-2007
014 */
015public class ColorUtil {
016
017    /*
018     * Color lists for screen colors.
019     */
020    public final static String ColorTrack = "track";
021    public final static String ColorBlack = "black";
022    public final static String ColorDarkGray = "darkGray";
023    public final static String ColorGray = "gray";
024    public final static String ColorLightGray = "lightGray";
025    public final static String ColorWhite = "white";
026    public final static String ColorRed = "red";
027    public final static String ColorPink = "pink";
028    public final static String ColorOrange = "orange";
029    public final static String ColorYellow = "yellow";
030    public final static String ColorGreen = "green";
031    public final static String ColorBlue = "blue";
032    public final static String ColorMagenta = "magenta";
033    public final static String ColorCyan = "cyan";
034    public final static String ColorClear = "clear";
035    public final static String ColorBrown = "brown";
036
037    public final static Color clear = setAlpha(Color.BLACK, 0);
038    public final static Color CLEAR = clear;
039    public final static Color BROWN = new Color(102, 51, 0);
040
041    /**
042     * Handles known colors plus special value for track.
043     *
044     * @param color the color or null
045     * @return the name of the color or "black" if a color was provided; "track"
046     *         if color is null
047     */
048    @Nonnull
049    public static String colorToString(@CheckForNull Color color) {
050        if (color == null) {
051            return ColorTrack;
052        }
053        String colorName = colorToName(color);
054        if (colorName != null) {
055            return colorName;
056        }
057        log.error("unknown color sent to colorToString");
058        return ColorBlack;
059    }
060
061    /**
062     * Returns known color name or hex value in form #RRGGBB.
063     *
064     * @param color the color
065     * @return the name or hex value of color; returns null if color is null
066     */
067    @CheckForNull
068    public static String colorToColorName(@CheckForNull Color color) {
069        if (color == null) {
070            return null;
071        }
072        String colorName = colorToName(color);
073        if (colorName != null) {
074            return colorName;
075        }
076        return colorToHexString(color);
077    }
078
079    /**
080     * Returns localized color name or hex value in form #RRGGBB.
081     *
082     * @since 4.13.1
083     * @param color the color object
084     * @return the localized name or hex value of color; returns null if color is null
085     */
086    @CheckForNull
087    public static String colorToLocalizedName(@CheckForNull Color color) {
088        if (color == null) {
089            return null;
090        }
091        String colorName = colorToName(color);
092        if (colorName != null) {
093            colorName = Character.toUpperCase(colorName.charAt(0)) + colorName.substring(1);
094            return Bundle.getMessage(colorName);
095        }
096        return colorToHexString(color);
097    }
098
099    /**
100     * @param string Either a hexadecimal representation of the rgb value of a
101     *                   color or a color name defined as a constant.
102     * @return the color from the string or null if the string equals
103     *         {@value #ColorTrack} or equals the localized value for "None"
104     * @throws IllegalArgumentException if string cannot be converted into a Color
105     */
106    public static Color stringToColor(String string) {
107        try {
108            return Color.decode(string);
109        } catch (NumberFormatException nfe) {
110            switch (string) {
111               case ColorBlack:
112                   return Color.black;
113               case ColorDarkGray:
114                   return Color.darkGray;
115               case ColorGray:
116                   return Color.gray;
117               case ColorLightGray:
118                   return Color.lightGray;
119               case ColorWhite:
120                   return Color.white;
121               case ColorRed:
122                   return Color.red;
123               case ColorPink:
124                   return Color.pink;
125               case ColorOrange:
126                   return Color.orange;
127               case ColorYellow:
128                   return Color.yellow;
129               case ColorGreen:
130                   return Color.green;
131               case ColorBlue:
132                   return Color.blue;
133               case ColorMagenta:
134                   return Color.magenta;
135               case ColorCyan:
136                   return Color.cyan;
137               case ColorBrown:
138                   return BROWN;
139               case ColorTrack:
140                   return null;
141               default:
142                   // check translated strings, just in case there is one in a data file.
143                    if (string.equals(Bundle.getMessage("Black"))) {
144                      return Color.black;
145                    }
146                    if (string.equals(Bundle.getMessage("DarkGray"))) {
147                      return Color.darkGray;
148                    }
149                    if (string.equals(Bundle.getMessage("Gray"))) {
150                      return Color.gray;
151                    }
152                    if (string.equals(Bundle.getMessage("LightGray"))) {
153                      return Color.lightGray;
154                    }
155                    if (string.equals(Bundle.getMessage("White"))) {
156                      return Color.white;
157                    }
158                    if (string.equals(Bundle.getMessage("Red"))) {
159                      return Color.red;
160                    }
161                    if (string.equals(Bundle.getMessage("Pink"))) {
162                      return Color.pink;
163                    }
164                    if (string.equals(Bundle.getMessage("Yellow"))) {
165                      return Color.yellow;
166                    }
167                    if (string.equals(Bundle.getMessage("Green"))) {
168                      return Color.green;
169                    }
170                    if (string.equals(Bundle.getMessage("Orange"))) {
171                      return Color.orange;
172                    }
173                    if (string.equals(Bundle.getMessage("Blue"))) {
174                      return Color.blue;
175                    }
176                    if (string.equals(Bundle.getMessage("Magenta"))) {
177                      return Color.magenta;
178                    }
179                    if (string.equals(Bundle.getMessage("Cyan"))) {
180                      return Color.cyan;
181                    }
182                    if (string.equals(Bundle.getMessage("ColorClear"))) {
183                        return clear;
184                    }
185                    if (string.equals(Bundle.getMessage("None"))) {
186                       return null;
187                    } else {
188                      log.error("unknown color text '{}' sent to stringToColor", string);
189                      throw new IllegalArgumentException("unknown color text '" + string + "'");
190                   }
191            }
192        }
193    }
194
195    /**
196     * Convert a color into hex value of form #RRGGBB.
197     *
198     * @param color the color or null
199     * @return the hex string or null if color is null
200     */
201    @CheckForNull
202    public static String colorToHexString(@CheckForNull Color color) {
203        if (color == null) {
204            return null;
205        }
206        return String.format("#%02X%02X%02X", color.getRed(), color.getGreen(), color.getBlue());
207    }
208
209    /**
210     * Internal method to return string name of several known colors.
211     *
212     * @param color the color
213     * @return the color name or null if not known/not in list
214     */
215    @CheckForNull
216    private static String colorToName(@CheckForNull Color color) {
217        if (color == null) {
218            return null;
219        }
220        if (color.equals(Color.black)) {
221            return ColorBlack;
222        } else if (color.equals(Color.darkGray)) {
223            return ColorDarkGray;
224        } else if (color.equals(Color.gray)) {
225            return ColorGray;
226        } else if (color.equals(Color.lightGray)) {
227            return ColorLightGray;
228        } else if (color.equals(Color.white)) {
229            return ColorWhite;
230        } else if (color.equals(Color.red)) {
231            return ColorRed;
232        } else if (color.equals(Color.pink)) {
233            return ColorPink;
234        } else if (color.equals(Color.orange)) {
235            return ColorOrange;
236        } else if (color.equals(Color.yellow)) {
237            return ColorYellow;
238        } else if (color.equals(Color.green)) {
239            return ColorGreen;
240        } else if (color.equals(Color.blue)) {
241            return ColorBlue;
242        } else if (color.equals(Color.magenta)) {
243            return ColorMagenta;
244        } else if (color.equals(Color.cyan)) {
245            return ColorCyan;
246        } else if (color.equals(BROWN)) {
247            return ColorBrown;
248        }
249        return null;
250    }
251
252    /**
253     * Return the color (Black/White) that most contrasts with the specified
254     * color.
255     *
256     * @param color the source color
257     * @return the contrasting color
258     */
259    public static Color contrast(@Nonnull Color color) {
260        int red = color.getRed();
261        int green = color.getGreen();
262        int blue = color.getBlue();
263        int average = (red + green + blue) / 3;
264
265        return (average >= 128) ? Color.BLACK : Color.WHITE;
266    }
267
268    /**
269     * Calculate the linear interpolation between two colors.
270     *
271     * @param colorA the first color
272     * @param colorB the second color
273     * @param t  the fraction (between 0 and 1)
274     * @return the linear interpolation between a and b for t
275     */
276    @CheckReturnValue
277    public static Color lerp(@Nonnull Color colorA, @Nonnull Color colorB, double t) {
278        return new Color(
279                MathUtil.lerp(colorA.getRed(), colorB.getRed(), t),
280                MathUtil.lerp(colorA.getGreen(), colorB.getGreen(), t),
281                MathUtil.lerp(colorA.getBlue(), colorB.getBlue(), t),
282                MathUtil.lerp(colorA.getAlpha(), colorB.getAlpha(), t)
283        );
284    }
285
286    /**
287     * Set the alpha component of a color.
288     *
289     * @param color the color
290     * @param alpha the alpha component (integer 0 - 255)
291     * @return the new color with the specified alpha
292     */
293    @CheckReturnValue
294    public static Color setAlpha(@Nonnull Color color, int alpha) {
295        return new Color(color.getRed(), color.getGreen(), color.getBlue(), alpha);
296    }
297
298    /**
299     * Set the alpha component of a color.
300     *
301     * @param color the color
302     * @param alpha the alpha component (double 0.0 - 1.0)
303     * @return the new color with the specified alpha
304     */
305    @CheckReturnValue
306    public static Color setAlpha(@Nonnull Color color, double alpha) {
307        return new Color(color.getRed(), color.getGreen(), color.getBlue(),
308                (int) (255.0 * alpha));
309    }
310
311    // initialize logging
312    private final static Logger log = LoggerFactory.getLogger(ColorUtil.class);
313
314}