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    }