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 */
25package sun.awt.image;
26
27import java.awt.Dimension;
28import java.awt.Image;
29import java.awt.geom.Dimension2D;
30import java.awt.image.ImageObserver;
31import java.util.Arrays;
32import java.util.List;
33import java.util.function.Function;
34import java.util.function.BiFunction;
35import java.util.stream.Collectors;
36import java.awt.image.MultiResolutionImage;
37import java.awt.image.AbstractMultiResolutionImage;
38
39public 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
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
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
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
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
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
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
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
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
146    public int getWidth(ImageObserver observer) {
147        updateInfo(observer, ImageObserver.WIDTH);
148        return baseImageWidth;
149    }
150
151    @Override
152    public int getHeight(ImageObserver observer) {
153        updateInfo(observer, ImageObserver.HEIGHT);
154        return baseImageHeight;
155    }
156
157    @Override
158    public Object getProperty(String name, ImageObserver observer) {
159        updateInfo(observer, ImageObserver.PROPERTIES);
160        return Image.UndefinedProperty;
161    }
162
163    @Override
164    public Image getScaledInstance(int width, int height, int hints) {
165        return getResolutionVariant(width, height);
166    }
167
168    @Override
169    protected Image getBaseImage() {
170        return getResolutionVariant(baseImageWidth, baseImageHeight);
171    }
172
173    private void updateInfo(ImageObserver observer, int info) {
174        availableInfo |= (observer == null) ? ImageObserver.ALLBITS : info;
175    }
176
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
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
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
220        public int getPixelCount() {
221            return pixelCount;
222        }
223
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
232        public int hashCode() {
233            return hash;
234        }
235
236        @Override
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