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.
 */

package org.netbeans.swing.tabcontrol.plaf;


import javax.swing.*;
import java.awt.*;
import java.awt.geom.Area;
import java.awt.image.BufferedImage;
import java.util.HashMap;
import java.util.Map;

/**
 * Utilities for manipulating colors, caching gradient paint objects, creating a
 * bitmap cache of the metal bumps texture and other generally useful stuff.
 *
 * @author Dafe Simonek, Tim Boudreau
 */
final class ColorUtil {
    private static Map gpCache = null;
    private static HashMap hintsMap = null;
    private static final boolean noGpCache = Boolean.getBoolean(
            "netbeans.winsys.nogpcache");  //NOI18N
    private static final boolean noAntialias = 
        Boolean.getBoolean("nb.no.antialias"); //NOI18N
    
    //Values for checking if we should flush the cache bitmap
    private static int focusedHeight = -1;
    private static int unfocusedHeight = -1;
    //Some handy ImageIcons for drawing the cache bitmap
    private static Icon unfocused = null;
    private static Icon focused = null;
    //A scratch rectangle for computing clip intersections
    private static Rectangle scratch = new Rectangle();
    //The width for the backing cache bitmap for the drag texture.
    //May want to profile and tune this a bit for optimum between memory
    //use and painting time
    private static final int DEFAULT_IMAGE_WIDTH = 200;
    /**
     * Constants for allowed texture types, to be passed to drag texture painter
     * methods
     */
    public static final int SEL_TYPE = 1;
    public static final int UNSEL_TYPE = 2;
    public static final int FOCUS_TYPE = 4;
    /**
     * constants for types of tab headers
     */
    public static final int XP_REGULAR_TAB = 0;
    public static final int XP_HIGHLIGHTED_TAB = 1;
    /**
     * constants for gradient borders
     */
    public static final int XP_BORDER_RIGHT = 1;
    public static final int XP_BORDER_BOTTOM = 2;
    /**
     * holds icon of XP style tab drag texture
     */
    private static Icon XP_DRAG_IMAGE;

    /**
     * Utility class, no instances should be created.
     */
    private ColorUtil() {
    }

    /**
     * Computes "middle" color in terms of rgb color space. Ignores alpha
     * (transparency) channel
     */
    public static Color getMiddle(Color c1, Color c2) {
        return new Color((c1.getRed() + c2.getRed()) / 2,
                         (c1.getGreen() + c2.getGreen()) / 2,
                         (c1.getBlue() + c2.getBlue()) / 2);
    }


    public static GradientPaint getGradientPaint(float x1, float y1,
                                                 Color upper, float x2,
                                                 float y2, Color lower) {
        return getGradientPaint(x1, y1, upper, x2, y2, lower, false);
    }

    /**
     * GradientPaint creation is somewhat expensive.  This method keeps cached
     * GradientPaint instances, and normalizes the resulting GradientPaint for
     * horizontal and vertical cases.  Note that this depends entirely on the
     * hashing algorithm for accuracy - there are hypothetical situations 
     * where the return value could be wrong, though none have as yet been 
     * encountered, and given that the number of gradient heights and widths
     * actually used in any UI are very small, such a situation is highly 
     * unlikely.
     */
    public static GradientPaint getGradientPaint(float x1, float y1,
                                                 Color upper, float x2,
                                                 float y2, Color lower,
                                                 boolean repeats) {
        if (noGpCache) {
            return new GradientPaint(x1, y1, upper, x2, y2, lower, repeats);
        }
        
        //Only for test runs with disabled customizations
        if (upper == null) {
            upper = Color.BLUE;
        }
        
        if (lower == null) {
            lower = Color.ORANGE;
        }
        
        if (gpCache == null) {
            gpCache = new HashMap(20);
        }
        //Normalize any non-repeating gradients
        boolean horizontal = x1 == x2;
        boolean vertical = y1 == y2;
        if (horizontal && vertical) {
            //Hack: gradient paint w/ 2 matching points causes endless loop 
            //in native code on mac os, so pick a random number to make sure
            //that can't happen
            y1 = x1 + 28; 
        } else if (horizontal && !repeats) {
            x1 = 0;
            x2 = 0;
        } else if (vertical && !repeats) {
            y1 = 0;
            y2 = 0;
        }
        //TODO: Normalize non-planar repeating gp's by vector/relative location
        
        //Generate a hash code for looking up an existing paint
        long bits = Double.doubleToLongBits(x1)
                + Double.doubleToLongBits(y1) * 37 + Double.doubleToLongBits(
                        x2) * 43 + Double.doubleToLongBits(y2) * 47;
        int hash = ((((int) bits) ^ ((int) (bits >> 32)))
                ^ upper.hashCode() ^ (lower.hashCode() * 17)) * (repeats ? 31 : 1);

        Object key = new Integer(hash);
        GradientPaint result = (GradientPaint) gpCache.get(key);
        if (result == null) {
            result =
                    new GradientPaint(x1, y1, upper, x2, y2, lower, repeats);
            if (gpCache.size() > 40) {
                gpCache.clear();
            }
            gpCache.put(key, result);
        }
        return result;
    }

