Merge branch 'releases/Release_2_11_3_Branch'
[jalview.git] / src / jalview / util / ColorUtils.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 /**
22  * author: Lauren Michelle Lui
23  */
24
25 package jalview.util;
26
27 import java.util.Locale;
28
29 import java.awt.Color;
30 import java.util.HashMap;
31 import java.util.Map;
32 import java.util.Random;
33
34 public class ColorUtils
35 {
36   private static final int MAX_CACHE_SIZE = 1729;
37
38   /*
39    * a cache for colours generated from text strings
40    */
41   static Map<String, Color> myColours = new HashMap<>();
42
43   /**
44    * Generates a random color, will mix with input color. Code taken from
45    * http://stackoverflow
46    * .com/questions/43044/algorithm-to-randomly-generate-an-aesthetically
47    * -pleasing-color-palette
48    * 
49    * @param mix
50    * @return Random color in RGB
51    */
52   public static final Color generateRandomColor(Color mix)
53   {
54     Random random = new Random();
55     int red = random.nextInt(256);
56     int green = random.nextInt(256);
57     int blue = random.nextInt(256);
58
59     // mix the color
60     if (mix != null)
61     {
62       red = (red + mix.getRed()) / 2;
63       green = (green + mix.getGreen()) / 2;
64       blue = (blue + mix.getBlue()) / 2;
65     }
66
67     Color color = new Color(red, green, blue);
68     return color;
69
70   }
71
72   /**
73    * 
74    * @return random color
75    */
76   public static final Color getARandomColor()
77   {
78
79     Color col = new Color((int) (Math.random() * 255),
80             (int) (Math.random() * 255), (int) (Math.random() * 255));
81     return col;
82   }
83
84   /**
85    * Convert to Tk colour code format
86    * 
87    * @param colour
88    * @return
89    * @see http
90    *      ://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/colortool.html#
91    *      tkcode
92    */
93   public static final String toTkCode(Color colour)
94   {
95     String colstring = "#" + ((colour.getRed() < 16) ? "0" : "")
96             + Integer.toHexString(colour.getRed())
97             + ((colour.getGreen() < 16) ? "0" : "")
98             + Integer.toHexString(colour.getGreen())
99             + ((colour.getBlue() < 16) ? "0" : "")
100             + Integer.toHexString(colour.getBlue());
101     return colstring;
102   }
103
104   /**
105    * Returns a colour three shades darker. Note you can't guarantee that
106    * brighterThan reverses this, as darkerThan may result in black.
107    * 
108    * @param col
109    * @return
110    */
111   public static Color darkerThan(Color col)
112   {
113     return col == null ? null : col.darker().darker().darker();
114   }
115
116   /**
117    * Returns a colour three shades brighter. Note you can't guarantee that
118    * darkerThan reverses this, as brighterThan may result in white.
119    * 
120    * @param col
121    * @return
122    */
123   public static Color brighterThan(Color col)
124   {
125     return col == null ? null : col.brighter().brighter().brighter();
126   }
127
128   /**
129    * Returns a color between minColour and maxColour; the RGB values are in
130    * proportion to where 'value' lies between minValue and maxValue
131    * 
132    * @param value
133    * @param minValue
134    * @param minColour
135    * @param maxValue
136    * @param maxColour
137    * @return
138    */
139   public static Color getGraduatedColour(float value, float minValue,
140           Color minColour, float maxValue, Color maxColour)
141   {
142     if (minValue == maxValue)
143     {
144       return minColour;
145     }
146     if (value < minValue)
147     {
148       value = minValue;
149     }
150     if (value > maxValue)
151     {
152       value = maxValue;
153     }
154
155     /*
156      * prop = proportion of the way value is from minValue to maxValue
157      */
158     float prop = (value - minValue) / (maxValue - minValue);
159     float r = minColour.getRed()
160             + prop * (maxColour.getRed() - minColour.getRed());
161     float g = minColour.getGreen()
162             + prop * (maxColour.getGreen() - minColour.getGreen());
163     float b = minColour.getBlue()
164             + prop * (maxColour.getBlue() - minColour.getBlue());
165     return new Color(r / 255, g / 255, b / 255);
166   }
167
168   /**
169    * 'Fades' the given colour towards white by the specified proportion. A
170    * factor of 1 or more results in White, a factor of 0 leaves the colour
171    * unchanged, and a factor between 0 and 1 results in a proportionate change
172    * of RGB values towards (255, 255, 255).
173    * <p>
174    * A negative bleachFactor can be specified to darken the colour towards Black
175    * (0, 0, 0).
176    * 
177    * @param colour
178    * @param bleachFactor
179    * @return
180    */
181   public static Color bleachColour(Color colour, float bleachFactor)
182   {
183     if (bleachFactor >= 1f)
184     {
185       return Color.WHITE;
186     }
187     if (bleachFactor <= -1f)
188     {
189       return Color.BLACK;
190     }
191     if (bleachFactor == 0f)
192     {
193       return colour;
194     }
195
196     int red = colour.getRed();
197     int green = colour.getGreen();
198     int blue = colour.getBlue();
199
200     if (bleachFactor > 0)
201     {
202       red += (255 - red) * bleachFactor;
203       green += (255 - green) * bleachFactor;
204       blue += (255 - blue) * bleachFactor;
205       return new Color(red, green, blue);
206     }
207     else
208     {
209       float factor = 1 + bleachFactor;
210       red *= factor;
211       green *= factor;
212       blue *= factor;
213       return new Color(red, green, blue);
214     }
215   }
216
217   /**
218    * Parses a string into a Color, where the accepted formats are
219    * <ul>
220    * <li>an AWT colour name e.g. white</li>
221    * <li>a hex colour value (without prefix) e.g. ff0000</li>
222    * <li>an rgb triple e.g. 100,50,150</li>
223    * </ul>
224    * 
225    * @param colour
226    * @return the parsed colour, or null if parsing fails
227    */
228   public static Color parseColourString(String colour)
229   {
230     if (colour == null)
231     {
232       return null;
233     }
234     colour = colour.trim();
235
236     Color col = null;
237     try
238     {
239       int value = Integer.parseInt(colour, 16);
240       col = new Color(value);
241     } catch (NumberFormatException ex)
242     {
243     }
244
245     if (col == null)
246     {
247       col = ColorUtils.getAWTColorFromName(colour);
248     }
249
250     if (col == null)
251     {
252       try
253       {
254         String[] tokens = colour.split(",");
255         if (tokens.length == 3)
256         {
257           int r = Integer.parseInt(tokens[0].trim());
258           int g = Integer.parseInt(tokens[1].trim());
259           int b = Integer.parseInt(tokens[2].trim());
260           col = new Color(r, g, b);
261         }
262       } catch (Exception ex)
263       {
264         // non-numeric token or out of 0-255 range
265       }
266     }
267
268     return col;
269   }
270
271   /**
272    * Constructs a colour from a text string. The hashcode of the whole string is
273    * scaled to the range 0-135. This is added to RGB values made from the
274    * hashcode of each third of the string, and scaled to the range 20-229.
275    * 
276    * @param name
277    * @return
278    */
279   public static Color createColourFromName(String name)
280   {
281     if (name == null)
282     {
283       return Color.white;
284     }
285     if (myColours.containsKey(name))
286     {
287       return myColours.get(name);
288     }
289     int lsize = name.length();
290     int start = 0;
291     int end = lsize / 3;
292
293     int rgbOffset = Math.abs(name.hashCode() % 10) * 15; // 0-135
294
295     /*
296      * red: first third
297      */
298     int r = Math.abs(name.substring(start, end).hashCode() + rgbOffset)
299             % 210 + 20;
300     start = end;
301     end += lsize / 3;
302     if (end > lsize)
303     {
304       end = lsize;
305     }
306
307     /*
308      * green: second third
309      */
310     int g = Math.abs(name.substring(start, end).hashCode() + rgbOffset)
311             % 210 + 20;
312
313     /*
314      * blue: third third
315      */
316     int b = Math.abs(name.substring(end).hashCode() + rgbOffset) % 210 + 20;
317
318     Color color = new Color(r, g, b);
319
320     if (myColours.size() < MAX_CACHE_SIZE)
321     {
322       myColours.put(name, color);
323     }
324
325     return color;
326   }
327
328   /**
329    * Returns the Color constant for a given colour name e.g. "pink", or null if
330    * the name is not recognised
331    * 
332    * @param name
333    * @return
334    */
335   public static Color getAWTColorFromName(String name)
336   {
337     if (name == null)
338     {
339       return null;
340     }
341     Color col = null;
342     name = name.toLowerCase(Locale.ROOT);
343
344     // or make a static map; or use reflection on the field name
345     switch (name)
346     {
347     case "black":
348       col = Color.black;
349       break;
350     case "blue":
351       col = Color.blue;
352       break;
353     case "cyan":
354       col = Color.cyan;
355       break;
356     case "darkgray":
357       col = Color.darkGray;
358       break;
359     case "gray":
360       col = Color.gray;
361       break;
362     case "green":
363       col = Color.green;
364       break;
365     case "lightgray":
366       col = Color.lightGray;
367       break;
368     case "magenta":
369       col = Color.magenta;
370       break;
371     case "orange":
372       col = Color.orange;
373       break;
374     case "pink":
375       col = Color.pink;
376       break;
377     case "red":
378       col = Color.red;
379       break;
380     case "white":
381       col = Color.white;
382       break;
383     case "yellow":
384       col = Color.yellow;
385       break;
386     }
387
388     return col;
389   }
390 }