KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > swing > plaf > util > RelativeColor


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19 /*
20  * RelativeColor.java
21  *
22  * Created on March 13, 2004, 1:37 PM
23  */

24
25 package org.netbeans.swing.plaf.util;
26
27 import javax.swing.*;
28 import java.awt.*;
29
30 /** A color which can be placed into UIDefaults, which is computed from:
31  * <ul>
32  * <li>A base color, as defined in a UI spec - this might be the expected value
33  * for window titlebars, for example </li>
34  * <li>A target color, as defined in a UI spec, whose color is not the base
35  * color, but has a relation to it (such as brighter or darker, or
36  * hue shifted)</li>
37  * <li>The actual color - which may differ from the base color if the user has
38  * customized their UI them (for example, changing the color defaults in
39  * Windows)</li>
40  * <li>(optional) A color that the result must contrast with sufficiently that
41  * text will be readable</li>
42  * </ul>
43  * When constructing the real value, a color will be generated which has the
44  * same relationship to the original value as the base color has to the target
45  * color.
46  *
47  * <h2>What this class is for</h2>
48  * A number of components in NetBeans have colors that should be based on a
49  * color taken from the desktop theme. Swing provides a mechanism for getting
50  * these colors, via UIManager, which will supply correct colors based on the
51  * desktop theme for a variety of operating systems.
52  * <p>
53  * But often the color in a UI specification is not the same as, but related to
54  * the color that should be used. For example, in windows classic, the tabs
55  * have a gradient based on a light blue color. The color should be related to
56  * the dark blue color normally used in Windows for window titles. However,
57  * if the user has set the window titlebar color to red, a reddish color should
58  * be used.
59  * <p>
60  * This class allows you to provide a base value (the default Windows
61  * titlebar color, hardcoded) and a prototype value (the blue color that should
62  * be used <i>if</i> the desktop colors are the defaults), and the <i>actual</i>
63  * value retrieved from the UI. The instance of this class is then dropped
64  * into <code>UIDefaults</code>; code can simply call
65  * <code>UIManager.getColor("someColor")</code> and get the right color without
66  * being cluttered with the details of deriving colors.
67  *
68  * <h2>How it does what it does</h2>
69  * The base and prototype are split into HSB color components. The relationship
70  * between the base and prototype values in saturation and brightness is then
71  * computed. This same relationship is then applied to the actual value
72  * <i>as a function of the divergence between the base and actual values</i>
73  * such that the more a color diverges, the less the relationship is applied -
74  * so that, if the base color is dark blue and the prototype color is light
75  * blue, but the actual color is light yellow, you get light yellow (as opposed
76  * to pure white, which a naive application of the relationship would get).
77  *
78  * <p><strong>Note:</strong> It <strong>is</strong> possible to create cyclic
79  * references between RelativeColor instances (for example, a RelativeColor
80  * that has its own key as one of the keys it should fetch). Don't do that.
81  *
82  * @author Tim Boudreau
83  */