    private static Map getHints() {
        if (hintsMap == null) {
            hintsMap = new HashMap();
            hintsMap.put(RenderingHints.KEY_FRACTIONALMETRICS,
                         RenderingHints.VALUE_FRACTIONALMETRICS_ON);
            hintsMap.put(RenderingHints.KEY_TEXT_ANTIALIASING,
                         RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
            hintsMap.put(RenderingHints.KEY_ANTIALIASING,
                         RenderingHints.VALUE_ANTIALIAS_ON);
        }
        return hintsMap;
    }

    public static final void setupAntialiasing(Graphics g) {
        if (noAntialias) return;
        ((Graphics2D) g).addRenderingHints(getHints());
    }
    
    private static final boolean antialias = Boolean.getBoolean(
        "nb.cellrenderer.antialiasing") || //NOI18N
        ("GTK".equals(UIManager.getLookAndFeel().getID()) && //NOI18N
        gtkShouldAntialias()) || 
        Boolean.getBoolean ("swing.aatext") || //NOI18N
        "Aqua".equals(UIManager.getLookAndFeel().getID()); 
    
    public static final boolean shouldAntialias() {
        return antialias;
    }
    
    private static final boolean gtkShouldAntialias() {
        Object o = Toolkit.getDefaultToolkit().getDesktopProperty("gnome.Xft/Antialias"); //NOI18N
        return new Integer(1).equals(o);
    }

    //**************Some static utility methods for color manipulation**********
    public static boolean isBrighter(Color a, Color b) {
        int[] ac = new int[]{a.getRed(), a.getGreen(), a.getBlue()};
        int[] bc = new int[]{b.getRed(), b.getGreen(), b.getBlue()};
        int dif = 0;

        for (int i = 0; i < 3; i++) {
            int currDif = ac[i] - bc[i];
            if (Math.abs(currDif) > Math.abs(dif)) {
                dif = currDif;
            }
        }
        return dif > 0;
    }

    private static int minMax(int i) {
        if (i < 0) {
            return 0;
        } else if (i > 255) {
            return 255;
        } else {
            return i;
        }
    }

    public static int averageDifference(Color a, Color b) {
        int[] ac = new int[]{a.getRed(), a.getGreen(), a.getBlue()};
        int[] bc = new int[]{b.getRed(), b.getGreen(), b.getBlue()};
        int dif = 0;
        for (int i = 0; i < 3; i++) {
            dif += bc[i] - ac[i];
        }
        return dif / 3;
    }

    public static Color adjustComponentsTowards(Color toAdjust, Color towards) {
        int r = toAdjust.getRed();
        int g = toAdjust.getGreen();
        int b = toAdjust.getBlue();

        int ra = towards.getRed();
        int ga = towards.getGreen();
        int ba = towards.getBlue();

        r += minMax((ra - r) / 3);
        g += minMax((ga - g) / 3);
        b += minMax((ba - b) / 3);

        return new Color(r, g, b);
    }

    public static Color adjustTowards(Color toAdjust, int amount,
                                      Color towards) {
        int r = toAdjust.getRed();
        int g = toAdjust.getGreen();
        int b = toAdjust.getBlue();
        int factor = isBrighter(towards, toAdjust) ? 1 : -1;
        r = minMax(r + (factor * amount));
        g = minMax(g + (factor * amount));
        b = minMax(b + (factor * amount));
        return new Color(r, g, b);
    }

    public static Color adjustBy(Color toAdjust, int amount) {
        int r = minMax(toAdjust.getRed() + amount);
        int g = minMax(toAdjust.getGreen() + amount);
        int b = minMax(toAdjust.getBlue() + amount);
        return new Color(r, g, b);
    }

    public static Color adjustBy(Color toAdjust, int[] amounts) {
        int r = minMax(toAdjust.getRed() + amounts[0]);
        int g = minMax(toAdjust.getGreen() + amounts[1]);
        int b = minMax(toAdjust.getBlue() + amounts[2]);
        return new Color(r, g, b);
    }

    /**
     * Rotates a float value around 0-1
     */
    private static float minMax(float f) {
        return Math.max(0, Math.min(1, f));
    }

    /**
     * Draws drag texture of given dimensions. Texture is appropriate for Metal
     * like view tabs
     */
    public static void paintViewTabBump(Graphics g, int x, int y, int width,
                                        int height, int type) {
        drawTexture(g, x, y, width, height, type, 0);
    }

    /**
     * Draws drag texture of given dimensions. Texture is appropriate for Metal
     * like document tabs
     */
    public static void paintDocTabBump(Graphics g, int x, int y, int width,
                                       int height, int type) {
        // decline set to 2, so that bump matches the spec
        drawTexture(g, x, y, width, height, type, 2);
    }

    /**
     * Actually draws the texture. yDecline parameter is initial y-coordination
     * decline in pixels, effective values are <0,3>, will change the "shape" of
     * texture a bit.
     */
    private static void _drawTexture(Graphics g, int x, int y, int width,
                                     int height, int type, int yDecline) {
        Color brightC = UIManager.getColor("TabbedPane.highlight");
        Color darkC;
        if (type == FOCUS_TYPE) {
            darkC = UIManager.getColor("TabbedPane.focus");
        } else {
            darkC = UIManager.getColor("controlDkShadow");
        }
        // assure that last column and row will be dark - make width even
        // and height odd
        if (width % 2 != 0) {
            width--;
        }
        if (height % 2 != 0) {
            height--;
        }
        for (int curX = x; curX < x + width; curX++) {
            g.setColor((curX - x) % 2 == 0 ? brightC : darkC);
            for (int curY = y + ((curX - x + yDecline) % 4); curY < y + height; curY +=
                    4) {
                g.drawLine(curX, curY, curX, curY);
            }
        }
    }

    /**
     * Draws the texture from a backing bitmap, creating it on the first call.
     * Profiling shows that 95% of the main window drawing time is spent
     * painting this texture on the output window border.  So instead, we
     * generate a bitmap on the first call and subsequent calls just blit it to
     * the screen.
     */
    private static void drawTexture(Graphics g, int x, int y, int width,
                                    int height, int type, int yDecline) {


        if (!g.hitClip(x, y, width, height)) {
            return;
        }
        if (type == FOCUS_TYPE) {
            if (focused == null || height > focusedHeight * 2) {
                //Create the focused backing bitmap
                Image img = createBitmap(height, type, yDecline);
                //Store the height in case somebody asks to paint a region
                //larger than our stored bitmap.  Probably will never happen,
                //but it would be difficult to diagnose if it did
                focusedHeight = height;

                focused = new ImageIcon(img);
            }
            blitBitmap(g, focused, x, y, width, height);

        } else {
            if (unfocused == null || unfocusedHeight > height * 2) {
                //create the unfocused backing bitmap
                Image img = createBitmap(height, type, yDecline);
                //Store the height in case somebody asks to paint a region
                //larger than our stored bitmap.  Probably will never happen,
                //but it would be difficult to diagnose if it did
                unfocusedHeight = height;
                unfocused = new ImageIcon(img);
            }
            blitBitmap(g, unfocused, x, y, width, height);
        }
    }

    /**
     * Create a backing bitmap, painting the texture into it with the specified
     * parameters.  The bitmap will be created at 2*height, so that even if
     * there is some minor variation in height, it will not force recreating the
     * bitmap
     */
    private static BufferedImage createBitmap(int height, int type,
                                              int yDecline) {

        //Create an optimal image for blitting to the screen with no format conversion
        BufferedImage result = GraphicsEnvironment.getLocalGraphicsEnvironment()
                .getDefaultScreenDevice().getDefaultConfiguration().createCompatibleImage(
                        200, height * 2);

        Graphics g = result.getGraphics();

        if (result.getAlphaRaster() == null) {
            Color c = type == FOCUS_TYPE ?
                    MetalViewTabDisplayerUI.getActBgColor() :
                    MetalViewTabDisplayerUI.getInactBgColor();
            g.setColor(c);
            g.fillRect(0, 0, DEFAULT_IMAGE_WIDTH, height * 2);
        }

        //draw the texture into the offscreen image
        _drawTexture(g, 0, 0, DEFAULT_IMAGE_WIDTH, height * 2, type, yDecline);
        return result;
    }

    /**
     * Paint a backing bitmap into the specified rectangle of the passed
     * Graphics object.  Sets the clipping rectangle to match the coordinates
     * and loops until the rectangle has been filled with the texture.
     */
    private static void blitBitmap(Graphics g, Icon icon, int x, int y, int w,
                                   int h) {
        //Store the current clip to reset it after
        Shape clip = g.getClip();

        if (clip == null) {
            //Limit it to the space we're requested to paint
            g.setClip(x, y, w, h);
        } else {
            //If there is an existing clip, get the intersection with the
            //rectangle we want to paint and set that

            scratch.setBounds(x, y, w, h);
            Area area = new Area(clip);
            area.intersect(new Area(scratch));
            g.setClip(area);
        }
        int iwidth = icon.getIconWidth();
        int widthPainted = 0;
        while (widthPainted < w) {
            //Loop until we've covered the entire area
            icon.paintIcon(null, g, x + widthPainted, y);
            widthPainted += iwidth;
        }

        //restore the clip
        g.setClip(clip);
    }

    /**
     * Paints XP style tab highlight on given coordinates and with given width.
     */
    public static void paintXpTabHeader(int type, Graphics g, int x, int y,
                                        int width) {
        Color capBorderC = getXpHeaderColor(type, false);
        Color capFillC = getXpHeaderColor(type, true);
        // paint header "cap" shape
        g.setColor(capBorderC);
        g.drawLine(x + 2, y, x + width - 3, y);
        g.drawLine(x + 2, y, x, y + 2);
        g.drawLine(x + width - 3, y, x + width - 1, y + 2);
        g.setColor(capFillC);
        g.drawLine(x + 2, y + 1, x + width - 3, y + 1);
        g.drawLine(x + 1, y + 2, x + width - 2, y + 2);
        // antialised effect around corners
        // TBD
    }

    /**
     * Gradient fill with left top light direction.
     */
    public static void xpFillRectGradient(Graphics2D g, Rectangle rect,
                                          Color brightC, Color darkC) {
        xpFillRectGradient(g, rect.x, rect.y, rect.width, rect.height, brightC,
                           darkC, XP_BORDER_BOTTOM | XP_BORDER_RIGHT);
    }

    /**
     * Fills given rectangle in gradient style from bright to dark colors, with
     * virtual light shining from left top direction.
     */
    public static void xpFillRectGradient(Graphics2D g, int x, int y,
                                          int width, int height, Color brightC,
                                          Color darkC) {
        xpFillRectGradient(g, x, y, width, height, brightC, darkC,
                           XP_BORDER_BOTTOM | XP_BORDER_RIGHT);
    }

    /**
     * Fills given rectangle in gradient style from bright to dark colors, with
     * optional emphasized borders.
     */
    public static void xpFillRectGradient(Graphics2D g, int x, int y,
                                          int width, int height, Color brightC,
                                          Color darkC, int borderType) {
        paintXpGradientBorder(g, x, y, width, height, darkC, borderType);
        int gradWidth = ((borderType & XP_BORDER_RIGHT) != 0) ?
                width - 2 : width;
        int gradHeight = ((borderType & XP_BORDER_BOTTOM) != 0) ?
                height - 2 : height;
        paintXpGradientFill(g, x, y, gradWidth, gradHeight, brightC, darkC);
    }

    /**
     * Draws drag texture of the tab in specified bounds.
     */
    public static void paintXpTabDragTexture(Component control, Graphics g,
                                             int x, int y, int height) {
        if (XP_DRAG_IMAGE == null) {
            XP_DRAG_IMAGE = initXpDragTextureImage();
        }
        int count = height / 4;
        int ypos = y;
        for (int i = 0; i < count; i++) {
            XP_DRAG_IMAGE.paintIcon(control, g, x, ypos);
            ypos += 4;
        }
    }

    /**
     * Adjusts color by given values, positive values means brightening,
     * negative values darkening of original color.
     *
     * @return adjusted color
     */
    public static Color adjustColor(Color c, int rDiff, int gDiff, int bDiff) {
        if (c == null) {
            c = Color.GRAY;
        }
        int red = Math.max(0, Math.min(255, c.getRed() + rDiff));
        int green = Math.max(0, Math.min(255, c.getGreen() + gDiff));
        int blue = Math.max(0, Math.min(255, c.getBlue() + bDiff));
        return new Color(red, green, blue);
    }

    /**
     * Paints border of given rectangle, which enhances "light shining" effect
     */
    private static void paintXpGradientBorder(Graphics g, int x, int y,
                                              int width, int height,
                                              Color darkC, int borderType) {
        // right and bottom border, darker
        if ((borderType & XP_BORDER_RIGHT) != 0) {
            Color color = adjustColor(darkC, -6, -5, -3);
            g.setColor(color);
            g.drawLine(x + width - 2, y, x + width - 2, y + height - 2);
            color = adjustColor(darkC, -27, -26, -20);
            g.setColor(color);
            g.drawLine(x + width - 1, y, x + width - 1, y + height - 1);
        }
        if ((borderType & XP_BORDER_BOTTOM) != 0) {
            Color color = adjustColor(darkC, -6, -5, -3);
            g.setColor(color);
            g.drawLine(x, y + height - 2, x + width - 2, y + height - 2);
            color = adjustColor(darkC, -27, -26, -20);
            g.setColor(color);
            g.drawLine(x, y + height - 1, x + width - 1, y + height - 1);
        }
    }

    /**
     * Fills given rectangle using top-down gradient fill of specified colors
     */
    private static void paintXpGradientFill(Graphics2D g, int x, int y,
                                            int width, int height,
                                            Color brightC, Color darkC) {
        GradientPaint gradient = getGradientPaint(x, y, brightC, x, y + height,
                                                  darkC);
        g.setPaint(gradient);
        g.fillRect(x, y, width, height);
    }

    /**
     * @return Header color of tab depending on tab type
     */
    private static Color getXpHeaderColor(int type, boolean fill) {
        String colorKey = null;
        switch (type) {
            case XP_REGULAR_TAB:
                colorKey = fill ? "tab_unsel_fill_bright" : "tab_border";
                break;
            case XP_HIGHLIGHTED_TAB:
                colorKey = fill ?
                        "tab_highlight_header_fill" : "tab_highlight_header";
                break;
            default:
                throw new IllegalArgumentException(
                        "Unknown type of tab header: " + type);
        }
        return UIManager.getColor(colorKey);
    }

    /**
     * Dynamically creates and returns drag texture icon
     */
    private static final Icon initXpDragTextureImage() {
        BufferedImage i = new BufferedImage(3, 3, BufferedImage.TYPE_INT_RGB);
        Color hl = UIManager.getColor("controlLtHighlight"); //NOI18N
        i.setRGB(2, 2, hl.getRGB());
        i.setRGB(2, 1, hl.getRGB());
        i.setRGB(1, 2, hl.getRGB());
        Color dk = UIManager.getColor("TabbedPane.darkShadow"); //NOI18N
        i.setRGB(1, 1, dk.getRGB());
        Color corners = UIManager.getColor("TabbedPane.light"); //NOI18N
        i.setRGB(0, 2, corners.getRGB());
        i.setRGB(2, 0, corners.getRGB());
        Color dk2 = UIManager.getColor("TabbedPane.shadow"); //NOI18N
        i.setRGB(0, 1, dk2.getRGB());
        i.setRGB(1, 0, dk2.getRGB());
        Color up = UIManager.getColor("inactiveCaptionBorder"); //NOI18N
        i.setRGB(0, 0, up.getRGB());
        return new ImageIcon(i);
    }
    
    public boolean isBlueprintTheme() {
            return ("blueprint".equals(//NOI18N
                Toolkit.getDefaultToolkit().getDesktopProperty(
                "gnome.Net/ThemeName"))); //NOI18N
    }
}
... 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.