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.nio.IntBuffer;
  34 import java.nio.ByteOrder;
  35 import java.security.AccessController;
  36 import java.security.PrivilegedAction;
  37 import java.util.Set;
  38 import java.util.HashSet;
  39 import java.util.concurrent.atomic.AtomicBoolean;
  40 import javafx.application.Platform;
  41 import javafx.scene.image.Image;
  42 import javafx.scene.image.PixelFormat;
  43 import javafx.scene.image.PixelReader;
  44 import javafx.scene.image.PixelWriter;
  45 import javafx.scene.image.WritableImage;
  46 import javafx.scene.image.WritablePixelFormat;
  47 import com.sun.javafx.application.PlatformImpl;
  48 import com.sun.javafx.tk.Toolkit;
  49 import com.sun.glass.ui.Pixels;
  50 import sun.awt.AWTAccessor;
  51 import sun.awt.FwDispatcher;
  52 import sun.awt.image.IntegerComponentRaster;
  53 
  54 import javax.swing.*;
  55 
  56 /**
  57  * This class provides utility methods for converting data types between
  58  * Swing/AWT and JavaFX formats.
  59  * @since JavaFX 2.2
  60  */
  61 public class SwingFXUtils {
  62     private SwingFXUtils() {} // no instances
  63     
  64     private static volatile int fxPixelsNativeFormat = -1; // initially undefined
  65     
  66     /**
  67      * Must be called on the FX event thread.
  68      */
  69     static void notifyFXInitialized() {
  70         if (!PlatformImpl.isFxApplicationThread()) {
  71             return;
  72         }
  73         fxPixelsNativeFormat = Pixels.getNativeFormat();
  74     }
  75     
  76     /**
  77      * Snapshots the specified {@link BufferedImage} and stores a copy of
  78      * its pixels into a JavaFX {@link Image} object, creating a new
  79      * object if needed.
  80      * The returned {@code Image} will be a static snapshot of the state
  81      * of the pixels in the {@code BufferedImage} at the time the method
  82      * completes.  Further changes to the {@code BufferedImage} will not
  83      * be reflected in the {@code Image}.
  84      * <p>
  85      * The optional JavaFX {@link WritableImage} parameter may be reused
  86      * to store the copy of the pixels.
  87      * A new {@code Image} will be created if the supplied object is null,
  88      * is too small or of a type which the image pixels cannot be easily
  89      * converted into.
  90      * 
  91      * @param bimg the {@code BufferedImage} object to be converted
  92      * @param wimg an optional {@code WritableImage} object that can be
  93      *        used to store the returned pixel data
  94      * @return an {@code Image} object representing a snapshot of the
  95      *         current pixels in the {@code BufferedImage}.
  96      * @since JavaFX 2.2
  97      */
  98     public static WritableImage toFXImage(BufferedImage bimg, WritableImage wimg) {
  99         int bw = bimg.getWidth();
 100         int bh = bimg.getHeight();
 101         switch (bimg.getType()) {
 102             case BufferedImage.TYPE_INT_ARGB:
 103             case BufferedImage.TYPE_INT_ARGB_PRE:
 104                 break;
 105             default:
 106                 BufferedImage converted =
 107                     new BufferedImage(bw, bh, BufferedImage.TYPE_INT_ARGB_PRE);
 108                 Graphics2D g2d = converted.createGraphics();
 109                 g2d.drawImage(bimg, 0, 0, null);
 110                 g2d.dispose();
 111                 bimg = converted;
 112                 break;
 113         }
 114         // assert(bimg.getType == TYPE_INT_ARGB[_PRE]);
 115         if (wimg != null) {
 116             int iw = (int) wimg.getWidth();
 117             int ih = (int) wimg.getHeight();
 118             if (iw < bw || ih < bh) {
 119                 wimg = null;
 120             } else if (bw < iw || bh < ih) {
 121                 int empty[] = new int[iw];
 122                 PixelWriter pw = wimg.getPixelWriter();
 123                 PixelFormat<IntBuffer> pf = PixelFormat.getIntArgbPreInstance();
 124                 if (bw < iw) {
 125                     pw.setPixels(bw, 0, iw-bw, bh, pf, empty, 0, 0);
 126                 }
 127                 if (bh < ih) {
 128                     pw.setPixels(0, bh, iw, ih-bh, pf, empty, 0, 0);
 129                 }
 130             }
 131         }
 132         if (wimg == null) {
 133             wimg = new WritableImage(bw, bh);
 134         }
 135         PixelWriter pw = wimg.getPixelWriter();
 136         IntegerComponentRaster icr = (IntegerComponentRaster) bimg.getRaster();
 137         int data[] = icr.getDataStorage();
 138         int offset = icr.getDataOffset(0);
 139         int scan = icr.getScanlineStride();
 140         PixelFormat<IntBuffer> pf = (bimg.isAlphaPremultiplied() ?
 141                                      PixelFormat.getIntArgbPreInstance() :
 142                                      PixelFormat.getIntArgbInstance());
 143         pw.setPixels(0, 0, bw, bh, pf, data, offset, scan);
 144         return wimg;
 145     }
 146     
 147     /**
 148      * Creates a BufferedImage object compatible with the FX pixels format.
 149      * 
 150      * @param w the width of the buffer
 151      * @param h the height of the buffer
 152      * @return the new buffer created or null until FX platform is initialized
 153      */
 154     static BufferedImage createCompatibleBufferedImage(int w, int h) {
 155         if (fxPixelsNativeFormat == -1) {
 156             return null;
 157         }
 158         ByteOrder byteorder = ByteOrder.nativeOrder();
 159         switch (fxPixelsNativeFormat) {
 160             case Pixels.Format.BYTE_BGRA_PRE:
 161                 if (byteorder == ByteOrder.LITTLE_ENDIAN) {
 162                     return new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB_PRE);
 163                 } else {
 164                     throw new UnsupportedOperationException("BYTE_BGRA_PRE pixel format on BIG_ENDIAN");
 165                 }
 166             case Pixels.Format.BYTE_ARGB:
 167                 if (byteorder == ByteOrder.BIG_ENDIAN) {
 168                     return new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
 169                 } else {
 170                     throw new UnsupportedOperationException("BYTE_ARGB pixel format on LITTLE_ENDIAN");
 171                 }
 172             default:
 173                 throw new UnsupportedOperationException("unrecognized pixel format: " + fxPixelsNativeFormat);
 174         }
 175     }
 176     
 177     /**
 178      * Determine the optimal BufferedImage type to use for the specified
 179      * {@code fxFormat} allowing for the specified {@code bimg} to be used
 180      * as a potential default storage space if it is not null and is compatible.
 181      * 
 182      * @param fxFormat the PixelFormat of the source FX Image
 183      * @param bimg an optional existing {@code BufferedImage} to be used
 184      *             for storage if it is compatible, or null
 185      * @return 
 186      */
 187     private static int
 188         getBestBufferedImageType(PixelFormat<?> fxFormat, BufferedImage bimg)
 189     {
 190         if (bimg != null) {
 191             int bimgType = bimg.getType();
 192             if (bimgType == BufferedImage.TYPE_INT_ARGB ||
 193                 bimgType == BufferedImage.TYPE_INT_ARGB_PRE)
 194             {
 195                 // We will allow the caller to give us a BufferedImage
 196                 // that has an alpha channel, but we might not otherwise
 197                 // construct one ourselves.
 198                 // We will also allow them to choose their own premultiply
 199                 // type which may not match the image.
 200                 // If left to our own devices we might choose a more specific
 201                 // format as indicated by the choices below.
 202                 return bimgType;
 203             }
 204         }
 205         switch (fxFormat.getType()) {
 206             default:
 207             case BYTE_BGRA_PRE:
 208             case INT_ARGB_PRE:
 209                 return BufferedImage.TYPE_INT_ARGB_PRE;
 210             case BYTE_BGRA:
 211             case INT_ARGB:
 212                 return BufferedImage.TYPE_INT_ARGB;
 213             case BYTE_RGB:
 214                 return BufferedImage.TYPE_INT_RGB;
 215             case BYTE_INDEXED:
 216                 return (fxFormat.isPremultiplied()
 217                         ? BufferedImage.TYPE_INT_ARGB_PRE
 218                         : BufferedImage.TYPE_INT_ARGB);
 219         }
 220     }
 221 
 222     /**
 223      * Determine the appropriate {@link WritablePixelFormat} type that can
 224      * be used to transfer data into the indicated BufferedImage.
 225      * 
 226      * @param bimg the BufferedImage that will be used as a destination for
 227      *             a {@code PixelReader<IntBuffer>#getPixels()} operation.
 228      * @return 
 229      */
 230     private static WritablePixelFormat<IntBuffer>
 231         getAssociatedPixelFormat(BufferedImage bimg)
 232     {
 233         switch (bimg.getType()) {
 234             // We lie here for xRGB, but we vetted that the src data was opaque
 235             // so we can ignore the alpha.  We use ArgbPre instead of Argb
 236             // just to get a loop that does not have divides in it if the
 237             // PixelReader happens to not know the data is opaque.
 238             case BufferedImage.TYPE_INT_RGB:
 239             case BufferedImage.TYPE_INT_ARGB_PRE:
 240                 return PixelFormat.getIntArgbPreInstance();
 241             case BufferedImage.TYPE_INT_ARGB:
 242                 return PixelFormat.getIntArgbInstance();
 243             default:
 244                 // Should not happen...
 245                 throw new InternalError("Failed to validate BufferedImage type");
 246         }
 247     }
 248 
 249     /**
 250      * Snapshots the specified JavaFX {@link Image} object and stores a
 251      * copy of its pixels into a {@link BufferedImage} object, creating
 252      * a new object if needed.
 253      * The method will only convert a JavaFX {@code Image} that is readable
 254      * as per the conditions on the
 255      * {@link Image#getPixelReader() Image.getPixelReader()}
 256      * method.
 257      * If the {@code Image} is not readable, as determined by its
 258      * {@code getPixelReader()} method, then this method will return null.
 259      * If the {@code Image} is a writable, or other dynamic image, then
 260      * the {@code BufferedImage} will only be set to the current state of
 261      * the pixels in the image as determined by its {@link PixelReader}.
 262      * Further changes to the pixels of the {@code Image} will not be
 263      * reflected in the returned {@code BufferedImage}.
 264      * <p>
 265      * The optional {@code BufferedImage} parameter may be reused to store
 266      * the copy of the pixels.
 267      * A new {@code BufferedImage} will be created if the supplied object
 268      * is null, is too small or of a type which the image pixels cannot
 269      * be easily converted into.
 270      * 
 271      * @param img the JavaFX {@code Image} to be converted
 272      * @param bimg an optional {@code BufferedImage} object that may be
 273      *        used to store the returned pixel data
 274      * @return a {@code BufferedImage} containing a snapshot of the JavaFX
 275      *         {@code Image}, or null if the {@code Image} is not readable.
 276      * @since JavaFX 2.2
 277      */
 278     public static BufferedImage fromFXImage(Image img, BufferedImage bimg) {
 279         PixelReader pr = img.getPixelReader();
 280         if (pr == null) {
 281             return null;
 282         }
 283         int iw = (int) img.getWidth();
 284         int ih = (int) img.getHeight();
 285         int prefBimgType = getBestBufferedImageType(pr.getPixelFormat(), bimg);
 286         if (bimg != null) {
 287             int bw = bimg.getWidth();
 288             int bh = bimg.getHeight();
 289             if (bw < iw || bh < ih || bimg.getType() != prefBimgType) {
 290                 bimg = null;
 291             } else if (iw < bw || ih < bh) {
 292                 Graphics2D g2d = bimg.createGraphics();
 293                 g2d.setComposite(AlphaComposite.Clear);
 294                 g2d.fillRect(0, 0, bw, bh);
 295                 g2d.dispose();
 296             }
 297         }
 298         if (bimg == null) {
 299             bimg = new BufferedImage(iw, ih, prefBimgType);
 300         }
 301         IntegerComponentRaster icr = (IntegerComponentRaster) bimg.getRaster();
 302         int offset = icr.getDataOffset(0);
 303         int scan = icr.getScanlineStride();
 304         int data[] = icr.getDataStorage();
 305         WritablePixelFormat<IntBuffer> pf = getAssociatedPixelFormat(bimg);
 306         pr.getPixels(0, 0, iw, ih, pf, data, offset, scan);
 307         return bimg;
 308     }
 309 
 310     /**
 311      * If called from the FX Application Thread
 312      * invokes a runnable directly blocking the calling code
 313      * Otherwise
 314      * uses Platform.runLater without blocking
 315      */
 316     static void runOnFxThread(Runnable runnable) {
 317         if (Platform.isFxApplicationThread()) {
 318             runnable.run();
 319         } else {
 320             Platform.runLater(runnable);
 321         }
 322     }
 323 
 324     /**
 325      * If called from the event dispatch thread
 326      * invokes a runnable directly blocking the calling code
 327      * Otherwise
 328      * uses SwingUtilities.invokeLater without blocking
 329      */
 330     static void runOnEDT(final Runnable r) {
 331         if (SwingUtilities.isEventDispatchThread()) {
 332             r.run();
 333         } else {
 334             SwingUtilities.invokeLater(r);
 335         }
 336     }
 337 
 338     private static final Set<Object> eventLoopKeys = new HashSet<>();
 339 
 340     /**
 341      * The runnable is responsible for leaving the nested event loop.
 342      */
 343     static void runOnEDTAndWait(Object nestedLoopKey, Runnable r) {
 344         Toolkit.getToolkit().checkFxUserThread();
 345 
 346         if (SwingUtilities.isEventDispatchThread()) {
 347             r.run();
 348         } else {
 349             eventLoopKeys.add(nestedLoopKey);
 350             SwingUtilities.invokeLater(r);
 351             Toolkit.getToolkit().enterNestedEventLoop(nestedLoopKey);
 352         }
 353     }
 354 
 355     static void leaveFXNestedLoop(Object nestedLoopKey) {
 356         if (!eventLoopKeys.contains(nestedLoopKey)) return;
 357 
 358         if (Platform.isFxApplicationThread()) {
 359             Toolkit.getToolkit().exitNestedEventLoop(nestedLoopKey, null);
 360         } else {
 361             Platform.runLater(() -> {
 362                 Toolkit.getToolkit().exitNestedEventLoop(nestedLoopKey, null);
 363             });
 364         }
 365 
 366         eventLoopKeys.remove(nestedLoopKey);
 367     }
 368 
 369     private static class FwSecondaryLoop implements SecondaryLoop {
 370 
 371         private final AtomicBoolean isRunning = new AtomicBoolean(false);
 372 
 373         @Override public boolean enter() {
 374             if (isRunning.compareAndSet(false, true)) {
 375                 PlatformImpl.runAndWait(() -> {
 376                     Toolkit.getToolkit().enterNestedEventLoop(FwSecondaryLoop.this);
 377                 });
 378                 return true;
 379             }
 380             return false;
 381         }
 382 
 383         @Override public boolean exit() {
 384             if (isRunning.compareAndSet(true, false)) {
 385                 PlatformImpl.runAndWait(() -> {
 386                     Toolkit.getToolkit().exitNestedEventLoop(FwSecondaryLoop.this, null);
 387                 });
 388                 return true;
 389             }
 390             return false;
 391         }
 392     }
 393 
 394     private static class FXDispatcher implements FwDispatcher {
 395         @Override public boolean isDispatchThread() {
 396             return Platform.isFxApplicationThread();
 397         }
 398 
 399         @Override public void scheduleDispatch(Runnable runnable) {
 400             Platform.runLater(runnable);
 401         }
 402 
 403         @Override public SecondaryLoop createSecondaryLoop() {
 404             return new FwSecondaryLoop();
 405         }
 406     }
 407 
 408     private static EventQueue getEventQueue() {
 409         return AccessController.doPrivileged(
 410                 (PrivilegedAction<EventQueue>) () -> java.awt.Toolkit.getDefaultToolkit().getSystemEventQueue());
 411     }
 412 
 413     //Called with reflection from PlatformImpl to avoid dependency
 414     private static void installFwEventQueue() {
 415         AWTAccessor.getEventQueueAccessor().setFwDispatcher(getEventQueue(), new FXDispatcher());
 416     }
 417 
 418     //Called with reflection from PlatformImpl to avoid dependency
 419     private static void removeFwEventQueue() {
 420         AWTAccessor.getEventQueueAccessor().setFwDispatcher(getEventQueue(), null);
 421     }
 422 }