1 /* 2 * Copyright (c) 2005, 2013, 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 import sun.reflect.misc.MethodUtil; 35 36 /** 37 * Convenient base class for defining Painter instances for rendering a 38 * region or component in Nimbus. 39 * 40 * @author Jasper Potts 41 * @author Richard Bair 42 */ 43 public abstract class AbstractRegionPainter implements Painter<JComponent> { 44 /** 45 * PaintContext, which holds a lot of the state needed for cache hinting and x/y value decoding 46 * The data contained within the context is typically only computed once and reused over 47 * multiple paint calls, whereas the other values (w, h, f, leftWidth, etc) are recomputed 48 * for each call to paint. 49 * 50 * This field is retrieved from subclasses on each paint operation. It is up 51 * to the subclass to compute and cache the PaintContext over multiple calls. 52 */ 53 private PaintContext ctx; 54 /** 55 * The scaling factor. Recomputed on each call to paint. 56 */ 57 private float f; 58 /* 59 Various metrics used for decoding x/y values based on the canvas size 60 and stretching insets. 61 62 On each call to paint, we first ask the subclass for the PaintContext. 63 From the context we get the canvas size and stretching insets, and whether 64 the algorithm should be "inverted", meaning the center section remains 65 a fixed size and the other sections scale. 66 67 We then use these values to compute a series of metrics (listed below) 68 which are used to decode points in a specific axis (x or y). 69 70 The leftWidth represents the distance from the left edge of the region 71 to the first stretching inset, after accounting for any scaling factor 72 (such as DPI scaling). The centerWidth is the distance between the leftWidth 73 and the rightWidth. The rightWidth is the distance from the right edge, 74 to the right inset (after scaling has been applied). 75 76 The same logic goes for topHeight, centerHeight, and bottomHeight. 77 78 The leftScale represents the proportion of the width taken by the left section. 79 The same logic is applied to the other scales. 80 81 The various widths/heights are used to decode control points. The 82 various scales are used to decode bezier handles (or anchors). 83 */ 84 /** 85 * The width of the left section. Recomputed on each call to paint. 86 */ 87 private float leftWidth; 88 /** 89 * The height of the top section. Recomputed on each call to paint. 90 */ 91 private float topHeight; 92 /** 93 * The width of the center section. Recomputed on each call to paint. 94 */ 95 private float centerWidth; 96 /** 97 * The height of the center section. Recomputed on each call to paint. 98 */ 99 private float centerHeight; 100 /** 101 * The width of the right section. Recomputed on each call to paint. 102 */ 103 private float rightWidth; 104 /** 105 * The height of the bottom section. Recomputed on each call to paint. 106 */ 107 private float bottomHeight; 108 /** 109 * The scaling factor to use for the left section. Recomputed on each call to paint. 110 */ 111 private float leftScale; 112 /** 113 * The scaling factor to use for the top section. Recomputed on each call to paint. 114 */ 115 private float topScale; 116 /** 117 * The scaling factor to use for the center section, in the horizontal 118 * direction. Recomputed on each call to paint. 119 */ 120 private float centerHScale; 121 /** 122 * The scaling factor to use for the center section, in the vertical 123 * direction. Recomputed on each call to paint. 124 */ 125 private float centerVScale; 126 /** 127 * The scaling factor to use for the right section. Recomputed on each call to paint. 128 */ 129 private float rightScale; 130 /** 131 * The scaling factor to use for the bottom section. Recomputed on each call to paint. 132 */ 133 private float bottomScale; 134 135 /** 136 * Create a new AbstractRegionPainter 137 */ AbstractRegionPainter()138 protected AbstractRegionPainter() { } 139 140 /** 141 * {@inheritDoc} 142 */ 143 @Override paint(Graphics2D g, JComponent c, int w, int h)144 public final void paint(Graphics2D g, JComponent c, int w, int h) { 145 //don't render if the width/height are too small 146 if (w <= 0 || h <=0) return; 147 148 Object[] extendedCacheKeys = getExtendedCacheKeys(c); 149 ctx = getPaintContext(); 150 PaintContext.CacheMode cacheMode = ctx == null ? PaintContext.CacheMode.NO_CACHING : ctx.cacheMode; 151 if (cacheMode == PaintContext.CacheMode.NO_CACHING || 152 !ImageCache.getInstance().isImageCachable(w, h) || 153 g instanceof PrinterGraphics) { 154 // no caching so paint directly 155 paint0(g, c, w, h, extendedCacheKeys); 156 } else if (cacheMode == PaintContext.CacheMode.FIXED_SIZES) { 157 paintWithFixedSizeCaching(g, c, w, h, extendedCacheKeys); 158 } else { 159 // 9 Square caching 160 paintWith9SquareCaching(g, ctx, c, w, h, extendedCacheKeys); 161 } 162 } 163 164 /** 165 * Get any extra attributes which the painter implementation would like 166 * to include in the image cache lookups. This is checked for every call 167 * of the paint(g, c, w, h) method. 168 * 169 * @param c The component on the current paint call 170 * @return Array of extra objects to be included in the cache key 171 */ getExtendedCacheKeys(JComponent c)172 protected Object[] getExtendedCacheKeys(JComponent c) { 173 return null; 174 } 175 176 /** 177 * <p>Gets the PaintContext for this painting operation. This method is called on every 178 * paint, and so should be fast and produce no garbage. The PaintContext contains 179 * information such as cache hints. It also contains data necessary for decoding 180 * points at runtime, such as the stretching insets, the canvas size at which the 181 * encoded points were defined, and whether the stretching insets are inverted.</p> 182 * 183 * <p> This method allows for subclasses to package the painting of different states 184 * with possibly different canvas sizes, etc, into one AbstractRegionPainter implementation.</p> 185 * 186 * @return a PaintContext associated with this paint operation. 187 */ getPaintContext()188 protected abstract PaintContext getPaintContext(); 189 190 /** 191 * <p>Configures the given Graphics2D. Often, rendering hints or compositing rules are 192 * applied to a Graphics2D object prior to painting, which should affect all of the 193 * subsequent painting operations. This method provides a convenient hook for configuring 194 * the Graphics object prior to rendering, regardless of whether the render operation is 195 * performed to an intermediate buffer or directly to the display.</p> 196 * 197 * @param g The Graphics2D object to configure. Will not be null. 198 */ configureGraphics(Graphics2D g)199 protected void configureGraphics(Graphics2D g) { 200 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 201 } 202 203 /** 204 * Actually performs the painting operation. Subclasses must implement this method. 205 * The graphics object passed may represent the actual surface being rendered to, 206 * or it may be an intermediate buffer. It has also been pre-translated. Simply render 207 * the component as if it were located at 0, 0 and had a width of <code>width</code> 208 * and a height of <code>height</code>. For performance reasons, you may want to read 209 * the clip from the Graphics2D object and only render within that space. 210 * 211 * @param g The Graphics2D surface to paint to 212 * @param c The JComponent related to the drawing event. For example, if the 213 * region being rendered is Button, then <code>c</code> will be a 214 * JButton. If the region being drawn is ScrollBarSlider, then the 215 * component will be JScrollBar. This value may be null. 216 * @param width The width of the region to paint. Note that in the case of 217 * painting the foreground, this value may differ from c.getWidth(). 218 * @param height The height of the region to paint. Note that in the case of 219 * painting the foreground, this value may differ from c.getHeight(). 220 * @param extendedCacheKeys The result of the call to getExtendedCacheKeys() 221 */ doPaint(Graphics2D g, JComponent c, int width, int height, Object[] extendedCacheKeys)222 protected abstract void doPaint(Graphics2D g, JComponent c, int width, 223 int height, Object[] extendedCacheKeys); 224 225 /** 226 * Decodes and returns a float value representing the actual pixel location for 227 * the given encoded X value. 228 * 229 * @param x an encoded x value (0...1, or 1...2, or 2...3) 230 * @return the decoded x value 231 * @throws IllegalArgumentException 232 * if {@code x < 0} or {@code x > 3} 233 */ decodeX(float x)234 protected final float decodeX(float x) { 235 if (x >= 0 && x <= 1) { 236 return x * leftWidth; 237 } else if (x > 1 && x < 2) { 238 return ((x-1) * centerWidth) + leftWidth; 239 } else if (x >= 2 && x <= 3) { 240 return ((x-2) * rightWidth) + leftWidth + centerWidth; 241 } else { 242 throw new IllegalArgumentException("Invalid x"); 243 } 244 } 245 246 /** 247 * Decodes and returns a float value representing the actual pixel location for 248 * the given encoded y value. 249 * 250 * @param y an encoded y value (0...1, or 1...2, or 2...3) 251 * @return the decoded y value 252 * @throws IllegalArgumentException 253 * if {@code y < 0} or {@code y > 3} 254 */ decodeY(float y)255 protected final float decodeY(float y) { 256 if (y >= 0 && y <= 1) { 257 return y * topHeight; 258 } else if (y > 1 && y < 2) { 259 return ((y-1) * centerHeight) + topHeight; 260 } else if (y >= 2 && y <= 3) { 261 return ((y-2) * bottomHeight) + topHeight + centerHeight; 262 } else { 263 throw new IllegalArgumentException("Invalid y"); 264 } 265 } 266 267 /** 268 * Decodes and returns a float value representing the actual pixel location for 269 * the anchor point given the encoded X value of the control point, and the offset 270 * distance to the anchor from that control point. 271 * 272 * @param x an encoded x value of the bezier control point (0...1, or 1...2, or 2...3) 273 * @param dx the offset distance to the anchor from the control point x 274 * @return the decoded x location of the control point 275 * @throws IllegalArgumentException 276 * if {@code x < 0} or {@code x > 3} 277 */ decodeAnchorX(float x, float dx)278 protected final float decodeAnchorX(float x, float dx) { 279 if (x >= 0 && x <= 1) { 280 return decodeX(x) + (dx * leftScale); 281 } else if (x > 1 && x < 2) { 282 return decodeX(x) + (dx * centerHScale); 283 } else if (x >= 2 && x <= 3) { 284 return decodeX(x) + (dx * rightScale); 285 } else { 286 throw new IllegalArgumentException("Invalid x"); 287 } 288 } 289 290 /** 291 * Decodes and returns a float value representing the actual pixel location for 292 * the anchor point given the encoded Y value of the control point, and the offset 293 * distance to the anchor from that control point. 294 * 295 * @param y an encoded y value of the bezier control point (0...1, or 1...2, or 2...3) 296 * @param dy the offset distance to the anchor from the control point y 297 * @return the decoded y position of the control point 298 * @throws IllegalArgumentException 299 * if {@code y < 0} or {@code y > 3} 300 */ decodeAnchorY(float y, float dy)301 protected final float decodeAnchorY(float y, float dy) { 302 if (y >= 0 && y <= 1) { 303 return decodeY(y) + (dy * topScale); 304 } else if (y > 1 && y < 2) { 305 return decodeY(y) + (dy * centerVScale); 306 } else if (y >= 2 && y <= 3) { 307 return decodeY(y) + (dy * bottomScale); 308 } else { 309 throw new IllegalArgumentException("Invalid y"); 310 } 311 } 312 313 /** 314 * Decodes and returns a color, which is derived from a base color in UI 315 * defaults. 316 * 317 * @param key A key corresponding to the value in the UI Defaults table 318 * of UIManager where the base color is defined 319 * @param hOffset The hue offset used for derivation. 320 * @param sOffset The saturation offset used for derivation. 321 * @param bOffset The brightness offset used for derivation. 322 * @param aOffset The alpha offset used for derivation. Between 0...255 323 * @return The derived color, whose color value will change if the parent 324 * uiDefault color changes. 325 */ decodeColor(String key, float hOffset, float sOffset, float bOffset, int aOffset)326 protected final Color decodeColor(String key, float hOffset, float sOffset, 327 float bOffset, int aOffset) { 328 if (UIManager.getLookAndFeel() instanceof NimbusLookAndFeel){ 329 NimbusLookAndFeel laf = (NimbusLookAndFeel) UIManager.getLookAndFeel(); 330 return laf.getDerivedColor(key, hOffset, sOffset, bOffset, aOffset, true); 331 } else { 332 // can not give a right answer as painter sould not be used outside 333 // of nimbus laf but do the best we can 334 return Color.getHSBColor(hOffset,sOffset,bOffset); 335 } 336 } 337 338 /** 339 * Decodes and returns a color, which is derived from a offset between two 340 * other colors. 341 * 342 * @param color1 The first color 343 * @param color2 The second color 344 * @param midPoint The offset between color 1 and color 2, a value of 0.0 is 345 * color 1 and 1.0 is color 2; 346 * @return The derived color 347 */ decodeColor(Color color1, Color color2, float midPoint)348 protected final Color decodeColor(Color color1, Color color2, 349 float midPoint) { 350 return new Color(NimbusLookAndFeel.deriveARGB(color1, color2, midPoint)); 351 } 352 353 /** 354 * Given parameters for creating a LinearGradientPaint, this method will 355 * create and return a linear gradient paint. One primary purpose for this 356 * method is to avoid creating a LinearGradientPaint where the start and 357 * end points are equal. In such a case, the end y point is slightly 358 * increased to avoid the overlap. 359 * 360 * @param x1 361 * @param y1 362 * @param x2 363 * @param y2 364 * @param midpoints 365 * @param colors 366 * @return a valid LinearGradientPaint. This method never returns null. 367 * @throws NullPointerException 368 * if {@code midpoints} array is null, 369 * or {@code colors} array is null, 370 * @throws IllegalArgumentException 371 * if start and end points are the same points, 372 * or {@code midpoints.length != colors.length}, 373 * or {@code colors} is less than 2 in size, 374 * or a {@code midpoints} value is less than 0.0 or greater than 1.0, 375 * or the {@code midpoints} are not provided in strictly increasing order 376 */ decodeGradient(float x1, float y1, float x2, float y2, float[] midpoints, Color[] colors)377 protected final LinearGradientPaint decodeGradient(float x1, float y1, float x2, float y2, float[] midpoints, Color[] colors) { 378 if (x1 == x2 && y1 == y2) { 379 y2 += .00001f; 380 } 381 return new LinearGradientPaint(x1, y1, x2, y2, midpoints, colors); 382 } 383 384 /** 385 * Given parameters for creating a RadialGradientPaint, this method will 386 * create and return a radial gradient paint. One primary purpose for this 387 * method is to avoid creating a RadialGradientPaint where the radius 388 * is non-positive. In such a case, the radius is just slightly 389 * increased to avoid 0. 390 * 391 * @param x 392 * @param y 393 * @param r 394 * @param midpoints 395 * @param colors 396 * @return a valid RadialGradientPaint. This method never returns null. 397 * @throws NullPointerException 398 * if {@code midpoints} array is null, 399 * or {@code colors} array is null 400 * @throws IllegalArgumentException 401 * if {@code r} is non-positive, 402 * or {@code midpoints.length != colors.length}, 403 * or {@code colors} is less than 2 in size, 404 * or a {@code midpoints} value is less than 0.0 or greater than 1.0, 405 * or the {@code midpoints} are not provided in strictly increasing order 406 */ decodeRadialGradient(float x, float y, float r, float[] midpoints, Color[] colors)407 protected final RadialGradientPaint decodeRadialGradient(float x, float y, float r, float[] midpoints, Color[] colors) { 408 if (r == 0f) { 409 r = .00001f; 410 } 411 return new RadialGradientPaint(x, y, r, midpoints, colors); 412 } 413 414 /** 415 * Get a color property from the given JComponent. First checks for a 416 * <code>getXXX()</code> method and if that fails checks for a client 417 * property with key <code>property</code>. If that still fails to return 418 * a Color then <code>defaultColor</code> is returned. 419 * 420 * @param c The component to get the color property from 421 * @param property The name of a bean style property or client property 422 * @param defaultColor The color to return if no color was obtained from 423 * the component. 424 * @return The color that was obtained from the component or defaultColor 425 */ getComponentColor(JComponent c, String property, Color defaultColor, float saturationOffset, float brightnessOffset, int alphaOffset)426 protected final Color getComponentColor(JComponent c, String property, 427 Color defaultColor, 428 float saturationOffset, 429 float brightnessOffset, 430 int alphaOffset) { 431 Color color = null; 432 if (c != null) { 433 // handle some special cases for performance 434 if ("background".equals(property)) { 435 color = c.getBackground(); 436 } else if ("foreground".equals(property)) { 437 color = c.getForeground(); 438 } else if (c instanceof JList && "selectionForeground".equals(property)) { 439 color = ((JList) c).getSelectionForeground(); 440 } else if (c instanceof JList && "selectionBackground".equals(property)) { 441 color = ((JList) c).getSelectionBackground(); 442 } else if (c instanceof JTable && "selectionForeground".equals(property)) { 443 color = ((JTable) c).getSelectionForeground(); 444 } else if (c instanceof JTable && "selectionBackground".equals(property)) { 445 color = ((JTable) c).getSelectionBackground(); 446 } else { 447 String s = "get" + Character.toUpperCase(property.charAt(0)) + property.substring(1); 448 try { 449 Method method = MethodUtil.getMethod(c.getClass(), s, null); 450 color = (Color) MethodUtil.invoke(method, c, null); 451 } catch (Exception e) { 452 //don't do anything, it just didn't work, that's all. 453 //This could be a normal occurance if you use a property 454 //name referring to a key in clientProperties instead of 455 //a real property 456 } 457 if (color == null) { 458 Object value = c.getClientProperty(property); 459 if (value instanceof Color) { 460 color = (Color) value; 461 } 462 } 463 } 464 } 465 // we return the defaultColor if the color found is null, or if 466 // it is a UIResource. This is done because the color for the 467 // ENABLED state is set on the component, but you don't want to use 468 // that color for the over state. So we only respect the color 469 // specified for the property if it was set by the user, as opposed 470 // to set by us. 471 if (color == null || color instanceof UIResource) { 472 return defaultColor; 473 } else if (saturationOffset != 0 || brightnessOffset != 0 || alphaOffset != 0) { 474 float[] tmp = Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), null); 475 tmp[1] = clamp(tmp[1] + saturationOffset); 476 tmp[2] = clamp(tmp[2] + brightnessOffset); 477 int alpha = clamp(color.getAlpha() + alphaOffset); 478 return new Color((Color.HSBtoRGB(tmp[0], tmp[1], tmp[2]) & 0xFFFFFF) | (alpha <<24)); 479 } else { 480 return color; 481 } 482 } 483 484 /** 485 * A class encapsulating state useful when painting. Generally, instances of this 486 * class are created once, and reused for each paint request without modification. 487 * This class contains values useful when hinting the cache engine, and when decoding 488 * control points and bezier curve anchors. 489 */ 490 protected static class PaintContext { 491 protected static enum CacheMode { 492 NO_CACHING, FIXED_SIZES, NINE_SQUARE_SCALE 493 } 494 495 private static Insets EMPTY_INSETS = new Insets(0, 0, 0, 0); 496 497 private Insets stretchingInsets; 498 private Dimension canvasSize; 499 private boolean inverted; 500 private CacheMode cacheMode; 501 private double maxHorizontalScaleFactor; 502 private double maxVerticalScaleFactor; 503 504 private float a; // insets.left 505 private float b; // canvasSize.width - insets.right 506 private float c; // insets.top 507 private float d; // canvasSize.height - insets.bottom; 508 private float aPercent; // only used if inverted == true 509 private float bPercent; // only used if inverted == true 510 private float cPercent; // only used if inverted == true 511 private float dPercent; // only used if inverted == true 512 513 /** 514 * Creates a new PaintContext which does not attempt to cache or scale any cached 515 * images. 516 * 517 * @param insets The stretching insets. May be null. If null, then assumed to be 0, 0, 0, 0. 518 * @param canvasSize The size of the canvas used when encoding the various x/y values. May be null. 519 * If null, then it is assumed that there are no encoded values, and any calls 520 * to one of the "decode" methods will return the passed in value. 521 * @param inverted Whether to "invert" the meaning of the 9-square grid and stretching insets 522 */ PaintContext(Insets insets, Dimension canvasSize, boolean inverted)523 public PaintContext(Insets insets, Dimension canvasSize, boolean inverted) { 524 this(insets, canvasSize, inverted, null, 1, 1); 525 } 526 527 /** 528 * Creates a new PaintContext. 529 * 530 * @param insets The stretching insets. May be null. If null, then assumed to be 0, 0, 0, 0. 531 * @param canvasSize The size of the canvas used when encoding the various x/y values. May be null. 532 * If null, then it is assumed that there are no encoded values, and any calls 533 * to one of the "decode" methods will return the passed in value. 534 * @param inverted Whether to "invert" the meaning of the 9-square grid and stretching insets 535 * @param cacheMode A hint as to which caching mode to use. If null, then set to no caching. 536 * @param maxH The maximum scale in the horizontal direction to use before punting and redrawing from scratch. 537 * For example, if maxH is 2, then we will attempt to scale any cached images up to 2x the canvas 538 * width before redrawing from scratch. Reasonable maxH values may improve painting performance. 539 * If set too high, then you may get poor looking graphics at higher zoom levels. Must be >= 1. 540 * @param maxV The maximum scale in the vertical direction to use before punting and redrawing from scratch. 541 * For example, if maxV is 2, then we will attempt to scale any cached images up to 2x the canvas 542 * height before redrawing from scratch. Reasonable maxV values may improve painting performance. 543 * If set too high, then you may get poor looking graphics at higher zoom levels. Must be >= 1. 544 */ PaintContext(Insets insets, Dimension canvasSize, boolean inverted, CacheMode cacheMode, double maxH, double maxV)545 public PaintContext(Insets insets, Dimension canvasSize, boolean inverted, 546 CacheMode cacheMode, double maxH, double maxV) { 547 if (maxH < 1 || maxH < 1) { 548 throw new IllegalArgumentException("Both maxH and maxV must be >= 1"); 549 } 550 551 this.stretchingInsets = insets == null ? EMPTY_INSETS : insets; 552 this.canvasSize = canvasSize; 553 this.inverted = inverted; 554 this.cacheMode = cacheMode == null ? CacheMode.NO_CACHING : cacheMode; 555 this.maxHorizontalScaleFactor = maxH; 556 this.maxVerticalScaleFactor = maxV; 557 558 if (canvasSize != null) { 559 a = stretchingInsets.left; 560 b = canvasSize.width - stretchingInsets.right; 561 c = stretchingInsets.top; 562 d = canvasSize.height - stretchingInsets.bottom; 563 this.canvasSize = canvasSize; 564 this.inverted = inverted; 565 if (inverted) { 566 float available = canvasSize.width - (b - a); 567 aPercent = available > 0f ? a / available : 0f; 568 bPercent = available > 0f ? b / available : 0f; 569 available = canvasSize.height - (d - c); 570 cPercent = available > 0f ? c / available : 0f; 571 dPercent = available > 0f ? d / available : 0f; 572 } 573 } 574 } 575 } 576 577 //---------------------- private methods 578 579 //initializes the class to prepare it for being able to decode points prepare(float w, float h)580 private void prepare(float w, float h) { 581 //if no PaintContext has been specified, reset the values and bail 582 //also bail if the canvasSize was not set (since decoding will not work) 583 if (ctx == null || ctx.canvasSize == null) { 584 f = 1f; 585 leftWidth = centerWidth = rightWidth = 0f; 586 topHeight = centerHeight = bottomHeight = 0f; 587 leftScale = centerHScale = rightScale = 0f; 588 topScale = centerVScale = bottomScale = 0f; 589 return; 590 } 591 592 //calculate the scaling factor, and the sizes for the various 9-square sections 593 Number scale = (Number)UIManager.get("scale"); 594 f = scale == null ? 1f : scale.floatValue(); 595 596 if (ctx.inverted) { 597 centerWidth = (ctx.b - ctx.a) * f; 598 float availableSpace = w - centerWidth; 599 leftWidth = availableSpace * ctx.aPercent; 600 rightWidth = availableSpace * ctx.bPercent; 601 centerHeight = (ctx.d - ctx.c) * f; 602 availableSpace = h - centerHeight; 603 topHeight = availableSpace * ctx.cPercent; 604 bottomHeight = availableSpace * ctx.dPercent; 605 } else { 606 leftWidth = ctx.a * f; 607 rightWidth = (float)(ctx.canvasSize.getWidth() - ctx.b) * f; 608 centerWidth = w - leftWidth - rightWidth; 609 topHeight = ctx.c * f; 610 bottomHeight = (float)(ctx.canvasSize.getHeight() - ctx.d) * f; 611 centerHeight = h - topHeight - bottomHeight; 612 } 613 614 leftScale = ctx.a == 0f ? 0f : leftWidth / ctx.a; 615 centerHScale = (ctx.b - ctx.a) == 0f ? 0f : centerWidth / (ctx.b - ctx.a); 616 rightScale = (ctx.canvasSize.width - ctx.b) == 0f ? 0f : rightWidth / (ctx.canvasSize.width - ctx.b); 617 topScale = ctx.c == 0f ? 0f : topHeight / ctx.c; 618 centerVScale = (ctx.d - ctx.c) == 0f ? 0f : centerHeight / (ctx.d - ctx.c); 619 bottomScale = (ctx.canvasSize.height - ctx.d) == 0f ? 0f : bottomHeight / (ctx.canvasSize.height - ctx.d); 620 } 621 paintWith9SquareCaching(Graphics2D g, PaintContext ctx, JComponent c, int w, int h, Object[] extendedCacheKeys)622 private void paintWith9SquareCaching(Graphics2D g, PaintContext ctx, 623 JComponent c, int w, int h, 624 Object[] extendedCacheKeys) { 625 // check if we can scale to the requested size 626 Dimension canvas = ctx.canvasSize; 627 Insets insets = ctx.stretchingInsets; 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 paintWithFixedSizeCaching(Graphics2D g, JComponent c, int w, int h, Object[] extendedCacheKeys)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 */ getImage(GraphicsConfiguration config, JComponent c, int w, int h, Object[] extendedCacheKeys)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. paint0(Graphics2D g, JComponent c, int width, int height, Object[] extendedCacheKeys)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 clamp(float value)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 clamp(int value)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 } 753