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