/* * $Id: AbstractPainter.java 4082 2011-11-15 18:39:43Z kschaefe $ * * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle, * Santa Clara, California 95054, U.S.A. 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. * * 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. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package org.jdesktop.swingx.painter; import java.awt.AlphaComposite; import java.awt.Composite; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.image.BufferedImage; import java.awt.image.BufferedImageOp; import java.lang.ref.SoftReference; import org.jdesktop.beans.AbstractBean; import org.jdesktop.swingx.util.GraphicsUtilities; /** *
A convenient base class from which concrete {@link Painter} implementations may
* extend. It extends {@link org.jdesktop.beans.AbstractBean} as a convenience for
* adding property change notification support. In addition, AbstractPainter
* provides subclasses with the ability to cacheable painting operations, configure the
* drawing surface with common settings (such as antialiasing and interpolation), and
* toggle whether a subclass paints or not via the visibility property.
Subclasses of AbstractPainter generally need only override the
* {@link #doPaint(Graphics2D, Object, int, int)} method. If a subclass requires more control
* over whether caching is enabled, or for configuring the graphics state, then it
* may override the appropriate protected methods to interpose its own behavior.
For example, here is the doPaint method of a simple Painter that
* paints an opaque rectangle:
*
* public void doPaint(Graphics2D g, T obj, int width, int height) {
* g.setPaint(Color.BLUE);
* g.fillRect(0, 0, width, height);
* }
*
*
* @author rbair
* @param A convenience method for specifying the filters to use based on * BufferedImageOps. These will each be individually wrapped by an ImageFilter * and then setFilters(Effect... filters) will be called with the resulting * array
* * * @param effects the BufferedImageOps to wrap as filters */ public void setFilters(BufferedImageOp ... effects) { if (effects == null) effects = new BufferedImageOp[0]; BufferedImageOp[] old = getFilters(); this.filters = new BufferedImageOp[effects.length]; System.arraycopy(effects, 0, this.filters, 0, this.filters.length); setDirty(true); firePropertyChange("filters", old, getFilters()); } /** * Returns if antialiasing is turned on or not. The default value is true. * This is a bound property. * @return the current antialiasing setting */ public boolean isAntialiasing() { return antialiasing; } /** * Sets the antialiasing setting. This is a bound property. * @param value the new antialiasing setting */ public void setAntialiasing(boolean value) { boolean old = isAntialiasing(); antialiasing = value; if (old != value) setDirty(true); firePropertyChange("antialiasing", old, isAntialiasing()); } /** * Gets the current interpolation setting. This property determines if interpolation will * be used when drawing scaled images. @see java.awt.RenderingHints.KEY_INTERPOLATION. * @return the current interpolation setting */ public Interpolation getInterpolation() { return interpolation; } /** * Sets a new value for the interpolation setting. This setting determines if interpolation * should be used when drawing scaled images. @see java.awt.RenderingHints.KEY_INTERPOLATION. * @param value the new interpolation setting */ public void setInterpolation(Interpolation value) { Object old = getInterpolation(); this.interpolation = value == null ? Interpolation.NearestNeighbor : value; if (old != value) setDirty(true); firePropertyChange("interpolation", old, getInterpolation()); } /** * Gets the visible property. This controls if the painter should * paint itself. It is true by default. Setting visible to false * is good when you want to temporarily turn off a painter. An example * of this is a painter that you only use when a button is highlighted. * * @return current value of visible property */ public boolean isVisible() { return this.visible; } /** *Sets the visible property. This controls if the painter should * paint itself. It is true by default. Setting visible to false * is good when you want to temporarily turn off a painter. An example * of this is a painter that you only use when a button is highlighted.
* * @param visible New value of visible property. */ public void setVisible(boolean visible) { boolean old = isVisible(); this.visible = visible; if (old != visible) setDirty(true); //not the most efficient, but I must do this otherwise a CompoundPainter //or other aggregate painter won't know that it is now invalid //there might be a tricky solution but that is a performance optimization firePropertyChange("visible", old, isVisible()); } /** *Gets whether this AbstractPainter can be cached as an image.
* If caching is enabled, then it is the responsibility of the developer to
* invalidate the painter (via {@link #clearCache}) if external state has
* changed in such a way that the painter is invalidated and needs to be
* repainted.
Sets whether this AbstractPainter can be cached as an image.
* If true, this is treated as a hint. That is, a cacheable may or may not be used.
* The {@link #shouldUseCache} method actually determines whether the cacheable is used.
* However, if false, then this is treated as an absolute value. That is, no
* cacheable will be used.
If set to false, then #clearCache is called to free system resources.
* * @param cacheable the cache flag */ public void setCacheable(boolean cacheable) { boolean old = isCacheable(); this.cacheable = cacheable; firePropertyChange("cacheable", old, isCacheable()); if (!isCacheable()) { clearCache(); } } /** *Call this method to clear the cacheable. This may be called whether there is
* a cacheable being used or not. If cleared, on the next call to paint,
* the painting routines will be called.
SubclassesIf overridden in subclasses, you * must call super.clearCache, or physical * resources (such as an Image) may leak.
*/ public void clearCache() { BufferedImage cache = cachedImage == null ? null : cachedImage.get(); if (cache != null) { cache.flush(); } cacheCleared = true; if (!isCacheable()) { cachedImage = null; } } // /** // * Only made package private for testing. Don't call this method outside // * of this class! This is NOT a bound property // */ // private boolean isCacheCleared() { // return cacheCleared; // } /** *Called to allow Painter subclasses a chance to see if any state
* in the given object has changed from the last paint operation. If it has, then
* the Painter has a chance to mark itself as dirty, thus causing a
* repaint, even if cached.
Painter is dirty.
*/
protected void setDirty(boolean d) {
boolean old = isDirty();
this.dirty = d;
firePropertyChange("dirty", old, isDirty());
if (isDirty()) {
clearCache();
}
}
boolean isInPaintContext() {
return inPaintContext;
}
void setInPaintContext(boolean inPaintContext) {
this.inPaintContext = inPaintContext;
}
/**
* Returns true if the painter should use caching. This method allows subclasses to
* specify the heuristics regarding whether to cache or not. If a Painter
* has intelligent rules regarding painting times, and can more accurately indicate
* whether it should be cached, it could implement that logic in this method.
This method is called by the paint method prior to
* any drawing operations to configure the drawing surface. The default
* implementation sets the rendering hints that have been specified for
* this AbstractPainter.
This method can be overridden by subclasses to modify the drawing * surface before any painting happens.
* * @param g the graphics surface to configure. This will never be null. * @see #paint(Graphics2D, Object, int, int) */ protected void configureGraphics(Graphics2D g) { //configure antialiasing if(isAntialiasing()) { g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); } else { g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); } getInterpolation().configureGraphics(g); } /** * Subclasses must implement this method and perform custom painting operations * here. * @param width the width * @param height the height * @param g The Graphics2D object in which to paint * @param object an optional configuration parameter */ protected abstract void doPaint(Graphics2D g, T object, int width, int height); @Override public final void paint(Graphics2D g, T obj, int width, int height) { if (g == null) { throw new NullPointerException("The Graphics2D must be supplied"); } if(!isVisible() || width < 1 || height < 1) { return; } configureGraphics(g); //paint to a temporary image if I'm caching, or if there are filters to apply if (shouldUseCache() || filters.length > 0) { validate(obj); BufferedImage cache = cachedImage == null ? null : cachedImage.get(); boolean invalidCache = null == cache || cache.getWidth() != width || cache.getHeight() != height; if (cacheCleared || invalidCache || isDirty()) { //rebuild the cacheable. I do this both if a cacheable is needed, and if any //filters exist. I only *save* the resulting image if caching is turned on if (invalidCache) { cache = GraphicsUtilities.createCompatibleTranslucentImage(width, height); } if (cache != null) { Graphics2D gfx = cache.createGraphics(); try { gfx.setClip(0, 0, width, height); if (!invalidCache) { // If we are doing a repaint, but we didn't have to // recreate the image, we need to clear it back // to a fully transparent background. Composite composite = gfx.getComposite(); gfx.setComposite(AlphaComposite.Clear); gfx.fillRect(0, 0, width, height); gfx.setComposite(composite); } configureGraphics(gfx); doPaint(gfx, obj, width, height); } finally { gfx.dispose(); } if (!isInPaintContext()) { for (BufferedImageOp f : getFilters()) { cache = f.filter(cache, null); } } //only save the temporary image as the cacheable if I'm caching if (shouldUseCache()) { cachedImage = new SoftReference