/* * Copyright (c) 2013, 2017, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ import java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Toolkit; import java.awt.image.BufferedImage; import java.io.File; import java.lang.reflect.Method; import java.net.URL; import javax.imageio.ImageIO; import sun.awt.SunHints; import java.awt.MediaTracker; import java.awt.RenderingHints; import java.awt.image.ImageObserver; import javax.swing.JPanel; import jdk.test.lib.Platform; import java.awt.image.MultiResolutionImage; /** * @test * @bug 8011059 * @key headful * @author Alexander Scherbatiy * @summary [macosx] Make JDK demos look perfect on retina displays * @library /test/lib * @build jdk.test.lib.Platform * @requires (os.family == "mac") * @modules java.desktop/sun.awt * java.desktop/sun.awt.image * java.desktop/sun.lwawt.macosx:open * @run main MultiResolutionImageTest TOOLKIT_PREPARE * @run main MultiResolutionImageTest TOOLKIT_LOAD * @run main MultiResolutionImageTest TOOLKIT */ public class MultiResolutionImageTest { private static final int IMAGE_WIDTH = 300; private static final int IMAGE_HEIGHT = 200; private static final Color COLOR_1X = Color.GREEN; private static final Color COLOR_2X = Color.BLUE; private static final String IMAGE_NAME_1X = "image.png"; private static final String IMAGE_NAME_2X = "image@2x.png"; public static void main(String[] args) throws Exception { System.out.println("args: " + args.length); if (args.length == 0) { throw new RuntimeException("Not found a test"); } String test = args[0]; System.out.println("TEST: " + test); // To automatically pass the test if the test is not run using JTReg. if (!Platform.isOSX()) { System.out.println("Non-Mac platform detected. Passing the test"); return; } switch (test) { case "TOOLKIT_PREPARE": testToolkitMultiResolutionImagePrepare(); break; case "TOOLKIT_LOAD": testToolkitMultiResolutionImageLoad(); break; case "TOOLKIT": testToolkitMultiResolutionImage(); testImageNameTo2xParsing(); break; default: throw new RuntimeException("Unknown test: " + test); } System.out.println("Test passed."); } static void testToolkitMultiResolutionImagePrepare() throws Exception { generateImages(); File imageFile = new File(IMAGE_NAME_1X); String fileName = imageFile.getAbsolutePath(); Image image = Toolkit.getDefaultToolkit().getImage(fileName); Toolkit toolkit = Toolkit.getDefaultToolkit(); toolkit.prepareImage(image, IMAGE_WIDTH, IMAGE_HEIGHT, new LoadImageObserver(image)); testToolkitMultiResolutionImageLoad(image); } static void testToolkitMultiResolutionImageLoad() throws Exception { generateImages(); File imageFile = new File(IMAGE_NAME_1X); String fileName = imageFile.getAbsolutePath(); Image image = Toolkit.getDefaultToolkit().getImage(fileName); testToolkitMultiResolutionImageLoad(image); } static void testToolkitMultiResolutionImageLoad(Image image) throws Exception { MediaTracker tracker = new MediaTracker(new JPanel()); tracker.addImage(image, 0); tracker.waitForID(0); if (tracker.isErrorAny()) { throw new RuntimeException("Error during image loading"); } tracker.removeImage(image, 0); testImageLoaded(image); int w = image.getWidth(null); int h = image.getHeight(null); Image resolutionVariant = ((MultiResolutionImage) image) .getResolutionVariant(2 * w, 2 * h); if (image == resolutionVariant) { throw new RuntimeException("Resolution variant is not loaded"); } testImageLoaded(resolutionVariant); } static void testImageLoaded(Image image) { Toolkit toolkit = Toolkit.getDefaultToolkit(); int flags = toolkit.checkImage(image, IMAGE_WIDTH, IMAGE_WIDTH, new SilentImageObserver()); if ((flags & (ImageObserver.FRAMEBITS | ImageObserver.ALLBITS)) == 0) { throw new RuntimeException("Image is not loaded!"); } } static class SilentImageObserver implements ImageObserver { @Override public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) { throw new RuntimeException("Observer should not be called!"); } } static class LoadImageObserver implements ImageObserver { Image image; public LoadImageObserver(Image image) { this.image = image; } @Override public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) { if (image != img) { throw new RuntimeException("Original image is not passed " + "to the observer"); } if ((infoflags & ImageObserver.WIDTH) != 0) { if (width != IMAGE_WIDTH) { throw new RuntimeException("Original width is not passed " + "to the observer"); } } if ((infoflags & ImageObserver.HEIGHT) != 0) { if (height != IMAGE_HEIGHT) { throw new RuntimeException("Original height is not passed " + "to the observer"); } } return (infoflags & ALLBITS) == 0; } } static void testToolkitMultiResolutionImage() throws Exception { generateImages(); File imageFile = new File(IMAGE_NAME_1X); String fileName = imageFile.getAbsolutePath(); URL url = imageFile.toURI().toURL(); testToolkitMultiResolutionImageChache(fileName, url); Image image = Toolkit.getDefaultToolkit().getImage(fileName); testToolkitImageObserver(image); testToolkitMultiResolutionImage(image, false); testToolkitMultiResolutionImage(image, true); image = Toolkit.getDefaultToolkit().getImage(url); testToolkitImageObserver(image); testToolkitMultiResolutionImage(image, false); testToolkitMultiResolutionImage(image, true); } static void testToolkitMultiResolutionImageChache(String fileName, URL url) { Image img1 = Toolkit.getDefaultToolkit().getImage(fileName); if (!(img1 instanceof MultiResolutionImage)) { throw new RuntimeException("Not a MultiResolutionImage"); } Image img2 = Toolkit.getDefaultToolkit().getImage(fileName); if (img1 != img2) { throw new RuntimeException("Image is not cached"); } img1 = Toolkit.getDefaultToolkit().getImage(url); if (!(img1 instanceof MultiResolutionImage)) { throw new RuntimeException("Not a MultiResolutionImage"); } img2 = Toolkit.getDefaultToolkit().getImage(url); if (img1 != img2) { throw new RuntimeException("Image is not cached"); } } static void testToolkitMultiResolutionImage(Image image, boolean enableImageScaling) throws Exception { MediaTracker tracker = new MediaTracker(new JPanel()); tracker.addImage(image, 0); tracker.waitForID(0); if (tracker.isErrorAny()) { throw new RuntimeException("Error during image loading"); } final BufferedImage bufferedImage1x = new BufferedImage(IMAGE_WIDTH, IMAGE_HEIGHT, BufferedImage.TYPE_INT_RGB); Graphics2D g1x = (Graphics2D) bufferedImage1x.getGraphics(); setImageScalingHint(g1x, false); g1x.drawImage(image, 0, 0, null); checkColor(bufferedImage1x.getRGB(3 * IMAGE_WIDTH / 4, 3 * IMAGE_HEIGHT / 4), false); Image resolutionVariant = ((MultiResolutionImage) image). getResolutionVariant(2 * IMAGE_WIDTH, 2 * IMAGE_HEIGHT); if (resolutionVariant == null) { throw new RuntimeException("Resolution variant is null"); } MediaTracker tracker2x = new MediaTracker(new JPanel()); tracker2x.addImage(resolutionVariant, 0); tracker2x.waitForID(0); if (tracker2x.isErrorAny()) { throw new RuntimeException("Error during scalable image loading"); } final BufferedImage bufferedImage2x = new BufferedImage(2 * IMAGE_WIDTH, 2 * IMAGE_HEIGHT, BufferedImage.TYPE_INT_RGB); Graphics2D g2x = (Graphics2D) bufferedImage2x.getGraphics(); setImageScalingHint(g2x, enableImageScaling); g2x.drawImage(image, 0, 0, 2 * IMAGE_WIDTH, 2 * IMAGE_HEIGHT, 0, 0, IMAGE_WIDTH, IMAGE_HEIGHT, null); checkColor(bufferedImage2x.getRGB(3 * IMAGE_WIDTH / 2, 3 * IMAGE_HEIGHT / 2), enableImageScaling); if (!(image instanceof MultiResolutionImage)) { throw new RuntimeException("Not a MultiResolutionImage"); } MultiResolutionImage multiResolutionImage = (MultiResolutionImage) image; Image image1x = multiResolutionImage.getResolutionVariant( IMAGE_WIDTH, IMAGE_HEIGHT); Image image2x = multiResolutionImage.getResolutionVariant( 2 * IMAGE_WIDTH, 2 * IMAGE_HEIGHT); if (image1x.getWidth(null) * 2 != image2x.getWidth(null) || image1x.getHeight(null) * 2 != image2x.getHeight(null)) { throw new RuntimeException("Wrong resolution variant size"); } } static void testToolkitImageObserver(final Image image) { ImageObserver observer = new ImageObserver() { @Override public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) { if (img != image) { throw new RuntimeException("Wrong image in observer"); } if ((infoflags & (ImageObserver.ERROR | ImageObserver.ABORT)) != 0) { throw new RuntimeException("Error during image loading"); } return (infoflags & ImageObserver.ALLBITS) == 0; } }; final BufferedImage bufferedImage2x = new BufferedImage(2 * IMAGE_WIDTH, 2 * IMAGE_HEIGHT, BufferedImage.TYPE_INT_RGB); Graphics2D g2x = (Graphics2D) bufferedImage2x.getGraphics(); setImageScalingHint(g2x, true); g2x.drawImage(image, 0, 0, 2 * IMAGE_WIDTH, 2 * IMAGE_HEIGHT, 0, 0, IMAGE_WIDTH, IMAGE_HEIGHT, observer); } static void setImageScalingHint(Graphics2D g2d, boolean enableImageScaling) { g2d.setRenderingHint(SunHints.KEY_RESOLUTION_VARIANT, enableImageScaling ? RenderingHints.VALUE_RESOLUTION_VARIANT_DEFAULT : RenderingHints.VALUE_RESOLUTION_VARIANT_BASE); } static void checkColor(int rgb, boolean isImageScaled) { if (!isImageScaled && COLOR_1X.getRGB() != rgb) { throw new RuntimeException("Wrong 1x color: " + new Color(rgb)); } if (isImageScaled && COLOR_2X.getRGB() != rgb) { throw new RuntimeException("Wrong 2x color" + new Color(rgb)); } } static void generateImages() throws Exception { if (!new File(IMAGE_NAME_1X).exists()) { generateImage(1); } if (!new File(IMAGE_NAME_2X).exists()) { generateImage(2); } } static void generateImage(int scale) throws Exception { BufferedImage image = new BufferedImage( scale * IMAGE_WIDTH, scale * IMAGE_HEIGHT, BufferedImage.TYPE_INT_RGB); Graphics g = image.getGraphics(); g.setColor(scale == 1 ? COLOR_1X : COLOR_2X); g.fillRect(0, 0, scale * IMAGE_WIDTH, scale * IMAGE_HEIGHT); File file = new File(scale == 1 ? IMAGE_NAME_1X : IMAGE_NAME_2X); ImageIO.write(image, "png", file); } static void testImageNameTo2xParsing() throws Exception { for (String[] testNames : TEST_FILE_NAMES) { String testName = testNames[0]; String goldenName = testNames[1]; String resultName = getTestScaledImageName(testName); if (!isValidPath(testName) && resultName == null) { continue; } if (goldenName.equals(resultName)) { continue; } throw new RuntimeException("Test name " + testName + ", result name: " + resultName); } for (URL[] testURLs : TEST_URLS) { URL testURL = testURLs[0]; URL goldenURL = testURLs[1]; URL resultURL = getTestScaledImageURL(testURL); if (!isValidPath(testURL.getPath()) && resultURL == null) { continue; } if (goldenURL.equals(resultURL)) { continue; } throw new RuntimeException("Test url: " + testURL + ", result url: " + resultURL); } } static URL getTestScaledImageURL(URL url) throws Exception { Method method = getScalableImageMethod("getScaledImageURL", URL.class); return (URL) method.invoke(null, url); } static String getTestScaledImageName(String name) throws Exception { Method method = getScalableImageMethod( "getScaledImageName", String.class); return (String) method.invoke(null, name); } private static boolean isValidPath(String path) { return !path.isEmpty() && !path.endsWith("/") && !path.endsWith(".") && !path.contains("@2x"); } private static Method getScalableImageMethod(String name, Class... parameterTypes) throws Exception { Toolkit toolkit = Toolkit.getDefaultToolkit(); Method method = toolkit.getClass() . getDeclaredMethod(name, parameterTypes); method.setAccessible(true); return method; } private static final String[][] TEST_FILE_NAMES; private static final URL[][] TEST_URLS; static { TEST_FILE_NAMES = new String[][]{ {"", null}, {".", null}, {"..", null}, {"/", null}, {"/.", null}, {"dir/", null}, {"dir/.", null}, {"aaa@2x.png", null}, {"/dir/aaa@2x.png", null}, {"image", "image@2x"}, {"image.ext", "image@2x.ext"}, {"image.aaa.ext", "image.aaa@2x.ext"}, {"dir/image", "dir/image@2x"}, {"dir/image.ext", "dir/image@2x.ext"}, {"dir/image.aaa.ext", "dir/image.aaa@2x.ext"}, {"dir/aaa.bbb/image", "dir/aaa.bbb/image@2x"}, {"dir/aaa.bbb/image.ext", "dir/aaa.bbb/image@2x.ext"}, {"dir/aaa.bbb/image.ccc.ext", "dir/aaa.bbb/image.ccc@2x.ext"}, {"/dir/image", "/dir/image@2x"}, {"/dir/image.ext", "/dir/image@2x.ext"}, {"/dir/image.aaa.ext", "/dir/image.aaa@2x.ext"}, {"/dir/aaa.bbb/image", "/dir/aaa.bbb/image@2x"}, {"/dir/aaa.bbb/image.ext", "/dir/aaa.bbb/image@2x.ext"}, {"/dir/aaa.bbb/image.ccc.ext", "/dir/aaa.bbb/image.ccc@2x.ext"} }; try { TEST_URLS = new URL[][]{ // file {new URL("file:/aaa"), new URL("file:/aaa@2x")}, {new URL("file:/aaa.ext"), new URL("file:/aaa@2x.ext")}, {new URL("file:/aaa.bbb.ext"), new URL("file:/aaa.bbb@2x.ext")}, {new URL("file:/ccc/aaa.bbb.ext"), new URL("file:/ccc/aaa.bbb@2x.ext")}, {new URL("file:/ccc.ddd/aaa.bbb.ext"), new URL("file:/ccc.ddd/aaa.bbb@2x.ext")}, {new URL("file:///~/image"), new URL("file:///~/image@2x")}, {new URL("file:///~/image.ext"), new URL("file:///~/image@2x.ext")}, // http {new URL("http://www.test.com"), null}, {new URL("http://www.test.com/"), null}, {new URL("http://www.test.com///"), null}, {new URL("http://www.test.com/image"), new URL("http://www.test.com/image@2x")}, {new URL("http://www.test.com/image.ext"), new URL("http://www.test.com/image@2x.ext")}, {new URL("http://www.test.com/dir/image"), new URL("http://www.test.com/dir/image@2x")}, {new URL("http://www.test.com:80/dir/image.aaa.ext"), new URL("http://www.test.com:80/dir/image.aaa@2x.ext")}, {new URL("http://www.test.com:8080/dir/image.aaa.ext"), new URL("http://www.test.com:8080/dir/image.aaa@2x.ext")}, // jar {new URL("jar:file:/dir/Java2D.jar!/image"), new URL("jar:file:/dir/Java2D.jar!/image@2x")}, {new URL("jar:file:/dir/Java2D.jar!/image.aaa.ext"), new URL("jar:file:/dir/Java2D.jar!/image.aaa@2x.ext")}, {new URL("jar:file:/dir/Java2D.jar!/images/image"), new URL("jar:file:/dir/Java2D.jar!/images/image@2x")}, {new URL("jar:file:/dir/Java2D.jar!/images/image.ext"), new URL("jar:file:/dir/Java2D.jar!/images/image@2x.ext")}, {new URL("jar:file:/aaa.bbb/Java2D.jar!/images/image.ext"), new URL("jar:file:/aaa.bbb/Java2D.jar!/" + "images/image@2x.ext")}, {new URL("jar:file:/dir/Java2D.jar!/aaa.bbb/image.ext"), new URL("jar:file:/dir/Java2D.jar!/" + "aaa.bbb/image@2x.ext")},}; } catch (Exception e) { throw new RuntimeException(e); } } static class PreloadedImageObserver implements ImageObserver { @Override public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) { throw new RuntimeException("Image should be already preloaded"); } } }