1 /*
2  * Copyright (c) 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 sun.awt.image;
26 
27 import java.awt.Dimension;
28 import java.awt.Image;
29 import java.awt.geom.Dimension2D;
30 import java.awt.image.ImageObserver;
31 import java.util.Arrays;
32 import java.util.List;
33 import java.util.function.Function;
34 import java.util.function.BiFunction;
35 import java.util.stream.Collectors;
36 import java.awt.image.MultiResolutionImage;
37 import java.awt.image.AbstractMultiResolutionImage;
38 
39 public class MultiResolutionCachedImage extends AbstractMultiResolutionImage {
40 
41     private final int baseImageWidth;
42     private final int baseImageHeight;
43     private final Dimension2D[] sizes;
44     private final BiFunction<Integer, Integer, Image> mapper;
45     private int availableInfo;
46 
MultiResolutionCachedImage(int baseImageWidth, int baseImageHeight, BiFunction<Integer, Integer, Image> mapper)47     public MultiResolutionCachedImage(int baseImageWidth, int baseImageHeight,
48                                       BiFunction<Integer, Integer, Image> mapper)
49     {
50         this(baseImageWidth, baseImageHeight,
51              new Dimension[]{new Dimension( baseImageWidth, baseImageHeight)
52         }, mapper);
53     }
54 
MultiResolutionCachedImage(int baseImageWidth, int baseImageHeight, Dimension2D[] sizes, BiFunction<Integer, Integer, Image> mapper)55     public MultiResolutionCachedImage(int baseImageWidth, int baseImageHeight,
56                                       Dimension2D[] sizes,
57                                       BiFunction<Integer, Integer, Image> mapper)
58     {
59         this(baseImageWidth, baseImageHeight, sizes, mapper, true);
60     }
61 
MultiResolutionCachedImage(int baseImageWidth, int baseImageHeight, Dimension2D[] sizes, BiFunction<Integer, Integer, Image> mapper, boolean copySizes)62     private MultiResolutionCachedImage(int baseImageWidth, int baseImageHeight,
63                                        Dimension2D[] sizes,
64                                        BiFunction<Integer, Integer, Image> mapper,
65                                        boolean copySizes)
66     {
67         this.baseImageWidth = baseImageWidth;
68         this.baseImageHeight = baseImageHeight;
69         this.sizes = (copySizes && sizes != null)
70                                 ? Arrays.copyOf(sizes, sizes.length)
71                                 : sizes;
72         this.mapper = mapper;
73     }
74 
75     @Override
getResolutionVariant(double destWidth, double destHeight)76     public Image getResolutionVariant(double destWidth, double destHeight) {
77         checkSize(destWidth, destHeight);
78         int width = (int) Math.ceil(destWidth);
79         int height = (int) Math.ceil(destHeight);
80         ImageCache cache = ImageCache.getInstance();
81         ImageCacheKey key = new ImageCacheKey(this, width, height);
82         Image resolutionVariant = cache.getImage(key);
83         if (resolutionVariant == null) {
84             resolutionVariant = mapper.apply(width, height);
85             cache.setImage(key, resolutionVariant);
86         }
87         preload(resolutionVariant, availableInfo);
88         return resolutionVariant;
89     }
90 
checkSize(double width, double height)91     private static void checkSize(double width, double height) {
92         if (width <= 0 || height <= 0) {
93             throw new IllegalArgumentException(String.format(
94                     "Width (%s) or height (%s) cannot be <= 0", width, height));
95         }
96 
97         if (!Double.isFinite(width) || !Double.isFinite(height)) {
98             throw new IllegalArgumentException(String.format(
99                     "Width (%s) or height (%s) is not finite", width, height));
100         }
101     }
102 
103     @Override
getResolutionVariants()104     public List<Image> getResolutionVariants() {
105         return Arrays.stream(sizes).map((Function<Dimension2D, Image>) size
106                 -> getResolutionVariant(size.getWidth(), size.getHeight()))
107                 .collect(Collectors.toList());
108     }
109 
map(Function<Image, Image> mapper)110     public MultiResolutionCachedImage map(Function<Image, Image> mapper) {
111         return new MultiResolutionCachedImage(baseImageWidth, baseImageHeight,
112                 sizes, (width, height) ->
113                         mapper.apply(getResolutionVariant(width, height)));
114     }
115 
map(MultiResolutionImage mrImage, Function<Image, Image> mapper)116     public static Image map(MultiResolutionImage mrImage,
117                             Function<Image, Image> mapper) {
118 
119         if (mrImage instanceof MultiResolutionToolkitImage) {
120             MultiResolutionToolkitImage mrtImage =
121                     (MultiResolutionToolkitImage) mrImage;
122             return MultiResolutionToolkitImage.map(mrtImage, mapper);
123         }
124 
125         BiFunction<Integer, Integer, Image> sizeMapper
126                 = (w, h) -> mapper.apply(mrImage.getResolutionVariant(w, h));
127 
128         if (mrImage instanceof MultiResolutionCachedImage) {
129             MultiResolutionCachedImage mrcImage
130                     = (MultiResolutionCachedImage) mrImage;
131 
132             return new MultiResolutionCachedImage(mrcImage.baseImageWidth,
133                                                   mrcImage.baseImageHeight,
134                                                   mrcImage.sizes,
135                                                   sizeMapper,
136                                                   false);
137         }
138 
139         Image image = (Image) mrImage;
140         int width = image.getWidth(null);
141         int height = image.getHeight(null);
142         return new MultiResolutionCachedImage(width, height, sizeMapper);
143     }
144 
145     @Override
getWidth(ImageObserver observer)146     public int getWidth(ImageObserver observer) {
147         updateInfo(observer, ImageObserver.WIDTH);
148         return baseImageWidth;
149     }
150 
151     @Override
getHeight(ImageObserver observer)152     public int getHeight(ImageObserver observer) {
153         updateInfo(observer, ImageObserver.HEIGHT);
154         return baseImageHeight;
155     }
156 
157     @Override
getProperty(String name, ImageObserver observer)158     public Object getProperty(String name, ImageObserver observer) {
159         updateInfo(observer, ImageObserver.PROPERTIES);
160         return Image.UndefinedProperty;
161     }
162 
163     @Override
getScaledInstance(int width, int height, int hints)164     public Image getScaledInstance(int width, int height, int hints) {
165         return getResolutionVariant(width, height);
166     }
167 
168     @Override
getBaseImage()169     protected Image getBaseImage() {
170         return getResolutionVariant(baseImageWidth, baseImageHeight);
171     }
172 
updateInfo(ImageObserver observer, int info)173     private void updateInfo(ImageObserver observer, int info) {
174         availableInfo |= (observer == null) ? ImageObserver.ALLBITS : info;
175     }
176 
getInfo(Image image)177     private static int getInfo(Image image) {
178         if (image instanceof ToolkitImage) {
179             return ((ToolkitImage) image).getImageRep().check(
180                     (img, infoflags, x, y, w, h) -> false);
181         }
182         return 0;
183     }
184 
preload(Image image, int availableInfo)185     private static void preload(Image image, int availableInfo) {
186         if (availableInfo != 0 && image instanceof ToolkitImage) {
187             ((ToolkitImage) image).preload(new ImageObserver() {
188                 int flags = availableInfo;
189 
190                 @Override
191                 public boolean imageUpdate(Image img, int infoflags,
192                         int x, int y, int width, int height) {
193                     flags &= ~infoflags;
194                     return (flags != 0) && ((infoflags
195                             & (ImageObserver.ERROR | ImageObserver.ABORT)) == 0);
196                 }
197             });
198         }
199     }
200 
201     private static class ImageCacheKey implements ImageCache.PixelsKey {
202 
203         private final int pixelCount;
204         private final int hash;
205 
206         private final int w;
207         private final int h;
208         private final Image baseImage;
209 
ImageCacheKey(final Image baseImage, final int w, final int h)210         ImageCacheKey(final Image baseImage,
211                 final int w, final int h) {
212             this.baseImage = baseImage;
213             this.w = w;
214             this.h = h;
215             this.pixelCount = w * h;
216             hash = hash();
217         }
218 
219         @Override
getPixelCount()220         public int getPixelCount() {
221             return pixelCount;
222         }
223 
hash()224         private int hash() {
225             int hash = baseImage.hashCode();
226             hash = 31 * hash + w;
227             hash = 31 * hash + h;
228             return hash;
229         }
230 
231         @Override
hashCode()232         public int hashCode() {
233             return hash;
234         }
235 
236         @Override
equals(Object obj)237         public boolean equals(Object obj) {
238             if (obj instanceof ImageCacheKey) {
239                 ImageCacheKey key = (ImageCacheKey) obj;
240                 return baseImage == key.baseImage && w == key.w && h == key.h;
241             }
242             return false;
243         }
244     }
245 }
246