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 }