1 /* 2 * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package javax.swing.plaf.nimbus; 26 27 import java.awt.*; 28 import java.awt.image.*; 29 import java.lang.reflect.Method; 30 import javax.swing.*; 31 import javax.swing.plaf.UIResource; 32 import javax.swing.Painter; 33 import java.awt.print.PrinterGraphics; 34 35 /** 36 * Convenient base class for defining Painter instances for rendering a 37 * region or component in Nimbus. 38 * 39 * @author Jasper Potts 40 * @author Richard Bair 41 */ 42 public abstract class AbstractRegionPainter implements Painter<JComponent> { 43 /** 44 * PaintContext, which holds a lot of the state needed for cache hinting and x/y value decoding 45 * The data contained within the context is typically only computed once and reused over 46 * multiple paint calls, whereas the other values (w, h, f, leftWidth, etc) are recomputed 47 * for each call to paint. 48 * 49 * This field is retrieved from subclasses on each paint operation. It is up 50 * to the subclass to compute and cache the PaintContext over multiple calls. 51 */ 52 private PaintContext ctx; 53 /** 54 * The scaling factor. Recomputed on each call to paint. 55 */ 56 private float f; 57 /* 58 Various metrics used for decoding x/y values based on the canvas size 59 and stretching insets. 60 61 On each call to paint, we first ask the subclass for the PaintContext. 62 From the context we get the canvas size and stretching insets, and whether 63 the algorithm should be "inverted", meaning the center section remains 64 a fixed size and the other sections scale. 65 66 We then use these values to compute a series of metrics (listed below) 67 which are used to decode points in a specific axis (x or y). 68 69 The leftWidth represents the distance from the left edge of the region 70 to the first stretching inset, after accounting for any scaling factor 71 (such as DPI scaling). The centerWidth is the distance between the leftWidth 72 and the rightWidth. The rightWidth is the distance from the right edge, 73 to the right inset (after scaling has been applied). 74 75 The same logic goes for topHeight, centerHeight, and bottomHeight. 76 77 The leftScale represents the proportion of the width taken by the left section. 78 The same logic is applied to the other scales. 79 80 The various widths/heights are used to decode control points. The 81 various scales are used to decode bezier handles (or anchors). 82 */ 83 /** 84 * The width of the left section. Recomputed on each call to paint. 85 */ 86 private float leftWidth; 87 /** 88 * The height of the top section. Recomputed on each call to paint. 89 */ 90 private float topHeight; 91 /** 92 * The width of the center section. Recomputed on each call to paint. 93 */ 94 private float centerWidth; 95 /** 96 * The height of the center section. Recomputed on each call to paint. 97 */ 98 private float centerHeight; 99 /** 100 * The width of the right section. Recomputed on each call to paint. 101 */ 102 private float rightWidth; 103 /** 104 * The height of the bottom section. Recomputed on each call to paint. 105 */ 106 private float bottomHeight; 107 /** 108 * The scaling factor to use for the left section. Recomputed on each call to paint. 109 */ 110 private float leftScale; 111 /** 112 * The scaling factor to use for the top section. Recomputed on each call to paint. 113 */ 114 private float topScale; 115 /** 116 * The scaling factor to use for the center section, in the horizontal 117 * direction. Recomputed on each call to paint. 118 */ 119 private float centerHScale; 120 /** 121 * The scaling factor to use for the center section, in the vertical 122 * direction. Recomputed on each call to paint. 123 */ 124 private float centerVScale; 125 /** 126 * The scaling factor to use for the right section. Recomputed on each call to paint. 127 */ 128 private float rightScale; 129 /** 130 * The scaling factor to use for the bottom section. Recomputed on each call to paint. 131 */ 132 private float bottomScale; 133 134 /** 135 * Create a new AbstractRegionPainter 136 */ 137 protected AbstractRegionPainter() { } 138 139 /** 140 * {@inheritDoc} 141 */ 142 @Override 143 public final void paint(Graphics2D g, JComponent c, int w, int h) { 144 //don't render if the width/height are too small 145 if (w <= 0 || h <=0) return; 146 147 Object[] extendedCacheKeys = getExtendedCacheKeys(c); 148 ctx = getPaintContext(); 149 PaintContext.CacheMode cacheMode = ctx == null ? PaintContext.CacheMode.NO_CACHING : ctx.cacheMode; 150 if (cacheMode == PaintContext.CacheMode.NO_CACHING || 151 !ImageCache.getInstance().isImageCachable(w, h) || 152 g instanceof PrinterGraphics) { 153 // no caching so paint directly 154 paint0(g, c, w, h, extendedCacheKeys); 155 } else if (cacheMode == PaintContext.CacheMode.FIXED_SIZES) { 156 paintWithFixedSizeCaching(g, c, w, h, extendedCacheKeys); 157 } else { 158 // 9 Square caching 159 paintWith9SquareCaching(g, ctx, c, w, h, extendedCacheKeys); 160 } 161 } 162 163 /** 164 * Get any extra attributes which the painter implementation would like 165 * to include in the image cache lookups. This is checked for every call 166 * of the paint(g, c, w, h) method. 167 * 168 * @param c The component on the current paint call 169 * @return Array of extra objects to be included in the cache key 170 */ 171 protected Object[] getExtendedCacheKeys(JComponent c) { 172 return null; 173 } 174 175 /** 176 * <p>Gets the PaintContext for this painting operation. This method is called on every 177 * paint, and so should be fast and produce no garbage. The PaintContext contains 178 * information such as cache hints. It also contains data necessary for decoding 179 * points at runtime, such as the stretching insets, the canvas size at which the 180 * encoded points were defined, and whether the stretching insets are inverted.</p> 181 * 182 * <p> This method allows for subclasses to package the painting of different states 183 * with possibly different canvas sizes, etc, into one AbstractRegionPainter implementation.</p> 184 * 185 * @return a PaintContext associated with this paint operation. 186 */ 187 protected abstract PaintContext getPaintContext(); 188 189 /** 190 * <p>Configures the given Graphics2D. Often, rendering hints or compositing rules are 191 * applied to a Graphics2D object prior to painting, which should affect all of the 192 * subsequent painting operations. This method provides a convenient hook for configuring 193 * the Graphics object prior to rendering, regardless of whether the render operation is 194 * performed to an intermediate buffer or directly to the display.</p> 195 * 196 * @param g The Graphics2D object to configure. Will not be null. 197 */ 198 protected void configureGraphics(Graphics2D g) { 199 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 200 } 201 202 /** 203 * Actually performs the painting operation. Subclasses must implement this method. 204 * The graphics object passed may represent the actual surface being rendered to, 205 * or it may be an intermediate buffer. It has also been pre-translated. Simply render 206 * the component as if it were located at 0, 0 and had a width of <code>width</code> 207 * and a height of <code>height</code>. For performance reasons, you may want to read 208 * the clip from the Graphics2D object and only render within that space. 209 * 210 * @param g The Graphics2D surface to paint to 211 * @param c The JComponent related to the drawing event. For example, if the 212 * region being rendered is Button, then <code>c</code> will be a 213 * JButton. If the region being drawn is ScrollBarSlider, then the 214 * component will be JScrollBar. This value may be null. 215 * @param width The width of the region to paint. Note that in the case of 216 * painting the foreground, this value may differ from c.getWidth(). 217 * @param height The height of the region to paint. Note that in the case of 218 * painting the foreground, this value may differ from c.getHeight(). 219 * @param extendedCacheKeys The result of the call to getExtendedCacheKeys() 220 */ 221 protected abstract void doPaint(Graphics2D g, JComponent c, int width, 222 int height, Object[] extendedCacheKeys); 223 224 /** 225 * Decodes and returns a float value representing the actual pixel location for 226 * the given encoded X value. 227 * 228 * @param x an encoded x value (0...1, or 1...2, or 2...3) 229 * @return the decoded x value 230 * @throws IllegalArgumentException 231 * if {@code x < 0} or {@code x > 3} 232 */ 233 protected final float decodeX(float x) { 234 if (x >= 0 && x <= 1) { 235 return x * leftWidth; 236 } else if (x > 1 && x < 2) { 237 return ((x-1) * centerWidth) + leftWidth; 238 } else if (x >= 2 && x <= 3) { 239 return ((x-2) * rightWidth) + leftWidth + centerWidth; 240 } else { 241 throw new IllegalArgumentException("Invalid x"); 242 } 243 } 244 245 /** 246 * Decodes and returns a float value representing the actual pixel location for 247 * the given encoded y value. 248 * 249 * @param y an encoded y value (0...1, or 1...2, or 2...3) 250 * @return the decoded y value 251 * @throws IllegalArgumentException 252 * if {@code y < 0} or {@code y > 3} 253 */ 254 protected final float decodeY(float y) { 255 if (y >= 0 && y <= 1) { 256 return y * topHeight; 257 } else if (y > 1 && y < 2) { 258 return ((y-1) * centerHeight) + topHeight; 259 } else if (y >= 2 && y <= 3) { 260 return ((y-2) * bottomHeight) + topHeight + centerHeight; 261 } else { 262 throw new IllegalArgumentException("Invalid y"); 263 } 264 } 265 266 /** 267 * Decodes and returns a float value representing the actual pixel location for 268 * the anchor point given the encoded X value of the control point, and the offset 269 * distance to the anchor from that control point. 270 * 271 * @param x an encoded x value of the bezier control point (0...1, or 1...2, or 2...3) 272 * @param dx the offset distance to the anchor from the control point x 273 * @return the decoded x location of the control point 274 * @throws IllegalArgumentException 275 * if {@code x < 0} or {@code x > 3} 276 */ 277 protected final float decodeAnchorX(float x, float dx) { 278 if (x >= 0 && x <= 1) { 279 return decodeX(x) + (dx * leftScale); 280 } else if (x > 1 && x < 2) { 281 return decodeX(x) + (dx * centerHScale); 282 } else if (x >= 2 && x <= 3) { 283 return decodeX(x) + (dx * rightScale); 284 } else { 285 throw new IllegalArgumentException("Invalid x"); 286 } 287 } 288 289 /** 290 * Decodes and returns a float value representing the actual pixel location for 291 * the anchor point given the encoded Y value of the control point, and the offset 292 * distance to the anchor from that control point. 293 * 294 * @param y an encoded y value of the bezier control point (0...1, or 1...2, or 2...3) 295 * @param dy the offset distance to the anchor from the control point y 296 * @return the decoded y position of the control point 297 * @throws IllegalArgumentException 298 * if {@code y < 0} or {@code y > 3} 299 */ 300 protected final float decodeAnchorY(float y, float dy) { 301 if (y >= 0 && y <= 1) { 302 return decodeY(y) + (dy * topScale); 303 } else if (y > 1 && y < 2) { 304 return decodeY(y) + (dy * centerVScale); 305 } else if (y >= 2 && y <= 3) { 306 return decodeY(y) + (dy * bottomScale); 307 } else { 308 throw new IllegalArgumentException("Invalid y"); 309 } 310 } 311 312 /** 313 * Decodes and returns a color, which is derived from a base color in UI 314 * defaults. 315 * 316 * @param key A key corresponding to the value in the UI Defaults table 317 * of UIManager where the base color is defined 318 * @param hOffset The hue offset used for derivation. 319 * @param sOffset The saturation offset used for derivation. 320 * @param bOffset The brightness offset used for derivation. 321 * @param aOffset The alpha offset used for derivation. Between 0...255 322 * @return The derived color, whose color value will change if the parent 323 * uiDefault color changes. 324 */ 325 protected final Color decodeColor(String key, float hOffset, float sOffset, 326 float bOffset, int aOffset) { 327 if (UIManager.getLookAndFeel() instanceof NimbusLookAndFeel){ 328 NimbusLookAndFeel laf = (NimbusLookAndFeel) UIManager.getLookAndFeel(); 329 return laf.getDerivedColor(key, hOffset, sOffset, bOffset, aOffset, true); 330 } else { 331 // can not give a right answer as painter sould not be used outside 332 // of nimbus laf but do the best we can 333 return Color.getHSBColor(hOffset,sOffset,bOffset); 334 } 335 } 336 337 /** 338 * Decodes and returns a color, which is derived from a offset between two 339 * other colors. 340 * 341 * @param color1 The first color 342 * @param color2 The second color 343 * @param midPoint The offset between color 1 and color 2, a value of 0.0 is 344 * color 1 and 1.0 is color 2; 345 * @return The derived color 346 */ 347 protected final Color decodeColor(Color color1, Color color2, 348 float midPoint) { 349 return new Color(NimbusLookAndFeel.deriveARGB(color1, color2, midPoint)); 350 } 351 352 /** 353 * Given parameters for creating a LinearGradientPaint, this method will 354 * create and return a linear gradient paint. One primary purpose for this 355 * method is to avoid creating a LinearGradientPaint where the start and 356 * end points are equal. In such a case, the end y point is slightly 357 * increased to avoid the overlap. 358 * 359 * @param x1 x1 360 * @param y1 y1 361 * @param x2 x2 362 * @param y2 y2 363 * @param midpoints the midpoints 364 * @param colors the colors 365 * @return a valid LinearGradientPaint. This method never returns null. 366 * @throws NullPointerException 367 * if {@code midpoints} array is null, 368 * or {@code colors} array is null, 369 * @throws IllegalArgumentException 370 * if start and end points are the same points, 371 * or {@code midpoints.length != colors.length}, 372 * or {@code colors} is less than 2 in size, 373 * or a {@code midpoints} value is less than 0.0 or greater than 1.0, 374 * or the {@code midpoints} are not provided in strictly increasing order 375 */ 376 protected final LinearGradientPaint decodeGradient(float x1, float y1, float x2, float y2, float[] midpoints, Color[] colors) { 377 if (x1 == x2 && y1 == y2) { 378 y2 += .00001f; 379 } 380 return new LinearGradientPaint(x1, y1, x2, y2, midpoints, colors); 381 } 382 383 /** 384 * Given parameters for creating a RadialGradientPaint, this method will 385 * create and return a radial gradient paint. One primary purpose for this 386 * method is to avoid creating a RadialGradientPaint where the radius 387 * is non-positive. In such a case, the radius is just slightly 388 * increased to avoid 0. 389 * 390 * @param x x-coordinate 391 * @param y y-coordinate 392 * @param r radius 393 * @param midpoints the midpoints 394 * @param colors the colors 395 * @return a valid RadialGradientPaint. This method never returns null. 396 * @throws NullPointerException 397 * if {@code midpoints} array is null, 398 * or {@code colors} array is null 399 * @throws IllegalArgumentException 400 * if {@code r} is non-positive, 401 * or {@code midpoints.length != colors.length}, 402 * or {@code colors} is less than 2 in size, 403 * or a {@code midpoints} value is less than 0.0 or greater than 1.0, 404 * or the {@code midpoints} are not provided in strictly increasing order 405 */ 406 protected final RadialGradientPaint decodeRadialGradient(float x, float y, float r, float[] midpoints, Color[] colors) { 407 if (r == 0f) { 408 r = .00001f; 409 } 410 return new RadialGradientPaint(x, y, r, midpoints, colors); 411 } 412 413 /** 414 * Get a color property from the given JComponent. First checks for a 415 * <code>getXXX()</code> method and if that fails checks for a client 416 * property with key <code>property</code>. If that still fails to return 417 * a Color then <code>defaultColor</code> is returned. 418 * 419 * @param c The component to get the color property from 420 * @param property The name of a bean style property or client property 421 * @param defaultColor The color to return if no color was obtained from 422 * the component. 423 * @return The color that was obtained from the component or defaultColor 424 */ 425 protected final Color getComponentColor(JComponent c, String property, 426 Color defaultColor, 427 float saturationOffset, 428 float brightnessOffset, 429 int alphaOffset) { 430 Color color = null; 431 if (c != null) { 432 // handle some special cases for performance 433 if ("background".equals(property)) { 434 color = c.getBackground(); 435 } else if ("foreground".equals(property)) { 436 color = c.getForeground(); 437 } else if (c instanceof JList && "selectionForeground".equals(property)) { 438 color = ((JList) c).getSelectionForeground(); 439 } else if (c instanceof JList && "selectionBackground".equals(property)) { 440 color = ((JList) c).getSelectionBackground(); 441 } else if (c instanceof JTable && "selectionForeground".equals(property)) { 442 color = ((JTable) c).getSelectionForeground(); 443 } else if (c instanceof JTable && "selectionBackground".equals(property)) { 444 color = ((JTable) c).getSelectionBackground(); 445 } else { 446 String s = "get" + Character.toUpperCase(property.charAt(0)) + property.substring(1); 447 try { 448 Method method = c.getClass().getMethod(s); 449 color = (Color) method.invoke(c); 450 } catch (Exception e) { 451 //don't do anything, it just didn't work, that's all. 452 //This could be a normal occurance if you use a property 453 //name referring to a key in clientProperties instead of 454 //a real property 455 } 456 if (color == null) { 457 Object value = c.getClientProperty(property); 458 if (value instanceof Color) { 459 color = (Color) value; 460 } 461 } 462 } 463 } 464 // we return the defaultColor if the color found is null, or if 465 // it is a UIResource. This is done because the color for the 466 // ENABLED state is set on the component, but you don't want to use 467 // that color for the over state. So we only respect the color 468 // specified for the property if it was set by the user, as opposed 469 // to set by us. 470 if (color == null || color instanceof UIResource) { 471 return defaultColor; 472 } else if (saturationOffset != 0 || brightnessOffset != 0 || alphaOffset != 0) { 473 float[] tmp = Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), null); 474 tmp[1] = clamp(tmp[1] + saturationOffset); 475 tmp[2] = clamp(tmp[2] + brightnessOffset); 476 int alpha = clamp(color.getAlpha() + alphaOffset); 477 return new Color((Color.HSBtoRGB(tmp[0], tmp[1], tmp[2]) & 0xFFFFFF) | (alpha <<24)); 478 } else { 479 return color; 480 } 481 } 482 483 /** 484 * A class encapsulating state useful when painting. Generally, instances of this 485 * class are created once, and reused for each paint request without modification. 486 * This class contains values useful when hinting the cache engine, and when decoding 487 * control points and bezier curve anchors. 488 */ 489 protected static class PaintContext { 490 protected static enum CacheMode { 491 NO_CACHING, FIXED_SIZES, NINE_SQUARE_SCALE 492 } 493 494 private static Insets EMPTY_INSETS = new Insets(0, 0, 0, 0); 495 496 private Insets stretchingInsets; 497 private Dimension canvasSize; 498 private boolean inverted; 499 private CacheMode cacheMode; 500 private double maxHorizontalScaleFactor; 501 private double maxVerticalScaleFactor; 502 503 private float a; // insets.left 504 private float b; // canvasSize.width - insets.right 505 private float c; // insets.top 506 private float d; // canvasSize.height - insets.bottom; 507 private float aPercent; // only used if inverted == true 508 private float bPercent; // only used if inverted == true 509 private float cPercent; // only used if inverted == true 510 private float dPercent; // only used if inverted == true 511 512 /** 513 * Creates a new PaintContext which does not attempt to cache or scale any cached 514 * images. 515 * 516 * @param insets The stretching insets. May be null. If null, then assumed to be 0, 0, 0, 0. 517 * @param canvasSize The size of the canvas used when encoding the various x/y values. May be null. 518 * If null, then it is assumed that there are no encoded values, and any calls 519 * to one of the "decode" methods will return the passed in value. 520 * @param inverted Whether to "invert" the meaning of the 9-square grid and stretching insets 521 */ 522 public PaintContext(Insets insets, Dimension canvasSize, boolean inverted) { 523 this(insets, canvasSize, inverted, null, 1, 1); 524 } 525 526 /** 527 * Creates a new PaintContext. 528 * 529 * @param insets The stretching insets. May be null. If null, then assumed to be 0, 0, 0, 0. 530 * @param canvasSize The size of the canvas used when encoding the various x/y values. May be null. 531 * If null, then it is assumed that there are no encoded values, and any calls 532 * to one of the "decode" methods will return the passed in value. 533 * @param inverted Whether to "invert" the meaning of the 9-square grid and stretching insets 534 * @param cacheMode A hint as to which caching mode to use. If null, then set to no caching. 535 * @param maxH The maximum scale in the horizontal direction to use before punting and redrawing from scratch. 536 * For example, if maxH is 2, then we will attempt to scale any cached images up to 2x the canvas 537 * width before redrawing from scratch. Reasonable maxH values may improve painting performance. 538 * If set too high, then you may get poor looking graphics at higher zoom levels. Must be >= 1. 539 * @param maxV The maximum scale in the vertical direction to use before punting and redrawing from scratch. 540 * For example, if maxV is 2, then we will attempt to scale any cached images up to 2x the canvas 541 * height before redrawing from scratch. Reasonable maxV values may improve painting performance. 542 * If set too high, then you may get poor looking graphics at higher zoom levels. Must be >= 1. 543 */ 544 public PaintContext(Insets insets, Dimension canvasSize, boolean inverted, 545 CacheMode cacheMode, double maxH, double maxV) { 546 if (maxH < 1 || maxH < 1) { 547 throw new IllegalArgumentException("Both maxH and maxV must be >= 1"); 548 } 549 550 this.stretchingInsets = insets == null ? EMPTY_INSETS : insets; 551 this.canvasSize = canvasSize; 552 this.inverted = inverted; 553 this.cacheMode = cacheMode == null ? CacheMode.NO_CACHING : cacheMode; 554 this.maxHorizontalScaleFactor = maxH; 555 this.maxVerticalScaleFactor = maxV; 556 557 if (canvasSize != null) { 558 a = stretchingInsets.left; 559 b = canvasSize.width - stretchingInsets.right; 560 c = stretchingInsets.top; 561 d = canvasSize.height - stretchingInsets.bottom; 562 this.canvasSize = canvasSize; 563 this.inverted = inverted; 564 if (inverted) { 565 float available = canvasSize.width - (b - a); 566 aPercent = available > 0f ? a / available : 0f; 567 bPercent = available > 0f ? b / available : 0f; 568 available = canvasSize.height - (d - c); 569 cPercent = available > 0f ? c / available : 0f; 570 dPercent = available > 0f ? d / available : 0f; 571 } 572 } 573 } 574 } 575 576 //---------------------- private methods 577 578 //initializes the class to prepare it for being able to decode points 579 private void prepare(float w, float h) { 580 //if no PaintContext has been specified, reset the values and bail 581 //also bail if the canvasSize was not set (since decoding will not work) 582 if (ctx == null || ctx.canvasSize == null) { 583 f = 1f; 584 leftWidth = centerWidth = rightWidth = 0f; 585 topHeight = centerHeight = bottomHeight = 0f; 586 leftScale = centerHScale = rightScale = 0f; 587 topScale = centerVScale = bottomScale = 0f; 588 return; 589 } 590 591 //calculate the scaling factor, and the sizes for the various 9-square sections 592 Number scale = (Number)UIManager.get("scale"); 593 f = scale == null ? 1f : scale.floatValue(); 594 595 if (ctx.inverted) { 596 centerWidth = (ctx.b - ctx.a) * f; 597 float availableSpace = w - centerWidth; 598 leftWidth = availableSpace * ctx.aPercent; 599 rightWidth = availableSpace * ctx.bPercent; 600 centerHeight = (ctx.d - ctx.c) * f; 601 availableSpace = h - centerHeight; 602 topHeight = availableSpace * ctx.cPercent; 603 bottomHeight = availableSpace * ctx.dPercent; 604 } else { 605 leftWidth = ctx.a * f; 606 rightWidth = (float)(ctx.canvasSize.getWidth() - ctx.b) * f; 607 centerWidth = w - leftWidth - rightWidth; 608 topHeight = ctx.c * f; 609 bottomHeight = (float)(ctx.canvasSize.getHeight() - ctx.d) * f; 610 centerHeight = h - topHeight - bottomHeight; 611 } 612 613 leftScale = ctx.a == 0f ? 0f : leftWidth / ctx.a; 614 centerHScale = (ctx.b - ctx.a) == 0f ? 0f : centerWidth / (ctx.b - ctx.a); 615 rightScale = (ctx.canvasSize.width - ctx.b) == 0f ? 0f : rightWidth / (ctx.canvasSize.width - ctx.b); 616 topScale = ctx.c == 0f ? 0f : topHeight / ctx.c; 617 centerVScale = (ctx.d - ctx.c) == 0f ? 0f : centerHeight / (ctx.d - ctx.c); 618 bottomScale = (ctx.canvasSize.height - ctx.d) == 0f ? 0f : bottomHeight / (ctx.canvasSize.height - ctx.d); 619 } 620 621 private void paintWith9SquareCaching(Graphics2D g, PaintContext ctx, 622 JComponent c, int w, int h, 623 Object[] extendedCacheKeys) { 624 // check if we can scale to the requested size 625 Dimension canvas = ctx.canvasSize; 626 Insets insets = ctx.stretchingInsets; 627 628 if (w <= (canvas.width * ctx.maxHorizontalScaleFactor) && h <= (canvas.height * ctx.maxVerticalScaleFactor)) { 629 // get image at canvas size 630 VolatileImage img = getImage(g.getDeviceConfiguration(), c, canvas.width, canvas.height, extendedCacheKeys); 631 if (img != null) { 632 // calculate dst inserts 633 // todo: destination inserts need to take into acount scale factor for high dpi. Note: You can use f for this, I think 634 Insets dstInsets; 635 if (ctx.inverted){ 636 int leftRight = (w-(canvas.width-(insets.left+insets.right)))/2; 637 int topBottom = (h-(canvas.height-(insets.top+insets.bottom)))/2; 638 dstInsets = new Insets(topBottom,leftRight,topBottom,leftRight); 639 } else { 640 dstInsets = insets; 641 } 642 // paint 9 square scaled 643 Object oldScaleingHints = g.getRenderingHint(RenderingHints.KEY_INTERPOLATION); 644 g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,RenderingHints.VALUE_INTERPOLATION_BILINEAR); 645 ImageScalingHelper.paint(g, 0, 0, w, h, img, insets, dstInsets, 646 ImageScalingHelper.PaintType.PAINT9_STRETCH, ImageScalingHelper.PAINT_ALL); 647 g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, 648 oldScaleingHints!=null?oldScaleingHints:RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); 649 } else { 650 // render directly 651 paint0(g, c, w, h, extendedCacheKeys); 652 } 653 } else { 654 // paint directly 655 paint0(g, c, w, h, extendedCacheKeys); 656 } 657 } 658 659 private void paintWithFixedSizeCaching(Graphics2D g, JComponent c, int w, 660 int h, Object[] extendedCacheKeys) { 661 VolatileImage img = getImage(g.getDeviceConfiguration(), c, w, h, extendedCacheKeys); 662 if (img != null) { 663 //render cached image 664 g.drawImage(img, 0, 0, null); 665 } else { 666 // render directly 667 paint0(g, c, w, h, extendedCacheKeys); 668 } 669 } 670 671 /** Gets the rendered image for this painter at the requested size, either from cache or create a new one */ 672 private VolatileImage getImage(GraphicsConfiguration config, JComponent c, 673 int w, int h, Object[] extendedCacheKeys) { 674 ImageCache imageCache = ImageCache.getInstance(); 675 //get the buffer for this component 676 VolatileImage buffer = (VolatileImage) imageCache.getImage(config, w, h, this, extendedCacheKeys); 677 678 int renderCounter = 0; //to avoid any potential, though unlikely, infinite loop 679 do { 680 //validate the buffer so we can check for surface loss 681 int bufferStatus = VolatileImage.IMAGE_INCOMPATIBLE; 682 if (buffer != null) { 683 bufferStatus = buffer.validate(config); 684 } 685 686 //If the buffer status is incompatible or restored, then we need to re-render to the volatile image 687 if (bufferStatus == VolatileImage.IMAGE_INCOMPATIBLE || bufferStatus == VolatileImage.IMAGE_RESTORED) { 688 //if the buffer is null (hasn't been created), or isn't the right size, or has lost its contents, 689 //then recreate the buffer 690 if (buffer == null || buffer.getWidth() != w || buffer.getHeight() != h || 691 bufferStatus == VolatileImage.IMAGE_INCOMPATIBLE) { 692 //clear any resources related to the old back buffer 693 if (buffer != null) { 694 buffer.flush(); 695 buffer = null; 696 } 697 //recreate the buffer 698 buffer = config.createCompatibleVolatileImage(w, h, 699 Transparency.TRANSLUCENT); 700 // put in cache for future 701 imageCache.setImage(buffer, config, w, h, this, extendedCacheKeys); 702 } 703 //create the graphics context with which to paint to the buffer 704 Graphics2D bg = buffer.createGraphics(); 705 //clear the background before configuring the graphics 706 bg.setComposite(AlphaComposite.Clear); 707 bg.fillRect(0, 0, w, h); 708 bg.setComposite(AlphaComposite.SrcOver); 709 configureGraphics(bg); 710 // paint the painter into buffer 711 paint0(bg, c, w, h, extendedCacheKeys); 712 //close buffer graphics 713 bg.dispose(); 714 } 715 } while (buffer.contentsLost() && renderCounter++ < 3); 716 // check if we failed 717 if (renderCounter == 3) return null; 718 // return image 719 return buffer; 720 } 721 722 //convenience method which creates a temporary graphics object by creating a 723 //clone of the passed in one, configuring it, drawing with it, disposing it. 724 //These steps have to be taken to ensure that any hints set on the graphics 725 //are removed subsequent to painting. 726 private void paint0(Graphics2D g, JComponent c, int width, int height, 727 Object[] extendedCacheKeys) { 728 prepare(width, height); 729 g = (Graphics2D)g.create(); 730 configureGraphics(g); 731 doPaint(g, c, width, height, extendedCacheKeys); 732 g.dispose(); 733 } 734 735 private float clamp(float value) { 736 if (value < 0) { 737 value = 0; 738 } else if (value > 1) { 739 value = 1; 740 } 741 return value; 742 } 743 744 private int clamp(int value) { 745 if (value < 0) { 746 value = 0; 747 } else if (value > 255) { 748 value = 255; 749 } 750 return value; 751 } 752 }