1 /*
2  * Copyright (c) 2013, 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.
8  *
9  * This code is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * version 2 for more details (a copy is included in the LICENSE file that
13  * accompanied this code).
14  *
15  * You should have received a copy of the GNU General Public License version
16  * 2 along with this work; if not, write to the Free Software Foundation,
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18  *
19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20  * or visit www.oracle.com if you need additional information or have any
21  * questions.
22  */
23 
24 import java.awt.Color;
25 import java.awt.Graphics;
26 import java.awt.Graphics2D;
27 import java.awt.Image;
28 import java.awt.Toolkit;
29 import java.awt.image.BufferedImage;
30 import java.io.File;
31 import java.lang.reflect.Method;
32 import java.net.URL;
33 import javax.imageio.ImageIO;
34 import sun.awt.SunHints;
35 import java.awt.MediaTracker;
36 import java.awt.RenderingHints;
37 import java.awt.image.ImageObserver;
38 import javax.swing.JPanel;
39 import jdk.test.lib.Platform;
40 import java.awt.image.MultiResolutionImage;
41 
42 /**
43  * @test
44  * @bug 8011059
45  * @key headful
46  * @author Alexander Scherbatiy
47  * @summary [macosx] Make JDK demos look perfect on retina displays
48  * @library /test/lib
49  * @build jdk.test.lib.Platform
50  * @requires (os.family == "mac")
51  * @modules java.desktop/sun.awt
52  *          java.desktop/sun.awt.image
53  *          java.desktop/sun.lwawt.macosx:open
54  * @run main MultiResolutionImageTest TOOLKIT_PREPARE
55  * @run main MultiResolutionImageTest TOOLKIT_LOAD
56  * @run main MultiResolutionImageTest TOOLKIT
57  */
58 
59 public class MultiResolutionImageTest {
60 
61     private static final int IMAGE_WIDTH = 300;
62     private static final int IMAGE_HEIGHT = 200;
63     private static final Color COLOR_1X = Color.GREEN;
64     private static final Color COLOR_2X = Color.BLUE;
65     private static final String IMAGE_NAME_1X = "image.png";
66     private static final String IMAGE_NAME_2X = "image@2x.png";
67 
main(String[] args)68     public static void main(String[] args) throws Exception {
69 
70         System.out.println("args: " + args.length);
71 
72         if (args.length == 0) {
73             throw new RuntimeException("Not found a test");
74         }
75         String test = args[0];
76         System.out.println("TEST: " + test);
77 
78         // To automatically pass the test if the test is not run using JTReg.
79         if (!Platform.isOSX()) {
80             System.out.println("Non-Mac platform detected. Passing the test");
81             return;
82         }
83         switch (test) {
84             case "TOOLKIT_PREPARE":
85                 testToolkitMultiResolutionImagePrepare();
86                 break;
87             case "TOOLKIT_LOAD":
88                 testToolkitMultiResolutionImageLoad();
89                 break;
90             case "TOOLKIT":
91                 testToolkitMultiResolutionImage();
92                 testImageNameTo2xParsing();
93                 break;
94             default:
95                 throw new RuntimeException("Unknown test: " + test);
96         }
97         System.out.println("Test passed.");
98     }
99 
testToolkitMultiResolutionImagePrepare()100     static void testToolkitMultiResolutionImagePrepare() throws Exception {
101 
102         generateImages();
103 
104         File imageFile = new File(IMAGE_NAME_1X);
105         String fileName = imageFile.getAbsolutePath();
106 
107         Image image = Toolkit.getDefaultToolkit().getImage(fileName);
108 
109         Toolkit toolkit = Toolkit.getDefaultToolkit();
110         toolkit.prepareImage(image, IMAGE_WIDTH, IMAGE_HEIGHT,
111             new LoadImageObserver(image));
112 
113         testToolkitMultiResolutionImageLoad(image);
114     }
115 
testToolkitMultiResolutionImageLoad()116     static void testToolkitMultiResolutionImageLoad() throws Exception {
117 
118         generateImages();
119 
120         File imageFile = new File(IMAGE_NAME_1X);
121         String fileName = imageFile.getAbsolutePath();
122         Image image = Toolkit.getDefaultToolkit().getImage(fileName);
123         testToolkitMultiResolutionImageLoad(image);
124     }
125 
testToolkitMultiResolutionImageLoad(Image image)126     static void testToolkitMultiResolutionImageLoad(Image image)
127         throws Exception {
128 
129         MediaTracker tracker = new MediaTracker(new JPanel());
130         tracker.addImage(image, 0);
131         tracker.waitForID(0);
132         if (tracker.isErrorAny()) {
133             throw new RuntimeException("Error during image loading");
134         }
135         tracker.removeImage(image, 0);
136 
137         testImageLoaded(image);
138 
139         int w = image.getWidth(null);
140         int h = image.getHeight(null);
141 
142         Image resolutionVariant = ((MultiResolutionImage) image)
143             .getResolutionVariant(2 * w, 2 * h);
144 
145         if (image == resolutionVariant) {
146             throw new RuntimeException("Resolution variant is not loaded");
147         }
148 
149         testImageLoaded(resolutionVariant);
150     }
151 
testImageLoaded(Image image)152     static void testImageLoaded(Image image) {
153 
154         Toolkit toolkit = Toolkit.getDefaultToolkit();
155 
156         int flags = toolkit.checkImage(image, IMAGE_WIDTH, IMAGE_WIDTH,
157             new SilentImageObserver());
158         if ((flags & (ImageObserver.FRAMEBITS | ImageObserver.ALLBITS)) == 0) {
159             throw new RuntimeException("Image is not loaded!");
160         }
161     }
162 
163     static class SilentImageObserver implements ImageObserver {
164 
165         @Override
imageUpdate(Image img, int infoflags, int x, int y, int width, int height)166         public boolean imageUpdate(Image img, int infoflags, int x, int y,
167             int width, int height) {
168             throw new RuntimeException("Observer should not be called!");
169         }
170     }
171 
172     static class LoadImageObserver implements ImageObserver {
173 
174         Image image;
175 
LoadImageObserver(Image image)176         public LoadImageObserver(Image image) {
177             this.image = image;
178         }
179 
180         @Override
imageUpdate(Image img, int infoflags, int x, int y, int width, int height)181         public boolean imageUpdate(Image img, int infoflags, int x, int y,
182             int width, int height) {
183 
184             if (image != img) {
185                 throw new RuntimeException("Original image is not passed "
186                     + "to the observer");
187             }
188 
189             if ((infoflags & ImageObserver.WIDTH) != 0) {
190                 if (width != IMAGE_WIDTH) {
191                     throw new RuntimeException("Original width is not passed "
192                         + "to the observer");
193                 }
194             }
195 
196             if ((infoflags & ImageObserver.HEIGHT) != 0) {
197                 if (height != IMAGE_HEIGHT) {
198                     throw new RuntimeException("Original height is not passed "
199                         + "to the observer");
200                 }
201             }
202 
203             return (infoflags & ALLBITS) == 0;
204         }
205 
206     }
207 
testToolkitMultiResolutionImage()208     static void testToolkitMultiResolutionImage() throws Exception {
209 
210         generateImages();
211 
212         File imageFile = new File(IMAGE_NAME_1X);
213         String fileName = imageFile.getAbsolutePath();
214         URL url = imageFile.toURI().toURL();
215         testToolkitMultiResolutionImageChache(fileName, url);
216 
217         Image image = Toolkit.getDefaultToolkit().getImage(fileName);
218         testToolkitImageObserver(image);
219         testToolkitMultiResolutionImage(image, false);
220         testToolkitMultiResolutionImage(image, true);
221 
222         image = Toolkit.getDefaultToolkit().getImage(url);
223         testToolkitImageObserver(image);
224         testToolkitMultiResolutionImage(image, false);
225         testToolkitMultiResolutionImage(image, true);
226     }
227 
testToolkitMultiResolutionImageChache(String fileName, URL url)228     static void testToolkitMultiResolutionImageChache(String fileName,
229         URL url) {
230 
231         Image img1 = Toolkit.getDefaultToolkit().getImage(fileName);
232         if (!(img1 instanceof MultiResolutionImage)) {
233             throw new RuntimeException("Not a MultiResolutionImage");
234         }
235 
236         Image img2 = Toolkit.getDefaultToolkit().getImage(fileName);
237         if (img1 != img2) {
238             throw new RuntimeException("Image is not cached");
239         }
240 
241         img1 = Toolkit.getDefaultToolkit().getImage(url);
242         if (!(img1 instanceof MultiResolutionImage)) {
243             throw new RuntimeException("Not a MultiResolutionImage");
244         }
245 
246         img2 = Toolkit.getDefaultToolkit().getImage(url);
247         if (img1 != img2) {
248             throw new RuntimeException("Image is not cached");
249         }
250     }
251 
testToolkitMultiResolutionImage(Image image, boolean enableImageScaling)252     static void testToolkitMultiResolutionImage(Image image,
253         boolean enableImageScaling) throws Exception {
254 
255         MediaTracker tracker = new MediaTracker(new JPanel());
256         tracker.addImage(image, 0);
257         tracker.waitForID(0);
258         if (tracker.isErrorAny()) {
259             throw new RuntimeException("Error during image loading");
260         }
261 
262         final BufferedImage bufferedImage1x = new BufferedImage(IMAGE_WIDTH,
263             IMAGE_HEIGHT, BufferedImage.TYPE_INT_RGB);
264         Graphics2D g1x = (Graphics2D) bufferedImage1x.getGraphics();
265         setImageScalingHint(g1x, false);
266         g1x.drawImage(image, 0, 0, null);
267         checkColor(bufferedImage1x.getRGB(3 * IMAGE_WIDTH / 4,
268             3 * IMAGE_HEIGHT / 4), false);
269 
270         Image resolutionVariant = ((MultiResolutionImage) image).
271             getResolutionVariant(2 * IMAGE_WIDTH, 2 * IMAGE_HEIGHT);
272 
273         if (resolutionVariant == null) {
274             throw new RuntimeException("Resolution variant is null");
275         }
276 
277         MediaTracker tracker2x = new MediaTracker(new JPanel());
278         tracker2x.addImage(resolutionVariant, 0);
279         tracker2x.waitForID(0);
280         if (tracker2x.isErrorAny()) {
281             throw new RuntimeException("Error during scalable image loading");
282         }
283 
284         final BufferedImage bufferedImage2x = new BufferedImage(2 * IMAGE_WIDTH,
285             2 * IMAGE_HEIGHT, BufferedImage.TYPE_INT_RGB);
286         Graphics2D g2x = (Graphics2D) bufferedImage2x.getGraphics();
287         setImageScalingHint(g2x, enableImageScaling);
288         g2x.drawImage(image, 0, 0, 2 * IMAGE_WIDTH,
289             2 * IMAGE_HEIGHT, 0, 0, IMAGE_WIDTH, IMAGE_HEIGHT, null);
290         checkColor(bufferedImage2x.getRGB(3 * IMAGE_WIDTH / 2,
291             3 * IMAGE_HEIGHT / 2), enableImageScaling);
292 
293         if (!(image instanceof MultiResolutionImage)) {
294             throw new RuntimeException("Not a MultiResolutionImage");
295         }
296 
297         MultiResolutionImage multiResolutionImage
298             = (MultiResolutionImage) image;
299 
300         Image image1x = multiResolutionImage.getResolutionVariant(
301             IMAGE_WIDTH, IMAGE_HEIGHT);
302         Image image2x = multiResolutionImage.getResolutionVariant(
303             2 * IMAGE_WIDTH, 2 * IMAGE_HEIGHT);
304 
305         if (image1x.getWidth(null) * 2 != image2x.getWidth(null)
306             || image1x.getHeight(null) * 2 != image2x.getHeight(null)) {
307             throw new RuntimeException("Wrong resolution variant size");
308         }
309     }
310 
testToolkitImageObserver(final Image image)311     static void testToolkitImageObserver(final Image image) {
312 
313         ImageObserver observer = new ImageObserver() {
314 
315             @Override
316             public boolean imageUpdate(Image img, int infoflags, int x, int y,
317                 int width, int height) {
318 
319                 if (img != image) {
320                     throw new RuntimeException("Wrong image in observer");
321                 }
322 
323                 if ((infoflags & (ImageObserver.ERROR | ImageObserver.ABORT))
324                     != 0) {
325                     throw new RuntimeException("Error during image loading");
326                 }
327 
328                 return (infoflags & ImageObserver.ALLBITS) == 0;
329 
330             }
331         };
332 
333         final BufferedImage bufferedImage2x = new BufferedImage(2 * IMAGE_WIDTH,
334             2 * IMAGE_HEIGHT, BufferedImage.TYPE_INT_RGB);
335         Graphics2D g2x = (Graphics2D) bufferedImage2x.getGraphics();
336         setImageScalingHint(g2x, true);
337 
338         g2x.drawImage(image, 0, 0, 2 * IMAGE_WIDTH, 2 * IMAGE_HEIGHT, 0, 0,
339             IMAGE_WIDTH, IMAGE_HEIGHT, observer);
340 
341     }
342 
setImageScalingHint(Graphics2D g2d, boolean enableImageScaling)343     static void setImageScalingHint(Graphics2D g2d,
344         boolean enableImageScaling) {
345         g2d.setRenderingHint(SunHints.KEY_RESOLUTION_VARIANT, enableImageScaling
346             ? RenderingHints.VALUE_RESOLUTION_VARIANT_DEFAULT
347             : RenderingHints.VALUE_RESOLUTION_VARIANT_BASE);
348     }
349 
checkColor(int rgb, boolean isImageScaled)350     static void checkColor(int rgb, boolean isImageScaled) {
351 
352         if (!isImageScaled && COLOR_1X.getRGB() != rgb) {
353             throw new RuntimeException("Wrong 1x color: " + new Color(rgb));
354         }
355 
356         if (isImageScaled && COLOR_2X.getRGB() != rgb) {
357             throw new RuntimeException("Wrong 2x color" + new Color(rgb));
358         }
359     }
360 
generateImages()361     static void generateImages() throws Exception {
362         if (!new File(IMAGE_NAME_1X).exists()) {
363             generateImage(1);
364         }
365 
366         if (!new File(IMAGE_NAME_2X).exists()) {
367             generateImage(2);
368         }
369     }
370 
generateImage(int scale)371     static void generateImage(int scale) throws Exception {
372         BufferedImage image = new BufferedImage(
373             scale * IMAGE_WIDTH, scale * IMAGE_HEIGHT,
374             BufferedImage.TYPE_INT_RGB);
375         Graphics g = image.getGraphics();
376         g.setColor(scale == 1 ? COLOR_1X : COLOR_2X);
377         g.fillRect(0, 0, scale * IMAGE_WIDTH, scale * IMAGE_HEIGHT);
378         File file = new File(scale == 1 ? IMAGE_NAME_1X : IMAGE_NAME_2X);
379         ImageIO.write(image, "png", file);
380     }
381 
testImageNameTo2xParsing()382     static void testImageNameTo2xParsing() throws Exception {
383 
384         for (String[] testNames : TEST_FILE_NAMES) {
385             String testName = testNames[0];
386             String goldenName = testNames[1];
387             String resultName = getTestScaledImageName(testName);
388 
389             if (!isValidPath(testName) && resultName == null) {
390                 continue;
391             }
392 
393             if (goldenName.equals(resultName)) {
394                 continue;
395             }
396 
397             throw new RuntimeException("Test name " + testName
398                 + ", result name: " + resultName);
399         }
400 
401         for (URL[] testURLs : TEST_URLS) {
402             URL testURL = testURLs[0];
403             URL goldenURL = testURLs[1];
404             URL resultURL = getTestScaledImageURL(testURL);
405 
406             if (!isValidPath(testURL.getPath()) && resultURL == null) {
407                 continue;
408             }
409 
410             if (goldenURL.equals(resultURL)) {
411                 continue;
412             }
413 
414             throw new RuntimeException("Test url: " + testURL
415                 + ", result url: " + resultURL);
416         }
417 
418     }
419 
getTestScaledImageURL(URL url)420     static URL getTestScaledImageURL(URL url) throws Exception {
421         Method method = getScalableImageMethod("getScaledImageURL", URL.class);
422         return (URL) method.invoke(null, url);
423     }
424 
getTestScaledImageName(String name)425     static String getTestScaledImageName(String name) throws Exception {
426         Method method = getScalableImageMethod(
427             "getScaledImageName", String.class);
428         return (String) method.invoke(null, name);
429     }
430 
isValidPath(String path)431     private static boolean isValidPath(String path) {
432         return !path.isEmpty() && !path.endsWith("/") && !path.endsWith(".")
433             && !path.contains("@2x");
434     }
435 
getScalableImageMethod(String name, Class... parameterTypes)436     private static Method getScalableImageMethod(String name,
437         Class... parameterTypes) throws Exception {
438         Toolkit toolkit = Toolkit.getDefaultToolkit();
439         Method method = toolkit.getClass()
440             .
441             getDeclaredMethod(name, parameterTypes);
442         method.setAccessible(true);
443         return method;
444     }
445     private static final String[][] TEST_FILE_NAMES;
446     private static final URL[][] TEST_URLS;
447 
448     static {
449         TEST_FILE_NAMES = new String[][]{
450             {"", null},
451             {".", null},
452             {"..", null},
453             {"/", null},
454             {"/.", null},
455             {"dir/", null},
456             {"dir/.", null},
457             {"aaa@2x.png", null},
458             {"/dir/aaa@2x.png", null},
459             {"image", "image@2x"},
460             {"image.ext", "image@2x.ext"},
461             {"image.aaa.ext", "image.aaa@2x.ext"},
462             {"dir/image", "dir/image@2x"},
463             {"dir/image.ext", "dir/image@2x.ext"},
464             {"dir/image.aaa.ext", "dir/image.aaa@2x.ext"},
465             {"dir/aaa.bbb/image", "dir/aaa.bbb/image@2x"},
466             {"dir/aaa.bbb/image.ext", "dir/aaa.bbb/image@2x.ext"},
467             {"dir/aaa.bbb/image.ccc.ext", "dir/aaa.bbb/image.ccc@2x.ext"},
468             {"/dir/image", "/dir/image@2x"},
469             {"/dir/image.ext", "/dir/image@2x.ext"},
470             {"/dir/image.aaa.ext", "/dir/image.aaa@2x.ext"},
471             {"/dir/aaa.bbb/image", "/dir/aaa.bbb/image@2x"},
472             {"/dir/aaa.bbb/image.ext", "/dir/aaa.bbb/image@2x.ext"},
473             {"/dir/aaa.bbb/image.ccc.ext", "/dir/aaa.bbb/image.ccc@2x.ext"}
474         };
475         try {
476             TEST_URLS = new URL[][]{
477                 // file
478                 {new URL("file:/aaa"), new URL("file:/aaa@2x")},
479                 {new URL("file:/aaa.ext"), new URL("file:/aaa@2x.ext")},
480                 {new URL("file:/aaa.bbb.ext"), new URL("file:/aaa.bbb@2x.ext")},
481                 {new URL("file:/ccc/aaa.bbb.ext"),
482                     new URL("file:/ccc/aaa.bbb@2x.ext")},
483                 {new URL("file:/ccc.ddd/aaa.bbb.ext"),
484                     new URL("file:/ccc.ddd/aaa.bbb@2x.ext")},
485                 {new URL("file:///~/image"), new URL("file:///~/image@2x")},
486                 {new URL("file:///~/image.ext"),
487                     new URL("file:///~/image@2x.ext")},
488                 // http
489                 {new URL("http://www.test.com"), null},
490                 {new URL("http://www.test.com/"), null},
491                 {new URL("http://www.test.com///"), null},
492                 {new URL("http://www.test.com/image"),
493                     new URL("http://www.test.com/image@2x")},
494                 {new URL("http://www.test.com/image.ext"),
495                     new URL("http://www.test.com/image@2x.ext")},
496                 {new URL("http://www.test.com/dir/image"),
497                     new URL("http://www.test.com/dir/image@2x")},
498                 {new URL("http://www.test.com:80/dir/image.aaa.ext"),
499                     new URL("http://www.test.com:80/dir/image.aaa@2x.ext")},
500                 {new URL("http://www.test.com:8080/dir/image.aaa.ext"),
501                     new URL("http://www.test.com:8080/dir/image.aaa@2x.ext")},
502                 // jar
503                 {new URL("jar:file:/dir/Java2D.jar!/image"),
504                     new URL("jar:file:/dir/Java2D.jar!/image@2x")},
505                 {new URL("jar:file:/dir/Java2D.jar!/image.aaa.ext"),
506                     new URL("jar:file:/dir/Java2D.jar!/image.aaa@2x.ext")},
507                 {new URL("jar:file:/dir/Java2D.jar!/images/image"),
508                     new URL("jar:file:/dir/Java2D.jar!/images/image@2x")},
509                 {new URL("jar:file:/dir/Java2D.jar!/images/image.ext"),
510                     new URL("jar:file:/dir/Java2D.jar!/images/image@2x.ext")},
511                 {new URL("jar:file:/aaa.bbb/Java2D.jar!/images/image.ext"),
512                     new URL("jar:file:/aaa.bbb/Java2D.jar!/"
513                     + "images/image@2x.ext")},
514                 {new URL("jar:file:/dir/Java2D.jar!/aaa.bbb/image.ext"),
515                     new URL("jar:file:/dir/Java2D.jar!/"
516                     + "aaa.bbb/image@2x.ext")},};
517         } catch (Exception e) {
518             throw new RuntimeException(e);
519         }
520     }
521 
522     static class PreloadedImageObserver implements ImageObserver {
523 
524         @Override
imageUpdate(Image img, int infoflags, int x, int y, int width, int height)525         public boolean imageUpdate(Image img, int infoflags, int x, int y,
526             int width, int height) {
527             throw new RuntimeException("Image should be already preloaded");
528         }
529     }
530 }
531