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    }