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 &gt;= 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 &gt;= 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 }