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 }