jdk/src/java.desktop/share/classes/javax/swing/plaf/nimbus/AbstractRegionPainter.java
changeset 25859 3317bb8137f4
parent 25159 fdacfe8fd602
child 29894 3e16b51732f5
equal deleted inserted replaced
25858:836adbf7a2cd 25859:3317bb8137f4
       
     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 &gt;= 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 &gt;= 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 }