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