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}