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