001 /*
002 * $Id: AbstractPainter.java,v 1.17 2006/05/14 08:19:44 dmouse Exp $
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.Color;
025 import java.awt.Composite;
026 import java.awt.Font;
027 import java.awt.Graphics2D;
028 import java.awt.Paint;
029 import java.awt.RenderingHints;
030 import java.awt.Shape;
031 import java.awt.Stroke;
032 import java.awt.Transparency;
033 import java.awt.geom.AffineTransform;
034 import java.awt.geom.RoundRectangle2D;
035 import java.awt.image.BufferedImage;
036 import java.awt.image.BufferedImageOp;
037 import java.lang.ref.SoftReference;
038 import java.util.HashMap;
039 import java.util.Map;
040 import javax.swing.JComponent;
041 import org.jdesktop.swingx.JavaBean;
042 import org.jdesktop.swingx.util.PaintUtils;
043 import org.jdesktop.swingx.util.Resize;
044
045 /**
046 * <p>A convenient base class from which concrete Painter implementations may
047 * extend. It extends JavaBean and thus provides property change notification
048 * (which is crucial for the Painter implementations to be available in a
049 * GUI builder). It also saves off the Graphics2D state in its "saveState" method,
050 * and restores that state in the "restoreState" method. Sublasses simply need
051 * to extend AbstractPainter and implement the paintBackground method.
052 *
053 * <p>For example, here is the paintBackground method of BackgroundPainter:
054 * <pre><code>
055 * public void paintBackground(Graphics2D g, JComponent component) {
056 * g.setColor(component.getBackground());
057 * g.fillRect(0, 0, component.getWidth(), component.getHeight());
058 * }
059 * </code></pre>
060 *
061 * <p>AbstractPainter provides a very useful default implementation of
062 * the paint method. It:
063 * <ol>
064 * <li>Saves off the old state</li>
065 * <li>Sets any specified rendering hints</li>
066 * <li>Sets the Clip if there is one</li>
067 * <li>Sets the Composite if there is one</li>
068 * <li>Delegates to paintBackground</li>
069 * <li>Restores the original Graphics2D state</li>
070 * <ol></p>
071 *
072 * <p>Specifying rendering hints can greatly improve the visual impact of your
073 * applications. For example, by default Swing doesn't do much in the way of
074 * antialiasing (except for Fonts, but that's another story). Pinstripes don't
075 * look so good without antialiasing. So if I were going to paint pinstripes, I
076 * might do it like this:
077 * <pre><code>
078 * PinstripePainter p = new PinstripePainter();
079 * p.setAntialiasing(RenderingHints.VALUE_ANTIALIAS_ON);
080 * </code></pre></p>
081 *
082 * <p>You can read more about antialiasing and other rendering hints in the
083 * java.awt.RenderingHints documentation. <strong>By nature, changing the rendering
084 * hints may have an impact on performance. Certain hints require more
085 * computation, others require less</strong></p>
086 *
087 * @author rbair
088 */
089 public abstract class AbstractPainter<T extends JComponent> extends JavaBean implements Painter<T> {
090 //------------------------------------------------- Saved Graphics State
091 private boolean stateSaved = false;
092 private Paint oldPaint;
093 private Font oldFont;
094 private Stroke oldStroke;
095 private AffineTransform oldTransform;
096 private Composite oldComposite;
097 private Shape oldClip;
098 private Color oldBackground;
099 private Color oldColor;
100 private RenderingHints oldRenderingHints;
101
102 //--------------------------------------------------- Instance Variables
103 /**
104 * A Shape that is used to clip the graphics area. Anything within this
105 * clip shape is included in the final output.
106 */
107 private Shape clip;
108 /**
109 * A Resize value indicating if and how the clip should be resized
110 * according to the size of the Component
111 */
112 private Resize resizeClip = Resize.BOTH;
113 /**
114 * The composite to use. By default this is a reasonable AlphaComposite,
115 * but you may want to specify a different composite
116 */
117 private Composite composite;
118 /**
119 * RenderingHints to apply when painting
120 */
121 private Map<RenderingHints.Key,Object> renderingHints;
122 /**
123 * A hint as to whether or not to attempt caching the image
124 */
125 private boolean useCache = false;
126 /**
127 * The cached image, if useCache is true
128 */
129 private SoftReference<BufferedImage> cachedImage;
130 /**
131 * The Effects to apply to the results of the paint() operation
132 */
133 private Effect[] effects = new Effect[0];
134
135 /**
136 * Creates a new instance of AbstractPainter
137 */
138 public AbstractPainter() {
139 renderingHints = new HashMap<RenderingHints.Key,Object>();
140 }
141
142 /**
143 * <p>Sets whether to cache the painted image with a SoftReference in a BufferedImage
144 * between calls. If true, and if the size of the component hasn't changed,
145 * then the cached image will be used rather than causing a painting operation.</p>
146 *
147 * <p>This should be considered a hint, rather than absolute. Several factors may
148 * force repainting, including low memory, different component sizes, or possibly
149 * new rendering hint settings, etc.</p>
150 *
151 * @param b whether or not to use the cache
152 */
153 public void setUseCache(boolean b) {
154 boolean old = isUseCache();
155 useCache = b;
156 firePropertyChange("useCache", old, isUseCache());
157 //if there was a cached image and I'm no longer using the cache, blow it away
158 if (cachedImage != null && !isUseCache()) {
159 cachedImage = null;
160 }
161 }
162
163 /**
164 * @return whether or not the cache should be used
165 */
166 public boolean isUseCache() {
167 return useCache;
168 }
169
170 /**
171 * <p>Sets the effects to apply to the results of the AbstractPainter's
172 * painting operation. Some common effects include blurs, shadows, embossing,
173 * and so forth. If the given effects is a null array, no effects will be used</p>
174 *
175 * @param effects the Effects to apply to the results of the AbstractPainter's
176 * painting operation
177 */
178 public void setEffects(Effect... effects) {
179 Effect[] old = getEffects();
180 this.effects = new Effect[effects == null ? 0 : effects.length];
181 if (effects != null) {
182 System.arraycopy(effects, 0, this.effects, 0, effects.length);
183 }
184 firePropertyChange("effects", old, getEffects());
185 firePropertyChange("effects", old, getEffects());
186 }
187
188 /**
189 * <p>A convenience method for specifying the effects to use based on
190 * BufferedImageOps. These will each be individually wrapped by an ImageEffect
191 * and then setEffects(Effect... effects) will be called with the resulting
192 * array</p>
193 *
194 * @param filters the BufferedImageOps to wrap as effects
195 */
196 public void setEffects(BufferedImageOp... filters) {
197 Effect[] effects = new Effect[filters == null ? 0 : filters.length];
198 if (filters != null) {
199 int index = 0;
200 for (BufferedImageOp op : filters) {
201 effects[index++] = new ImageEffect(op);
202 }
203 }
204 setEffects(effects);
205 }
206
207 /**
208 * @return effects a defensive copy of the Effects to apply to the results
209 * of the AbstractPainter's painting operation. Will never null
210 */
211 public Effect[] getEffects() {
212 Effect[] results = new Effect[effects.length];
213 System.arraycopy(effects, 0, results, 0, results.length);
214 return results;
215 }
216
217 /**
218 * Specifies the Shape to use for clipping the painting area. This
219 * may be null
220 *
221 * @param clip the Shape to use to clip the area. Whatever is inside this
222 * shape will be kept, everything else "clipped". May be null. If
223 * null, the clipping is not set on the graphics object
224 */
225 public void setClip(Shape clip) {
226 Shape old = getClip();
227 this.clip = clip;
228 firePropertyChange("clip", old, getClip());
229 }
230
231 /**
232 * @return the clipping shape
233 */
234 public Shape getClip() {
235 return clip;
236 }
237
238 /**
239 * Specifies the resize behavior of the clip. As with all other properties
240 * that rely on Resize, the value of the width/height of the shape will
241 * represent a percentage of the width/height of the component, as a value
242 * between 0 and 1
243 *
244 * @param r value indication whether/how to resize the clip. If null,
245 * Resize.NONE will be used
246 */
247 public void setResizeClip(Resize r) {
248 Resize old = getResizeClip();
249 this.resizeClip = r == null ? Resize.NONE : r;
250 firePropertyChange("resizeClip", old, getResizeClip());
251 }
252
253 /**
254 * @return value indication whether/how to resize the clip. Will never be null
255 */
256 public Resize getResizeClip() {
257 return resizeClip;
258 }
259
260 /**
261 * Sets the Composite to use. For example, you may specify a specific
262 * AlphaComposite so that when this Painter paints, any content in the
263 * drawing area is handled properly
264 *
265 * @param c The composite to use. If null, then no composite will be
266 * specified on the graphics object
267 */
268 public void setComposite(Composite c) {
269 Composite old = getComposite();
270 this.composite = c;
271 firePropertyChange("composite", old, getComposite());
272 }
273
274 /**
275 * @return the composite
276 */
277 public Composite getComposite() {
278 return composite;
279 }
280
281 /**
282 * @return the technique used for interpolating alpha values. May be one
283 * of:
284 * <ul>
285 * <li>RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED</li>
286 * <li>RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY</li>
287 * <li>RenderingHints.VALUE_ALPHA_INTERPOLATION_DEFAULT</li>
288 * </ul>
289 */
290 public Object getAlphaInterpolation() {
291 return renderingHints.get(RenderingHints.KEY_ALPHA_INTERPOLATION);
292 }
293
294 /**
295 * Sets the technique used for interpolating alpha values.
296 *
297 * @param alphaInterpolation
298 * May be one of:
299 * <ul>
300 * <li>RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED</li>
301 * <li>RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY</li>
302 * <li>RenderingHints.VALUE_ALPHA_INTERPOLATION_DEFAULT</li>
303 * </ul>
304 */
305 public void setAlphaInterpolation(Object alphaInterpolation) {
306 if (alphaInterpolation != null &&
307 !RenderingHints.KEY_ALPHA_INTERPOLATION.isCompatibleValue(alphaInterpolation)) {
308 throw new IllegalArgumentException(alphaInterpolation + " is not an acceptable value");
309 }
310 Object old = getAlphaInterpolation();
311 renderingHints.put(RenderingHints.KEY_ALPHA_INTERPOLATION, alphaInterpolation);
312 firePropertyChange("alphaInterpolation", old, getAlphaInterpolation());
313 }
314
315 /**
316 * @return whether or not to antialias
317 * May be one of:
318 * <ul>
319 * <li>RenderingHints.VALUE_ANTIALIAS_DEFAULT</li>
320 * <li>RenderingHints.VALUE_ANTIALIAS_OFF</li>
321 * <li>RenderingHints.VALUE_ANTIALIAS_ON</li>
322 * </ul>
323 */
324 public Object getAntialiasing() {
325 return renderingHints.get(RenderingHints.KEY_ANTIALIASING);
326 }
327
328 /**
329 * Sets whether or not to antialias
330 * @param antialiasing
331 * May be one of:
332 * <ul>
333 * <li>RenderingHints.VALUE_ANTIALIAS_DEFAULT</li>
334 * <li>RenderingHints.VALUE_ANTIALIAS_OFF</li>
335 * <li>RenderingHints.VALUE_ANTIALIAS_ON</li>
336 * </ul>
337 */
338 public void setAntialiasing(Object antialiasing) {
339 if (antialiasing != null &&
340 !RenderingHints.KEY_ANTIALIASING.isCompatibleValue(antialiasing)) {
341 throw new IllegalArgumentException(antialiasing + " is not an acceptable value");
342 }
343 Object old = getAntialiasing();
344 renderingHints.put(RenderingHints.KEY_ANTIALIASING, antialiasing);
345 firePropertyChange("antialiasing", old, getAntialiasing());
346 }
347
348 /**
349 * @return the technique to use for rendering colors
350 * May be one of:
351 * <ul>
352 * <li>RenderingHints.VALUE_COLOR_RENDER_DEFAULT</li>
353 * <li>RenderingHints.VALUE_RENDER_QUALITY</li>
354 * <li>RenderingHints.VALUE_RENDER_SPEED</li>
355 * </ul>
356 */
357 public Object getColorRendering() {
358 return renderingHints.get(RenderingHints.KEY_COLOR_RENDERING);
359 }
360
361 /**
362 * Sets the technique to use for rendering colors
363 * @param colorRendering
364 * May be one of:
365 * <ul>
366 * <li>RenderingHints.VALUE_COLOR_RENDER_DEFAULT</li>
367 * <li>RenderingHints.VALUE_RENDER_QUALITY</li>
368 * <li>RenderingHints.VALUE_RENDER_SPEED</li>
369 * </ul>
370 */
371 public void setColorRendering(Object colorRendering) {
372 if (colorRendering != null &&
373 !RenderingHints.KEY_COLOR_RENDERING.isCompatibleValue(colorRendering)) {
374 throw new IllegalArgumentException(colorRendering + " is not an acceptable value");
375 }
376 Object old = getColorRendering();
377 renderingHints.put(RenderingHints.KEY_COLOR_RENDERING, colorRendering);
378 firePropertyChange("colorRendering", old, getColorRendering());
379 }
380
381 /**
382 * @return whether or not to dither
383 * May be one of:
384 * <ul>
385 * <li>RenderingHints.VALUE_DITHER_DEFAULT</li>
386 * <li>RenderingHints.VALUE_DITHER_ENABLE</li>
387 * <li>RenderingHints.VALUE_DITHER_DISABLE</li>
388 * </ul>
389 */
390 public Object getDithering() {
391 return renderingHints.get(RenderingHints.KEY_DITHERING);
392 }
393
394 /**
395 * Sets whether or not to dither
396 * @param dithering
397 * May be one of:
398 * <ul>
399 * <li>RenderingHints.VALUE_DITHER_DEFAULT</li>
400 * <li>RenderingHints.VALUE_DITHER_ENABLE</li>
401 * <li>RenderingHints.VALUE_DITHER_DISABLE</li>
402 * </ul>
403 */
404 public void setDithering(Object dithering) {
405 if (dithering != null &&
406 !RenderingHints.KEY_DITHERING.isCompatibleValue(dithering)) {
407 throw new IllegalArgumentException(dithering + " is not an acceptable value");
408 }
409 Object old = getDithering();
410 renderingHints.put(RenderingHints.KEY_DITHERING, dithering);
411 firePropertyChange("dithering", old, getDithering());
412 }
413
414 /**
415 * @return whether or not to use fractional metrics
416 * May be one of:
417 * <ul>
418 * <li>RenderingHints.VALUE_FRACTIONALMETRICS_DEFAULT</li>
419 * <li>RenderingHints.VALUE_FRACTIONALMETRICS_OFF</li>
420 * <li>RenderingHints.VALUE_FRACTIONALMETRICS_ON</li>
421 * </ul>
422 */
423 public Object getFractionalMetrics() {
424 return renderingHints.get(RenderingHints.KEY_FRACTIONALMETRICS);
425 }
426
427 /**
428 * Sets whether or not to use fractional metrics
429 *
430 * @param fractionalMetrics
431 * May be one of:
432 * <ul>
433 * <li>RenderingHints.VALUE_FRACTIONALMETRICS_DEFAULT</li>
434 * <li>RenderingHints.VALUE_FRACTIONALMETRICS_OFF</li>
435 * <li>RenderingHints.VALUE_FRACTIONALMETRICS_ON</li>
436 * </ul>
437 */
438 public void setFractionalMetrics(Object fractionalMetrics) {
439 if (fractionalMetrics != null &&
440 !RenderingHints.KEY_FRACTIONALMETRICS.isCompatibleValue(fractionalMetrics)) {
441 throw new IllegalArgumentException(fractionalMetrics + " is not an acceptable value");
442 }
443 Object old = getFractionalMetrics();
444 renderingHints.put(RenderingHints.KEY_FRACTIONALMETRICS, fractionalMetrics);
445 firePropertyChange("fractionalMetrics", old, getFractionalMetrics());
446 }
447
448 /**
449 * @return the technique to use for interpolation (used esp. when scaling)
450 * May be one of:
451 * <ul>
452 * <li>RenderingHints.VALUE_INTERPOLATION_BICUBIC</li>
453 * <li>RenderingHints.VALUE_INTERPOLATION_BILINEAR</li>
454 * <li>RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR</li>
455 * </ul>
456 */
457 public Object getInterpolation() {
458 return renderingHints.get(RenderingHints.KEY_INTERPOLATION);
459 }
460
461 /**
462 * Sets the technique to use for interpolation (used esp. when scaling)
463 * @param interpolation
464 * May be one of:
465 * <ul>
466 * <li>RenderingHints.VALUE_INTERPOLATION_BICUBIC</li>
467 * <li>RenderingHints.VALUE_INTERPOLATION_BILINEAR</li>
468 * <li>RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR</li>
469 * </ul>
470 */
471 public void setInterpolation(Object interpolation) {
472 if (interpolation != null &&
473 !RenderingHints.KEY_INTERPOLATION.isCompatibleValue(interpolation)) {
474 throw new IllegalArgumentException(interpolation + " is not an acceptable value");
475 }
476 Object old = getInterpolation();
477 renderingHints.put(RenderingHints.KEY_INTERPOLATION, interpolation);
478 firePropertyChange("interpolation", old, getInterpolation());
479 }
480
481 /**
482 * @return a hint as to techniques to use with regards to rendering quality vs. speed
483 * May be one of:
484 * <ul>
485 * <li>RenderingHints.VALUE_RENDER_QUALITY</li>
486 * <li>RenderingHints.VALUE_RENDER_SPEED</li>
487 * <li>RenderingHints.VALUE_RENDER_DEFAULT</li>
488 * </ul>
489 */
490 public Object getRendering() {
491 return renderingHints.get(RenderingHints.KEY_RENDERING);
492 }
493
494 /**
495 * Specifies a hint as to techniques to use with regards to rendering quality vs. speed
496 *
497 * @param rendering
498 * May be one of:
499 * <ul>
500 * <li>RenderingHints.VALUE_RENDER_QUALITY</li>
501 * <li>RenderingHints.VALUE_RENDER_SPEED</li>
502 * <li>RenderingHints.VALUE_RENDER_DEFAULT</li>
503 * </ul>
504 */
505 public void setRendering(Object rendering) {
506 if (rendering != null &&
507 !RenderingHints.KEY_RENDERING.isCompatibleValue(rendering)) {
508 throw new IllegalArgumentException(rendering + " is not an acceptable value");
509 }
510 Object old = getRendering();
511 renderingHints.put(RenderingHints.KEY_RENDERING, rendering);
512 firePropertyChange("rendering", old, getRendering());
513 }
514
515 /**
516 * @return technique for rendering strokes
517 * May be one of:
518 * <ul>
519 * <li>RenderingHints.VALUE_STROKE_DEFAULT</li>
520 * <li>RenderingHints.VALUE_STROKE_NORMALIZE</li>
521 * <li>RenderingHints.VALUE_STROKE_PURE</li>
522 * </ul>
523 */
524 public Object getStrokeControl() {
525 return renderingHints.get(RenderingHints.KEY_STROKE_CONTROL);
526 }
527
528 /**
529 * Specifies a technique for rendering strokes
530 *
531 * @param strokeControl
532 * May be one of:
533 * <ul>
534 * <li>RenderingHints.VALUE_STROKE_DEFAULT</li>
535 * <li>RenderingHints.VALUE_STROKE_NORMALIZE</li>
536 * <li>RenderingHints.VALUE_STROKE_PURE</li>
537 * </ul>
538 */
539 public void setStrokeControl(Object strokeControl) {
540 if (strokeControl != null &&
541 !RenderingHints.KEY_STROKE_CONTROL.isCompatibleValue(strokeControl)) {
542 throw new IllegalArgumentException(strokeControl + " is not an acceptable value");
543 }
544 Object old = getStrokeControl();
545 renderingHints.put(RenderingHints.KEY_STROKE_CONTROL, strokeControl);
546 firePropertyChange("strokeControl", old, getStrokeControl());
547 }
548
549 /**
550 * @return technique for anti-aliasing text.
551 * (TODO this needs to be updated for Mustang. You may use the
552 * new Mustang values, and everything will work, but support in
553 * the GUI builder and documentation need to be added once we
554 * branch for Mustang)<br/>
555 * May be one of:
556 * <ul>
557 * <li>RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT</li>
558 * <li>RenderingHints.VALUE_TEXT_ANTIALIAS_OFF</li>
559 * <li>RenderingHints.VALUE_TEXT_ANTIALIAS_ON</li>
560 * </ul>
561 */
562 public Object getTextAntialiasing() {
563 return renderingHints.get(RenderingHints.KEY_TEXT_ANTIALIASING);
564 }
565
566 /**
567 * Sets the technique for anti-aliasing text.
568 * (TODO this needs to be updated for Mustang. You may use the
569 * new Mustang values, and everything will work, but support in
570 * the GUI builder and documentation need to be added once we
571 * branch for Mustang)<br/>
572 *
573 * @param textAntialiasing
574 * May be one of:
575 * <ul>
576 * <li>RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT</li>
577 * <li>RenderingHints.VALUE_TEXT_ANTIALIAS_OFF</li>
578 * <li>RenderingHints.VALUE_TEXT_ANTIALIAS_ON</li>
579 * </ul>
580 */
581 public void setTextAntialiasing(Object textAntialiasing) {
582 if (textAntialiasing != null &&
583 !RenderingHints.KEY_TEXT_ANTIALIASING.isCompatibleValue(textAntialiasing)) {
584 throw new IllegalArgumentException(textAntialiasing + " is not an acceptable value");
585 }
586 Object old = getTextAntialiasing();
587 renderingHints.put(RenderingHints.KEY_TEXT_ANTIALIASING, textAntialiasing);
588 firePropertyChange("textAntialiasing", old, getTextAntialiasing());
589 }
590
591 /**
592 * @return the rendering hint associated with the given key. May return null
593 */
594 public Object getRenderingHint(RenderingHints.Key key) {
595 return renderingHints.get(key);
596 }
597
598 /**
599 * Set the given hint for the given key. This will end up firing the appropriate
600 * property change event if the key is recognized. For example, if the key is
601 * RenderingHints.KEY_ANTIALIASING, then the setAntialiasing method will be
602 * called firing an "antialiasing" property change event if necessary. If
603 * the key is not recognized, no event will be fired but the key will be saved.
604 * The key must not be null
605 *
606 * @param key cannot be null
607 * @param hint must be a hint compatible with the given key
608 */
609 public void setRenderingHint(RenderingHints.Key key, Object hint) {
610 if (key == RenderingHints.KEY_ALPHA_INTERPOLATION) {
611 setAlphaInterpolation(hint);
612 } else if (key == RenderingHints.KEY_ANTIALIASING) {
613 setAntialiasing(hint);
614 } else if (key == RenderingHints.KEY_COLOR_RENDERING) {
615 setColorRendering(hint);
616 } else if (key == RenderingHints.KEY_DITHERING) {
617 setDithering(hint);
618 } else if (key == RenderingHints.KEY_FRACTIONALMETRICS) {
619 setFractionalMetrics(hint);
620 } else if (key == RenderingHints.KEY_INTERPOLATION) {
621 setInterpolation(hint);
622 } else if (key == RenderingHints.KEY_RENDERING) {
623 setRendering(hint);
624 } else if (key == RenderingHints.KEY_STROKE_CONTROL) {
625 setStrokeControl(hint);
626 } else if (key == RenderingHints.KEY_TEXT_ANTIALIASING) {
627 setTextAntialiasing(hint);
628 } else {
629 renderingHints.put(key, hint);
630 }
631 }
632
633 /**
634 * @return a copy of the map of rendering hints held by this class. This
635 * returned value will never be null
636 */
637 public Map<RenderingHints.Key,Object> getRenderingHints() {
638 return new HashMap<RenderingHints.Key,Object>(renderingHints);
639 }
640
641 /**
642 * Sets the rendering hints to use. This will <strong>replace</strong> the
643 * rendering hints entirely, clearing any hints that were previously set.
644 *
645 * @param renderingHints map of hints. May be null. I null, a new Map of
646 * rendering hints will be created
647 */
648 public void setRenderingHints(Map<RenderingHints.Key,Object> renderingHints) {
649 if (renderingHints != null) {
650 this.renderingHints = new HashMap<RenderingHints.Key,Object>(renderingHints);
651 } else {
652 this.renderingHints = new HashMap<RenderingHints.Key, Object>();
653 }
654 firePropertyChange("renderingHints", null, getRenderingHints());
655 }
656
657 /**
658 * Saves the state in the given Graphics2D object so that it may be
659 * restored later.
660 *
661 * @param g the Graphics2D object who's state will be saved
662 */
663 protected void saveState(Graphics2D g) {
664 oldPaint = g.getPaint();
665 oldFont = g.getFont();
666 oldStroke = g.getStroke();
667 oldTransform = g.getTransform();
668 oldComposite = g.getComposite();
669 oldClip = g.getClip();
670 oldBackground = g.getBackground();
671 oldColor = g.getColor();
672
673 //save off the old rendering hints
674 oldRenderingHints = (RenderingHints)g.getRenderingHints().clone();
675
676 stateSaved = true;
677 }
678
679 /**
680 * Restores previously saved state. A call to saveState must have occured
681 * prior to calling restoreState, or an IllegalStateException will be thrown.
682 *
683 * @param g the Graphics2D object to restore previously saved state to
684 */
685 protected void restoreState(Graphics2D g) {
686 if (!stateSaved) {
687 throw new IllegalStateException("A call to saveState must occur " +
688 "prior to calling restoreState");
689 }
690
691 g.setPaint(oldPaint);
692 g.setFont(oldFont);
693 g.setTransform(oldTransform);
694 g.setStroke(oldStroke);
695 g.setComposite(oldComposite);
696 g.setClip(oldClip);
697 g.setBackground(oldBackground);
698 g.setColor(oldColor);
699
700 //restore the rendering hints
701 g.setRenderingHints(oldRenderingHints);
702
703 stateSaved = false;
704 }
705
706 /**
707 * @inheritDoc
708 */
709 public void paint(Graphics2D g, T component) {
710 saveState(g);
711
712 configureGraphics(g, component);
713
714 //if I am cacheing, and the cache is not null, and the image has the
715 //same dimensions as the component, then simply paint the image
716 BufferedImage image = cachedImage == null ? null : cachedImage.get();
717 if (isUseCache() && image != null
718 && image.getWidth() == component.getWidth()
719 && image.getHeight() == component.getHeight()) {
720 g.drawImage(image, 0, 0, null);
721 } else {
722 Effect[] effects = getEffects();
723 if (effects.length > 0 || isUseCache()) {
724 image = PaintUtils.createCompatibleImage(
725 component.getWidth(),
726 component.getHeight(),
727 Transparency.TRANSLUCENT);
728
729 Graphics2D gfx = image.createGraphics();
730 configureGraphics(gfx, component);
731 paintBackground(gfx, component);
732 gfx.dispose();
733
734 for (Effect effect : effects) {
735 image = effect.apply(image);
736 }
737
738 g.drawImage(image, 0, 0, null);
739
740 if (isUseCache()) {
741 cachedImage = new SoftReference<BufferedImage>(image);
742 }
743 } else {
744 paintBackground(g, component);
745 }
746 }
747
748 restoreState(g);
749 }
750
751 /**
752 * Utility method for configuring the given Graphics2D with the rendering hints,
753 * composite, and clip
754 */
755 private void configureGraphics(Graphics2D g, T c) {
756 Map<RenderingHints.Key,Object> hints = getRenderingHints();
757 //merge these hints with the existing ones, otherwise I won't inherit
758 //any of the hints from the Graphics2D
759 for (Object key : hints.keySet()) {
760 Object value = hints.get(key);
761 if (value != null) {
762 g.setRenderingHint((RenderingHints.Key)key, hints.get(key));
763 }
764 }
765
766 if (getComposite() != null) {
767 g.setComposite(getComposite());
768 }
769 Shape clip = getClip();
770 if (clip != null) {
771 //resize the clip if necessary
772 double width = 1;
773 double height = 1;
774 Resize resizeClip = getResizeClip();
775 if (resizeClip == Resize.HORIZONTAL || resizeClip == Resize.BOTH) {
776 width = c.getWidth();
777 }
778 if (resizeClip == Resize.VERTICAL || resizeClip == Resize.BOTH) {
779 height = c.getHeight();
780 }
781 if (clip instanceof RoundRectangle2D) {
782 RoundRectangle2D rect = (RoundRectangle2D)clip;
783 clip = new RoundRectangle2D.Double(
784 rect.getX(), rect.getY(), width, height,
785 rect.getArcWidth(), rect.getArcHeight());
786 } else {
787 clip = AffineTransform.getScaleInstance(
788 width, height).createTransformedShape(clip);
789 }
790 g.setClip(clip);
791 }
792 }
793
794 /**
795 * Subclasses should implement this method and perform custom painting operations
796 * here. Common behavior, such as setting the clip and composite, saving and restoring
797 * state, is performed in the "paint" method automatically, and then delegated here.
798 *
799 * @param g The Graphics2D object in which to paint
800 * @param component The JComponent that the Painter is delegate for.
801 */
802 protected abstract void paintBackground(Graphics2D g, T component);
803 }