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