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