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