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