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