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