alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

What this is

This file is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Other links

The source code

/*
 *                 Sun Public License Notice
 * 
 * The contents of this file are subject to the Sun Public License
 * Version 1.0 (the "License"). You may not use this file except in
 * compliance with the License. A copy of the License is available at
 * http://www.sun.com/
 * 
 * The Original Code is NetBeans. The Initial Developer of the Original
 * Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun
 * Microsystems, Inc. All Rights Reserved.
 */
/*
 * RelativeColor.java
 *
 * Created on March 13, 2004, 1:37 PM
 */

package org.netbeans.swing.plaf.util;

import javax.swing.*;
import java.awt.*;

/** A color which can be placed into UIDefaults, which is computed from:
 * 
    *
  • A base color, as defined in a UI spec - this might be the expected value * for window titlebars, for example
  • *
  • A target color, as defined in a UI spec, whose color is not the base * color, but has a relation to it (such as brighter or darker, or * hue shifted)
  • *
  • The actual color - which may differ from the base color if the user has * customized their UI them (for example, changing the color defaults in * Windows)
  • *
  • (optional) A color that the result must contrast with sufficiently that * text will be readable
  • *
* When constructing the real value, a color will be generated which has the * same relationship to the original value as the base color has to the target * color. * *

What this class is for

* A number of components in NetBeans have colors that should be based on a * color taken from the desktop theme. Swing provides a mechanism for getting * these colors, via UIManager, which will supply correct colors based on the * desktop theme for a variety of operating systems. *

* But often the color in a UI specification is not the same as, but related to * the color that should be used. For example, in windows classic, the tabs * have a gradient based on a light blue color. The color should be related to * the dark blue color normally used in Windows for window titles. However, * if the user has set the window titlebar color to red, a reddish color should * be used. *

* This class allows you to provide a base value (the default Windows * titlebar color, hardcoded) and a prototype value (the blue color that should * be used if the desktop colors are the defaults), and the actual * value retrieved from the UI. The instance of this class is then dropped * into UIDefaults; code can simply call * UIManager.getColor("someColor") and get the right color without * being cluttered with the details of deriving colors. * *

How it does what it does

* The base and prototype are split into HSB color components. The relationship * between the base and prototype values in saturation and brightness is then * computed. This same relationship is then applied to the actual value * as a function of the divergence between the base and actual values * such that the more a color diverges, the less the relationship is applied - * so that, if the base color is dark blue and the prototype color is light * blue, but the actual color is light yellow, you get light yellow (as opposed * to pure white, which a naive application of the relationship would get). * *

Note: It is possible to create cyclic * references between RelativeColor instances (for example, a RelativeColor * that has its own key as one of the keys it should fetch). Don't do that. * * @author Tim Boudreau */ public class RelativeColor implements UIDefaults.LazyValue { private Color value = null; private Color fallback = null; /** Creates a new instance of RelativeColor. * * @param base A Color or UIManager key for a color that the target color is related to * @param target A Color or UIManager key for a color that is what the target color should be if the * actual color is equal to the base color * @param actual Either a Color object or a UIManager String key resolvable * to a color, which represents the * actual color, which may or may not match the target color * @param mustContrast Either a Color object or a UIManager String key * resolvable to a color which must contrast sufficiently with the derived * color that text will be readable. This parameter may be null; the others * may not. */ public RelativeColor(Object base, Object target, Object actual, Object mustContrast) { if (base == null || target == null || actual == null) { throw new NullPointerException ("Null argument(s): " + base + ',' + target + ',' + actual + ',' + mustContrast); } if (base instanceof String) { baseColorKey = (String) base; } else { baseColor = (Color) base; } if (target instanceof String) { targetColorKey = (String) target; } else { targetColor = (Color) target; } if (actual instanceof String) { actualColorKey = (String) actual; } else { actualColor = (Color) actual; } if (mustContrast != null) { if (mustContrast instanceof String) { mustContrastColorKey = (String) mustContrast; } else { mustContrastColor = (Color) mustContrast; } } } /** Creates a new instance of RelativeColor. * * @param base A Color that the target color is related to * @param target A Color that is what the target color should be if the * actual color is equal to the base color * @param actual Either a Color object or a UIManager String key resolvable * to a color, which represents the * actual color, which may or may not match the target color * @param mustContrast Either a Color object or a UIManager String key * resolvable to a color which must contrast sufficiently with the derived * color that text will be readable */ public RelativeColor(Color base, Color target, Object actual) { this (base, target, actual, null); } public void clear() { value = null; if (actualColorKey != null) { actualColor = null; } if (targetColorKey != null) { targetColor = null; } if (mustContrastColorKey != null) { mustContrastColor = null; } if (baseColorKey != null) { baseColor = null; } } public Object createValue(UIDefaults table) { if (value != null) { return value; } Color actual = getActualColor(); Color base = getBaseColor(); if (actual.equals(base)) { value = getTargetColor(); } else { value = deriveColor (base, actual, getTargetColor()); } if (hasMustContrastColor()) { value = ensureContrast(value, getMustContrastColor()); } return value; } /** Convenience getter, as this class is reasonably useful for creating * derived colors without putting them into UIDefaults */ public Color getColor() { return (Color) createValue(null); } private Color targetColor = null; private String targetColorKey = null; private Color getTargetColor() { if (checkState (targetColor, targetColorKey)) { targetColor = fetchColor(targetColorKey); } return targetColor; } private Color baseColor = null; private String baseColorKey = null; private Color getBaseColor() { if (checkState (baseColor, baseColorKey)) { baseColor = fetchColor(baseColorKey); } return baseColor; } private Color mustContrastColor = null; private String mustContrastColorKey = null; private Color getMustContrastColor() { if (checkState (mustContrastColor, mustContrastColorKey)) { mustContrastColor = fetchColor(mustContrastColorKey); } return mustContrastColor; } private Color actualColor = null; private String actualColorKey = null; private Color getActualColor() { if (checkState (actualColor, actualColorKey)) { actualColor = fetchColor(actualColorKey); } return actualColor; } private boolean hasMustContrastColor() { return mustContrastColor != null || mustContrastColorKey != null; } /** Ensures that the key and color are not null, and returns true if the * color needs to be loaded. */ private boolean checkState(Color color, String key) { if (color == null && key == null) { throw new NullPointerException("Both color and key are null for " + this); } return color == null; } private Color fetchColor(String key) { //Todo - check for cyclic references Color result = UIManager.getColor(key); if (result == null) { result = fallback; } return result; } /** Does the actual leg-work of deriving the color */ static Color deriveColor (Color base, Color actual, Color target) { float[] baseHSB = Color.RGBtoHSB(base.getRed(), base.getGreen(), base.getBlue(), null); float[] targHSB = Color.RGBtoHSB(target.getRed(), target.getGreen(), target.getBlue(), null); float[] actualHSB = Color.RGBtoHSB(actual.getRed(), actual.getGreen(), actual.getBlue(), null); float[] resultHSB = new float[3]; float[] finalHSB = new float[3]; float[] diff = percentageDiff (actualHSB, baseHSB); resultHSB[0] = actualHSB[0] + (diff[0] * (targHSB[0] - baseHSB[0])); resultHSB[1] = actualHSB[1] + (diff[1] * (targHSB[1] - baseHSB[1])); resultHSB[2] = actualHSB[2] + (diff[2] * (targHSB[2] - baseHSB[2])); finalHSB[0] = saturate (resultHSB[0]); finalHSB[1] = saturate (resultHSB[1]); finalHSB[2] = saturate (resultHSB[2]); //If the target had *some* color, so should our result - if it pretty //much doesn't, redistribute some of the brightness to the saturation value if (targHSB[1] > 0.1 && resultHSB[1] <= 0.1) { resultHSB[1] = resultHSB[2] * 0.25f; resultHSB[2] = resultHSB[2] - (resultHSB[2] * 0.25f); } Color result = new Color (Color.HSBtoRGB(finalHSB[0], finalHSB[1], finalHSB[2])); return result; } private static float[] percentageDiff (float[] a, float[] b) { float[] result = new float[3]; for (int i=0; i < 3; i++) { result[i] = 1 - Math.abs(a[i] - b[i]); if (result[i] == 0) { result[i] = 1- a[i]; } } return result; } private static final void out (String nm, float[] f) { //XXX for debugging - deleteme StringBuffer sb = new StringBuffer(nm); sb.append(": "); for (int i=0; i < f.length; i++) { sb.append (Math.round(f[i] * 100)); if (i != f.length-1) { sb.append(','); sb.append(' '); } } System.err.println(sb.toString()); } /** Saturate a float value, clamping values below 0 to 0 and above 1 to 1 */ private static float saturate (float f) { return Math.max(0, Math.min(1, f)); } static Color ensureContrast (Color target, Color contrast) { //XXX - this needs some work. What it should really do: //Determine the distance from 0.5 for brightness and saturation of the contrasting color, to //determine the direction in which to adjust. Then adjust in that //direction as a function of the diff between 0.25 and 0.5 of the //diff between the colors...or something like that. The point is //there's a danger zone around 0.5 where things that should be //adjusted away from each other aren't being float[] contHSB = Color.RGBtoHSB(contrast.getRed(), contrast.getGreen(), contrast.getBlue(), null); float[] targHSB = Color.RGBtoHSB(target.getRed(), target.getGreen(), target.getBlue(), null); float[] resultHSB = new float[3]; System.arraycopy(targHSB, 0, resultHSB, 0, 3); float satDiff = Math.abs (targHSB[1] - contHSB[1]); float briDiff = Math.abs (targHSB[2] - contHSB[2]); if (targHSB[1] > 0.6 && resultHSB[1] > 0.6 || (briDiff < 0.45f && satDiff < 0.4f)) { resultHSB[1] /= 3; // System.err.println("adjusting saturation to " + resultHSB[1] + " from " + targHSB[1]); satDiff = Math.abs (targHSB[1] - contHSB[1]); } if (briDiff < 0.3 || (satDiff < 0.3 && briDiff < 0.5)) { float dir = 1.5f * (0.5f - contHSB[2]); resultHSB[2] = saturate (resultHSB[2] + dir); // System.err.println("adjusting brightness to " + resultHSB[2] + " from " + targHSB[2]); } Color result = new Color (Color.HSBtoRGB(resultHSB[0], resultHSB[1], resultHSB[2])); return result; } }

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.