1 /* 2 * Copyright (c) 2005, 2014, 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 */ 138 protected AbstractRegionPainter() { } 139 140 /** 141 * {@inheritDoc} 142 */ 143 @Override 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 x1 361 * @param y1 y1 362 * @param x2 x2 363 * @param y2 y2 364 * @param midpoints the midpoints 365 * @param colors the 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 */ 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 x-coordinate 392 * @param y y-coordinate 393 * @param r radius 394 * @param midpoints the midpoints 395 * @param colors the 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 */ 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 * @param saturationOffset additively modifies the HSB saturation component 425 * of the color returned (ignored if default color is returned). 426 * @param brightnessOffset additively modifies the HSB brightness component 427 * of the color returned (ignored if default color is returned). 428 * @param alphaOffset additively modifies the ARGB alpha component of the 429 * color returned (ignored if default color is returned). 430 * 431 * @return The color that was obtained from the component or defaultColor 432 */ 433 protected final Color getComponentColor(JComponent c, String property, 434 Color defaultColor, 435 float saturationOffset, 436 float brightnessOffset, 437 int alphaOffset) { 438 Color color = null; 439 if (c != null) { 440 // handle some special cases for performance 441 if ("background".equals(property)) { 442 color = c.getBackground(); 443 } else if ("foreground".equals(property)) { 444 color = c.getForeground(); 445 } else if (c instanceof JList && "selectionForeground".equals(property)) { 446 color = ((JList) c).getSelectionForeground(); 447 } else if (c instanceof JList && "selectionBackground".equals(property)) { 448 color = ((JList) c).getSelectionBackground(); 449 } else if (c instanceof JTable && "selectionForeground".equals(property)) { 450 color = ((JTable) c).getSelectionForeground(); 451 } else if (c instanceof JTable && "selectionBackground".equals(property)) { 452 color = ((JTable) c).getSelectionBackground(); 453 } else { 454 String s = "get" + Character.toUpperCase(property.charAt(0)) + property.substring(1); 455 try { 456 Method method = MethodUtil.getMethod(c.getClass(), s, null); 457 color = (Color) MethodUtil.invoke(method, c, null); 458 } catch (Exception e) { 459 //don't do anything, it just didn't work, that's all. 460 //This could be a normal occurance if you use a property 461 //name referring to a key in clientProperties instead of 462 //a real property 463 } 464 if (color == null) { 465 Object value = c.getClientProperty(property); 466 if (value instanceof Color) { 467 color = (Color) value; 468 } 469 } 470 } 471 } 472 // we return the defaultColor if the color found is null, or if 473 // it is a UIResource. This is done because the color for the 474 // ENABLED state is set on the component, but you don't want to use 475 // that color for the over state. So we only respect the color 476 // specified for the property if it was set by the user, as opposed 477 // to set by us. 478 if (color == null || color instanceof UIResource) { 479 return defaultColor; 480 } else if (saturationOffset != 0 || brightnessOffset != 0 || alphaOffset != 0) { 481 float[] tmp = Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), null); 482 tmp[1] = clamp(tmp[1] + saturationOffset); 483 tmp[2] = clamp(tmp[2] + brightnessOffset); 484 int alpha = clamp(color.getAlpha() + alphaOffset); 485 return new Color((Color.HSBtoRGB(tmp[0], tmp[1], tmp[2]) & 0xFFFFFF) | (alpha <<24)); 486 } else { 487 return color; 488 } 489 } 490 491 /** 492 * A class encapsulating state useful when painting. Generally, instances of this 493 * class are created once, and reused for each paint request without modification. 494 * This class contains values useful when hinting the cache engine, and when decoding 495 * control points and bezier curve anchors. 496 */ 497 protected static class PaintContext { 498 protected static enum CacheMode { 499 NO_CACHING, FIXED_SIZES, NINE_SQUARE_SCALE 500 } 501 502 private static Insets EMPTY_INSETS = new Insets(0, 0, 0, 0); 503 504 private Insets stretchingInsets; 505 private Dimension canvasSize; 506 private boolean inverted; 507 private CacheMode cacheMode; 508 private double maxHorizontalScaleFactor; 509 private double maxVerticalScaleFactor; 510 511 private float a; // insets.left 512 private float b; // canvasSize.width - insets.right 513 private float c; // insets.top 514 private float d; // canvasSize.height - insets.bottom; 515 private float aPercent; // only used if inverted == true 516 private float bPercent; // only used if inverted == true 517 private float cPercent; // only used if inverted == true 518 private float dPercent; // only used if inverted == true 519 520 /** 521 * Creates a new PaintContext which does not attempt to cache or scale any cached 522 * images. 523 * 524 * @param insets The stretching insets. May be null. If null, then assumed to be 0, 0, 0, 0. 525 * @param canvasSize The size of the canvas used when encoding the various x/y values. May be null. 526 * If null, then it is assumed that there are no encoded values, and any calls 527 * to one of the "decode" methods will return the passed in value. 528 * @param inverted Whether to "invert" the meaning of the 9-square grid and stretching insets 529 */ 530 public PaintContext(Insets insets, Dimension canvasSize, boolean inverted) { 531 this(insets, canvasSize, inverted, null, 1, 1); 532 } 533 534 /** 535 * Creates a new PaintContext. 536 * 537 * @param insets The stretching insets. May be null. If null, then assumed to be 0, 0, 0, 0. 538 * @param canvasSize The size of the canvas used when encoding the various x/y values. May be null. 539 * If null, then it is assumed that there are no encoded values, and any calls 540 * to one of the "decode" methods will return the passed in value. 541 * @param inverted Whether to "invert" the meaning of the 9-square grid and stretching insets 542 * @param cacheMode A hint as to which caching mode to use. If null, then set to no caching. 543 * @param maxH The maximum scale in the horizontal direction to use before punting and redrawing from scratch. 544 * For example, if maxH is 2, then we will attempt to scale any cached images up to 2x the canvas 545 * width before redrawing from scratch. Reasonable maxH values may improve painting performance. 546 * If set too high, then you may get poor looking graphics at higher zoom levels. Must be >= 1. 547 * @param maxV The maximum scale in the vertical direction to use before punting and redrawing from scratch. 548 * For example, if maxV is 2, then we will attempt to scale any cached images up to 2x the canvas 549 * height before redrawing from scratch. Reasonable maxV values may improve painting performance. 550 * If set too high, then you may get poor looking graphics at higher zoom levels. Must be >= 1. 551 */ 552 public PaintContext(Insets insets, Dimension canvasSize, boolean inverted, 553 CacheMode cacheMode, double maxH, double maxV) { 554 if (maxH < 1 || maxH < 1) { 555 throw new IllegalArgumentException("Both maxH and maxV must be >= 1"); 556 } 557 558 this.stretchingInsets = insets == null ? EMPTY_INSETS : insets; 559 this.canvasSize = canvasSize; 560 this.inverted = inverted; 561 this.cacheMode = cacheMode == null ? CacheMode.NO_CACHING : cacheMode; 562 this.maxHorizontalScaleFactor = maxH; 563 this.maxVerticalScaleFactor = maxV; 564 565 if (canvasSize != null) { 566 a = stretchingInsets.left; 567 b = canvasSize.width - stretchingInsets.right; 568 c = stretchingInsets.top; 569 d = canvasSize.height - stretchingInsets.bottom; 570 this.canvasSize = canvasSize; 571 this.inverted = inverted; 572 if (inverted) { 573 float available = canvasSize.width - (b - a); 574 aPercent = available > 0f ? a / available : 0f; 575 bPercent = available > 0f ? b / available : 0f; 576 available = canvasSize.height - (d - c); 577 cPercent = available > 0f ? c / available : 0f; 578 dPercent = available > 0f ? d / available : 0f; 579 } 580 } 581 } 582 } 583 584 //---------------------- private methods 585 586 //initializes the class to prepare it for being able to decode points 587 private void prepare(float w, float h) { 588 //if no PaintContext has been specified, reset the values and bail 589 //also bail if the canvasSize was not set (since decoding will not work) 590 if (ctx == null || ctx.canvasSize == null) { 591 f = 1f; 592 leftWidth = centerWidth = rightWidth = 0f; 593 topHeight = centerHeight = bottomHeight = 0f; 594 leftScale = centerHScale = rightScale = 0f; 595 topScale = centerVScale = bottomScale = 0f; 596 return; 597 } 598 599 //calculate the scaling factor, and the sizes for the various 9-square sections 600 Number scale = (Number)UIManager.get("scale"); 601 f = scale == null ? 1f : scale.floatValue(); 602 603 if (ctx.inverted) { 604 centerWidth = (ctx.b - ctx.a) * f; 605 float availableSpace = w - centerWidth; 606 leftWidth = availableSpace * ctx.aPercent; 607 rightWidth = availableSpace * ctx.bPercent; 608 centerHeight = (ctx.d - ctx.c) * f; 609 availableSpace = h - centerHeight; 610 topHeight = availableSpace * ctx.cPercent; 611 bottomHeight = availableSpace * ctx.dPercent; 612 } else { 613 leftWidth = ctx.a * f; 614 rightWidth = (float)(ctx.canvasSize.getWidth() - ctx.b) * f; 615 centerWidth = w - leftWidth - rightWidth; 616 topHeight = ctx.c * f; 617 bottomHeight = (float)(ctx.canvasSize.getHeight() - ctx.d) * f; 618 centerHeight = h - topHeight - bottomHeight; 619 } 620 621 leftScale = ctx.a == 0f ? 0f : leftWidth / ctx.a; 622 centerHScale = (ctx.b - ctx.a) == 0f ? 0f : centerWidth / (ctx.b - ctx.a); 623 rightScale = (ctx.canvasSize.width - ctx.b) == 0f ? 0f : rightWidth / (ctx.canvasSize.width - ctx.b); 624 topScale = ctx.c == 0f ? 0f : topHeight / ctx.c; 625 centerVScale = (ctx.d - ctx.c) == 0f ? 0f : centerHeight / (ctx.d - ctx.c); 626 bottomScale = (ctx.canvasSize.height - ctx.d) == 0f ? 0f : bottomHeight / (ctx.canvasSize.height - ctx.d); 627 } 628 629 private void paintWith9SquareCaching(Graphics2D g, PaintContext ctx, 630 JComponent c, int w, int h, 631 Object[] extendedCacheKeys) { 632 // check if we can scale to the requested size 633 Dimension canvas = ctx.canvasSize; 634 Insets insets = ctx.stretchingInsets; 635 636 if (w <= (canvas.width * ctx.maxHorizontalScaleFactor) && h <= (canvas.height * ctx.maxVerticalScaleFactor)) { 637 // get image at canvas size 638 VolatileImage img = getImage(g.getDeviceConfiguration(), c, canvas.width, canvas.height, extendedCacheKeys); 639 if (img != null) { 640 // calculate dst inserts 641 // todo: destination inserts need to take into acount scale factor for high dpi. Note: You can use f for this, I think 642 Insets dstInsets; 643 if (ctx.inverted){ 644 int leftRight = (w-(canvas.width-(insets.left+insets.right)))/2; 645 int topBottom = (h-(canvas.height-(insets.top+insets.bottom)))/2; 646 dstInsets = new Insets(topBottom,leftRight,topBottom,leftRight); 647 } else { 648 dstInsets = insets; 649 } 650 // paint 9 square scaled 651 Object oldScaleingHints = g.getRenderingHint(RenderingHints.KEY_INTERPOLATION); 652 g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,RenderingHints.VALUE_INTERPOLATION_BILINEAR); 653 ImageScalingHelper.paint(g, 0, 0, w, h, img, insets, dstInsets, 654 ImageScalingHelper.PaintType.PAINT9_STRETCH, ImageScalingHelper.PAINT_ALL); 655 g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, 656 oldScaleingHints!=null?oldScaleingHints:RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); 657 } else { 658 // render directly 659 paint0(g, c, w, h, extendedCacheKeys); 660 } 661 } else { 662 // paint directly 663 paint0(g, c, w, h, extendedCacheKeys); 664 } 665 } 666 667 private void paintWithFixedSizeCaching(Graphics2D g, JComponent c, int w, 668 int h, Object[] extendedCacheKeys) { 669 VolatileImage img = getImage(g.getDeviceConfiguration(), c, w, h, extendedCacheKeys); 670 if (img != null) { 671 //render cached image 672 g.drawImage(img, 0, 0, null); 673 } else { 674 // render directly 675 paint0(g, c, w, h, extendedCacheKeys); 676 } 677 } 678 679 /** Gets the rendered image for this painter at the requested size, either from cache or create a new one */ 680 private VolatileImage getImage(GraphicsConfiguration config, JComponent c, 681 int w, int h, Object[] extendedCacheKeys) { 682 ImageCache imageCache = ImageCache.getInstance(); 683 //get the buffer for this component 684 VolatileImage buffer = (VolatileImage) imageCache.getImage(config, w, h, this, extendedCacheKeys); 685 686 int renderCounter = 0; //to avoid any potential, though unlikely, infinite loop 687 do { 688 //validate the buffer so we can check for surface loss 689 int bufferStatus = VolatileImage.IMAGE_INCOMPATIBLE; 690 if (buffer != null) { 691 bufferStatus = buffer.validate(config); 692 } 693 694 //If the buffer status is incompatible or restored, then we need to re-render to the volatile image 695 if (bufferStatus == VolatileImage.IMAGE_INCOMPATIBLE || bufferStatus == VolatileImage.IMAGE_RESTORED) { 696 //if the buffer is null (hasn't been created), or isn't the right size, or has lost its contents, 697 //then recreate the buffer 698 if (buffer == null || buffer.getWidth() != w || buffer.getHeight() != h || 699 bufferStatus == VolatileImage.IMAGE_INCOMPATIBLE) { 700 //clear any resources related to the old back buffer 701 if (buffer != null) { 702 buffer.flush(); 703 buffer = null; 704 } 705 //recreate the buffer 706 buffer = config.createCompatibleVolatileImage(w, h, 707 Transparency.TRANSLUCENT); 708 // put in cache for future 709 imageCache.setImage(buffer, config, w, h, this, extendedCacheKeys); 710 } 711 //create the graphics context with which to paint to the buffer 712 Graphics2D bg = buffer.createGraphics(); 713 //clear the background before configuring the graphics 714 bg.setComposite(AlphaComposite.Clear); 715 bg.fillRect(0, 0, w, h); 716 bg.setComposite(AlphaComposite.SrcOver); 717 configureGraphics(bg); 718 // paint the painter into buffer 719 paint0(bg, c, w, h, extendedCacheKeys); 720 //close buffer graphics 721 bg.dispose(); 722 } 723 } while (buffer.contentsLost() && renderCounter++ < 3); 724 // check if we failed 725 if (renderCounter == 3) return null; 726 // return image 727 return buffer; 728 } 729 730 //convenience method which creates a temporary graphics object by creating a 731 //clone of the passed in one, configuring it, drawing with it, disposing it. 732 //These steps have to be taken to ensure that any hints set on the graphics 733 //are removed subsequent to painting. 734 private void paint0(Graphics2D g, JComponent c, int width, int height, 735 Object[] extendedCacheKeys) { 736 prepare(width, height); 737 g = (Graphics2D)g.create(); 738 configureGraphics(g); 739 doPaint(g, c, width, height, extendedCacheKeys); 740 g.dispose(); 741 } 742 743 private float clamp(float value) { 744 if (value < 0) { 745 value = 0; 746 } else if (value > 1) { 747 value = 1; 748 } 749 return value; 750 } 751 752 private int clamp(int value) { 753 if (value < 0) { 754 value = 0; 755 } else if (value > 255) { 756 value = 255; 757 } 758 return value; 759 } 760 }