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