001 package echopoint.util;
002 /*
003 * This file is part of the Echo Point Project. This project is a collection
004 * of Components that have extended the Echo Web Application Framework.
005 *
006 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
007 *
008 * The contents of this file are subject to the Mozilla Public License Version
009 * 1.1 (the "License"); you may not use this file except in compliance with
010 * the License. You may obtain a copy of the License at
011 * http://www.mozilla.org/MPL/
012 *
013 * Software distributed under the License is distributed on an "AS IS" basis,
014 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
015 * for the specific language governing rights and limitations under the
016 * License.
017 *
018 * Alternatively, the contents of this file may be used under the terms of
019 * either the GNU General Public License Version 2 or later (the "GPL"), or
020 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
021 * in which case the provisions of the GPL or the LGPL are applicable instead
022 * of those above. If you wish to allow use of your version of this file only
023 * under the terms of either the GPL or the LGPL, and not to allow others to
024 * use your version of this file under the terms of the MPL, indicate your
025 * decision by deleting the provisions above and replace them with the notice
026 * and other provisions required by the GPL or the LGPL. If you do not delete
027 * the provisions above, a recipient may use your version of this file under
028 * the terms of any one of the MPL, the GPL or the LGPL.
029 */
030 import echopoint.util.collections.ConcurrentReaderHashMap;
031
032 import java.util.Map;
033
034 import nextapp.echo.app.Color;
035 import nextapp.echo.app.Component;
036
037 /**
038 * A utility to class to help with Color manipulation
039 *
040 * @author Brad Baker
041 */
042 public class ColorKit {
043
044 /** Array for acceptable Color Constant strings*/
045 public static String ColorConstantStrings[] = {
046 "Color.BLACK", "BLACK",
047 "Color.BLUE", "BLUE",
048 "Color.CYAN", "CYAN",
049 "Color.DARKGRAY", "DARKGRAY",
050 "Color.GREEN", "GREEN",
051 "Color.LIGHTGRAY", "LIGHTGRAY",
052 "Color.MAGENTA", "MAGENTA",
053 "Color.ORANGE", "ORANGE",
054 "Color.PINK", "PINK",
055 "Color.RED", "RED",
056 "Color.WHITE", "WHITE",
057 "Color.YELLOW", "YELLOW",
058 };
059
060 /** An array for Color values matching ColorConstantStrings*/
061 public static Color ColorConstantValues[] =
062 {
063 Color.BLACK, Color.BLACK,
064 Color.BLUE, Color.BLUE,
065 Color.CYAN, Color.CYAN,
066 Color.DARKGRAY, Color.DARKGRAY,
067 Color.GREEN, Color.GREEN,
068 Color.LIGHTGRAY, Color.LIGHTGRAY,
069 Color.MAGENTA, Color.MAGENTA,
070 Color.ORANGE, Color.ORANGE,
071 Color.PINK, Color.PINK,
072 Color.RED, Color.RED,
073 Color.WHITE, Color.WHITE,
074 Color.YELLOW, Color.YELLOW,
075 };
076
077 /**
078 * The default scale factor applied to the brighter and darker functions
079 */
080 public static final double COLOR_FACTOR = 0.7;
081
082 private static Map colorMap = new ConcurrentReaderHashMap();
083
084 /** not instantiable */
085 private ColorKit() {
086 }
087
088 /*
089 * POarse a string in the form #99999
090 */
091 private static Color _parseHashHexString(String hasHexString) {
092 StringBuffer sb = new StringBuffer(hasHexString.substring(1));
093 while (sb.length() < 6) {
094 sb.append("0");
095 }
096 String hex = sb.substring(0,6);
097 int rgb = Integer.parseInt(hex,16);
098 return new Color(rgb);
099 }
100
101
102 /*
103 * Create a zero fronted hex string
104 */
105 private static String _toHexString(int i) {
106 String hex = Integer.toHexString(i);
107 if (hex.length() < 2)
108 hex = "0" + hex;
109 return hex.toUpperCase();
110 }
111
112 /*
113 * Returns true if the propertyValue is a valid representation of a base 10 Integer
114 * value.
115 */
116 private static boolean _isInteger(String propertyValue) {
117 try {
118 Integer.parseInt(propertyValue.trim());
119 } catch (NumberFormatException nfe) {
120 return false;
121 }
122 return true;
123 }
124
125 /*
126 * Returns true if the propertyValue is a valid representation of a base 16 Integer
127 * value.
128 */
129 private static boolean _isHexInteger(String propertyValue) {
130 propertyValue = propertyValue.trim().toLowerCase();
131 if (propertyValue.indexOf("0x") == 0)
132 propertyValue = propertyValue.substring(2);
133
134 try {
135 Integer.parseInt(propertyValue, 16);
136 } catch (NumberFormatException nfe) {
137 return false;
138 }
139 return true;
140 }
141
142 /*
143 * Returns the index of the string in an array of strings, case insensitive
144 * or -1 if its not found
145 */
146 private static int _arrayIndexOf(String s, String[] array) {
147 if (array == null)
148 return -1;
149 for (int i = 0; i < array.length; i++) {
150 if (s.equalsIgnoreCase(array[i]))
151 return i;
152 }
153 return -1;
154 }
155
156 /**
157 * Creates a brighter version of this color.
158 * <p>
159 * This method applies a 0.7 scale factor to each of the three RGB
160 * components of the color to create a brighter version of the same
161 * color. Although <code>brighter</code> and <code>darker</code> are
162 * inverse operations, the results of a series of invocations of
163 * these two methods may be inconsistent because of rounding errors.
164 *
165 * @param color - the color to make brighter
166 * @return a new <code>Color</code> object, a brighter version of the color.
167 *
168 */
169 public static Color brighter(Color color) {
170 return brighter(color,COLOR_FACTOR);
171 }
172
173 /**
174 * Creates a brighter version of this color, according to the provided
175 * factor.
176 * <p>
177 * This method applies an the scale factor to each of the three RGB
178 * components of the color to create a brighter version of the same
179 * color. Although <code>brighter</code> and <code>darker</code> are
180 * inverse operations, the results of a series of invocations of
181 * these two methods may be inconsistent because of rounding errors.
182 *
183 * @param color - the color to make brighter
184 * @param factor - the factor by which to make it brighter.
185 *
186 * @return a new <code>Color</code> object, a brighter version of the color.
187 *
188 */
189 public static Color brighter(Color color, double factor) {
190
191 int r = color.getRed();
192 int g = color.getGreen();
193 int b = color.getBlue();
194
195 /*
196 * 1. black.brighter() should return grey
197 * 2. applying brighter to blue will always return blue, brighter
198 * 3. non pure color (non zero rgb) will eventually return white
199 */
200 int i = (int) (1.0 / (1.0 - factor));
201 if (r == 0 && g == 0 && b == 0) {
202 return new Color(i, i, i);
203 }
204 if (r > 0 && r < i)
205 r = i;
206 if (g > 0 && g < i)
207 g = i;
208 if (b > 0 && b < i)
209 b = i;
210
211 return makeColor(Math.min((int) (r / factor), 255), Math.min((int) (g / factor), 255), Math.min((int) (b / factor), 255));
212 }
213
214 /**
215 * Creates a darker version of this color.
216 * <p>
217 * This method applies an arbitrary 0.7 scale factor to each of the three RGB
218 * components of the color to create a darker version of the same
219 * color. Although <code>brighter</code> and <code>darker</code> are
220 * inverse operations, the results of a series of invocations of
221 * these two methods may be inconsistent because of rounding errors.
222 *
223 * @param color - the color to make darker
224 * @return a new <code>Color</code> object,a darker version of this color.
225 *
226 */
227 public static Color darker(Color color) {
228 return darker(color,COLOR_FACTOR);
229 }
230
231 /**
232 * Creates a darker version of this color, according to the provided
233 * factor.
234 * <p>
235 * This method applies an the scale factor to each of the three RGB
236 * components of the color to create a darker version of the same
237 * color. Although <code>brighter</code> and <code>darker</code> are
238 * inverse operations, the results of a series of invocations of
239 * these two methods may be inconsistent because of rounding errors.
240 *
241 * @param color - the color to make darker
242 * @param factor - the factor by whcih to make it darker.
243 * @return a new <code>Color</code> object,a darker version of the color.
244 *
245 */
246 public static Color darker(Color color, double factor) {
247 return makeColor(Math.max((int) (color.getRed() * factor), 0), Math.max((int) (color.getGreen() * factor), 0), Math.max((int) (color.getBlue() * factor), 0));
248 }
249
250 /**
251 * Searchs the heirarchy tree of the component and finds the first
252 * non null background Color object. It will return
253 * <code>Color.WHITE</code> if no ancestor components have
254 * a background set, but in practice this is unlikely to be the case.
255 *
256 * @param comp - the component to start searching at
257 * @return - the background color of the component or its parents
258 *
259 */
260 public static Color findBackground(Component comp) {
261 while (comp != null) {
262 Color clr = comp.getBackground();
263 if (clr != null)
264 return clr;
265 comp = comp.getParent();
266 }
267 return Color.WHITE;
268 }
269
270 /**
271 * Searchs the heirarchy tree of the component and finds the first
272 * non null foreground Color object. It will return
273 * <code>Color.BLACK.</code> if no ancestor components have
274 * a background set, but in practice this is unlikely to be the case.
275 *
276 * @param comp - the component to start searching at
277 * @return - the foreground color of the component or its parents
278 */
279 public static Color findForeground(Component comp) {
280 while (comp != null) {
281 Color clr = comp.getForeground();
282 if (clr != null)
283 return clr;
284 comp = comp.getParent();
285 }
286 return Color.BLACK;
287 }
288
289 /**
290 * Returns the inversion of a color.
291 *
292 * @param color - the color to invert
293 * @return the inverted color
294 */
295 public static Color invertColor(Color color) {
296 int r = Math.abs(255 - color.getRed());
297 int g = Math.abs(255 - color.getGreen());
298 int b = Math.abs(255 - color.getBlue());
299
300 return makeColor(r, g, b);
301 }
302
303 /**
304 * Creates an java.awt.Color object from a nextapp.echo.Color object, or
305 * uses the default AWT if the <code>echoColor</code> object is null
306 *
307 * @param echoColor - the nextapp.echo2.app.Color object to convert
308 * @param defaultAwtColor - the AWT color to use if the echoColor is null
309 * @return a new AWT color object
310 *
311 */
312 public static java.awt.Color makeAwtColor(Color echoColor, java.awt.Color defaultAwtColor) {
313 if (echoColor == null)
314 return defaultAwtColor;
315 else
316 return new java.awt.Color(echoColor.getRgb(), false);
317 }
318
319 /**
320 * Returns the Hex W3C CSS color string for a given color
321 * ie #rrggbb
322 *
323 * @param color - the color to convert to a W3C hex CSS string value
324 * @return the W3C hex CSS string value
325 */
326 public static String makeCSSColor(Color color) {
327 StringBuffer b = new StringBuffer();
328
329 b.append("#");
330 b.append(makeHexColor(color));
331 return b.toString().toUpperCase();
332 }
333
334 /**
335 * Returns the Color in the form : color(r,g,b);
336 *
337 * @param color - the color to convert to a color string representation
338 * @return the color string representation
339 */
340 public static String makeColorString(Color color) {
341 StringBuffer b = new StringBuffer();
342
343 b.append("color(");
344 b.append(color.getRed());
345 b.append(",");
346 b.append(color.getGreen());
347 b.append(",");
348 b.append(color.getBlue());
349 b.append(")");
350 return b.toString();
351 }
352
353 /**
354 * Returns the Hex string for a given color for example 'rrggbb'
355 *
356 * Note it does NOT have the # character at the front
357 *
358 * @param color the color in question
359 * @return the string in the form rrggbb
360 */
361 public static String makeHexColor(Color color) {
362 StringBuffer b = new StringBuffer();
363
364 b.append(_toHexString(color.getRed()));
365 b.append(_toHexString(color.getGreen()));
366 b.append(_toHexString(color.getBlue()));
367
368 return b.toString().toLowerCase();
369 }
370
371 /**
372 * Returns a Color object from the string representation
373 * <p>
374 * The color string must be in the format :<br>
375 * - #rrggbb where rr, gg, bb are hexidecimal integer values<br>
376 * - rgb( r, g, b) where r,g,b are integer values<br>
377 * - color( r, g, b) where r,g,b are integer values<br>
378 * - null will return a null color
379 * <p>
380 * otherwise an IllegalArgumentException is thrown.
381 * <p>
382 * The results of these operations are cached in a static
383 * cache, so that Color objects can be re-used. This is okay
384 * since Color objects are immutable.
385 *
386 * @param colorString - the color string representation
387 * @return a new Color object
388 *
389 * @throws IllegalArgumentException - if the string cannot be converted
390 * to a color
391 */
392 public static Color makeColor(String colorString) {
393 if (colorString == null)
394 throw new IllegalArgumentException("Illegal null color string");
395
396 colorString = colorString.trim().toLowerCase();
397 Color color = (Color) colorMap.get(colorString);
398 if (color != null)
399 return color;
400
401 if (! isColor(colorString))
402 throw new IllegalArgumentException("Illegal color string" + colorString);
403
404 String tokens[] = TokenizerKit.tokenize(colorString, "(,)");
405 if (tokens.length == 1) {
406 int index = _arrayIndexOf(colorString, ColorConstantStrings);
407 if (index != -1)
408 color = ColorConstantValues[index];
409 }
410 if (color == null) {
411 if (colorString.indexOf('#') == 0) {
412 color = _parseHashHexString(colorString);
413 } else {
414 int r = Integer.parseInt(tokens[1].trim());
415 int g = Integer.parseInt(tokens[2].trim());
416 int b = Integer.parseInt(tokens[3].trim());
417
418 color = new Color(r,g,b);
419 }
420 }
421 colorMap.put(colorString, color);
422 return color;
423 }
424
425 /**
426 * Returns true if the propertyValue is a valid representation of a Color
427 * value.
428 * <p>
429 * The allowable forms are : <p>
430 * - colorconstant where colorconstant in (red,blue,green...)
431 * - #rrggbb where rr, gg, bb are hexidecimal integer values<br>
432 * - rgb( r, g, b) where r,g,b are integer values<br>
433 * - color( r, g, b) where r,g,b are integer values<br>
434 */
435 public static boolean isColor(String colorString) {
436 if (colorString == null)
437 return false;
438
439 colorString = colorString.trim();
440 String tokens[] = TokenizerKit.tokenize(colorString, "(,)");
441
442 if (tokens.length == 1) {
443 if (_arrayIndexOf(colorString, ColorConstantStrings) != -1)
444 return true;
445 }
446
447 if (colorString.indexOf('#') == 0) {
448 return _isHexInteger(colorString.substring(1).trim());
449 } else if (colorString.indexOf("rgb(") == 0 || colorString.indexOf("color(") == 0) {
450 if (tokens.length != 4)
451 return false;
452 String r = tokens[1].trim();
453 String g = tokens[2].trim();
454 String b = tokens[3].trim();
455
456 return _isInteger(r) && _isInteger(g) && _isInteger(b);
457 }
458 return false;
459 }
460
461 /**
462 * Shortcut synonym for makeColor(colorString);
463 * <p>
464 * @see ColorKit#makeColor(String)
465 */
466 public static Color clr(String colorString) {
467 return makeColor(colorString);
468 }
469
470 /**
471 * Shortcut synonym for makeColor(r,g,b);
472 * <p>
473 * @see ColorKit#makeColor(int, int, int)
474 */
475 public static Color clr(int r, int g, int b) {
476 return makeColor(r,g,b);
477 }
478
479 /**
480 * Shortcut synonym for makeColor(rgb);
481 * <p>
482 * @see ColorKit#makeColor(int)
483 */
484 public static Color clr(int rgb) {
485 return makeColor(rgb);
486 }
487
488 /**
489 * Returns a Color object from the red, green and blue integer values.
490 * <p>
491 * The results of these operations are cached in a static
492 * cache, so that Color objects can be re-used. This is okay
493 * since Color objects are immutable.
494 *
495 * @param r - the red color component
496 * @param g - the green color component
497 * @param b - the blue color component
498 * @return a new Color object
499 */
500 public static Color makeColor(int r, int g, int b) {
501 StringBuffer buf = new StringBuffer();
502 buf.append('#');
503 buf.append(_toHexString(r));
504 buf.append(_toHexString(g));
505 buf.append(_toHexString(b));
506 //
507 // since we know we have produced a correct
508 // color string we can go direct to the cache!
509 //
510 String colorString = buf.toString().toLowerCase();
511 Color color = (Color) colorMap.get(colorString);
512 if (color == null) {
513 color = new Color(r,g,b);
514 colorMap.put(colorString,color);
515 }
516 return color;
517 }
518
519 /**
520 * Returns a Color object from the red, green and blue integer values.
521 * <p>
522 * The results of these operations are cached in a static
523 * cache, so that Color objects can be re-used. This is okay
524 * since Color objects are immutable.
525 *
526 * @param rgb - the red/blue/green integer color value
527 * @return a new Color object
528 */
529 public static Color makeColor(int rgb) {
530 StringBuffer buf = new StringBuffer();
531 buf.append('#');
532 buf.append(_toHexString(rgb));
533 //
534 // since we know we have produced a correct
535 // color string we can go direct to the cache!
536 //
537 String colorString = buf.toString().toLowerCase();
538 Color color = (Color) colorMap.get(colorString);
539 if (color == null) {
540 color = new Color(rgb);
541 colorMap.put(colorString,color);
542 }
543 return color;
544 }
545
546 /**
547 * Tints a given color by a factor given in red, blue and green.
548 * <p>
549 * The red, green and blue arguments should be around 1.0
550 */
551 public static Color tint(Color clr, double red, double green, double blue)
552 {
553 double newRed = clr.getRed() * red;
554 double newGreen = clr.getGreen() * green;
555 double newBlue = clr.getBlue() * blue;
556
557 double normFactor = Math.max(newRed, Math.max(newGreen, newBlue)) / 255;
558
559 newRed /= normFactor;
560 newGreen /= normFactor;
561 newBlue /= normFactor;
562
563 return makeColor((int)newRed, (int)newGreen, (int)newBlue);
564 }
565
566 /** swapRGB Operation - R = B, G = G, B = R */
567 public static final int SWAP_OP_BGR = 0;
568 /** swapRGB Operation - R = B, G = R, B = G */
569 public static final int SWAP_OP_BRG = 1;
570 /** swapRGB Operation - R = G, G = B, B = R */
571 public static final int SWAP_OP_GBR = 2;
572 /** swapRGB Operation - R = G, G = R, B = B */
573 public static final int SWAP_OP_GRB = 3;
574 /** swapRGB Operation - R = R, G = B, B = G */
575 public static final int SWAP_OP_RBG = 4;
576
577 /**
578 * This will swap the red/green/blue elements of a color
579 * according to the swapOperation, which may be one of :
580 * <ul>
581 * <li>SWAP_OP_RBG</li>
582 * <li>SWAP_OP_BGR</li>
583 * <li>SWAP_OP_BRG</li>
584 * <li>SWAP_OP_GRB</li>
585 * <li>SWAP_OP_GBR</li>
586 * <ul>
587 *
588 * @param swapColor - the color to swap the RGB elements
589 * @param swapOperation - the operation to perform
590 * @return - a new swapped Color or null if the swapColor was null.
591 */
592 public static Color swapRGB(Color swapColor, int swapOperation) {
593 if (swapColor == null)
594 return swapColor;
595
596 int r = swapColor.getRed();
597 int g = swapColor.getGreen();
598 int b = swapColor.getBlue();
599 switch (swapOperation) {
600 case SWAP_OP_RBG:
601 return makeColor(r,b,g);
602 case SWAP_OP_BGR:
603 return makeColor(b,g,r);
604 case SWAP_OP_BRG:
605 return makeColor(b,r,g);
606 case SWAP_OP_GBR:
607 return makeColor(g,b,r);
608 case SWAP_OP_GRB:
609 return makeColor(g,r,b);
610 default :
611 return swapColor;
612 }
613 }
614 }