1 /* 2 * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.apple.laf; 27 28 import java.awt.*; 29 import java.awt.image.*; 30 import java.util.HashMap; 31 32 import com.apple.laf.AquaImageFactory.RecyclableSlicedImageControl; 33 import com.apple.laf.AquaImageFactory.NineSliceMetrics; 34 import com.apple.laf.AquaImageFactory.SlicedImageControl; 35 36 import sun.awt.image.*; 37 import sun.java2d.*; 38 import sun.print.*; 39 import apple.laf.*; 40 import apple.laf.JRSUIUtils.NineSliceMetricsProvider; 41 import sun.awt.image.ImageCache; 42 43 abstract class AquaPainter <T extends JRSUIState> { 44 static <T extends JRSUIState> AquaPainter<T> create(final T state) { 45 return new AquaSingleImagePainter<>(state); 46 } 47 48 static <T extends JRSUIState> AquaPainter<T> create(final T state, final int minWidth, final int minHeight, final int westCut, final int eastCut, final int northCut, final int southCut) { 49 return create(state, minWidth, minHeight, westCut, eastCut, northCut, southCut, true); 50 } 51 52 static <T extends JRSUIState> AquaPainter<T> create(final T state, final int minWidth, final int minHeight, final int westCut, final int eastCut, final int northCut, final int southCut, final boolean useMiddle) { 53 return create(state, minWidth, minHeight, westCut, eastCut, northCut, southCut, useMiddle, true, true); 54 } 55 56 static <T extends JRSUIState> AquaPainter<T> create(final T state, final int minWidth, final int minHeight, final int westCut, final int eastCut, final int northCut, final int southCut, final boolean useMiddle, final boolean stretchHorizontally, final boolean stretchVertically) { 57 return create(state, new NineSliceMetricsProvider() { 58 @Override 59 public NineSliceMetrics getNineSliceMetricsForState(JRSUIState state) { 60 return new NineSliceMetrics(minWidth, minHeight, westCut, eastCut, northCut, southCut, useMiddle, stretchHorizontally, stretchVertically); 61 } 62 }); 63 } 64 65 static <T extends JRSUIState> AquaPainter<T> create(final T state, final NineSliceMetricsProvider metricsProvider) { 66 return new AquaNineSlicingImagePainter<>(state, metricsProvider); 67 } 68 69 abstract void paint(Graphics2D g, T stateToPaint); 70 71 final Rectangle boundsRect = new Rectangle(); 72 final JRSUIControl control; 73 T state; 74 AquaPainter(final JRSUIControl control, final T state) { 75 this.control = control; 76 this.state = state; 77 } 78 79 final JRSUIControl getControl() { 80 control.set(state = state.derive()); 81 return control; 82 } 83 84 final void paint(final Graphics g, final Component c, final int x, 85 final int y, final int w, final int h) { 86 boundsRect.setBounds(x, y, w, h); 87 88 final T nextState = state.derive(); 89 final Graphics2D g2d = getGraphics2D(g); 90 if (g2d != null) paint(g2d, nextState); 91 state = nextState; 92 } 93 94 private static class AquaNineSlicingImagePainter<T extends JRSUIState> 95 extends AquaPainter<T> { 96 97 private final HashMap<T, RecyclableJRSUISlicedImageControl> slicedControlImages; 98 private final NineSliceMetricsProvider metricsProvider; 99 100 AquaNineSlicingImagePainter(final T state) { 101 this(state, null); 102 } 103 104 AquaNineSlicingImagePainter(final T state, final NineSliceMetricsProvider metricsProvider) { 105 super(new JRSUIControl(false), state); 106 this.metricsProvider = metricsProvider; 107 slicedControlImages = new HashMap<>(); 108 } 109 110 @Override 111 void paint(final Graphics2D g, final T stateToPaint) { 112 if (metricsProvider == null) { 113 AquaSingleImagePainter.paintFromSingleCachedImage(g, control, stateToPaint, boundsRect); 114 return; 115 } 116 117 RecyclableJRSUISlicedImageControl slicesRef = slicedControlImages.get(stateToPaint); 118 if (slicesRef == null) { 119 final NineSliceMetrics metrics = metricsProvider.getNineSliceMetricsForState(stateToPaint); 120 if (metrics == null) { 121 AquaSingleImagePainter.paintFromSingleCachedImage(g, control, stateToPaint, boundsRect); 122 return; 123 } 124 slicesRef = new RecyclableJRSUISlicedImageControl(control, stateToPaint, metrics); 125 slicedControlImages.put(stateToPaint, slicesRef); 126 } 127 final SlicedImageControl slices = slicesRef.get(); 128 slices.paint(g, boundsRect.x, boundsRect.y, boundsRect.width, boundsRect.height); 129 } 130 } 131 132 private static final class AquaSingleImagePainter<T extends JRSUIState> 133 extends AquaPainter<T> { 134 135 AquaSingleImagePainter(final T state) { 136 super(new JRSUIControl(false), state); 137 } 138 139 @Override 140 void paint(final Graphics2D g, final T stateToPaint) { 141 paintFromSingleCachedImage(g, control, stateToPaint, boundsRect); 142 } 143 144 static void paintFromSingleCachedImage(final Graphics2D g, 145 final JRSUIControl control, final JRSUIState controlState, 146 final Rectangle bounds) { 147 if (bounds.width <= 0 || bounds.height <= 0) { 148 return; 149 } 150 151 final GraphicsConfiguration config = g.getDeviceConfiguration(); 152 final ImageCache cache = ImageCache.getInstance(); 153 final int width = bounds.width; 154 final int height = bounds.height; 155 AquaPixelsKey key = new AquaPixelsKey(config, 156 width, height, bounds, controlState); 157 Image img = (BufferedImage) cache.getImage(key); 158 if (img == null) { 159 160 Image baseImage = createImage(width, height, bounds, control, 161 controlState); 162 163 img = new MultiResolutionBufferedImage(baseImage, 164 (rvWidth, rvHeight) -> createImage(rvWidth, rvHeight, 165 bounds, control, controlState)); 166 167 if (!controlState.is(JRSUIConstants.Animating.YES)) { 168 cache.setImage(key, img); 169 } 170 } 171 172 g.drawImage(img, bounds.x, bounds.y, bounds.width, bounds.height, null); 173 } 174 175 private static Image createImage(int imgW, int imgH, final Rectangle bounds, 176 final JRSUIControl control, JRSUIState controlState) { 177 BufferedImage img = new BufferedImage(imgW, imgH, 178 BufferedImage.TYPE_INT_ARGB_PRE); 179 180 final WritableRaster raster = img.getRaster(); 181 final DataBufferInt buffer = (DataBufferInt) raster.getDataBuffer(); 182 183 control.set(controlState); 184 control.paint(SunWritableRaster.stealData(buffer, 0), 185 imgW, imgH, 0, 0, bounds.width, bounds.height); 186 SunWritableRaster.markDirty(buffer); 187 return img; 188 } 189 } 190 191 private static class AquaPixelsKey implements ImageCache.PixelsKey { 192 193 private final int pixelCount; 194 private final int hash; 195 196 // key parts 197 private final GraphicsConfiguration config; 198 private final int w; 199 private final int h; 200 private final Rectangle bounds; 201 private final JRSUIState state; 202 203 AquaPixelsKey(final GraphicsConfiguration config, 204 final int w, final int h, final Rectangle bounds, 205 final JRSUIState state) { 206 this.pixelCount = w * h; 207 this.config = config; 208 this.w = w; 209 this.h = h; 210 this.bounds = bounds; 211 this.state = state; 212 this.hash = hash(); 213 } 214 215 public int getPixelCount() { 216 return pixelCount; 217 } 218 219 private int hash() { 220 int hash = config != null ? config.hashCode() : 0; 221 hash = 31 * hash + w; 222 hash = 31 * hash + h; 223 hash = 31 * hash + bounds.hashCode(); 224 hash = 31 * hash + state.hashCode(); 225 return hash; 226 } 227 228 @Override 229 public int hashCode() { 230 return hash; 231 } 232 233 @Override 234 public boolean equals(Object obj) { 235 if (obj instanceof AquaPixelsKey) { 236 AquaPixelsKey key = (AquaPixelsKey) obj; 237 return config == key.config && w == key.w && h == key.h 238 && bounds.equals(key.bounds) && state.equals(key.state); 239 } 240 return false; 241 } 242 } 243 244 private static class RecyclableJRSUISlicedImageControl 245 extends RecyclableSlicedImageControl { 246 247 private final JRSUIControl control; 248 private final JRSUIState state; 249 250 RecyclableJRSUISlicedImageControl(final JRSUIControl control, final JRSUIState state, final NineSliceMetrics metrics) { 251 super(metrics); 252 this.control = control; 253 this.state = state; 254 } 255 256 @Override 257 protected Image createTemplateImage(int width, int height) { 258 BufferedImage image = new BufferedImage(metrics.minW, metrics.minH, BufferedImage.TYPE_INT_ARGB_PRE); 259 260 final WritableRaster raster = image.getRaster(); 261 final DataBufferInt buffer = (DataBufferInt)raster.getDataBuffer(); 262 263 control.set(state); 264 control.paint(SunWritableRaster.stealData(buffer, 0), metrics.minW, metrics.minH, 0, 0, metrics.minW, metrics.minH); 265 266 SunWritableRaster.markDirty(buffer); 267 268 return image; 269 } 270 } 271 272 private Graphics2D getGraphics2D(final Graphics g) { 273 try { 274 return (SunGraphics2D)g; // doing a blind try is faster than checking instanceof 275 } catch (Exception ignored) { 276 if (g instanceof PeekGraphics) { 277 // if it is a peek just dirty the region 278 g.fillRect(boundsRect.x, boundsRect.y, boundsRect.width, boundsRect.height); 279 } else if (g instanceof ProxyGraphics2D) { 280 final ProxyGraphics2D pg = (ProxyGraphics2D)g; 281 final Graphics2D g2d = pg.getDelegate(); 282 if (g2d instanceof SunGraphics2D) { 283 return g2d; 284 } 285 } else if (g instanceof Graphics2D) { 286 return (Graphics2D) g; 287 } 288 } 289 290 return null; 291 } 292 }