1 /* 2 * Copyright (c) 2004, 2017, 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 sun.swing; 26 27 import sun.awt.image.SurfaceManager; 28 import sun.java2d.SurfaceData; 29 import java.awt.Component; 30 import java.awt.Graphics; 31 import java.awt.Graphics2D; 32 import java.awt.GraphicsConfiguration; 33 import java.awt.Image; 34 import java.awt.geom.AffineTransform; 35 import java.awt.image.AbstractMultiResolutionImage; 36 import java.awt.image.BufferedImage; 37 import java.awt.image.ImageObserver; 38 import java.awt.image.VolatileImage; 39 import java.util.Arrays; 40 import java.util.HashMap; 41 import java.util.Map; 42 43 /** 44 * A base class used for icons or images that are expensive to paint. 45 * A subclass will do the following: 46 * <ol> 47 * <li>Invoke <code>paint</code> when you want to paint the image, 48 * if you are implementing <code>Icon</code> you'll invoke this from 49 * <code>paintIcon</code>. 50 * The args argument is useful when additional state is needed. 51 * <li>Override <code>paintToImage</code> to render the image. The code that 52 * lives here is equivalent to what previously would go in 53 * <code>paintIcon</code>, for an <code>Icon</code>. 54 * </ol> 55 * The two ways to use this class are: 56 * <ol> 57 * <li>Invoke <code>paint</code> to draw the cached reprensentation at 58 * the specified location. 59 * <li>Invoke <code>getImage</code> to get the cached reprensentation and 60 * draw the image yourself. This is primarly useful when you are not 61 * using <code>VolatileImage</code>. 62 * </ol> 63 * 64 * 65 */ 66 public abstract class CachedPainter { 67 // CacheMap maps from class to ImageCache. 68 private static final Map<Object,ImageCache> cacheMap = new HashMap<>(); 69 getCache(Object key)70 private static ImageCache getCache(Object key) { 71 synchronized(CachedPainter.class) { 72 ImageCache cache = cacheMap.get(key); 73 if (cache == null) { 74 if (key == PainterMultiResolutionCachedImage.class) { 75 cache = new ImageCache(32); 76 } else { 77 cache = new ImageCache(1); 78 } 79 cacheMap.put(key, cache); 80 } 81 return cache; 82 } 83 } 84 85 /** 86 * Creates an instance of <code>CachedPainter</code> that will cache up 87 * to <code>cacheCount</code> images of this class. 88 * 89 * @param cacheCount Max number of images to cache 90 */ CachedPainter(int cacheCount)91 public CachedPainter(int cacheCount) { 92 getCache(getClass()).setMaxCount(cacheCount); 93 } 94 95 /** 96 * Renders the cached image to the passed in <code>Graphic</code>. 97 * If there is no cached image <code>paintToImage</code> will be invoked. 98 * <code>paintImage</code> is invoked to paint the cached image. 99 * 100 * @param c Component rendering to, this may be null. 101 * @param g Graphics to paint to 102 * @param x X-coordinate to render to 103 * @param y Y-coordinate to render to 104 * @param w Width to render in 105 * @param h Height to render in 106 * @param args Variable arguments that will be passed to paintToImage 107 */ paint(Component c, Graphics g, int x, int y, int w, int h, Object... args)108 public void paint(Component c, Graphics g, int x, 109 int y, int w, int h, Object... args) { 110 if (w <= 0 || h <= 0) { 111 return; 112 } 113 synchronized (CachedPainter.class) { 114 paint0(c, g, x, y, w, h, args); 115 } 116 } 117 getImage(Object key, Component c, int baseWidth, int baseHeight, int w, int h, Object... args)118 private Image getImage(Object key, Component c, 119 int baseWidth, int baseHeight, 120 int w, int h, Object... args) { 121 GraphicsConfiguration config = getGraphicsConfiguration(c); 122 ImageCache cache = getCache(key); 123 Image image = cache.getImage(key, config, w, h, args); 124 int attempts = 0; 125 VolatileImage volatileImage = (image instanceof VolatileImage) 126 ? (VolatileImage) image 127 : null; 128 do { 129 boolean draw = false; 130 if (volatileImage != null) { 131 // See if we need to recreate the image 132 switch (volatileImage.validate(config)) { 133 case VolatileImage.IMAGE_INCOMPATIBLE: 134 volatileImage.flush(); 135 image = null; 136 break; 137 case VolatileImage.IMAGE_RESTORED: 138 draw = true; 139 break; 140 } 141 } 142 if (image == null) { 143 // Recreate the image 144 if( config != null && (w != baseHeight || h != baseWidth)) { 145 AffineTransform tx = config.getDefaultTransform(); 146 double sx = tx.getScaleX(); 147 double sy = tx.getScaleY(); 148 if ( Double.compare(sx, 1) != 0 || 149 Double.compare(sy, 1) != 0) { 150 if (Math.abs(sx * baseWidth - w) < 1 && 151 Math.abs(sy * baseHeight - h) < 1) { 152 w = baseWidth; 153 h = baseHeight; 154 } else { 155 w = (int)Math.ceil(w / sx); 156 h = (int)Math.ceil(w / sy); 157 } 158 } 159 } 160 image = createImage(c, w, h, config, args); 161 cache.setImage(key, config, w, h, args, image); 162 draw = true; 163 volatileImage = (image instanceof VolatileImage) 164 ? (VolatileImage) image 165 : null; 166 } 167 if (draw) { 168 // Render to the Image 169 Graphics2D g2 = (Graphics2D) image.getGraphics(); 170 if (volatileImage == null) { 171 if ((w != baseWidth || h != baseHeight)) { 172 g2.scale((double) w / baseWidth, 173 (double) h / baseHeight); 174 } 175 paintToImage(c, image, g2, baseWidth, baseHeight, args); 176 } else { 177 SurfaceData sd = SurfaceManager.getManager(volatileImage) 178 .getPrimarySurfaceData(); 179 double sx = sd.getDefaultScaleX(); 180 double sy = sd.getDefaultScaleY(); 181 if ( Double.compare(sx, 1) != 0 || 182 Double.compare(sy, 1) != 0) { 183 g2.scale(1 / sx, 1 / sy); 184 } 185 paintToImage(c, image, g2, (int)Math.ceil(w * sx), 186 (int)Math.ceil(h * sy), args); 187 } 188 g2.dispose(); 189 } 190 191 // If we did this 3 times and the contents are still lost 192 // assume we're painting to a VolatileImage that is bogus and 193 // give up. Presumably we'll be called again to paint. 194 } while ((volatileImage != null) && 195 volatileImage.contentsLost() && ++attempts < 3); 196 197 return image; 198 } 199 paint0(Component c, Graphics g, int x, int y, int w, int h, Object... args)200 private void paint0(Component c, Graphics g, int x, 201 int y, int w, int h, Object... args) { 202 Object key = getClass(); 203 GraphicsConfiguration config = getGraphicsConfiguration(c); 204 ImageCache cache = getCache(key); 205 Image image = cache.getImage(key, config, w, h, args); 206 207 if (image == null) { 208 image = new PainterMultiResolutionCachedImage(w, h); 209 cache.setImage(key, config, w, h, args, image); 210 } 211 212 if (image instanceof PainterMultiResolutionCachedImage) { 213 ((PainterMultiResolutionCachedImage) image).setParams(c, args); 214 } 215 216 // Render to the passed in Graphics 217 paintImage(c, g, x, y, w, h, image, args); 218 } 219 220 /** 221 * Paints the representation to cache to the supplied Graphics. 222 * 223 * @param c Component painting to, may be null. 224 * @param image Image to paint to 225 * @param g Graphics to paint to, obtained from the passed in Image. 226 * @param w Width to paint to 227 * @param h Height to paint to 228 * @param args Arguments supplied to <code>paint</code> 229 */ paintToImage(Component c, Image image, Graphics g, int w, int h, Object[] args)230 protected abstract void paintToImage(Component c, Image image, Graphics g, 231 int w, int h, Object[] args); 232 233 234 /** 235 * Paints the image to the specified location. 236 * 237 * @param c Component painting to 238 * @param g Graphics to paint to 239 * @param x X coordinate to paint to 240 * @param y Y coordinate to paint to 241 * @param w Width to paint to 242 * @param h Height to paint to 243 * @param image Image to paint 244 * @param args Arguments supplied to <code>paint</code> 245 */ paintImage(Component c, Graphics g, int x, int y, int w, int h, Image image, Object[] args)246 protected void paintImage(Component c, Graphics g, 247 int x, int y, int w, int h, Image image, 248 Object[] args) { 249 g.drawImage(image, x, y, null); 250 } 251 252 /** 253 * Creates the image to cache. This returns an opaque image, subclasses 254 * that require translucency or transparency will need to override this 255 * method. 256 * 257 * @param c Component painting to 258 * @param w Width of image to create 259 * @param h Height to image to create 260 * @param config GraphicsConfiguration that will be 261 * rendered to, this may be null. 262 * @param args Arguments passed to paint 263 */ createImage(Component c, int w, int h, GraphicsConfiguration config, Object[] args)264 protected Image createImage(Component c, int w, int h, 265 GraphicsConfiguration config, Object[] args) { 266 if (config == null) { 267 return new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); 268 } 269 return config.createCompatibleVolatileImage(w, h); 270 } 271 272 /** 273 * Clear the image cache 274 */ flush()275 protected void flush() { 276 synchronized(CachedPainter.class) { 277 getCache(getClass()).flush(); 278 } 279 } 280 getGraphicsConfiguration(Component c)281 private GraphicsConfiguration getGraphicsConfiguration(Component c) { 282 if (c == null) { 283 return null; 284 } 285 return c.getGraphicsConfiguration(); 286 } 287 288 class PainterMultiResolutionCachedImage extends AbstractMultiResolutionImage { 289 290 private final int baseWidth; 291 private final int baseHeight; 292 private Component c; 293 private Object[] args; 294 PainterMultiResolutionCachedImage(int baseWidth, int baseHeight)295 public PainterMultiResolutionCachedImage(int baseWidth, int baseHeight) { 296 this.baseWidth = baseWidth; 297 this.baseHeight = baseHeight; 298 } 299 setParams(Component c, Object[] args)300 public void setParams(Component c, Object[] args) { 301 this.c = c; 302 this.args = args; 303 } 304 305 @Override getWidth(ImageObserver observer)306 public int getWidth(ImageObserver observer) { 307 return baseWidth; 308 } 309 310 @Override getHeight(ImageObserver observer)311 public int getHeight(ImageObserver observer) { 312 return baseHeight; 313 } 314 315 @Override getResolutionVariant(double destWidth, double destHeight)316 public Image getResolutionVariant(double destWidth, double destHeight) { 317 int w = (int) Math.ceil(destWidth); 318 int h = (int) Math.ceil(destHeight); 319 return getImage(PainterMultiResolutionCachedImage.class, 320 c, baseWidth, baseHeight, w, h, args); 321 } 322 323 @Override getBaseImage()324 protected Image getBaseImage() { 325 return getResolutionVariant(baseWidth, baseHeight); 326 } 327 328 @Override getResolutionVariants()329 public java.util.List<Image> getResolutionVariants() { 330 return Arrays.asList(getResolutionVariant(baseWidth, baseHeight)); 331 } 332 } 333 } 334