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 }