1 /* 2 * Copyright (c) 2005, 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package java.awt; 26 27 import java.io.IOException; 28 import java.awt.image.*; 29 import java.net.URL; 30 import java.net.URLConnection; 31 import java.io.File; 32 import sun.util.logging.PlatformLogger; 33 import sun.awt.image.SunWritableRaster; 34 35 /** 36 * The splash screen can be displayed at application startup, before the 37 * Java Virtual Machine (JVM) starts. The splash screen is displayed as an 38 * undecorated window containing an image. You can use GIF, JPEG, or PNG files 39 * for the image. Animation is supported for the GIF format, while transparency 40 * is supported both for GIF and PNG. The window is positioned at the center 41 * of the screen. The position on multi-monitor systems is not specified. It is 42 * platform and implementation dependent. The splash screen window is closed 43 * automatically as soon as the first window is displayed by Swing/AWT (may be 44 * also closed manually using the Java API, see below). 45 * <P> 46 * If your application is packaged in a jar file, you can use the 47 * "SplashScreen-Image" option in a manifest file to show a splash screen. 48 * Place the image in the jar archive and specify the path in the option. 49 * The path should not have a leading slash. 50 * <BR> 51 * For example, in the {@code manifest.mf} file: 52 * <PRE> 53 * Manifest-Version: 1.0 54 * Main-Class: Test 55 * SplashScreen-Image: filename.gif 56 * </PRE> 57 * <P> 58 * If the Java implementation provides the command-line interface and you run 59 * your application by using the command line or a shortcut, use the Java 60 * application launcher option to show a splash screen. The Oracle reference 61 * implementation allows you to specify the splash screen image location with 62 * the {@code -splash:} option. 63 * <BR> 64 * For example: 65 * <PRE> 66 * java -splash:filename.gif Test 67 * </PRE> 68 * HiDPI scaled image is also supported. 69 * Unscaled image name i.e. filename.gif should be passed in 70 * {@code manifest.mf}/{@code -splash:} option for all image types irrespective of 71 * HiDPI and Non-HiDPI. 72 * Following is the naming convention for scaled images. 73 * Screen scale 1.25: filename@125pct.gif 74 * Screen scale 1.50: filename@150pct.gif 75 * Screen scale 2: filename@200pct.gif and filename@2x.gif both are supported 76 * Screen scale 2.50: filename@250pct.gif 77 * Screen scale 3: filename@300pct.gif and filename@3x.gif both are supported 78 * The command line interface has higher precedence over the manifest 79 * setting. 80 * <p> 81 * The splash screen will be displayed as faithfully as possible to present the 82 * whole splash screen image given the limitations of the target platform and 83 * display. 84 * <p> 85 * It is implied that the specified image is presented on the screen "as is", 86 * i.e. preserving the exact color values as specified in the image file. Under 87 * certain circumstances, though, the presented image may differ, e.g. when 88 * applying color dithering to present a 32 bits per pixel (bpp) image on a 16 89 * or 8 bpp screen. The native platform display configuration may also affect 90 * the colors of the displayed image (e.g. color profiles, etc.) 91 * <p> 92 * The {@code SplashScreen} class provides the API for controlling the splash 93 * screen. This class may be used to close the splash screen, change the splash 94 * screen image, get the splash screen native window position/size, and paint 95 * in the splash screen. It cannot be used to create the splash screen. You 96 * should use the options provided by the Java implementation for that. 97 * <p> 98 * This class cannot be instantiated. Only a single instance of this class 99 * can exist, and it may be obtained by using the {@link #getSplashScreen()} 100 * static method. In case the splash screen has not been created at 101 * application startup via the command line or manifest file option, 102 * the {@code getSplashScreen} method returns {@code null}. 103 * 104 * @author Oleg Semenov 105 * @since 1.6 106 */ 107 public final class SplashScreen { 108 109 SplashScreen(long ptr) { // non-public constructor 110 splashPtr = ptr; 111 } 112 113 /** 114 * Returns the {@code SplashScreen} object used for 115 * Java startup splash screen control on systems that support display. 116 * 117 * @throws UnsupportedOperationException if the splash screen feature is not 118 * supported by the current toolkit 119 * @throws HeadlessException if {@code GraphicsEnvironment.isHeadless()} 120 * returns true 121 * @return the {@link SplashScreen} instance, or {@code null} if there is 122 * none or it has already been closed 123 */ 124 public static SplashScreen getSplashScreen() { 125 synchronized (SplashScreen.class) { 126 if (GraphicsEnvironment.isHeadless()) { 127 throw new HeadlessException(); 128 } 129 // SplashScreen class is now a singleton 130 if (!wasClosed && theInstance == null) { 131 java.security.AccessController.doPrivileged( 132 new java.security.PrivilegedAction<Void>() { 133 public Void run() { 134 System.loadLibrary("splashscreen"); 135 return null; 136 } 137 }); 138 long ptr = _getInstance(); 139 if (ptr != 0 && _isVisible(ptr)) { 140 theInstance = new SplashScreen(ptr); 141 } 142 } 143 return theInstance; 144 } 145 } 146 147 /** 148 * Changes the splash screen image. The new image is loaded from the 149 * specified URL; GIF, JPEG and PNG image formats are supported. 150 * The method returns after the image has finished loading and the window 151 * has been updated. 152 * The splash screen window is resized according to the size of 153 * the image and is centered on the screen. 154 * 155 * @param imageURL the non-{@code null} URL for the new 156 * splash screen image 157 * @throws NullPointerException if {@code imageURL} is {@code null} 158 * @throws IOException if there was an error while loading the image 159 * @throws IllegalStateException if the splash screen has already been 160 * closed 161 */ 162 public void setImageURL(URL imageURL) throws NullPointerException, IOException, IllegalStateException { 163 checkVisible(); 164 URLConnection connection = imageURL.openConnection(); 165 connection.connect(); 166 int length = connection.getContentLength(); 167 java.io.InputStream stream = connection.getInputStream(); 168 byte[] buf = new byte[length]; 169 int off = 0; 170 while(true) { 171 // check for available data 172 int available = stream.available(); 173 if (available <= 0) { 174 // no data available... well, let's try reading one byte 175 // we'll see what happens then 176 available = 1; 177 } 178 // check for enough room in buffer, realloc if needed 179 // the buffer always grows in size 2x minimum 180 if (off + available > length) { 181 length = off*2; 182 if (off + available > length) { 183 length = available+off; 184 } 185 byte[] oldBuf = buf; 186 buf = new byte[length]; 187 System.arraycopy(oldBuf, 0, buf, 0, off); 188 } 189 // now read the data 190 int result = stream.read(buf, off, available); 191 if (result < 0) { 192 break; 193 } 194 off += result; 195 } 196 synchronized(SplashScreen.class) { 197 checkVisible(); 198 if (!_setImageData(splashPtr, buf)) { 199 throw new IOException("Bad image format or i/o error when loading image"); 200 } 201 this.imageURL = imageURL; 202 } 203 } 204 205 private void checkVisible() { 206 if (!isVisible()) { 207 throw new IllegalStateException("no splash screen available"); 208 } 209 } 210 /** 211 * Returns the current splash screen image. 212 * 213 * @return URL for the current splash screen image file 214 * @throws IllegalStateException if the splash screen has already been closed 215 */ 216 @SuppressWarnings("deprecation") 217 public URL getImageURL() throws IllegalStateException { 218 synchronized (SplashScreen.class) { 219 checkVisible(); 220 if (imageURL == null) { 221 try { 222 String fileName = _getImageFileName(splashPtr); 223 String jarName = _getImageJarName(splashPtr); 224 if (fileName != null) { 225 if (jarName != null) { 226 imageURL = new URL("jar:"+(new File(jarName).toURL().toString())+"!/"+fileName); 227 } else { 228 imageURL = new File(fileName).toURL(); 229 } 230 } 231 } 232 catch(java.net.MalformedURLException e) { 233 if (log.isLoggable(PlatformLogger.Level.FINE)) { 234 log.fine("MalformedURLException caught in the getImageURL() method", e); 235 } 236 } 237 } 238 return imageURL; 239 } 240 } 241 242 /** 243 * Returns the bounds of the splash screen window as a {@link Rectangle}. 244 * This may be useful if, for example, you want to replace the splash 245 * screen with your window at the same location. 246 * <p> 247 * You cannot control the size or position of the splash screen. 248 * The splash screen size is adjusted automatically when the image changes. 249 * <p> 250 * The image may contain transparent areas, and thus the reported bounds may 251 * be larger than the visible splash screen image on the screen. 252 * 253 * @return a {@code Rectangle} containing the splash screen bounds 254 * @throws IllegalStateException if the splash screen has already been closed 255 */ 256 public Rectangle getBounds() throws IllegalStateException { 257 synchronized (SplashScreen.class) { 258 checkVisible(); 259 float scale = _getScaleFactor(splashPtr); 260 Rectangle bounds = _getBounds(splashPtr); 261 assert scale > 0; 262 if (scale > 0 && scale != 1) { 263 bounds.setSize((int) (bounds.getWidth() / scale), 264 (int) (bounds.getHeight() / scale)); 265 } 266 return bounds; 267 } 268 } 269 270 /** 271 * Returns the size of the splash screen window as a {@link Dimension}. 272 * This may be useful if, for example, 273 * you want to draw on the splash screen overlay surface. 274 * <p> 275 * You cannot control the size or position of the splash screen. 276 * The splash screen size is adjusted automatically when the image changes. 277 * <p> 278 * The image may contain transparent areas, and thus the reported size may 279 * be larger than the visible splash screen image on the screen. 280 * 281 * @return a {@link Dimension} object indicating the splash screen size 282 * @throws IllegalStateException if the splash screen has already been closed 283 */ 284 public Dimension getSize() throws IllegalStateException { 285 return getBounds().getSize(); 286 } 287 288 /** 289 * Creates a graphics context (as a {@link Graphics2D} object) for the splash 290 * screen overlay image, which allows you to draw over the splash screen. 291 * Note that you do not draw on the main image but on the image that is 292 * displayed over the main image using alpha blending. Also note that drawing 293 * on the overlay image does not necessarily update the contents of splash 294 * screen window. You should call {@code update()} on the 295 * {@code SplashScreen} when you want the splash screen to be 296 * updated immediately. 297 * <p> 298 * The pixel (0, 0) in the coordinate space of the graphics context 299 * corresponds to the origin of the splash screen native window bounds (see 300 * {@link #getBounds()}). 301 * 302 * @return graphics context for the splash screen overlay surface 303 * @throws IllegalStateException if the splash screen has already been closed 304 */ 305 public Graphics2D createGraphics() throws IllegalStateException { 306 synchronized (SplashScreen.class) { 307 checkVisible(); 308 if (image==null) { 309 // get unscaled splash image size 310 Dimension dim = _getBounds(splashPtr).getSize(); 311 image = new BufferedImage(dim.width, dim.height, 312 BufferedImage.TYPE_INT_ARGB); 313 } 314 float scale = _getScaleFactor(splashPtr); 315 Graphics2D g = image.createGraphics(); 316 assert (scale > 0); 317 if (scale <= 0) { 318 scale = 1; 319 } 320 g.scale(scale, scale); 321 return g; 322 } 323 } 324 325 /** 326 * Updates the splash window with current contents of the overlay image. 327 * 328 * @throws IllegalStateException if the overlay image does not exist; 329 * for example, if {@code createGraphics} has never been called, 330 * or if the splash screen has already been closed 331 */ 332 public void update() throws IllegalStateException { 333 BufferedImage image; 334 synchronized (SplashScreen.class) { 335 checkVisible(); 336 image = this.image; 337 } 338 if (image == null) { 339 throw new IllegalStateException("no overlay image available"); 340 } 341 DataBuffer buf = image.getRaster().getDataBuffer(); 342 if (!(buf instanceof DataBufferInt)) { 343 throw new AssertionError("Overlay image DataBuffer is of invalid type == "+buf.getClass().getName()); 344 } 345 int numBanks = buf.getNumBanks(); 346 if (numBanks!=1) { 347 throw new AssertionError("Invalid number of banks =="+numBanks+" in overlay image DataBuffer"); 348 } 349 if (!(image.getSampleModel() instanceof SinglePixelPackedSampleModel)) { 350 throw new AssertionError("Overlay image has invalid sample model == "+image.getSampleModel().getClass().getName()); 351 } 352 SinglePixelPackedSampleModel sm = (SinglePixelPackedSampleModel)image.getSampleModel(); 353 int scanlineStride = sm.getScanlineStride(); 354 Rectangle rect = image.getRaster().getBounds(); 355 // Note that we steal the data array here, but just for reading 356 // so we do not need to mark the DataBuffer dirty... 357 int[] data = SunWritableRaster.stealData((DataBufferInt)buf, 0); 358 synchronized(SplashScreen.class) { 359 checkVisible(); 360 _update(splashPtr, data, rect.x, rect.y, rect.width, rect.height, scanlineStride); 361 } 362 } 363 364 /** 365 * Hides the splash screen, closes the window, and releases all associated 366 * resources. 367 * 368 * @throws IllegalStateException if the splash screen has already been closed 369 */ 370 public void close() throws IllegalStateException { 371 synchronized (SplashScreen.class) { 372 checkVisible(); 373 _close(splashPtr); 374 image = null; 375 SplashScreen.markClosed(); 376 } 377 } 378 379 static void markClosed() { 380 synchronized (SplashScreen.class) { 381 wasClosed = true; 382 theInstance = null; 383 } 384 } 385 386 387 /** 388 * Determines whether the splash screen is visible. The splash screen may 389 * be hidden using {@link #close()}, it is also hidden automatically when 390 * the first AWT/Swing window is made visible. 391 * <p> 392 * Note that the native platform may delay presenting the splash screen 393 * native window on the screen. The return value of {@code true} for this 394 * method only guarantees that the conditions to hide the splash screen 395 * window have not occurred yet. 396 * 397 * @return true if the splash screen is visible (has not been closed yet), 398 * false otherwise 399 */ 400 public boolean isVisible() { 401 synchronized (SplashScreen.class) { 402 return !wasClosed && _isVisible(splashPtr); 403 } 404 } 405 406 private BufferedImage image; // overlay image 407 408 private final long splashPtr; // pointer to native Splash structure 409 private static boolean wasClosed = false; 410 411 private URL imageURL; 412 413 /** 414 * The instance reference for the singleton. 415 * ({@code null} if no instance exists yet.) 416 * 417 * @see #getSplashScreen 418 * @see #close 419 */ 420 private static SplashScreen theInstance = null; 421 422 private static final PlatformLogger log = PlatformLogger.getLogger("java.awt.SplashScreen"); 423 424 private static native void _update(long splashPtr, int[] data, int x, int y, int width, int height, int scanlineStride); 425 private static native boolean _isVisible(long splashPtr); 426 private static native Rectangle _getBounds(long splashPtr); 427 private static native long _getInstance(); 428 private static native void _close(long splashPtr); 429 private static native String _getImageFileName(long splashPtr); 430 private static native String _getImageJarName(long SplashPtr); 431 private static native boolean _setImageData(long SplashPtr, byte[] data); 432 private static native float _getScaleFactor(long SplashPtr); 433 434 }