84 public class RelativeColor implements UIDefaults.LazyValue {
85     private Color value = null;
86     private Color fallback = null;
87     /** Creates a new instance of RelativeColor.
88      *
89      * @param base A Color or UIManager key for a color that the target color is related to
90      * @param target A Color or UIManager key for a color that is what the target color should be if the
91      * actual color is equal to the base color
92      * @param actual Either a Color object or a UIManager String key resolvable
93      * to a color, which represents the
94      * actual color, which may or may not match the target color
95      * @param mustContrast Either a Color object or a UIManager String key
96      * resolvable to a color which must contrast sufficiently with the derived
97      * color that text will be readable. This parameter may be null; the others
98      * may not. */

99     public RelativeColor(Object JavaDoc base, Object JavaDoc target, Object JavaDoc actual, Object JavaDoc mustContrast) {
100         if (base == null || target == null || actual == null) {
101             throw new NullPointerException JavaDoc ("Null argument(s): " + base + ','
102                 + target + ',' + actual + ',' + mustContrast);
103         }
104         if (base instanceof String JavaDoc) {
105             baseColorKey = (String JavaDoc) base;
106         } else {
107             baseColor = (Color) base;
108         }
109         if (target instanceof String JavaDoc) {
110             targetColorKey = (String JavaDoc) target;
111         } else {
112             targetColor = (Color) target;
113         }
114         if (actual instanceof String JavaDoc) {
115             actualColorKey = (String JavaDoc) actual;
116         } else {
117             actualColor = (Color) actual;
118         }
119         if (mustContrast != null) {
120             if (mustContrast instanceof String JavaDoc) {
121                 mustContrastColorKey = (String JavaDoc) mustContrast;
122             } else {
123                 mustContrastColor = (Color) mustContrast;
124             }
125         }
126     }
127     
128     /** Creates a new instance of RelativeColor.
129      *
130      * @param base A Color that the target color is related to
131      * @param target A Color that is what the target color should be if the
132      * actual color is equal to the base color
133      * @param actual Either a Color object or a UIManager String key resolvable
134      * to a color, which represents the
135      * actual color, which may or may not match the target color
136      * @param mustContrast Either a Color object or a UIManager String key
137      * resolvable to a color which must contrast sufficiently with the derived
138      * color that text will be readable
139      */

140     public RelativeColor(Color base, Color target, Object JavaDoc actual) {
141         this (base, target, actual, null);
142     }
143     
144     public void clear() {
145         value = null;
146         if (actualColorKey != null) {
147             actualColor = null;
148         }
149         if (targetColorKey != null) {
150             targetColor = null;
151         }
152         if (mustContrastColorKey != null) {
153             mustContrastColor = null;
154         }
155         if (baseColorKey != null) {
156             baseColor = null;
157         }
158     }
159     
160     public Object JavaDoc createValue(UIDefaults table) {
161         if (value != null) {
162             return value;
163         }
164         Color actual = getActualColor();
165         Color base = getBaseColor();
166         if (actual.equals(base)) {
167             value = getTargetColor();
168         } else {
169             value = deriveColor (base, actual, getTargetColor());
170         }
171         if (hasMustContrastColor()) {
172             value = ensureContrast(value, getMustContrastColor());
173         }
174         return value;
175     }
176
177     /** Convenience getter, as this class is reasonably useful for creating
178      * derived colors without putting them into UIDefaults */

179     public Color getColor() {
180         return (Color) createValue(null);
181     }
182     
183     private Color targetColor = null;
184     private String JavaDoc targetColorKey = null;
185     private Color getTargetColor() {
186         if (checkState (targetColor, targetColorKey)) {
187             targetColor = fetchColor(targetColorKey);
188         }
189         return targetColor;
190     }
191     
192     private Color baseColor = null;
193     private String JavaDoc baseColorKey = null;
194     private Color getBaseColor() {
195         if (checkState (baseColor, baseColorKey)) {
196             baseColor = fetchColor(baseColorKey);
197         }
198         return baseColor;
199     }
200     
201     private Color mustContrastColor = null;
202     private String JavaDoc mustContrastColorKey = null;
203     private Color getMustContrastColor() {
204         if (checkState (mustContrastColor, mustContrastColorKey)) {
205             mustContrastColor = fetchColor(mustContrastColorKey);
206         }
207         return mustContrastColor;
208     }
209     
210     private Color actualColor = null;
211     private String JavaDoc actualColorKey = null;
212     private Color getActualColor() {
213         if (checkState (actualColor, actualColorKey)) {
214             actualColor = fetchColor(actualColorKey);
215         }
216         return actualColor;
217     }
218     
219     private boolean hasMustContrastColor() {
220         return mustContrastColor != null || mustContrastColorKey != null;
221     }
222     
223     /** Ensures that the key and color are not null, and returns true if the
224      * color needs to be loaded. */

225     private boolean checkState(Color color, String JavaDoc key) {
226         if (color == null && key == null) {
227             throw new NullPointerException JavaDoc("Both color and key are null for " +
228                 this);
229         }
230         return color == null;
231     }
232     
233     private Color fetchColor(String JavaDoc key) {
234         //Todo - check for cyclic references
235
Color result = UIManager.getColor(key);
236         if (result == null) {
237             result = fallback;
238         }
239         return result;
240     }
241     
242     /** Does the actual leg-work of deriving the color */
243     static Color deriveColor (Color base, Color actual, Color target) {
244         float[] baseHSB = Color.RGBtoHSB(base.getRed(), base.getGreen(),
245             base.getBlue(), null);
246         
247         float[] targHSB = Color.RGBtoHSB(target.getRed(), target.getGreen(),
248             target.getBlue(), null);
249         
250         float[] actualHSB = Color.RGBtoHSB(actual.getRed(), actual.getGreen(),
251             actual.getBlue(), null);
252         
253         float[] resultHSB = new float[3];
254         float[] finalHSB = new float[3];
255         
256         float[] diff = percentageDiff (actualHSB, baseHSB);
257         
258         resultHSB[0] = actualHSB[0] + (diff[0] * (targHSB[0] - baseHSB[0]));
259         resultHSB[1] = actualHSB[1] + (diff[1] * (targHSB[1] - baseHSB[1]));
260         resultHSB[2] = actualHSB[2] + (diff[2] * (targHSB[2] - baseHSB[2]));
261         
262         finalHSB[0] = saturate (resultHSB[0]);
263         finalHSB[1] = saturate (resultHSB[1]);
264         finalHSB[2] = saturate (resultHSB[2]);
265         
266         //If the target had *some* color, so should our result - if it pretty
267
//much doesn't, redistribute some of the brightness to the saturation value
268
if (targHSB[1] > 0.1 && resultHSB[1] <= 0.1) {
269             resultHSB[1] = resultHSB[2] * 0.25f;
270             resultHSB[2] = resultHSB[2] - (resultHSB[2] * 0.25f);
271         }
272
273         Color result = new Color (Color.HSBtoRGB(finalHSB[0], finalHSB[1], finalHSB[2]));
274         return result;
275     }
276     
277     private static float[] percentageDiff (float[] a, float[] b) {
278         float[] result = new float[3];
279         for (int i=0; i < 3; i++) {
280             result[i] = 1 - Math.abs(a[i] - b[i]);
281             if (result[i] == 0) {
282                 result[i] = 1- a[i];
283             }
284         }
285         return result;
286     }
287     
288     private static final void out (String JavaDoc nm, float[] f) {
289         //XXX for debugging - deleteme
290
StringBuffer JavaDoc sb = new StringBuffer JavaDoc(nm);
291         sb.append(": ");
292         for (int i=0; i < f.length; i++) {
293             sb.append (Math.round(f[i] * 100));
294             if (i != f.length-1) {
295                 sb.append(',');
296                 sb.append(' ');
297             }
298         }
299         System.err.println(sb.toString());
300     }
301     
302     /** Saturate a float value, clamping values below 0 to 0 and above 1 to 1 */
303     private static float saturate (float f) {
304         return Math.max(0, Math.min(1, f));
305     }
306
307
308     static Color ensureContrast (Color target, Color contrast) {
309         //XXX - this needs some work. What it should really do:
310
//Determine the distance from 0.5 for brightness and saturation of the contrasting color, to
311
//determine the direction in which to adjust. Then adjust in that
312
//direction as a function of the diff between 0.25 and 0.5 of the
313
//diff between the colors...or something like that. The point is
314
//there's a danger zone around 0.5 where things that should be
315
//adjusted away from each other aren't being
316

317         float[] contHSB = Color.RGBtoHSB(contrast.getRed(), contrast.getGreen(),
318             contrast.getBlue(), null);
319         
320         float[] targHSB = Color.RGBtoHSB(target.getRed(), target.getGreen(),
321             target.getBlue(), null);
322         
323         float[] resultHSB = new float[3];
324         System.arraycopy(targHSB, 0, resultHSB, 0, 3);
325
326         float satDiff = Math.abs (targHSB[1] - contHSB[1]);
327         float briDiff = Math.abs (targHSB[2] - contHSB[2]);
328
329         if (targHSB[1] > 0.6 && resultHSB[1] > 0.6 || (briDiff < 0.45f && satDiff < 0.4f)) {
330             resultHSB[1] /= 3;
331 // System.err.println("adjusting saturation to " + resultHSB[1] + " from " + targHSB[1]);
332
satDiff = Math.abs (targHSB[1] - contHSB[1]);
333         }
334         
335         if (briDiff < 0.3 || (satDiff < 0.3 && briDiff < 0.5)) {
336             float dir = 1.5f * (0.5f - contHSB[2]);
337             resultHSB[2] = saturate (resultHSB[2] + dir);
338 // System.err.println("adjusting brightness to " + resultHSB[2] + " from " + targHSB[2]);
339
}
340         
341         Color result = new Color (Color.HSBtoRGB(resultHSB[0], resultHSB[1], resultHSB[2]));
342         return result;
343     }
344 }
345
Popular Tags