001 /*
002 * $Id: AbstractPainter.java 3256 2009-02-10 20:09:41Z kschaefe $
003 *
004 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
005 * Santa Clara, California 95054, U.S.A. All rights reserved.
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * You should have received a copy of the GNU Lesser General Public
018 * License along with this library; if not, write to the Free Software
019 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
020 */
021
022 package org.jdesktop.swingx.painter;
023
024 import java.awt.*;
025 import java.awt.image.BufferedImage;
026 import java.awt.image.BufferedImageOp;
027 import java.lang.ref.SoftReference;
028
029 import org.jdesktop.beans.AbstractBean;
030 import org.jdesktop.swingx.graphics.GraphicsUtilities;
031
032 /**
033 * <p>A convenient base class from which concrete {@link Painter} implementations may
034 * extend. It extends {@link org.jdesktop.beans.AbstractBean} as a convenience for
035 * adding property change notification support. In addition, <code>AbstractPainter</code>
036 * provides subclasses with the ability to cacheable painting operations, configure the
037 * drawing surface with common settings (such as antialiasing and interpolation), and
038 * toggle whether a subclass paints or not via the <code>visibility</code> property.</p>
039 *
040 * <p>Subclasses of <code>AbstractPainter</code> generally need only override the
041 * {@link #doPaint(Graphics2D, Object, int, int)} method. If a subclass requires more control
042 * over whether cacheing is enabled, or for configuring the graphics state, then it
043 * may override the appropriate protected methods to interpose its own behavior.</p>
044 *
045 * <p>For example, here is the doPaint method of a simple <code>Painter</code> that
046 * paints an opaque rectangle:
047 * <pre><code>
048 * public void doPaint(Graphics2D g, T obj, int width, int height) {
049 * g.setPaint(Color.BLUE);
050 * g.fillRect(0, 0, width, height);
051 * }
052 * </code></pre></p>
053 *
054 * @author rbair
055 */
056 public abstract class AbstractPainter<T> extends AbstractBean implements Painter<T> {
057 /**
058 * An enum representing the possible interpolation values of Bicubic, Bilinear, and
059 * Nearest Neighbor. These map to the underlying RenderingHints,
060 * but are easier to use and serialization safe.
061 */
062 public enum Interpolation {
063 /**
064 * use bicubic interpolation
065 */
066 Bicubic(RenderingHints.VALUE_INTERPOLATION_BICUBIC),
067 /**
068 * use bilinear interpolation
069 */
070 Bilinear(RenderingHints.VALUE_INTERPOLATION_BILINEAR),
071 /**
072 * use nearest neighbor interpolation
073 */
074 NearestNeighbor(RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
075
076 private Object value;
077 Interpolation(Object value) {
078 this.value = value;
079 }
080 private void configureGraphics(Graphics2D g) {
081 g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, value);
082 }
083 }
084
085 //--------------------------------------------------- Instance Variables
086 /**
087 * The cached image, if shouldUseCache() returns true
088 */
089 private transient SoftReference<BufferedImage> cachedImage;
090 private boolean cacheCleared = true;
091 private boolean cacheable = false;
092 private boolean dirty = false;
093 private BufferedImageOp[] filters = new BufferedImageOp[0];
094 private boolean antialiasing = true;
095 private Interpolation interpolation = Interpolation.NearestNeighbor;
096 private boolean visible = true;
097
098 /**
099 * Creates a new instance of AbstractPainter.
100 */
101 public AbstractPainter() { }
102
103 /**
104 * Creates a new instance of AbstractPainter.
105 * @param cacheable indicates if this painter should be cacheable
106 */
107 public AbstractPainter(boolean cacheable) {
108 setCacheable(cacheable);
109 }
110
111 /**
112 * A defensive copy of the Effects to apply to the results
113 * of the AbstractPainter's painting operation. The array may
114 * be empty but it will never be null.
115 * @return the array of filters applied to this painter
116 */
117 public final BufferedImageOp[] getFilters() {
118 BufferedImageOp[] results = new BufferedImageOp[filters.length];
119 System.arraycopy(filters, 0, results, 0, results.length);
120 return results;
121 }
122
123 /**
124 * <p>A convenience method for specifying the filters to use based on
125 * BufferedImageOps. These will each be individually wrapped by an ImageFilter
126 * and then setFilters(Effect... filters) will be called with the resulting
127 * array</p>
128 *
129 *
130 * @param effects the BufferedImageOps to wrap as filters
131 */
132 public void setFilters(BufferedImageOp ... effects) {
133 if (effects == null) effects = new BufferedImageOp[0];
134 BufferedImageOp[] old = getFilters();
135 this.filters = new BufferedImageOp[effects == null ? 0 : effects.length];
136 System.arraycopy(effects, 0, this.filters, 0, this.filters.length);
137 setDirty(true);
138 firePropertyChange("filters", old, getFilters());
139 }
140
141 /**
142 * Returns if antialiasing is turned on or not. The default value is true.
143 * This is a bound property.
144 * @return the current antialiasing setting
145 */
146 public boolean isAntialiasing() {
147 return antialiasing;
148 }
149 /**
150 * Sets the antialiasing setting. This is a bound property.
151 * @param value the new antialiasing setting
152 */
153 public void setAntialiasing(boolean value) {
154 boolean old = isAntialiasing();
155 antialiasing = value;
156 if (old != value) setDirty(true);
157 firePropertyChange("antialiasing", old, isAntialiasing());
158 }
159
160 /**
161 * Gets the current interpolation setting. This property determines if interpolation will
162 * be used when drawing scaled images. @see java.awt.RenderingHints.KEY_INTERPOLATION.
163 * @return the current interpolation setting
164 */
165 public Interpolation getInterpolation() {
166 return interpolation;
167 }
168
169 /**
170 * Sets a new value for the interpolation setting. This setting determines if interpolation
171 * should be used when drawing scaled images. @see java.awt.RenderingHints.KEY_INTERPOLATION.
172 * @param value the new interpolation setting
173 */
174 public void setInterpolation(Interpolation value) {
175 Object old = getInterpolation();
176 this.interpolation = value == null ? Interpolation.NearestNeighbor : value;
177 if (old != value) setDirty(true);
178 firePropertyChange("interpolation", old, getInterpolation());
179 }
180
181 /**
182 * Gets the visible property. This controls if the painter should
183 * paint itself. It is true by default. Setting visible to false
184 * is good when you want to temporarily turn off a painter. An example
185 * of this is a painter that you only use when a button is highlighted.
186 *
187 * @return current value of visible property
188 */
189 public boolean isVisible() {
190 return this.visible;
191 }
192
193 /**
194 * <p>Sets the visible property. This controls if the painter should
195 * paint itself. It is true by default. Setting visible to false
196 * is good when you want to temporarily turn off a painter. An example
197 * of this is a painter that you only use when a button is highlighted.</p>
198 *
199 * @param visible New value of visible property.
200 */
201 public void setVisible(boolean visible) {
202 boolean old = isVisible();
203 this.visible = visible;
204 if (old != visible) setDirty(true); //not the most efficient, but I must do this otherwise a CompoundPainter
205 //or other aggregate painter won't know that it is now invalid
206 //there might be a tricky solution but that is a performance optimization
207 firePropertyChange("visible", old, isVisible());
208 }
209
210 /**
211 * <p>Gets whether this <code>AbstractPainter</code> can be cached as an image.
212 * If cacheing is enabled, then it is the responsibility of the developer to
213 * invalidate the painter (via {@link #clearCache}) if external state has
214 * changed in such a way that the painter is invalidated and needs to be
215 * repainted.</p>
216 *
217 * @return whether this is cacheable
218 */
219 public boolean isCacheable() {
220 return cacheable;
221 }
222
223 /**
224 * <p>Sets whether this <code>AbstractPainter</code> can be cached as an image.
225 * If true, this is treated as a hint. That is, a cacheable may or may not be used.
226 * The {@link #shouldUseCache} method actually determines whether the cacheable is used.
227 * However, if false, then this is treated as an absolute value. That is, no
228 * cacheable will be used.</p>
229 *
230 * <p>If set to false, then #clearCache is called to free system resources.</p>
231 *
232 * @param cacheable
233 */
234 public void setCacheable(boolean cacheable) {
235 boolean old = isCacheable();
236 this.cacheable = cacheable;
237 firePropertyChange("cacheable", old, isCacheable());
238 if (!isCacheable()) {
239 clearCache();
240 }
241 }
242
243 /**
244 * <p>Call this method to clear the cacheable. This may be called whether there is
245 * a cacheable being used or not. If cleared, on the next call to <code>paint</code>,
246 * the painting routines will be called.</p>
247 *
248 * <p><strong>Subclasses</strong>If overridden in subclasses, you
249 * <strong>must</strong> call super.clearCache, or physical
250 * resources (such as an Image) may leak.</p>
251 */
252 public void clearCache() {
253 BufferedImage cache = cachedImage == null ? null : cachedImage.get();
254 if (cache != null) {
255 cache.flush();
256 }
257 cacheCleared = true;
258 if (!isCacheable()) {
259 cachedImage = null;
260 }
261 }
262
263 /**
264 * Only made package private for testing. Don't call this method outside
265 * of this class! This is NOT a bound property
266 */
267 boolean isCacheCleared() {
268 return cacheCleared;
269 }
270
271 /**
272 * <p>Called to allow <code>Painter</code> subclasses a chance to see if any state
273 * in the given object has changed from the last paint operation. If it has, then
274 * the <code>Painter</code> has a chance to mark itself as dirty, thus causing a
275 * repaint, even if cached.</p>
276 *
277 * @param object
278 */
279 protected void validate(T object) { }
280
281 /**
282 * Ye olde dirty bit. If true, then the painter is considered dirty and in need of
283 * being repainted. This is a bound property.
284 *
285 * @return true if the painter state has changed and the painter needs to be
286 * repainted.
287 */
288 protected boolean isDirty() {
289 return dirty;
290 }
291
292 /**
293 * Sets the dirty bit. If true, then the painter is considered dirty, and the cache
294 * will be cleared. This property is bound.
295 *
296 * @param d whether this <code>Painter</code> is dirty.
297 */
298 protected void setDirty(boolean d) {
299 boolean old = isDirty();
300 this.dirty = d;
301 firePropertyChange("dirty", old, isDirty());
302 if (isDirty()) {
303 clearCache();
304 }
305 }
306
307 /**
308 * <p>Returns true if the painter should use caching. This method allows subclasses to
309 * specify the heuristics regarding whether to cache or not. If a <code>Painter</code>
310 * has intelligent rules regarding painting times, and can more accurately indicate
311 * whether it should be cached, it could implement that logic in this method.</p>
312 *
313 * @return whether or not a cache should be used
314 */
315 protected boolean shouldUseCache() {
316 return isCacheable() || filters.length > 0; //NOTE, I can only do this because getFilters() is final
317 }
318
319 /**
320 * <p>This method is called by the <code>paint</code> method prior to
321 * any drawing operations to configure the drawing surface. The default
322 * implementation sets the rendering hints that have been specified for
323 * this <code>AbstractPainter</code>.</p>
324 *
325 * <p>This method can be overriden by subclasses to modify the drawing
326 * surface before any painting happens.</p>
327 *
328 * @param g the graphics surface to configure. This will never be null.
329 * @see #paint(Graphics2D, Object, int, int)
330 */
331 protected void configureGraphics(Graphics2D g) {
332 //configure antialiasing
333 if(isAntialiasing()) {
334 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
335 RenderingHints.VALUE_ANTIALIAS_ON);
336 } else {
337 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
338 RenderingHints.VALUE_ANTIALIAS_OFF);
339 }
340
341 getInterpolation().configureGraphics(g);
342 }
343
344
345 /**
346 * Subclasses must implement this method and perform custom painting operations
347 * here.
348 * @param width
349 * @param height
350 * @param g The Graphics2D object in which to paint
351 * @param object
352 */
353 protected abstract void doPaint(Graphics2D g, T object, int width, int height);
354
355 /**
356 * @inheritDoc
357 */
358 public final void paint(Graphics2D g, T obj, int width, int height) {
359 if (g == null) {
360 throw new NullPointerException("The Graphics2D must be supplied");
361 }
362
363 if(!isVisible() || width < 1 || height < 1) {
364 return;
365 }
366
367 configureGraphics(g);
368
369 //paint to a temporary image if I'm caching, or if there are filters to apply
370 if (shouldUseCache() || filters.length > 0) {
371 validate(obj);
372 BufferedImage cache = cachedImage == null ? null : cachedImage.get();
373 boolean invalidCache = null == cache ||
374 cache.getWidth() != width ||
375 cache.getHeight() != height;
376
377 if (cacheCleared || invalidCache || isDirty()) {
378 //rebuild the cacheable. I do this both if a cacheable is needed, and if any
379 //filters exist. I only *save* the resulting image if caching is turned on
380 if (invalidCache) {
381 cache = GraphicsUtilities.createCompatibleTranslucentImage(width, height);
382 }
383 Graphics2D gfx = cache.createGraphics();
384
385 try {
386 gfx.setClip(0, 0, width, height);
387
388 if (!invalidCache) {
389 // If we are doing a repaint, but we didn't have to
390 // recreate the image, we need to clear it back
391 // to a fully transparent background.
392 Composite composite = gfx.getComposite();
393 gfx.setComposite(AlphaComposite.Clear);
394 gfx.fillRect(0, 0, width, height);
395 gfx.setComposite(composite);
396 }
397
398 configureGraphics(gfx);
399 doPaint(gfx, obj, width, height);
400 } finally {
401 gfx.dispose();
402 }
403
404 for (BufferedImageOp f : getFilters()) {
405 cache = f.filter(cache, null);
406 }
407
408 //only save the temporary image as the cacheable if I'm caching
409 if (shouldUseCache()) {
410 cachedImage = new SoftReference<BufferedImage>(cache);
411 cacheCleared = false;
412 }
413 }
414
415 g.drawImage(cache, 0, 0, null);
416 } else {
417 //can't use the cacheable, so just paint
418 doPaint(g, obj, width, height);
419 }
420
421 //painting has occured, so restore the dirty bit to false
422 setDirty(false);
423 }
424 }