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