1173N/A/*
2362N/A * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved.
1173N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
1173N/A *
1173N/A * This code is free software; you can redistribute it and/or modify it
1173N/A * under the terms of the GNU General Public License version 2 only, as
2362N/A * published by the Free Software Foundation. Oracle designates this
1173N/A * particular file as subject to the "Classpath" exception as provided
2362N/A * by Oracle in the LICENSE file that accompanied this code.
1173N/A *
1173N/A * This code is distributed in the hope that it will be useful, but WITHOUT
1173N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
1173N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
1173N/A * version 2 for more details (a copy is included in the LICENSE file that
1173N/A * accompanied this code).
1173N/A *
1173N/A * You should have received a copy of the GNU General Public License version
1173N/A * 2 along with this work; if not, write to the Free Software Foundation,
1173N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
1173N/A *
2362N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2362N/A * or visit www.oracle.com if you need additional information or have any
2362N/A * questions.
1173N/A */
1173N/Apackage javax.swing.plaf.nimbus;
1173N/A
1173N/Aimport java.awt.*;
1173N/Aimport java.awt.image.*;
1173N/Aimport java.lang.reflect.Method;
1173N/Aimport javax.swing.*;
1173N/Aimport javax.swing.plaf.UIResource;
1173N/Aimport javax.swing.Painter;
1173N/Aimport java.awt.print.PrinterGraphics;
1173N/A
1173N/A/**
1173N/A * Convenient base class for defining Painter instances for rendering a
1173N/A * region or component in Nimbus.
1173N/A *
1173N/A * @author Jasper Potts
1173N/A * @author Richard Bair
1173N/A */
1173N/Apublic abstract class AbstractRegionPainter implements Painter<JComponent> {
1173N/A /**
1173N/A * PaintContext, which holds a lot of the state needed for cache hinting and x/y value decoding
1173N/A * The data contained within the context is typically only computed once and reused over
1173N/A * multiple paint calls, whereas the other values (w, h, f, leftWidth, etc) are recomputed
1173N/A * for each call to paint.
1173N/A *
1173N/A * This field is retrieved from subclasses on each paint operation. It is up
1173N/A * to the subclass to compute and cache the PaintContext over multiple calls.
1173N/A */
1173N/A private PaintContext ctx;
1173N/A /**
1173N/A * The scaling factor. Recomputed on each call to paint.
1173N/A */
1173N/A private float f;
1173N/A /*
1173N/A Various metrics used for decoding x/y values based on the canvas size
1173N/A and stretching insets.
1173N/A
1173N/A On each call to paint, we first ask the subclass for the PaintContext.
1173N/A From the context we get the canvas size and stretching insets, and whether
1173N/A the algorithm should be "inverted", meaning the center section remains
1173N/A a fixed size and the other sections scale.
1173N/A
1173N/A We then use these values to compute a series of metrics (listed below)
1173N/A which are used to decode points in a specific axis (x or y).
1173N/A
1173N/A The leftWidth represents the distance from the left edge of the region
1173N/A to the first stretching inset, after accounting for any scaling factor
1173N/A (such as DPI scaling). The centerWidth is the distance between the leftWidth
1173N/A and the rightWidth. The rightWidth is the distance from the right edge,
1173N/A to the right inset (after scaling has been applied).
1173N/A
1173N/A The same logic goes for topHeight, centerHeight, and bottomHeight.
1173N/A
1173N/A The leftScale represents the proportion of the width taken by the left section.
1173N/A The same logic is applied to the other scales.
1173N/A
1173N/A The various widths/heights are used to decode control points. The
1173N/A various scales are used to decode bezier handles (or anchors).
1173N/A */
1173N/A /**
1173N/A * The width of the left section. Recomputed on each call to paint.
1173N/A */
1173N/A private float leftWidth;
1173N/A /**
1173N/A * The height of the top section. Recomputed on each call to paint.
1173N/A */
1173N/A private float topHeight;
1173N/A /**
1173N/A * The width of the center section. Recomputed on each call to paint.
1173N/A */
1173N/A private float centerWidth;
1173N/A /**
1173N/A * The height of the center section. Recomputed on each call to paint.
1173N/A */
1173N/A private float centerHeight;
1173N/A /**
1173N/A * The width of the right section. Recomputed on each call to paint.
1173N/A */
1173N/A private float rightWidth;
1173N/A /**
1173N/A * The height of the bottom section. Recomputed on each call to paint.
1173N/A */
1173N/A private float bottomHeight;
1173N/A /**
1173N/A * The scaling factor to use for the left section. Recomputed on each call to paint.
1173N/A */
1173N/A private float leftScale;
1173N/A /**
1173N/A * The scaling factor to use for the top section. Recomputed on each call to paint.
1173N/A */
1173N/A private float topScale;
1173N/A /**
1173N/A * The scaling factor to use for the center section, in the horizontal
1173N/A * direction. Recomputed on each call to paint.
1173N/A */
1173N/A private float centerHScale;
1173N/A /**
1173N/A * The scaling factor to use for the center section, in the vertical
1173N/A * direction. Recomputed on each call to paint.
1173N/A */
1173N/A private float centerVScale;
1173N/A /**
1173N/A * The scaling factor to use for the right section. Recomputed on each call to paint.
1173N/A */
1173N/A private float rightScale;
1173N/A /**
1173N/A * The scaling factor to use for the bottom section. Recomputed on each call to paint.
1173N/A */
1173N/A private float bottomScale;
1173N/A
1173N/A /**
1173N/A * Create a new AbstractRegionPainter
1173N/A */
1173N/A protected AbstractRegionPainter() { }
1173N/A
1173N/A /**
1173N/A * @inheritDoc
1173N/A */
1173N/A @Override
1173N/A public final void paint(Graphics2D g, JComponent c, int w, int h) {
1173N/A //don't render if the width/height are too small
1173N/A if (w <= 0 || h <=0) return;
1173N/A
1173N/A Object[] extendedCacheKeys = getExtendedCacheKeys(c);
1173N/A ctx = getPaintContext();
1173N/A PaintContext.CacheMode cacheMode = ctx == null ? PaintContext.CacheMode.NO_CACHING : ctx.cacheMode;
1173N/A if (cacheMode == PaintContext.CacheMode.NO_CACHING ||
1173N/A !ImageCache.getInstance().isImageCachable(w, h) ||
1173N/A g instanceof PrinterGraphics) {
1173N/A // no caching so paint directly
1173N/A paint0(g, c, w, h, extendedCacheKeys);
1173N/A } else if (cacheMode == PaintContext.CacheMode.FIXED_SIZES) {
1173N/A paintWithFixedSizeCaching(g, c, w, h, extendedCacheKeys);
1173N/A } else {
1173N/A // 9 Square caching
1173N/A paintWith9SquareCaching(g, ctx, c, w, h, extendedCacheKeys);
1173N/A }
1173N/A }
1173N/A
1173N/A /**
1173N/A * Get any extra attributes which the painter implementation would like
1173N/A * to include in the image cache lookups. This is checked for every call
1173N/A * of the paint(g, c, w, h) method.
1173N/A *
1173N/A * @param c The component on the current paint call
1173N/A * @return Array of extra objects to be included in the cache key
1173N/A */
1173N/A protected Object[] getExtendedCacheKeys(JComponent c) {
1173N/A return null;
1173N/A }
1173N/A
1173N/A /**
1173N/A * <p>Gets the PaintContext for this painting operation. This method is called on every
1173N/A * paint, and so should be fast and produce no garbage. The PaintContext contains
1173N/A * information such as cache hints. It also contains data necessary for decoding
1173N/A * points at runtime, such as the stretching insets, the canvas size at which the
1173N/A * encoded points were defined, and whether the stretching insets are inverted.</p>
1173N/A *
1173N/A * <p> This method allows for subclasses to package the painting of different states
1173N/A * with possibly different canvas sizes, etc, into one AbstractRegionPainter implementation.</p>
1173N/A *
1173N/A * @return a PaintContext associated with this paint operation.
1173N/A */
1173N/A protected abstract PaintContext getPaintContext();
1173N/A
1173N/A /**
1173N/A * <p>Configures the given Graphics2D. Often, rendering hints or compositiing rules are
1173N/A * applied to a Graphics2D object prior to painting, which should affect all of the
1173N/A * subsequent painting operations. This method provides a convenient hook for configuring
1173N/A * the Graphics object prior to rendering, regardless of whether the render operation is
1173N/A * performed to an intermediate buffer or directly to the display.</p>
1173N/A *
1173N/A * @param g The Graphics2D object to configure. Will not be null.
1173N/A */
1173N/A protected void configureGraphics(Graphics2D g) {
1173N/A g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
1173N/A }
1173N/A
1173N/A /**
1173N/A * Actually performs the painting operation. Subclasses must implement this method.
1173N/A * The graphics object passed may represent the actual surface being rendererd to,
1173N/A * or it may be an intermediate buffer. It has also been pre-translated. Simply render
1173N/A * the component as if it were located at 0, 0 and had a width of <code>width</code>
1173N/A * and a height of <code>height</code>. For performance reasons, you may want to read
1173N/A * the clip from the Graphics2D object and only render within that space.
1173N/A *
1173N/A * @param g The Graphics2D surface to paint to
1173N/A * @param c The JComponent related to the drawing event. For example, if the
1173N/A * region being rendered is Button, then <code>c</code> will be a
1173N/A * JButton. If the region being drawn is ScrollBarSlider, then the
1173N/A * component will be JScrollBar. This value may be null.
1173N/A * @param width The width of the region to paint. Note that in the case of
1173N/A * painting the foreground, this value may differ from c.getWidth().
1173N/A * @param height The height of the region to paint. Note that in the case of
1173N/A * painting the foreground, this value may differ from c.getHeight().
1173N/A * @param extendedCacheKeys The result of the call to getExtendedCacheKeys()
1173N/A */
1173N/A protected abstract void doPaint(Graphics2D g, JComponent c, int width,
1173N/A int height, Object[] extendedCacheKeys);
1173N/A
1173N/A /**
1173N/A * Decodes and returns a float value representing the actual pixel location for
1173N/A * the given encoded X value.
1173N/A *
1173N/A * @param x an encoded x value (0...1, or 1...2, or 2...3)
1173N/A * @return the decoded x value
1451N/A * @throws IllegalArgumentException
1451N/A * if {@code x < 0} or {@code x > 3}
1173N/A */
1173N/A protected final float decodeX(float x) {
1173N/A if (x >= 0 && x <= 1) {
1173N/A return x * leftWidth;
1173N/A } else if (x > 1 && x < 2) {
1173N/A return ((x-1) * centerWidth) + leftWidth;
1173N/A } else if (x >= 2 && x <= 3) {
1173N/A return ((x-2) * rightWidth) + leftWidth + centerWidth;
1173N/A } else {
1451N/A throw new IllegalArgumentException("Invalid x");
1173N/A }
1173N/A }
1173N/A
1173N/A /**
1173N/A * Decodes and returns a float value representing the actual pixel location for
1173N/A * the given encoded y value.
1173N/A *
1173N/A * @param y an encoded y value (0...1, or 1...2, or 2...3)
1173N/A * @return the decoded y value
1451N/A * @throws IllegalArgumentException
1451N/A * if {@code y < 0} or {@code y > 3}
1173N/A */
1173N/A protected final float decodeY(float y) {
1173N/A if (y >= 0 && y <= 1) {
1173N/A return y * topHeight;
1173N/A } else if (y > 1 && y < 2) {
1173N/A return ((y-1) * centerHeight) + topHeight;
1173N/A } else if (y >= 2 && y <= 3) {
1173N/A return ((y-2) * bottomHeight) + topHeight + centerHeight;
1173N/A } else {
1451N/A throw new IllegalArgumentException("Invalid y");
1173N/A }
1173N/A }
1173N/A
1173N/A /**
1173N/A * Decodes and returns a float value representing the actual pixel location for
1173N/A * the anchor point given the encoded X value of the control point, and the offset
1173N/A * distance to the anchor from that control point.
1173N/A *
1173N/A * @param x an encoded x value of the bezier control point (0...1, or 1...2, or 2...3)
1173N/A * @param dx the offset distance to the anchor from the control point x
1173N/A * @return the decoded x location of the control point
1451N/A * @throws IllegalArgumentException
1451N/A * if {@code x < 0} or {@code x > 3}
1173N/A */
1173N/A protected final float decodeAnchorX(float x, float dx) {
1173N/A if (x >= 0 && x <= 1) {
1173N/A return decodeX(x) + (dx * leftScale);
1173N/A } else if (x > 1 && x < 2) {
1173N/A return decodeX(x) + (dx * centerHScale);
1173N/A } else if (x >= 2 && x <= 3) {
1173N/A return decodeX(x) + (dx * rightScale);
1173N/A } else {
1451N/A throw new IllegalArgumentException("Invalid x");
1173N/A }
1173N/A }
1173N/A
1173N/A /**
1173N/A * Decodes and returns a float value representing the actual pixel location for
1173N/A * the anchor point given the encoded Y value of the control point, and the offset
1173N/A * distance to the anchor from that control point.
1173N/A *
1173N/A * @param y an encoded y value of the bezier control point (0...1, or 1...2, or 2...3)
1173N/A * @param dy the offset distance to the anchor from the control point y
1173N/A * @return the decoded y position of the control point
1451N/A * @throws IllegalArgumentException
1451N/A * if {@code y < 0} or {@code y > 3}
1173N/A */
1173N/A protected final float decodeAnchorY(float y, float dy) {
1173N/A if (y >= 0 && y <= 1) {
1173N/A return decodeY(y) + (dy * topScale);
1173N/A } else if (y > 1 && y < 2) {
1173N/A return decodeY(y) + (dy * centerVScale);
1173N/A } else if (y >= 2 && y <= 3) {
1173N/A return decodeY(y) + (dy * bottomScale);
1173N/A } else {
1451N/A throw new IllegalArgumentException("Invalid y");
1173N/A }
1173N/A }
1173N/A
1173N/A /**
1173N/A * Decodes and returns a color, which is derived from a base color in UI
1173N/A * defaults.
1173N/A *
1173N/A * @param key A key corrosponding to the value in the UI Defaults table
1173N/A * of UIManager where the base color is defined
1173N/A * @param hOffset The hue offset used for derivation.
1173N/A * @param sOffset The saturation offset used for derivation.
1173N/A * @param bOffset The brightness offset used for derivation.
1173N/A * @param aOffset The alpha offset used for derivation. Between 0...255
1173N/A * @return The derived color, whos color value will change if the parent
1173N/A * uiDefault color changes.
1173N/A */
1173N/A protected final Color decodeColor(String key, float hOffset, float sOffset,
1173N/A float bOffset, int aOffset) {
1173N/A if (UIManager.getLookAndFeel() instanceof NimbusLookAndFeel){
1173N/A NimbusLookAndFeel laf = (NimbusLookAndFeel) UIManager.getLookAndFeel();
1173N/A return laf.getDerivedColor(key, hOffset, sOffset, bOffset, aOffset, true);
1173N/A } else {
1173N/A // can not give a right answer as painter sould not be used outside
1173N/A // of nimbus laf but do the best we can
1173N/A return Color.getHSBColor(hOffset,sOffset,bOffset);
1173N/A }
1173N/A }
1173N/A
1173N/A /**
1173N/A * Decodes and returns a color, which is derived from a offset between two
1173N/A * other colors.
1173N/A *
1173N/A * @param color1 The first color
1173N/A * @param color2 The second color
1173N/A * @param midPoint The offset between color 1 and color 2, a value of 0.0 is
1173N/A * color 1 and 1.0 is color 2;
1173N/A * @return The derived color
1173N/A */
1173N/A protected final Color decodeColor(Color color1, Color color2,
1173N/A float midPoint) {
1361N/A return new Color(NimbusLookAndFeel.deriveARGB(color1, color2, midPoint));
1173N/A }
1173N/A
1173N/A /**
1173N/A * Given parameters for creating a LinearGradientPaint, this method will
1173N/A * create and return a linear gradient paint. One primary purpose for this
1173N/A * method is to avoid creating a LinearGradientPaint where the start and
1173N/A * end points are equal. In such a case, the end y point is slightly
1173N/A * increased to avoid the overlap.
1173N/A *
1173N/A * @param x1
1173N/A * @param y1
1173N/A * @param x2
1173N/A * @param y2
1173N/A * @param midpoints
1173N/A * @param colors
1173N/A * @return a valid LinearGradientPaint. This method never returns null.
1451N/A * @throws NullPointerException
1451N/A * if {@code midpoints} array is null,
1451N/A * or {@code colors} array is null,
1451N/A * @throws IllegalArgumentException
1451N/A * if start and end points are the same points,
1451N/A * or {@code midpoints.length != colors.length},
1451N/A * or {@code colors} is less than 2 in size,
1451N/A * or a {@code midpoints} value is less than 0.0 or greater than 1.0,
1451N/A * or the {@code midpoints} are not provided in strictly increasing order
1173N/A */
1173N/A protected final LinearGradientPaint decodeGradient(float x1, float y1, float x2, float y2, float[] midpoints, Color[] colors) {
1173N/A if (x1 == x2 && y1 == y2) {
1173N/A y2 += .00001f;
1173N/A }
1173N/A return new LinearGradientPaint(x1, y1, x2, y2, midpoints, colors);
1173N/A }
1173N/A
1173N/A /**
1173N/A * Given parameters for creating a RadialGradientPaint, this method will
1173N/A * create and return a radial gradient paint. One primary purpose for this
1173N/A * method is to avoid creating a RadialGradientPaint where the radius
1173N/A * is non-positive. In such a case, the radius is just slightly
1173N/A * increased to avoid 0.
1173N/A *
1173N/A * @param x
1173N/A * @param y
1173N/A * @param r
1173N/A * @param midpoints
1173N/A * @param colors
1173N/A * @return a valid RadialGradientPaint. This method never returns null.
1451N/A * @throws NullPointerException
1451N/A * if {@code midpoints} array is null,
1451N/A * or {@code colors} array is null
1451N/A * @throws IllegalArgumentException
1451N/A * if {@code r} is non-positive,
1451N/A * or {@code midpoints.length != colors.length},
1451N/A * or {@code colors} is less than 2 in size,
1451N/A * or a {@code midpoints} value is less than 0.0 or greater than 1.0,
1451N/A * or the {@code midpoints} are not provided in strictly increasing order
1173N/A */
1173N/A protected final RadialGradientPaint decodeRadialGradient(float x, float y, float r, float[] midpoints, Color[] colors) {
1173N/A if (r == 0f) {
1173N/A r = .00001f;
1173N/A }
1173N/A return new RadialGradientPaint(x, y, r, midpoints, colors);
1173N/A }
1173N/A
1173N/A /**
1173N/A * Get a color property from the given JComponent. First checks for a
1173N/A * <code>getXXX()</code> method and if that fails checks for a client
1173N/A * property with key <code>property</code>. If that still fails to return
1173N/A * a Color then <code>defaultColor</code> is returned.
1173N/A *
1173N/A * @param c The component to get the color property from
1173N/A * @param property The name of a bean style property or client property
1173N/A * @param defaultColor The color to return if no color was obtained from
1173N/A * the component.
1173N/A * @return The color that was obtained from the component or defaultColor
1173N/A */
1173N/A protected final Color getComponentColor(JComponent c, String property,
1173N/A Color defaultColor,
1173N/A float saturationOffset,
1173N/A float brightnessOffset,
1173N/A int alphaOffset) {
1173N/A Color color = null;
1173N/A if (c != null) {
1173N/A // handle some special cases for performance
1173N/A if ("background".equals(property)) {
1173N/A color = c.getBackground();
1173N/A } else if ("foreground".equals(property)) {
1173N/A color = c.getForeground();
1173N/A } else if (c instanceof JList && "selectionForeground".equals(property)) {
1173N/A color = ((JList) c).getSelectionForeground();
1173N/A } else if (c instanceof JList && "selectionBackground".equals(property)) {
1173N/A color = ((JList) c).getSelectionBackground();
1173N/A } else if (c instanceof JTable && "selectionForeground".equals(property)) {
1173N/A color = ((JTable) c).getSelectionForeground();
1173N/A } else if (c instanceof JTable && "selectionBackground".equals(property)) {
1173N/A color = ((JTable) c).getSelectionBackground();
1173N/A } else {
1173N/A String s = "get" + Character.toUpperCase(property.charAt(0)) + property.substring(1);
1173N/A try {
1173N/A Method method = c.getClass().getMethod(s);
1173N/A color = (Color) method.invoke(c);
1173N/A } catch (Exception e) {
1173N/A //don't do anything, it just didn't work, that's all.
1173N/A //This could be a normal occurance if you use a property
1173N/A //name referring to a key in clientProperties instead of
1173N/A //a real property
1173N/A }
1173N/A if (color == null) {
1173N/A Object value = c.getClientProperty(property);
1173N/A if (value instanceof Color) {
1173N/A color = (Color) value;
1173N/A }
1173N/A }
1173N/A }
1173N/A }
1173N/A // we return the defaultColor if the color found is null, or if
1173N/A // it is a UIResource. This is done because the color for the
1173N/A // ENABLED state is set on the component, but you don't want to use
1173N/A // that color for the over state. So we only respect the color
1173N/A // specified for the property if it was set by the user, as opposed
1173N/A // to set by us.
1173N/A if (color == null || color instanceof UIResource) {
1173N/A return defaultColor;
1173N/A } else if (saturationOffset != 0 || brightnessOffset != 0 || alphaOffset != 0) {
1173N/A float[] tmp = Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), null);
1173N/A tmp[1] = clamp(tmp[1] + saturationOffset);
1173N/A tmp[2] = clamp(tmp[2] + brightnessOffset);
1173N/A int alpha = clamp(color.getAlpha() + alphaOffset);
1173N/A return new Color((Color.HSBtoRGB(tmp[0], tmp[1], tmp[2]) & 0xFFFFFF) | (alpha <<24));
1173N/A } else {
1173N/A return color;
1173N/A }
1173N/A }
1173N/A
1173N/A /**
1173N/A * A class encapsulating state useful when painting. Generally, instances of this
1173N/A * class are created once, and reused for each paint request without modification.
1173N/A * This class contains values useful when hinting the cache engine, and when decoding
1173N/A * control points and bezier curve anchors.
1173N/A */
1173N/A protected static class PaintContext {
1173N/A protected static enum CacheMode {
1173N/A NO_CACHING, FIXED_SIZES, NINE_SQUARE_SCALE
1173N/A }
1173N/A
1173N/A private static Insets EMPTY_INSETS = new Insets(0, 0, 0, 0);
1173N/A
1173N/A private Insets stretchingInsets;
1173N/A private Dimension canvasSize;
1173N/A private boolean inverted;
1173N/A private CacheMode cacheMode;
1173N/A private double maxHorizontalScaleFactor;
1173N/A private double maxVerticalScaleFactor;
1173N/A
1173N/A private float a; // insets.left
1173N/A private float b; // canvasSize.width - insets.right
1173N/A private float c; // insets.top
1173N/A private float d; // canvasSize.height - insets.bottom;
1173N/A private float aPercent; // only used if inverted == true
1173N/A private float bPercent; // only used if inverted == true
1173N/A private float cPercent; // only used if inverted == true
1173N/A private float dPercent; // only used if inverted == true
1173N/A
1173N/A /**
1173N/A * Creates a new PaintContext which does not attempt to cache or scale any cached
1173N/A * images.
1173N/A *
1173N/A * @param insets The stretching insets. May be null. If null, then assumed to be 0, 0, 0, 0.
1173N/A * @param canvasSize The size of the canvas used when encoding the various x/y values. May be null.
1173N/A * If null, then it is assumed that there are no encoded values, and any calls
1173N/A * to one of the "decode" methods will return the passed in value.
1173N/A * @param inverted Whether to "invert" the meaning of the 9-square grid and stretching insets
1173N/A */
1173N/A public PaintContext(Insets insets, Dimension canvasSize, boolean inverted) {
1173N/A this(insets, canvasSize, inverted, null, 1, 1);
1173N/A }
1173N/A
1173N/A /**
1173N/A * Creates a new PaintContext.
1173N/A *
1173N/A * @param insets The stretching insets. May be null. If null, then assumed to be 0, 0, 0, 0.
1173N/A * @param canvasSize The size of the canvas used when encoding the various x/y values. May be null.
1173N/A * If null, then it is assumed that there are no encoded values, and any calls
1173N/A * to one of the "decode" methods will return the passed in value.
1173N/A * @param inverted Whether to "invert" the meaning of the 9-square grid and stretching insets
1173N/A * @param cacheMode A hint as to which caching mode to use. If null, then set to no caching.
1173N/A * @param maxH The maximium scale in the horizontal direction to use before punting and redrawing from scratch.
1173N/A * For example, if maxH is 2, then we will attempt to scale any cached images up to 2x the canvas
1173N/A * width before redrawing from scratch. Reasonable maxH values may improve painting performance.
1173N/A * If set too high, then you may get poor looking graphics at higher zoom levels. Must be >= 1.
1173N/A * @param maxV The maximium scale in the vertical direction to use before punting and redrawing from scratch.
1173N/A * For example, if maxV is 2, then we will attempt to scale any cached images up to 2x the canvas
1173N/A * height before redrawing from scratch. Reasonable maxV values may improve painting performance.
1173N/A * If set too high, then you may get poor looking graphics at higher zoom levels. Must be >= 1.
1173N/A */
1173N/A public PaintContext(Insets insets, Dimension canvasSize, boolean inverted,
1173N/A CacheMode cacheMode, double maxH, double maxV) {
1173N/A if (maxH < 1 || maxH < 1) {
1173N/A throw new IllegalArgumentException("Both maxH and maxV must be >= 1");
1173N/A }
1173N/A
1173N/A this.stretchingInsets = insets == null ? EMPTY_INSETS : insets;
1173N/A this.canvasSize = canvasSize;
1173N/A this.inverted = inverted;
1173N/A this.cacheMode = cacheMode == null ? CacheMode.NO_CACHING : cacheMode;
1173N/A this.maxHorizontalScaleFactor = maxH;
1173N/A this.maxVerticalScaleFactor = maxV;
1173N/A
1173N/A if (canvasSize != null) {
1451N/A a = stretchingInsets.left;
1451N/A b = canvasSize.width - stretchingInsets.right;
1451N/A c = stretchingInsets.top;
1451N/A d = canvasSize.height - stretchingInsets.bottom;
1173N/A this.canvasSize = canvasSize;
1173N/A this.inverted = inverted;
1173N/A if (inverted) {
1173N/A float available = canvasSize.width - (b - a);
1173N/A aPercent = available > 0f ? a / available : 0f;
1173N/A bPercent = available > 0f ? b / available : 0f;
1173N/A available = canvasSize.height - (d - c);
1173N/A cPercent = available > 0f ? c / available : 0f;
1173N/A dPercent = available > 0f ? d / available : 0f;
1173N/A }
1173N/A }
1173N/A }
1173N/A }
1173N/A
1173N/A //---------------------- private methods
1173N/A
1173N/A //initializes the class to prepare it for being able to decode points
1173N/A private void prepare(float w, float h) {
1173N/A //if no PaintContext has been specified, reset the values and bail
1173N/A //also bail if the canvasSize was not set (since decoding will not work)
1173N/A if (ctx == null || ctx.canvasSize == null) {
1173N/A f = 1f;
1173N/A leftWidth = centerWidth = rightWidth = 0f;
1173N/A topHeight = centerHeight = bottomHeight = 0f;
1173N/A leftScale = centerHScale = rightScale = 0f;
1173N/A topScale = centerVScale = bottomScale = 0f;
1173N/A return;
1173N/A }
1173N/A
1173N/A //calculate the scaling factor, and the sizes for the various 9-square sections
1173N/A Number scale = (Number)UIManager.get("scale");
1173N/A f = scale == null ? 1f : scale.floatValue();
1173N/A
1173N/A if (ctx.inverted) {
1173N/A centerWidth = (ctx.b - ctx.a) * f;
1173N/A float availableSpace = w - centerWidth;
1173N/A leftWidth = availableSpace * ctx.aPercent;
1173N/A rightWidth = availableSpace * ctx.bPercent;
1173N/A centerHeight = (ctx.d - ctx.c) * f;
1173N/A availableSpace = h - centerHeight;
1173N/A topHeight = availableSpace * ctx.cPercent;
1173N/A bottomHeight = availableSpace * ctx.dPercent;
1173N/A } else {
1173N/A leftWidth = ctx.a * f;
1173N/A rightWidth = (float)(ctx.canvasSize.getWidth() - ctx.b) * f;
1173N/A centerWidth = w - leftWidth - rightWidth;
1173N/A topHeight = ctx.c * f;
1173N/A bottomHeight = (float)(ctx.canvasSize.getHeight() - ctx.d) * f;
1173N/A centerHeight = h - topHeight - bottomHeight;
1173N/A }
1173N/A
1173N/A leftScale = ctx.a == 0f ? 0f : leftWidth / ctx.a;
1173N/A centerHScale = (ctx.b - ctx.a) == 0f ? 0f : centerWidth / (ctx.b - ctx.a);
1173N/A rightScale = (ctx.canvasSize.width - ctx.b) == 0f ? 0f : rightWidth / (ctx.canvasSize.width - ctx.b);
1173N/A topScale = ctx.c == 0f ? 0f : topHeight / ctx.c;
1173N/A centerVScale = (ctx.d - ctx.c) == 0f ? 0f : centerHeight / (ctx.d - ctx.c);
1173N/A bottomScale = (ctx.canvasSize.height - ctx.d) == 0f ? 0f : bottomHeight / (ctx.canvasSize.height - ctx.d);
1173N/A }
1173N/A
1173N/A private void paintWith9SquareCaching(Graphics2D g, PaintContext ctx,
1173N/A JComponent c, int w, int h,
1173N/A Object[] extendedCacheKeys) {
1173N/A // check if we can scale to the requested size
1173N/A Dimension canvas = ctx.canvasSize;
1173N/A Insets insets = ctx.stretchingInsets;
1173N/A
1173N/A if (w <= (canvas.width * ctx.maxHorizontalScaleFactor) && h <= (canvas.height * ctx.maxVerticalScaleFactor)) {
1173N/A // get image at canvas size
1173N/A VolatileImage img = getImage(g.getDeviceConfiguration(), c, canvas.width, canvas.height, extendedCacheKeys);
1173N/A if (img != null) {
1173N/A // calculate dst inserts
1173N/A // todo: destination inserts need to take into acount scale factor for high dpi. Note: You can use f for this, I think
1173N/A Insets dstInsets;
1173N/A if (ctx.inverted){
1173N/A int leftRight = (w-(canvas.width-(insets.left+insets.right)))/2;
1173N/A int topBottom = (h-(canvas.height-(insets.top+insets.bottom)))/2;
1173N/A dstInsets = new Insets(topBottom,leftRight,topBottom,leftRight);
1173N/A } else {
1173N/A dstInsets = insets;
1173N/A }
1173N/A // paint 9 square scaled
1173N/A Object oldScaleingHints = g.getRenderingHint(RenderingHints.KEY_INTERPOLATION);
1173N/A g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,RenderingHints.VALUE_INTERPOLATION_BILINEAR);
1173N/A ImageScalingHelper.paint(g, 0, 0, w, h, img, insets, dstInsets,
1173N/A ImageScalingHelper.PaintType.PAINT9_STRETCH, ImageScalingHelper.PAINT_ALL);
1173N/A g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
1173N/A oldScaleingHints!=null?oldScaleingHints:RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
1173N/A } else {
1173N/A // render directly
1173N/A paint0(g, c, w, h, extendedCacheKeys);
1173N/A }
1173N/A } else {
1173N/A // paint directly
1173N/A paint0(g, c, w, h, extendedCacheKeys);
1173N/A }
1173N/A }
1173N/A
1173N/A private void paintWithFixedSizeCaching(Graphics2D g, JComponent c, int w,
1173N/A int h, Object[] extendedCacheKeys) {
1173N/A VolatileImage img = getImage(g.getDeviceConfiguration(), c, w, h, extendedCacheKeys);
1173N/A if (img != null) {
1173N/A //render cached image
1173N/A g.drawImage(img, 0, 0, null);
1173N/A } else {
1173N/A // render directly
1173N/A paint0(g, c, w, h, extendedCacheKeys);
1173N/A }
1173N/A }
1173N/A
1173N/A /** Gets the rendered image for this painter at the requested size, either from cache or create a new one */
1173N/A private VolatileImage getImage(GraphicsConfiguration config, JComponent c,
1173N/A int w, int h, Object[] extendedCacheKeys) {
1173N/A ImageCache imageCache = ImageCache.getInstance();
1173N/A //get the buffer for this component
1173N/A VolatileImage buffer = (VolatileImage) imageCache.getImage(config, w, h, this, extendedCacheKeys);
1173N/A
1173N/A int renderCounter = 0; //to avoid any potential, though unlikely, infinite loop
1173N/A do {
1173N/A //validate the buffer so we can check for surface loss
1173N/A int bufferStatus = VolatileImage.IMAGE_INCOMPATIBLE;
1173N/A if (buffer != null) {
1173N/A bufferStatus = buffer.validate(config);
1173N/A }
1173N/A
1173N/A //If the buffer status is incompatible or restored, then we need to re-render to the volatile image
1173N/A if (bufferStatus == VolatileImage.IMAGE_INCOMPATIBLE || bufferStatus == VolatileImage.IMAGE_RESTORED) {
1173N/A //if the buffer is null (hasn't been created), or isn't the right size, or has lost its contents,
1173N/A //then recreate the buffer
1173N/A if (buffer == null || buffer.getWidth() != w || buffer.getHeight() != h ||
1173N/A bufferStatus == VolatileImage.IMAGE_INCOMPATIBLE) {
1173N/A //clear any resources related to the old back buffer
1173N/A if (buffer != null) {
1173N/A buffer.flush();
1173N/A buffer = null;
1173N/A }
1173N/A //recreate the buffer
1173N/A buffer = config.createCompatibleVolatileImage(w, h,
1173N/A Transparency.TRANSLUCENT);
1173N/A // put in cache for future
1173N/A imageCache.setImage(buffer, config, w, h, this, extendedCacheKeys);
1173N/A }
1173N/A //create the graphics context with which to paint to the buffer
1173N/A Graphics2D bg = buffer.createGraphics();
1173N/A //clear the background before configuring the graphics
1173N/A bg.setComposite(AlphaComposite.Clear);
1173N/A bg.fillRect(0, 0, w, h);
1173N/A bg.setComposite(AlphaComposite.SrcOver);
1173N/A configureGraphics(bg);
1173N/A // paint the painter into buffer
1173N/A paint0(bg, c, w, h, extendedCacheKeys);
1173N/A //close buffer graphics
1173N/A bg.dispose();
1173N/A }
1173N/A } while (buffer.contentsLost() && renderCounter++ < 3);
1173N/A // check if we failed
1173N/A if (renderCounter == 3) return null;
1173N/A // return image
1173N/A return buffer;
1173N/A }
1173N/A
1173N/A //convenience method which creates a temporary graphics object by creating a
1173N/A //clone of the passed in one, configuring it, drawing with it, disposing it.
1173N/A //These steps have to be taken to ensure that any hints set on the graphics
1173N/A //are removed subsequent to painting.
1173N/A private void paint0(Graphics2D g, JComponent c, int width, int height,
1173N/A Object[] extendedCacheKeys) {
1173N/A prepare(width, height);
1173N/A g = (Graphics2D)g.create();
1173N/A configureGraphics(g);
1173N/A doPaint(g, c, width, height, extendedCacheKeys);
1173N/A g.dispose();
1173N/A }
1173N/A
1173N/A private float clamp(float value) {
1173N/A if (value < 0) {
1173N/A value = 0;
1173N/A } else if (value > 1) {
1173N/A value = 1;
1173N/A }
1173N/A return value;
1173N/A }
1173N/A
1173N/A private int clamp(int value) {
1173N/A if (value < 0) {
1173N/A value = 0;
1173N/A } else if (value > 255) {
1173N/A value = 255;
1173N/A }
1173N/A return value;
1173N/A }
1173N/A}