001: /*
002: * @(#)FastGradientPainter.java 12/12/2004
003: *
004: * Copyright 2002 - 2005 JIDE Software Inc. All rights reserved.
005: */
006: package com.jidesoft.swing;
007:
008: import java.awt.*;
009: import java.awt.image.BufferedImage;
010: import java.lang.ref.ReferenceQueue;
011: import java.lang.ref.SoftReference;
012:
013: class FastGradientPainter {
014: private static GradientCache gradientCache = new GradientCache();
015:
016: //no instantiation
017: private FastGradientPainter() {
018: }
019:
020: /**
021: * Draws a rectangular gradient in a vertical or horizontal direction.
022: * The drawing operations are hardware optimized whenever possible using the
023: * Java2D hardware rendering facilities. The result is gradient rendering
024: * approaching the performance of flat color rendering.
025: *
026: * @param g2 Graphics2D instance to use for rendering
027: * @param s shape confines of gradient
028: * @param startColor starting color for gradient
029: * @param endColor ending color fro gradient
030: * @param isVertical specifies a vertical or horizontal gradient
031: */
032: public static void drawGradient(Graphics2D g2, Shape s,
033: Color startColor, Color endColor, boolean isVertical) {
034: Rectangle r = s.getBounds();
035: if (r.height <= 0 || r.width <= 0)
036: return;
037:
038: int length = isVertical ? r.height : r.width;
039: GradientInfo info = new GradientInfo(g2
040: .getDeviceConfiguration(), length, startColor,
041: endColor, isVertical);
042:
043: BufferedImage gradient = gradientCache.retrieve(info);
044: if (gradient == null) {
045: gradient = createGradientTile(info);
046: gradientCache.store(info, gradient);
047: }
048:
049: Shape prevClip = null;
050: boolean nonRectangular = false;
051: if (!r.equals(s)) {
052: nonRectangular = true;
053: prevClip = g2.getClip();
054: g2.clip(s);
055: }
056: if (isVertical) {
057: int w = gradient.getWidth();
058: int loops = r.width / w;
059: for (int i = 0; i < loops; i++)
060: g2.drawImage(gradient, r.x + i * w, r.y, null);
061: int rem = r.width % w;
062: if (rem > 0) {
063: g2.drawImage(gradient, r.x + loops * w, r.y, r.x
064: + loops * w + rem, r.y + length, 0, 0, rem,
065: length, null);
066: }
067: } else {
068: int h = gradient.getHeight();
069: int loops = r.height / h;
070: for (int i = 0; i < loops; i++)
071: g2.drawImage(gradient, r.x, r.y + i * h, null);
072: int rem = r.height % h;
073: if (rem > 0) {
074: g2.drawImage(gradient, r.x, r.y + loops * h, r.x
075: + length, r.y + loops * h + rem, 0, 0, length,
076: rem, null);
077: }
078: }
079: if (nonRectangular) {
080: g2.setClip(prevClip);
081: }
082: }
083:
084: private static BufferedImage createGradientTile(GradientInfo info) {
085: boolean t = info.startColor.getTransparency() > 1
086: || info.endColor.getTransparency() > 1;
087:
088: int dx, dy, w, h;
089: if (info.isVertical) {
090: dx = 0;
091: h = dy = info.length;
092: w = 32;
093: } else {
094: w = dx = info.length;
095: dy = 0;
096: h = 32;
097: }
098: BufferedImage img = info.gfxConfig.createCompatibleImage(w, h,
099: t ? Transparency.TRANSLUCENT : Transparency.OPAQUE);
100: Paint gp = new GradientPaint(0, 0, info.startColor, dx, dy,
101: info.endColor);
102:
103: Graphics2D g = img.createGraphics();
104: g.setPaint(gp);
105: g.fillRect(0, 0, w, h);
106: g.dispose();
107: return img;
108: }
109: }
110:
111: /**
112: * Containts all information pertaining to a particular gradient.
113: */
114: class GradientInfo {
115: GraphicsConfiguration gfxConfig;
116: int length;
117: Color startColor, endColor;
118: boolean isVertical;
119:
120: public GradientInfo(GraphicsConfiguration gc, int ln, Color sc,
121: Color ec, boolean v) {
122: gfxConfig = gc;
123: length = ln;
124: startColor = sc;
125: endColor = ec;
126: isVertical = v;
127: }
128:
129: boolean isEquivalent(GradientInfo gi) {
130: return (gi.gfxConfig.equals(gfxConfig) && gi.length == length
131: && gi.startColor.equals(startColor)
132: && gi.endColor.equals(endColor) && gi.isVertical == isVertical);
133: }
134:
135: @Override
136: public boolean equals(Object o) {
137: if (!(o instanceof GradientInfo))
138: return false;
139: return isEquivalent((GradientInfo) o);
140: }
141:
142: @Override
143: public String toString() {
144: return "Direction:" + (isVertical ? "ver" : "hor")
145: + ", Length: " + Integer.toString(length)
146: + ", Color1: "
147: + Integer.toString(startColor.getRGB(), 16)
148: + ", Color2: "
149: + Integer.toString(endColor.getRGB(), 16);
150: }
151: }
152:
153: /**
154: * A cache utilizing SoftReferences under the hood for memory efficient handling
155: * of gradients.
156: */
157: class GradientCache {
158: private GradientCacheEntry[] gradients;
159: private int size;
160: private int threshold;
161: private final float loadFactor;
162: private final ReferenceQueue queue = new ReferenceQueue();
163:
164: GradientCache() {
165: this .loadFactor = 0.75f;
166: threshold = 16;
167: gradients = new GradientCacheEntry[16];
168: }
169:
170: BufferedImage retrieve(GradientInfo info) {
171: int ln = info.length;
172: GradientCacheEntry[] grads = getGradients();
173: int index = bucket(ln, grads.length);
174: GradientCacheEntry e = grads[index];
175:
176: while (e != null) {
177: GradientInfo egi = e.getInfo();
178: try {
179: if (egi != null) {
180: if (e.length == ln && egi.isEquivalent(info)) {
181: return e.gradient;
182: }
183: }
184: } catch (NullPointerException npe) {
185: // apparently egi will or e will be cleared anyways sometimes,
186: // so we have to catch a possible NPE
187: // I print the values to get a better understanding of the situation.
188: // comment this if unacceptable or change to use logging if needed
189: // System.err.println("e = " + e);
190: // System.err.println("egi = " + egi);
191: }
192: e = e.next;
193: }
194: return null;
195: }
196:
197: Object store(GradientInfo info, BufferedImage gradient) {
198: GradientCacheEntry[] grads = getGradients();
199: int i = bucket(info.length, grads.length);
200:
201: GradientCacheEntry e = grads[i];
202:
203: if (!entryNotInCache(e, info)) {
204: System.err.println("Duplicate entry found!");
205: }
206:
207: grads[i] = new GradientCacheEntry(info, gradient, queue, e);
208: if (++size >= threshold)
209: resize(grads.length << 1);
210: return null;
211: }
212:
213: private boolean entryNotInCache(GradientCacheEntry e,
214: GradientInfo info) {
215: while (e != null && e.getInfo() != null) { // to fix a NPE
216: if (e.length == info.length
217: && e.getInfo().isEquivalent(info)) {
218: return false;
219: }
220: e = e.next;
221: }
222: return true;
223: }
224:
225: private void resize(int newCapacity) {
226: GradientCacheEntry[] oldArray = getGradients();
227: int oldCapacity = oldArray.length;
228: if (oldCapacity == ((Integer.MAX_VALUE >> 1) + 1)) {
229: threshold = Integer.MAX_VALUE;
230: return;
231: }
232:
233: GradientCacheEntry[] newArray = new GradientCacheEntry[newCapacity];
234: moveEntries(oldArray, newArray);
235: gradients = newArray;
236:
237: if (size >= (threshold >> 1)) {
238: threshold = (int) (newCapacity * loadFactor);
239: } else {
240: cleanOldCacheEntries();
241: moveEntries(newArray, oldArray);
242: gradients = oldArray;
243: }
244: }
245:
246: private GradientCacheEntry[] getGradients() {
247: cleanOldCacheEntries();
248: return gradients;
249: }
250:
251: private static int bucket(int h, int length) {
252: return h & (length - 1);
253: }
254:
255: private void moveEntries(GradientCacheEntry[] src,
256: GradientCacheEntry[] dest) {
257: for (int j = 0; j < src.length; ++j) {
258: GradientCacheEntry e = src[j];
259: src[j] = null;
260: while (e != null) {
261: GradientCacheEntry next = e.next;
262: Object o = e.get();
263: if (o == null) {
264: e.next = null;
265: e.gradient = null;
266: size--;
267: } else {
268: int i = bucket(e.length, dest.length);
269: e.next = dest[i];
270: dest[i] = e;
271: }
272: e = next;
273: }
274: }
275: }
276:
277: private void cleanOldCacheEntries() {
278: GradientCacheEntry e;
279: while ((e = (GradientCacheEntry) queue.poll()) != null) {
280: int i = bucket(e.length, gradients.length);
281:
282: GradientCacheEntry prev = gradients[i];
283: GradientCacheEntry p = prev;
284: while (p != null) {
285: GradientCacheEntry next = p.next;
286: if (p == e) {
287: if (prev == e)
288: gradients[i] = next;
289: else
290: prev.next = next;
291: e.next = null;
292: e.gradient = null;
293: size--;
294: break;
295: }
296: prev = p;
297: p = next;
298: }
299: }
300: }
301: }
302:
303: class GradientCacheEntry extends SoftReference {
304: GradientCacheEntry next;
305: BufferedImage gradient;
306: int length;
307:
308: GradientCacheEntry(GradientInfo info, BufferedImage gradient,
309: ReferenceQueue queue, GradientCacheEntry next) {
310: super (info, queue);
311: this .next = next;
312: this .gradient = gradient;
313: length = info.length;
314: }
315:
316: GradientInfo getInfo() {
317: return (GradientInfo) get();
318: }
319:
320: BufferedImage getGradient() {
321: return gradient;
322: }
323: }
|