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