1 /*
   2  * Copyright (c) 2012, 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 
  26 package javafx.embed.swing;
  27 
  28 import java.awt.AlphaComposite;
  29 import java.awt.EventQueue;
  30 import java.awt.Graphics2D;
  31 import java.awt.SecondaryLoop;
  32 import java.awt.image.BufferedImage;
  33 import java.lang.reflect.InvocationTargetException;
  34 import java.lang.reflect.Method;
  35 import java.nio.IntBuffer;
  36 import java.security.AccessController;
  37 import java.security.PrivilegedAction;
  38 import java.util.concurrent.atomic.AtomicBoolean;
  39 import javafx.application.Platform;
  40 import javafx.scene.image.Image;
  41 import javafx.scene.image.PixelFormat;
  42 import javafx.scene.image.PixelReader;
  43 import javafx.scene.image.PixelWriter;
  44 import javafx.scene.image.WritableImage;
  45 import javafx.scene.image.WritablePixelFormat;
  46 import com.sun.javafx.application.PlatformImpl;
  47 import com.sun.javafx.tk.Toolkit;
  48 import sun.awt.AWTAccessor;
  49 import sun.awt.FwDispatcher;
  50 import sun.awt.image.IntegerComponentRaster;
  51 
  52 import javax.swing.*;
  53 
  54 /**
  55  * This class provides utility methods for converting data types between
  56  * Swing/AWT and JavaFX formats.
  57  * @since JavaFX 2.2
  58  */
  59 public class SwingFXUtils {
  60     private SwingFXUtils() {} // no instances
  61 
  62     /**
  63      * Snapshots the specified {@link BufferedImage} and stores a copy of
  64      * its pixels into a JavaFX {@link Image} object, creating a new
  65      * object if needed.
  66      * The returned {@code Image} will be a static snapshot of the state
  67      * of the pixels in the {@code BufferedImage} at the time the method
  68      * completes.  Further changes to the {@code BufferedImage} will not
  69      * be reflected in the {@code Image}.
  70      * <p>
  71      * The optional JavaFX {@link WritableImage} parameter may be reused
  72      * to store the copy of the pixels.
  73      * A new {@code Image} will be created if the supplied object is null,
  74      * is too small or of a type which the image pixels cannot be easily
  75      * converted into.
  76      * 
  77      * @param bimg the {@code BufferedImage} object to be converted
  78      * @param wimg an optional {@code WritableImage} object that can be
  79      *        used to store the returned pixel data
  80      * @return an {@code Image} object representing a snapshot of the
  81      *         current pixels in the {@code BufferedImage}.
  82      * @since JavaFX 2.2
  83      */
  84     public static WritableImage toFXImage(BufferedImage bimg, WritableImage wimg) {
  85         int bw = bimg.getWidth();
  86         int bh = bimg.getHeight();
  87         switch (bimg.getType()) {
  88             case BufferedImage.TYPE_INT_ARGB:
  89             case BufferedImage.TYPE_INT_ARGB_PRE:
  90                 break;
  91             default:
  92                 BufferedImage converted =
  93                     new BufferedImage(bw, bh, BufferedImage.TYPE_INT_ARGB_PRE);
  94                 Graphics2D g2d = converted.createGraphics();
  95                 g2d.drawImage(bimg, 0, 0, null);
  96                 g2d.dispose();
  97                 bimg = converted;
  98                 break;
  99         }
 100         // assert(bimg.getType == TYPE_INT_ARGB[_PRE]);
 101         if (wimg != null) {
 102             int iw = (int) wimg.getWidth();
 103             int ih = (int) wimg.getHeight();
 104             if (iw < bw || ih < bh) {
 105                 wimg = null;
 106             } else if (bw < iw || bh < ih) {
 107                 int empty[] = new int[iw];
 108                 PixelWriter pw = wimg.getPixelWriter();
 109                 PixelFormat pf = PixelFormat.getIntArgbPreInstance();
 110                 if (bw < iw) {
 111                     pw.setPixels(bw, 0, iw-bw, bh, pf, empty, 0, 0);
 112                 }
 113                 if (bh < ih) {
 114                     pw.setPixels(0, bh, iw, ih-bh, pf, empty, 0, 0);
 115                 }
 116             }
 117         }
 118         if (wimg == null) {
 119             wimg = new WritableImage(bw, bh);
 120         }
 121         PixelWriter pw = wimg.getPixelWriter();
 122         IntegerComponentRaster icr = (IntegerComponentRaster) bimg.getRaster();
 123         int data[] = icr.getDataStorage();
 124         int offset = icr.getDataOffset(0);
 125         int scan = icr.getScanlineStride();
 126         PixelFormat<IntBuffer> pf = (bimg.isAlphaPremultiplied() ?
 127                                      PixelFormat.getIntArgbPreInstance() :
 128                                      PixelFormat.getIntArgbInstance());
 129         pw.setPixels(0, 0, bw, bh, pf, data, offset, scan);
 130         return wimg;
 131     }
 132 
 133     /**
 134      * Snapshots the specified JavaFX {@link Image} object and stores a
 135      * copy of its pixels into a {@link BufferedImage} object, creating
 136      * a new object if needed.
 137      * The method will only convert a JavaFX {@code Image} that is readable
 138      * as per the conditions on the
 139      * {@link Image#getPixelReader() Image.getPixelReader()}
 140      * method.
 141      * If the {@code Image} is not readable, as determined by its
 142      * {@code getPixelReader()} method, then this method will return null.
 143      * If the {@code Image} is a writable, or other dynamic image, then
 144      * the {@code BufferedImage} will only be set to the current state of
 145      * the pixels in the image as determined by its {@link PixelReader}.
 146      * Further changes to the pixels of the {@code Image} will not be
 147      * reflected in the returned {@code BufferedImage}.
 148      * <p>
 149      * The optional {@code BufferedImage} parameter may be reused to store
 150      * the copy of the pixels.
 151      * A new {@code BufferedImage} will be created if the supplied object
 152      * is null, is too small or of a type which the image pixels cannot
 153      * be easily converted into.
 154      * 
 155      * @param img the JavaFX {@code Image} to be converted
 156      * @param bimg an optional {@code BufferedImage} object that may be
 157      *        used to store the returned pixel data
 158      * @return a {@code BufferedImage} containing a snapshot of the JavaFX
 159      *         {@code Image}, or null if the {@code Image} is not readable.
 160      * @since JavaFX 2.2
 161      */
 162     public static BufferedImage fromFXImage(Image img, BufferedImage bimg) {
 163         PixelReader pr = img.getPixelReader();
 164         if (pr == null) {
 165             return null;
 166         }
 167         int iw = (int) img.getWidth();
 168         int ih = (int) img.getHeight();
 169         if (bimg != null) {
 170             int type = bimg.getType();
 171             int bw = bimg.getWidth();
 172             int bh = bimg.getHeight();
 173             if (bw < iw || bh < ih ||
 174                 (type != BufferedImage.TYPE_INT_ARGB &&
 175                  type != BufferedImage.TYPE_INT_ARGB_PRE))
 176             {
 177                 bimg = null;
 178             } else if (iw < bw || ih < bh) {
 179                 Graphics2D g2d = bimg.createGraphics();
 180                 g2d.setComposite(AlphaComposite.Clear);
 181                 g2d.fillRect(0, 0, bw, bh);
 182                 g2d.dispose();
 183             }
 184         }
 185         if (bimg == null) {
 186             bimg = new BufferedImage(iw, ih, BufferedImage.TYPE_INT_ARGB_PRE);
 187         }
 188         IntegerComponentRaster icr = (IntegerComponentRaster) bimg.getRaster();
 189         int offset = icr.getDataOffset(0);
 190         int scan = icr.getScanlineStride();
 191         int data[] = icr.getDataStorage();
 192         WritablePixelFormat<IntBuffer> pf = (bimg.isAlphaPremultiplied() ?
 193                                              PixelFormat.getIntArgbPreInstance() :
 194                                              PixelFormat.getIntArgbInstance());
 195         pr.getPixels(0, 0, iw, ih, pf, data, offset, scan);
 196         return bimg;
 197     }
 198 
 199     /**
 200      * If called from the FX Application Thread
 201      * invokes a runnable directly blocking the calling code
 202      * Otherwise
 203      * uses Platform.runLater without blocking
 204      */
 205     static void runOnFxThread(Runnable runnable) {
 206         if (Platform.isFxApplicationThread()) {
 207             runnable.run();
 208         } else {
 209             Platform.runLater(runnable);
 210         }
 211     }
 212 
 213     /**
 214      * If called from the event dispatch thread
 215      * invokes a runnable directly blocking the calling code
 216      * Otherwise
 217      * uses SwingUtilities.invokeLater without blocking
 218      */
 219     static void runOnEDT(final Runnable r) {
 220         if (SwingUtilities.isEventDispatchThread()) {
 221             r.run();
 222         } else {
 223             SwingUtilities.invokeLater(r);
 224         }
 225     }
 226 
 227     private static class FwSecondaryLoop implements SecondaryLoop {
 228 
 229         private final AtomicBoolean isRunning = new AtomicBoolean(false);
 230 
 231         @Override public boolean enter() {
 232             if (isRunning.compareAndSet(false, true)) {
 233                 PlatformImpl.runAndWait(() -> {
 234                     Toolkit.getToolkit().enterNestedEventLoop(FwSecondaryLoop.this);
 235                 });
 236                 return true;
 237             }
 238             return false;
 239         }
 240 
 241         @Override public boolean exit() {
 242             if (isRunning.compareAndSet(true, false)) {
 243                 PlatformImpl.runAndWait(() -> {
 244                     Toolkit.getToolkit().exitNestedEventLoop(FwSecondaryLoop.this, null);
 245                 });
 246                 return true;
 247             }
 248             return false;
 249         }
 250     }
 251 
 252     private static class FXDispatcher implements FwDispatcher {
 253         @Override public boolean isDispatchThread() {
 254             return Platform.isFxApplicationThread();
 255         }
 256 
 257         @Override public void scheduleDispatch(Runnable runnable) {
 258             Platform.runLater(runnable);
 259         }
 260 
 261         @Override public SecondaryLoop createSecondaryLoop() {
 262             return new FwSecondaryLoop();
 263         }
 264     }
 265 
 266     private static EventQueue getEventQueue() {
 267         return AccessController.doPrivileged(
 268                 (PrivilegedAction<EventQueue>) () -> java.awt.Toolkit.getDefaultToolkit().getSystemEventQueue());
 269     }
 270 
 271     //Called with reflection from PlatformImpl to avoid dependency
 272     private static void installFwEventQueue() {
 273         AWTAccessor.getEventQueueAccessor().setFwDispatcher(getEventQueue(), new FXDispatcher());
 274     }
 275 
 276     //Called with reflection from PlatformImpl to avoid dependency
 277     private static void removeFwEventQueue() {
 278         AWTAccessor.getEventQueueAccessor().setFwDispatcher(getEventQueue(), null);
 279     }
 280 }