/* * @(#)FastGradientPainter.java 12/12/2004 * * Copyright 2002 - 2005 JIDE Software Inc. All rights reserved. */ package com.jidesoft.swing; import java.awt.*; import java.awt.image.BufferedImage; import java.lang.ref.ReferenceQueue; import java.lang.ref.SoftReference; class FastGradientPainter { private static GradientCache gradientCache = new GradientCache(); //no instantiation private FastGradientPainter() { } /** * Clears the gradient cache */ public static void clearGradientCache() { gradientCache.clear(); } /** * Draws a rectangular gradient in a vertical or horizontal direction. The drawing operations are hardware optimized * whenever possible using the Java2D hardware rendering facilities. The result is gradient rendering approaching * the performance of flat color rendering. * * @param g2 Graphics2D instance to use for rendering * @param s shape confines of gradient * @param startColor starting color for gradient * @param endColor ending color fro gradient * @param isVertical specifies a vertical or horizontal gradient */ public static void drawGradient(Graphics2D g2, Shape s, Color startColor, Color endColor, boolean isVertical) { Rectangle r = s.getBounds(); if (r.height <= 0 || r.width <= 0) return; int length = isVertical ? r.height : r.width; GradientInfo info = new GradientInfo(g2.getDeviceConfiguration(), length, startColor, endColor, isVertical); BufferedImage gradient = gradientCache.retrieve(info); if (gradient == null) { gradient = createGradientTile(info); gradientCache.store(info, gradient); } Shape prevClip = null; boolean nonRectangular = false; if (!r.equals(s)) { nonRectangular = true; prevClip = g2.getClip(); g2.clip(s); } if (isVertical) { int w = gradient.getWidth(); int loops = r.width / w; for (int i = 0; i < loops; i++) g2.drawImage(gradient, r.x + i * w, r.y, null); int rem = r.width % w; if (rem > 0) { g2.drawImage(gradient, r.x + loops * w, r.y, r.x + loops * w + rem, r.y + length, 0, 0, rem, length, null); } } else { int h = gradient.getHeight(); int loops = r.height / h; for (int i = 0; i < loops; i++) g2.drawImage(gradient, r.x, r.y + i * h, null); int rem = r.height % h; if (rem > 0) { g2.drawImage(gradient, r.x, r.y + loops * h, r.x + length, r.y + loops * h + rem, 0, 0, length, rem, null); } } if (nonRectangular) { g2.setClip(prevClip); } } private static BufferedImage createGradientTile(GradientInfo info) { boolean t = info.startColor.getTransparency() > 1 || info.endColor.getTransparency() > 1; int dx, dy, w, h; if (info.isVertical) { dx = 0; h = dy = info.length; w = 32; } else { w = dx = info.length; dy = 0; h = 32; } BufferedImage img = info.gfxConfig.createCompatibleImage(w, h, t ? Transparency.TRANSLUCENT : Transparency.OPAQUE); Paint gp = new GradientPaint(0, 0, info.startColor, dx, dy, info.endColor); Graphics2D g = img.createGraphics(); g.setPaint(gp); g.fillRect(0, 0, w, h); g.dispose(); return img; } } /** * Contains all information pertaining to a particular gradient. */ class GradientInfo { GraphicsConfiguration gfxConfig; int length; Color startColor, endColor; boolean isVertical; public GradientInfo(GraphicsConfiguration gc, int ln, Color sc, Color ec, boolean v) { gfxConfig = gc; length = ln; startColor = sc; endColor = ec; isVertical = v; } boolean isEquivalent(GradientInfo gi) { return (gi.gfxConfig.equals(gfxConfig) && gi.length == length && gi.startColor.equals(startColor) && gi.endColor.equals(endColor) && gi.isVertical == isVertical); } @Override public boolean equals(Object o) { if (!(o instanceof GradientInfo)) return false; return isEquivalent((GradientInfo) o); } @Override public String toString() { return "Direction:" + (isVertical ? "ver" : "hor") + ", Length: " + Integer.toString(length) + ", Color1: " + Integer.toString(startColor.getRGB(), 16) + ", Color2: " + Integer.toString(endColor.getRGB(), 16); } } /** * A cache utilizing SoftReferences under the hood for memory efficient handling of gradients. */ class GradientCache { private GradientCacheEntry[] gradients; private int size; private int threshold; private final float loadFactor; private final ReferenceQueue queue = new ReferenceQueue(); GradientCache() { this.loadFactor = 0.75f; threshold = 16; gradients = new GradientCacheEntry[16]; } BufferedImage retrieve(GradientInfo info) { int ln = info.length; GradientCacheEntry[] grads = getGradients(); int index = bucket(ln, grads.length); GradientCacheEntry e = grads[index]; while (e != null) { GradientInfo egi = e.getInfo(); try { if (egi != null) { if (e.length == ln && egi.isEquivalent(info)) { return e.gradient; } } } catch (NullPointerException npe) { // apparently egi will or e will be cleared anyways sometimes, // so we have to catch a possible NPE // I print the values to get a better understanding of the situation. // comment this if unacceptable or change to use logging if needed // System.err.println("e = " + e); // System.err.println("egi = " + egi); } e = e.next; } return null; } Object store(GradientInfo info, BufferedImage gradient) { GradientCacheEntry[] grads = getGradients(); int i = bucket(info.length, grads.length); GradientCacheEntry e = grads[i]; if (!entryNotInCache(e, info)) { System.err.println("Duplicate entry found!"); } grads[i] = new GradientCacheEntry(info, gradient, queue, e); if (++size >= threshold) resize(grads.length << 1); return null; } void clear() { GradientCacheEntry[] a = getGradients(); for(int i=0;i<a.length;i++) { a[i]=null; } size=0; threshold = 16; gradients = new GradientCacheEntry[16]; } private boolean entryNotInCache(GradientCacheEntry e, GradientInfo info) { while (e != null && e.getInfo() != null) { // to fix a NPE if (e.length == info.length && e.getInfo().isEquivalent(info)) { return false; } e = e.next; } return true; } private void resize(int newCapacity) { GradientCacheEntry[] oldArray = getGradients(); int oldCapacity = oldArray.length; if (oldCapacity == ((Integer.MAX_VALUE >> 1) + 1)) { threshold = Integer.MAX_VALUE; return; } GradientCacheEntry[] newArray = new GradientCacheEntry[newCapacity]; moveEntries(oldArray, newArray); gradients = newArray; if (size >= (threshold >> 1)) { threshold = (int) (newCapacity * loadFactor); } else { cleanOldCacheEntries(); moveEntries(newArray, oldArray); gradients = oldArray; } } private GradientCacheEntry[] getGradients() { cleanOldCacheEntries(); return gradients; } private static int bucket(int h, int length) { return h & (length - 1); } private void moveEntries(GradientCacheEntry[] src, GradientCacheEntry[] dest) { for (int j = 0; j < src.length; ++j) { GradientCacheEntry e = src[j]; src[j] = null; while (e != null) { GradientCacheEntry next = e.next; Object o = e.get(); if (o == null) { e.next = null; e.gradient = null; size--; } else { int i = bucket(e.length, dest.length); e.next = dest[i]; dest[i] = e; } e = next; } } } private void cleanOldCacheEntries() { GradientCacheEntry e; while ((e = (GradientCacheEntry) queue.poll()) != null) { int i = bucket(e.length, gradients.length); GradientCacheEntry prev = gradients[i]; GradientCacheEntry p = prev; while (p != null) { GradientCacheEntry next = p.next; if (p == e) { if (prev == e) gradients[i] = next; else prev.next = next; e.next = null; e.gradient = null; size--; break; } prev = p; p = next; } } } } class GradientCacheEntry extends SoftReference { GradientCacheEntry next; BufferedImage gradient; int length; GradientCacheEntry(GradientInfo info, BufferedImage gradient, ReferenceQueue queue, GradientCacheEntry next) { super(info, queue); this.next = next; this.gradient = gradient; length = info.length; } GradientInfo getInfo() { return (GradientInfo) get(); } BufferedImage getGradient() { return gradient; } }