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