/* Copyright (c) 2006-2007 Timothy Wall, All Rights Reserved * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * <p/> * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package furbelow; import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Component; import java.awt.Composite; import java.awt.CompositeContext; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.RenderingHints; import java.awt.Transparency; import java.awt.image.BufferedImage; import java.awt.image.ColorModel; import java.awt.image.Raster; import java.awt.image.WritableRaster; import javax.swing.Icon; /** Paint an icon appropriately for a selection to ensure that the icon is * visible and to avoid having to generate minor icon variations. This may * mean a mouse-over, a selected button, or a selected row in a tree or * table. * Primarily to make icons in selected rows of a table on Windows 2000 show up * properly, since that setup has a dark blue selection background which makes * any black pixels difficult to see. This class replaces any pixels of the * given foreground/background with a different foreground/background. * @author twall */ public class SelectionIcon implements Icon { private static int TOLERANCE = 56; private static int TOTAL_TOLERANCE = 2*TOLERANCE; private static float TINT = 0.2f; public static void setTolerance(int tol) { TOLERANCE = tol; } public static int getTolerance() { return TOLERANCE; } public static void setTint(float tint) { TINT = tint; } public static float getTint() { return TINT; } private Icon icon; private Color fg, bg, sfg, sbg; private Composite composite; /** Create an icon that paints a modified version given {@link Icon}. */ public SelectionIcon(Icon icon, Color fg, Color bg, Color sfg, Color sbg) { if (fg == null) throw new NullPointerException("Foreground may not be null"); if (bg == null) throw new NullPointerException("Background may not be null"); if (sfg == null) throw new NullPointerException("Selection foreground may not be null"); if (sbg == null) throw new NullPointerException("Selection Background may not be null"); this.fg = fg; this.bg = bg; this.sfg = sfg; this.sbg = sbg; this.icon = icon; this.composite = new CustomComposite(); } /** Width is same as the original. */ public int getIconWidth() { return icon.getIconWidth(); } /** Height is same as the original. */ public int getIconHeight() { return icon.getIconHeight(); } /** Returns same value as the original. */ public String toString() { return icon.toString() + " (selection)"; } /** Paints the original, but replaces the original fg/bg pixels with new * ones. Transparency is preserved. */ public void paintIcon(Component c, Graphics g, int x, int y) { // Paint the icon into an image buffer so we can capture its // pixels, then re-paint those pixels into the real target. // A bug in JRE 1.4 prevents us from setting the composite on the // original Graphics, so we use the one from the BufferedImage // instead. int w = getIconWidth(); int h = getIconHeight(); Image image = c == null ? new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB) : c.getGraphicsConfiguration().createCompatibleImage(w, h, Transparency.TRANSLUCENT); Graphics2D graphics = (Graphics2D)image.getGraphics(); graphics.setComposite(AlphaComposite.Clear); graphics.fillRect(0, 0, w, h); // Use the selection icon whenever the selection fg is different // from the normal foreground if (!sfg.equals(fg)) { graphics.setComposite(composite); } graphics.translate(-x, -y); icon.paintIcon(c, graphics, x, y); g.drawImage(image, x, y, c); } /** Composites the source image directly into the destination, ignoring the * destination pixels. Pixels found in the source which match the * foreground or background colors are modified to new values. */ private class CustomComposite implements Composite { public CompositeContext createContext(final ColorModel srcCM, final ColorModel dstCM, RenderingHints hints) { //System.out.println("create"); return new CompositeContext() { private boolean near(Color c1, Color c2, int tol) { int dr = Math.abs(c1.getRed() - c2.getRed()); int dg = Math.abs(c1.getGreen() - c2.getGreen()); int db = Math.abs(c1.getBlue() - c2.getBlue()); return dr < TOLERANCE && dg < TOLERANCE && db < TOLERANCE && dr + dg + db < TOTAL_TOLERANCE; } private int mix(int base, int tint, float pct) { return (int)(pct*base + (1-pct)*tint); } public void dispose() { } public void compose(Raster src, Raster dst, WritableRaster out) { int[] pout = new int[4]; int w = Math.min(src.getWidth(), out.getWidth()); int h = Math.min(src.getHeight(), out.getHeight()); int xs = src.getMinX(); int ys = src.getMinY(); for (int x=0;x < w;x++) { for (int y=0;y < h;y++) { Object pixel = src.getDataElements(xs+x, ys+y, null); int alpha = srcCM.getAlpha(pixel); if (alpha != 0) { pout[0] = srcCM.getRed(pixel); pout[1] = srcCM.getGreen(pixel); pout[2] = srcCM.getBlue(pixel); pout[3] = alpha; Color color = new Color(pout[0], pout[1], pout[2], pout[3]); if (near(color, fg, TOLERANCE) || near(color, sbg, TOLERANCE)) { pout[0] = sfg.getRed(); pout[1] = sfg.getGreen(); pout[2] = sfg.getBlue(); } else if (near(color, bg, TOLERANCE)) { pout[0] = sbg.getRed(); pout[1] = sbg.getGreen(); pout[2] = sbg.getBlue(); } else { // mix in the selection background pout[0] = mix(sbg.getRed(), pout[0], TINT); pout[1] = mix(sbg.getGreen(), pout[1], TINT); pout[2] = mix(sbg.getBlue(), pout[2], TINT); } out.setPixel(out.getMinX()+x, out.getMinY()+y, pout); } } } } }; } } }