1 /*
   2  * Copyright (c) 2013, 2014, 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 java.awt.MediaTracker;
  36 import java.awt.RenderingHints;
  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 java.awt.image.MultiResolutionImage;
  44 
  45 /**
  46  * @test
  47  * @bug 8011059 8029339
  48  * @author Alexander Scherbatiy
  49  * @summary [macosx] Make JDK demos look perfect on retina displays
  50  * @run main MultiResolutionImageTest CUSTOM
  51  * @run main MultiResolutionImageTest TOOLKIT_PREPARE
  52  * @run main MultiResolutionImageTest TOOLKIT_LOAD
  53  * @run main MultiResolutionImageTest TOOLKIT
  54  */
  55 public class MultiResolutionImageTest {
  56 
  57     private static final int IMAGE_WIDTH = 300;
  58     private static final int IMAGE_HEIGHT = 200;
  59     private static final Color COLOR_1X = Color.GREEN;
  60     private static final Color COLOR_2X = Color.BLUE;
  61     private static final String IMAGE_NAME_1X = "image.png";
  62     private static final String IMAGE_NAME_2X = "image@2x.png";
  63     private static final AffineTransform IDENTITY
  64             = AffineTransform.getScaleInstance(1, 1);
  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(float logicalDPIX, float logicalDPIY,
 208                 float baseWidth, float baseHeight, float destWidth, float destHeight) {
 209             return getResolutionVariant(destWidth, destHeight);
 210         }
 211 
 212         public Image getResolutionVariant(float width, float height) {
 213             return ((width <= getWidth() && height <= getHeight()))
 214                     ? this : highResolutionImage;
 215         }
 216 
 217         @Override
 218         public List<Image> getResolutionVariants() {
 219             return Arrays.asList(this, highResolutionImage);
 220         }
 221 
 222         static float scaleWidth(double size, AffineTransform t) {
 223             return (float) (size * Math.hypot(t.getScaleX(), t.getShearY()));
 224         }
 225 
 226         static float scaleHeight(double size, AffineTransform t) {
 227             return (float) (size * Math.hypot(t.getShearX(), t.getScaleY()));
 228         }
 229     }
 230 
 231     static void testToolkitMultiResolutionImagePrepare() throws Exception {
 232 
 233         generateImages();
 234 
 235         File imageFile = new File(IMAGE_NAME_1X);
 236         String fileName = imageFile.getAbsolutePath();
 237 
 238         Image image = Toolkit.getDefaultToolkit().getImage(fileName);
 239 
 240         SunToolkit toolkit = (SunToolkit) Toolkit.getDefaultToolkit();
 241         toolkit.prepareImage(image, IMAGE_WIDTH, IMAGE_HEIGHT, new LoadImageObserver(image));
 242 
 243         testToolkitMultiResolutionImageLoad(image);
 244     }
 245 
 246     static void testToolkitMultiResolutionImageLoad() throws Exception {
 247 
 248         generateImages();
 249 
 250         File imageFile = new File(IMAGE_NAME_1X);
 251         String fileName = imageFile.getAbsolutePath();
 252         Image image = Toolkit.getDefaultToolkit().getImage(fileName);
 253         testToolkitMultiResolutionImageLoad(image);
 254     }
 255 
 256     static void testToolkitMultiResolutionImageLoad(Image image) throws Exception {
 257 
 258         MediaTracker tracker = new MediaTracker(new JPanel());
 259         tracker.addImage(image, 0);
 260         tracker.waitForID(0);
 261         if (tracker.isErrorAny()) {
 262             throw new RuntimeException("Error during image loading");
 263         }
 264         tracker.removeImage(image, 0);
 265 
 266         testImageLoaded(image);
 267 
 268         int w = image.getWidth(null);
 269         int h = image.getHeight(null);
 270 
 271         Image resolutionVariant = getOSDependentToolkitResolutionVariant(
 272                 (MultiResolutionImage) image, w, h);
 273 
 274         if (image == resolutionVariant) {
 275             throw new RuntimeException("Resolution variant is not loaded");
 276         }
 277 
 278         testImageLoaded(resolutionVariant);
 279     }
 280 
 281     static Image getOSDependentToolkitResolutionVariant(
 282             MultiResolutionImage mrImage, int w, int h) {
 283         if (OSInfo.OSType.WINDOWS.equals(OSInfo.getOSType())) {
 284             return mrImage.getResolutionVariant(2, 2, w, h, 2 * w, 2 * h);
 285         }
 286         return mrImage.getResolutionVariant(1, 1, w, h, 2 * w, 2 * h);
 287     }
 288 
 289     static void testImageLoaded(Image image) {
 290 
 291         SunToolkit toolkit = (SunToolkit) Toolkit.getDefaultToolkit();
 292 
 293         int flags = toolkit.checkImage(image, IMAGE_WIDTH, IMAGE_WIDTH, new SilentImageObserver());
 294         if ((flags & (ImageObserver.FRAMEBITS | ImageObserver.ALLBITS)) == 0) {
 295             throw new RuntimeException("Image is not loaded!");
 296         }
 297     }
 298 
 299     static class SilentImageObserver implements ImageObserver {
 300 
 301         @Override
 302         public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) {
 303             throw new RuntimeException("Observer should not be called!");
 304         }
 305     }
 306 
 307     static class LoadImageObserver implements ImageObserver {
 308 
 309         Image image;
 310 
 311         public LoadImageObserver(Image image) {
 312             this.image = image;
 313         }
 314 
 315         @Override
 316         public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) {
 317 
 318             if (image != img) {
 319                 throw new RuntimeException("Original image is not passed to the observer");
 320             }
 321 
 322             if ((infoflags & ImageObserver.WIDTH) != 0) {
 323                 if (width != IMAGE_WIDTH) {
 324                     throw new RuntimeException("Original width is not passed to the observer");
 325                 }
 326             }
 327 
 328             if ((infoflags & ImageObserver.HEIGHT) != 0) {
 329                 if (height != IMAGE_HEIGHT) {
 330                     throw new RuntimeException("Original height is not passed to the observer");
 331                 }
 332             }
 333 
 334             return (infoflags & ALLBITS) == 0;
 335         }
 336 
 337     }
 338 
 339     static void testToolkitMultiResolutionImage() throws Exception {
 340 
 341         generateImages();
 342 
 343         File imageFile = new File(IMAGE_NAME_1X);
 344         String fileName = imageFile.getAbsolutePath();
 345         URL url = imageFile.toURI().toURL();
 346         testToolkitMultiResolutionImageChache(fileName, url);
 347 
 348         Image image = Toolkit.getDefaultToolkit().getImage(fileName);
 349         testToolkitImageObserver(image);
 350         testToolkitMultiResolutionImage(image, false);
 351         testToolkitMultiResolutionImage(image, true);
 352 
 353         image = Toolkit.getDefaultToolkit().getImage(url);
 354         testToolkitImageObserver(image);
 355         testToolkitMultiResolutionImage(image, false);
 356         testToolkitMultiResolutionImage(image, true);
 357     }
 358 
 359     static void testToolkitMultiResolutionImageChache(String fileName, URL url) {
 360 
 361         Image img1 = Toolkit.getDefaultToolkit().getImage(fileName);
 362         if (!(img1 instanceof MultiResolutionImage)) {
 363             throw new RuntimeException("Not a MultiResolutionImage");
 364         }
 365 
 366         Image img2 = Toolkit.getDefaultToolkit().getImage(fileName);
 367         if (img1 != img2) {
 368             throw new RuntimeException("Image is not cached");
 369         }
 370 
 371         img1 = Toolkit.getDefaultToolkit().getImage(url);
 372         if (!(img1 instanceof MultiResolutionImage)) {
 373             throw new RuntimeException("Not a MultiResolutionImage");
 374         }
 375 
 376         img2 = Toolkit.getDefaultToolkit().getImage(url);
 377         if (img1 != img2) {
 378             throw new RuntimeException("Image is not cached");
 379         }
 380     }
 381 
 382     static void testToolkitMultiResolutionImage(Image image, boolean enableImageScaling)
 383             throws Exception {
 384 
 385         MediaTracker tracker = new MediaTracker(new JPanel());
 386         tracker.addImage(image, 0);
 387         tracker.waitForID(0);
 388         if (tracker.isErrorAny()) {
 389             throw new RuntimeException("Error during image loading");
 390         }
 391 
 392         final BufferedImage bufferedImage1x = new BufferedImage(IMAGE_WIDTH, IMAGE_HEIGHT,
 393                 BufferedImage.TYPE_INT_RGB);
 394         Graphics2D g1x = (Graphics2D) bufferedImage1x.getGraphics();
 395         setImageScalingHint(g1x, false);
 396         g1x.drawImage(image, 0, 0, null);
 397         checkColor(bufferedImage1x.getRGB(3 * IMAGE_WIDTH / 4, 3 * IMAGE_HEIGHT / 4), false);
 398 
 399         Image resolutionVariant = ((MultiResolutionImage) image)
 400                 .getResolutionVariant(2, 2, IMAGE_WIDTH, IMAGE_HEIGHT,
 401                         2 * IMAGE_WIDTH, 2 * IMAGE_HEIGHT);
 402 
 403         if (resolutionVariant == null) {
 404             throw new RuntimeException("Resolution variant is null");
 405         }
 406 
 407         MediaTracker tracker2x = new MediaTracker(new JPanel());
 408         tracker2x.addImage(resolutionVariant, 0);
 409         tracker2x.waitForID(0);
 410         if (tracker2x.isErrorAny()) {
 411             throw new RuntimeException("Error during scalable image loading");
 412         }
 413 
 414         final BufferedImage bufferedImage2x = new BufferedImage(2 * IMAGE_WIDTH,
 415                 2 * IMAGE_HEIGHT, BufferedImage.TYPE_INT_RGB);
 416         Graphics2D g2x = (Graphics2D) bufferedImage2x.getGraphics();
 417         setImageScalingHint(g2x, enableImageScaling);
 418         g2x.drawImage(image, 0, 0, 2 * IMAGE_WIDTH, 2 * IMAGE_HEIGHT, 0, 0, IMAGE_WIDTH, IMAGE_HEIGHT, null);
 419         checkColor(bufferedImage2x.getRGB(3 * IMAGE_WIDTH / 2, 3 * IMAGE_HEIGHT / 2), enableImageScaling);
 420 
 421         if (!(image instanceof MultiResolutionImage)) {
 422             throw new RuntimeException("Not a MultiResolutionImage");
 423         }
 424 
 425         MultiResolutionImage multiResolutionImage = (MultiResolutionImage) image;
 426 
 427         Image image1x = multiResolutionImage
 428                 .getResolutionVariant(1, 1, IMAGE_WIDTH, IMAGE_HEIGHT,
 429                         IMAGE_WIDTH, IMAGE_HEIGHT);
 430         Image image2x = getOSDependentToolkitResolutionVariant(
 431                 multiResolutionImage, IMAGE_WIDTH, IMAGE_WIDTH);
 432 
 433         if (image1x.getWidth(null) * 2 != image2x.getWidth(null)
 434                 || image1x.getHeight(null) * 2 != image2x.getHeight(null)) {
 435             throw new RuntimeException("Wrong resolution variant size");
 436         }
 437     }
 438 
 439     static void testToolkitImageObserver(final Image image) {
 440 
 441         ImageObserver observer = new ImageObserver() {
 442 
 443             @Override
 444             public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) {
 445 
 446                 if (img != image) {
 447                     throw new RuntimeException("Wrong image in observer");
 448                 }
 449 
 450                 if ((infoflags & (ImageObserver.ERROR | ImageObserver.ABORT)) != 0) {
 451                     throw new RuntimeException("Error during image loading");
 452                 }
 453 
 454                 return (infoflags & ImageObserver.ALLBITS) == 0;
 455 
 456             }
 457         };
 458 
 459         final BufferedImage bufferedImage2x = new BufferedImage(2 * IMAGE_WIDTH,
 460                 2 * IMAGE_HEIGHT, BufferedImage.TYPE_INT_RGB);
 461         Graphics2D g2x = (Graphics2D) bufferedImage2x.getGraphics();
 462         setImageScalingHint(g2x, true);
 463 
 464         g2x.drawImage(image, 0, 0, 2 * IMAGE_WIDTH, 2 * IMAGE_HEIGHT, 0, 0, IMAGE_WIDTH, IMAGE_HEIGHT, observer);
 465 
 466     }
 467 
 468     static void setImageScalingHint(Graphics2D g2d, boolean enableImageScaling) {
 469         g2d.setRenderingHint(RenderingHints.KEY_RESOLUTION_VARIANT, enableImageScaling
 470                 ? RenderingHints.VALUE_RESOLUTION_VARIANT_ON
 471                 : RenderingHints.VALUE_RESOLUTION_VARIANT_OFF);
 472     }
 473 
 474     static void checkColor(int rgb, boolean isImageScaled) {
 475 
 476         if (!isImageScaled && COLOR_1X.getRGB() != rgb) {
 477             throw new RuntimeException("Wrong 1x color: " + new Color(rgb));
 478         }
 479 
 480         if (isImageScaled && COLOR_2X.getRGB() != rgb) {
 481             throw new RuntimeException("Wrong 2x color" + new Color(rgb));
 482         }
 483     }
 484 
 485     static void generateImages() throws Exception {
 486         if (!new File(IMAGE_NAME_1X).exists()) {
 487             generateImage(1);
 488         }
 489 
 490         if (!new File(IMAGE_NAME_2X).exists()) {
 491             generateImage(2);
 492         }
 493     }
 494 
 495     static void generateImage(int scale) throws Exception {
 496         BufferedImage image = new BufferedImage(scale * IMAGE_WIDTH, scale * IMAGE_HEIGHT,
 497                 BufferedImage.TYPE_INT_RGB);
 498         Graphics g = image.getGraphics();
 499         g.setColor(scale == 1 ? COLOR_1X : COLOR_2X);
 500         g.fillRect(0, 0, scale * IMAGE_WIDTH, scale * IMAGE_HEIGHT);
 501         File file = new File(scale == 1 ? IMAGE_NAME_1X : IMAGE_NAME_2X);
 502         ImageIO.write(image, "png", file);
 503     }
 504 
 505     static void testImageNameTo2xParsing() throws Exception {
 506 
 507         for (String[] testNames : TEST_FILE_NAMES) {
 508             String testName = testNames[0];
 509             String goldenName = testNames[1];
 510             String resultName = getTestScaledImageName(testName);
 511 
 512             if (!isValidPath(testName) && resultName == null) {
 513                 continue;
 514             }
 515 
 516             if (goldenName.equals(resultName)) {
 517                 continue;
 518             }
 519 
 520             throw new RuntimeException("Test name " + testName
 521                     + ", result name: " + resultName);
 522         }
 523 
 524         for (URL[] testURLs : TEST_URLS) {
 525             URL testURL = testURLs[0];
 526             URL goldenURL = testURLs[1];
 527             URL resultURL = getTestScaledImageURL(testURL);
 528 
 529             if (!isValidPath(testURL.getPath()) && resultURL == null) {
 530                 continue;
 531             }
 532 
 533             if (goldenURL.equals(resultURL)) {
 534                 continue;
 535             }
 536 
 537             throw new RuntimeException("Test url: " + testURL
 538                     + ", result url: " + resultURL);
 539         }
 540 
 541     }
 542 
 543     static URL getTestScaledImageURL(URL url) throws Exception {
 544         Method method = getScalableImageMethod("getScaledImageURL", URL.class);
 545         return (URL) method.invoke(null, url);
 546     }
 547 
 548     static String getTestScaledImageName(String name) throws Exception {
 549         Method method = getScalableImageMethod("getScaledImageName", String.class);
 550         return (String) method.invoke(null, name);
 551     }
 552 
 553     private static boolean isValidPath(String path) {
 554         return !path.isEmpty() && !path.endsWith("/") && !path.endsWith(".")
 555                 && !path.contains("@2x");
 556     }
 557 
 558     private static Method getScalableImageMethod(String name,
 559             Class... parameterTypes) throws Exception {
 560         Toolkit toolkit = Toolkit.getDefaultToolkit();
 561         Method method = toolkit.getClass().getDeclaredMethod(name, parameterTypes);
 562         method.setAccessible(true);
 563         return method;
 564     }
 565     private static final String[][] TEST_FILE_NAMES;
 566     private static final URL[][] TEST_URLS;
 567 
 568     static {
 569         TEST_FILE_NAMES = new String[][]{
 570             {"", null},
 571             {".", null},
 572             {"..", null},
 573             {"/", null},
 574             {"/.", null},
 575             {"dir/", null},
 576             {"dir/.", null},
 577             {"aaa@2x.png", null},
 578             {"/dir/aaa@2x.png", null},
 579             {"image", "image@2x"},
 580             {"image.ext", "image@2x.ext"},
 581             {"image.aaa.ext", "image.aaa@2x.ext"},
 582             {"dir/image", "dir/image@2x"},
 583             {"dir/image.ext", "dir/image@2x.ext"},
 584             {"dir/image.aaa.ext", "dir/image.aaa@2x.ext"},
 585             {"dir/aaa.bbb/image", "dir/aaa.bbb/image@2x"},
 586             {"dir/aaa.bbb/image.ext", "dir/aaa.bbb/image@2x.ext"},
 587             {"dir/aaa.bbb/image.ccc.ext", "dir/aaa.bbb/image.ccc@2x.ext"},
 588             {"/dir/image", "/dir/image@2x"},
 589             {"/dir/image.ext", "/dir/image@2x.ext"},
 590             {"/dir/image.aaa.ext", "/dir/image.aaa@2x.ext"},
 591             {"/dir/aaa.bbb/image", "/dir/aaa.bbb/image@2x"},
 592             {"/dir/aaa.bbb/image.ext", "/dir/aaa.bbb/image@2x.ext"},
 593             {"/dir/aaa.bbb/image.ccc.ext", "/dir/aaa.bbb/image.ccc@2x.ext"}
 594         };
 595         try {
 596             TEST_URLS = new URL[][]{
 597                 // file
 598                 {new URL("file:/aaa"), new URL("file:/aaa@2x")},
 599                 {new URL("file:/aaa.ext"), new URL("file:/aaa@2x.ext")},
 600                 {new URL("file:/aaa.bbb.ext"), new URL("file:/aaa.bbb@2x.ext")},
 601                 {new URL("file:/ccc/aaa.bbb.ext"),
 602                     new URL("file:/ccc/aaa.bbb@2x.ext")},
 603                 {new URL("file:/ccc.ddd/aaa.bbb.ext"),
 604                     new URL("file:/ccc.ddd/aaa.bbb@2x.ext")},
 605                 {new URL("file:///~/image"), new URL("file:///~/image@2x")},
 606                 {new URL("file:///~/image.ext"),
 607                     new URL("file:///~/image@2x.ext")},
 608                 // http
 609                 {new URL("http://www.test.com"), null},
 610                 {new URL("http://www.test.com/"), null},
 611                 {new URL("http://www.test.com///"), null},
 612                 {new URL("http://www.test.com/image"),
 613                     new URL("http://www.test.com/image@2x")},
 614                 {new URL("http://www.test.com/image.ext"),
 615                     new URL("http://www.test.com/image@2x.ext")},
 616                 {new URL("http://www.test.com/dir/image"),
 617                     new URL("http://www.test.com/dir/image@2x")},
 618                 {new URL("http://www.test.com:80/dir/image.aaa.ext"),
 619                     new URL("http://www.test.com:80/dir/image.aaa@2x.ext")},
 620                 {new URL("http://www.test.com:8080/dir/image.aaa.ext"),
 621                     new URL("http://www.test.com:8080/dir/image.aaa@2x.ext")},
 622                 // jar
 623                 {new URL("jar:file:/dir/Java2D.jar!/image"),
 624                     new URL("jar:file:/dir/Java2D.jar!/image@2x")},
 625                 {new URL("jar:file:/dir/Java2D.jar!/image.aaa.ext"),
 626                     new URL("jar:file:/dir/Java2D.jar!/image.aaa@2x.ext")},
 627                 {new URL("jar:file:/dir/Java2D.jar!/images/image"),
 628                     new URL("jar:file:/dir/Java2D.jar!/images/image@2x")},
 629                 {new URL("jar:file:/dir/Java2D.jar!/images/image.ext"),
 630                     new URL("jar:file:/dir/Java2D.jar!/images/image@2x.ext")},
 631                 {new URL("jar:file:/aaa.bbb/Java2D.jar!/images/image.ext"),
 632                     new URL("jar:file:/aaa.bbb/Java2D.jar!/images/image@2x.ext")},
 633                 {new URL("jar:file:/dir/Java2D.jar!/aaa.bbb/image.ext"),
 634                     new URL("jar:file:/dir/Java2D.jar!/aaa.bbb/image@2x.ext")},};
 635         } catch (Exception e) {
 636             throw new RuntimeException(e);
 637         }
 638     }
 639 
 640     static class PreloadedImageObserver implements ImageObserver {
 641 
 642         @Override
 643         public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) {
 644             throw new RuntimeException("Image should be already preloaded");
 645         }
 646     }
 647 }