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