1 /* 2 * Copyright (c) 2011, 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. 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 26 package com.sun.javafx.tk.quantum; 27 28 import java.io.IOException; 29 import java.io.InputStream; 30 31 import com.sun.javafx.iio.ImageFrame; 32 import com.sun.javafx.iio.ImageLoadListener; 33 import com.sun.javafx.iio.ImageLoader; 34 import com.sun.javafx.iio.ImageMetadata; 35 import com.sun.javafx.iio.ImageStorage; 36 import com.sun.javafx.iio.ImageStorageException; 37 import com.sun.javafx.runtime.async.AbstractRemoteResource; 38 import com.sun.javafx.runtime.async.AsyncOperationListener; 39 import com.sun.javafx.tk.PlatformImage; 40 import com.sun.prism.Image; 41 import com.sun.prism.impl.PrismSettings; 42 import java.lang.reflect.UndeclaredThrowableException; 43 import java.security.AccessControlContext; 44 import java.security.AccessController; 45 import java.security.PrivilegedAction; 46 import java.security.PrivilegedActionException; 47 import java.security.PrivilegedExceptionAction; 48 import java.util.concurrent.ExecutorService; 49 import java.util.concurrent.Executors; 50 import java.util.concurrent.ThreadFactory; 51 import java.util.concurrent.ThreadPoolExecutor; 52 import java.util.concurrent.TimeUnit; 53 import sun.util.logging.PlatformLogger; 54 55 class PrismImageLoader2 implements com.sun.javafx.tk.ImageLoader { 56 57 private static PlatformLogger imageioLogger = null; 58 59 private Image[] images; 60 private int[] delayTimes; 61 private int loopCount; 62 private int width; 63 private int height; 64 private float pixelScale; 65 private Exception exception; 66 67 public PrismImageLoader2(String url, int width, int height, 68 boolean preserveRatio, float pixelScale, 69 boolean smooth) 70 { 71 loadAll(url, width, height, preserveRatio, pixelScale, smooth); 72 } 73 74 public PrismImageLoader2(InputStream stream, int width, int height, 75 boolean preserveRatio, boolean smooth) 76 { 77 loadAll(stream, width, height, preserveRatio, smooth); 78 } 79 80 public int getWidth() { 81 return width; 82 } 83 84 public int getHeight() { 85 return height; 86 } 87 88 public int getFrameCount() { 89 if (images == null) { 90 return 0; 91 } 92 return images.length; 93 } 94 95 public PlatformImage getFrame(int index) { 96 if (images == null) { 97 return null; 98 } 99 return images[index]; 100 } 101 102 public int getFrameDelay(int index) { 103 if (images == null) { 104 return 0; 105 } 106 return delayTimes[index]; 107 } 108 109 public int getLoopCount() { 110 if (images == null) { 111 return 0; 112 } 113 return loopCount; 114 } 115 116 public Exception getException() { 117 return exception; 118 } 119 120 private void loadAll(String url, int w, int h, 121 boolean preserveRatio, float pixelScale, 122 boolean smooth) 123 { 124 ImageLoadListener listener = new PrismLoadListener(); 125 try { 126 ImageFrame[] imgFrames = 127 ImageStorage.loadAll(url, listener, w, h, preserveRatio, pixelScale, smooth); 128 convertAll(imgFrames); 129 } catch (ImageStorageException e) { 130 handleException(e); 131 } catch (Exception e) { 132 handleException(e); 133 } 134 } 135 136 private void loadAll(InputStream stream, int w, int h, 137 boolean preserveRatio, boolean smooth) 138 { 139 ImageLoadListener listener = new PrismLoadListener(); 140 try { 141 ImageFrame[] imgFrames = 142 ImageStorage.loadAll(stream, listener, w, h, preserveRatio, 1.0f, smooth); 143 convertAll(imgFrames); 144 } catch (ImageStorageException e) { 145 handleException(e); 146 } catch (Exception e) { 147 handleException(e); 148 } 149 } 150 151 private void handleException(final ImageStorageException isException) { 152 // unwrap ImageStorageException if possible 153 final Throwable exceptionCause = isException.getCause(); 154 if (exceptionCause instanceof Exception) { 155 handleException((Exception) exceptionCause); 156 } else { 157 handleException((Exception) isException); 158 } 159 } 160 161 private void handleException(final Exception exception) { 162 if (PrismSettings.verbose) { 163 exception.printStackTrace(System.err); 164 } 165 this.exception = exception; 166 } 167 168 private void convertAll(ImageFrame[] imgFrames) { 169 int numFrames = imgFrames.length; 170 images = new Image[numFrames]; 171 delayTimes = new int[numFrames]; 172 for (int i = 0; i < numFrames; i++) { 173 ImageFrame frame = imgFrames[i]; 174 images[i] = com.sun.prism.Image.convertImageFrame(frame); 175 ImageMetadata metadata = frame.getMetadata(); 176 if (metadata != null) { 177 Integer delay = metadata.delayTime; 178 if (delay != null) { 179 delayTimes[i] = delay.intValue(); 180 } 181 Integer loopCount = metadata.loopCount; 182 if (loopCount != null) { 183 this.loopCount = loopCount; 184 } 185 } 186 if (i == 0) { 187 width = frame.getWidth(); 188 height = frame.getHeight(); 189 } 190 } 191 } 192 193 /** 194 * Returns the PlatformLogger for logging imageio-related activities. 195 */ 196 private static synchronized PlatformLogger getImageioLogger() { 197 if (imageioLogger == null) { 198 imageioLogger = PlatformLogger.getLogger("javafx.scene.image"); 199 } 200 201 return imageioLogger; 202 } 203 204 private class PrismLoadListener implements ImageLoadListener { 205 public void imageLoadWarning(ImageLoader loader, String message) { 206 getImageioLogger().warning(message); 207 } 208 209 public void imageLoadProgress(ImageLoader loader, 210 float percentageComplete) 211 { 212 // progress only matters when backgroundLoading=true, but 213 // currently we are relying on AbstractRemoteResource for tracking 214 // progress of the InputStream, so there's no need to implement 215 // this for now; eventually though we might want to consider 216 // moving away from AbstractRemoteResource and instead use 217 // the built-in support for progress in the javafx-iio library... 218 } 219 220 public void imageLoadMetaData(ImageLoader loader, ImageMetadata metadata) { 221 // We currently have no need to listen for ImageMetadata ready. 222 } 223 } 224 225 static final class AsyncImageLoader 226 extends AbstractRemoteResource<PrismImageLoader2> 227 { 228 private static final ExecutorService BG_LOADING_EXECUTOR = 229 createExecutor(); 230 231 private final AccessControlContext acc; 232 233 int width, height; 234 boolean preserveRatio; 235 boolean smooth; 236 237 public AsyncImageLoader( 238 AsyncOperationListener<PrismImageLoader2> listener, 239 String url, 240 int width, int height, boolean preserveRatio, boolean smooth) 241 { 242 super(url, listener); 243 this.width = width; 244 this.height = height; 245 this.preserveRatio = preserveRatio; 246 this.smooth = smooth; 247 this.acc = AccessController.getContext(); 248 } 249 250 @Override 251 protected PrismImageLoader2 processStream(InputStream stream) throws IOException { 252 return new PrismImageLoader2(stream, width, height, preserveRatio, smooth); 253 } 254 255 @Override 256 public PrismImageLoader2 call() throws IOException { 257 try { 258 return AccessController.doPrivileged( 259 (PrivilegedExceptionAction<PrismImageLoader2>) () -> AsyncImageLoader.super.call(), acc); 260 } catch (final PrivilegedActionException e) { 261 final Throwable cause = e.getCause(); 262 263 if (cause instanceof IOException) { 264 throw (IOException) cause; 265 } 266 267 throw new UndeclaredThrowableException(cause); 268 } 269 } 270 271 @Override 272 public void start() { 273 BG_LOADING_EXECUTOR.execute(future); 274 } 275 276 private static ExecutorService createExecutor() { 277 final ThreadGroup bgLoadingThreadGroup = 278 AccessController.doPrivileged( 279 (PrivilegedAction<ThreadGroup>) () -> new ThreadGroup( 280 QuantumToolkit.getFxUserThread() 281 .getThreadGroup(), 282 "Background image loading thread pool") 283 ); 284 285 final ThreadFactory bgLoadingThreadFactory = 286 runnable -> AccessController.doPrivileged( 287 (PrivilegedAction<Thread>) () -> { 288 final Thread newThread = 289 new Thread(bgLoadingThreadGroup, 290 runnable); 291 newThread.setPriority( 292 Thread.MIN_PRIORITY); 293 294 return newThread; 295 } 296 ); 297 298 final ExecutorService bgLoadingExecutor = 299 Executors.newCachedThreadPool(bgLoadingThreadFactory); 300 ((ThreadPoolExecutor) bgLoadingExecutor).setKeepAliveTime( 301 1, TimeUnit.SECONDS); 302 303 return bgLoadingExecutor; 304 } 305 } 306 }