|
1 | 1 | /******************************************************************************* |
2 | | - * Copyright (c) 2015-2017 Oak Ridge National Laboratory. |
| 2 | + * Copyright (c) 2015-2025 Oak Ridge National Laboratory. |
3 | 3 | * All rights reserved. This program and the accompanying materials |
4 | 4 | * are made available under the terms of the Eclipse Public License v1.0 |
5 | 5 | * which accompanies this distribution, and is available at |
6 | 6 | * http://www.eclipse.org/legal/epl-v10.html |
7 | 7 | *******************************************************************************/ |
8 | 8 | package org.phoebus.ui.javafx; |
9 | 9 |
|
10 | | -import java.awt.image.BufferedImage; |
11 | | -import java.io.File; |
12 | | - |
13 | | -import javax.imageio.ImageIO; |
14 | | - |
15 | 10 | import javafx.embed.swing.SwingFXUtils; |
16 | 11 | import javafx.scene.Node; |
17 | 12 | import javafx.scene.Scene; |
18 | 13 | import javafx.scene.image.Image; |
19 | 14 | import javafx.scene.image.WritableImage; |
20 | 15 | import javafx.scene.input.Clipboard; |
21 | 16 |
|
22 | | -/** Create screenshot of a JavaFX scene |
23 | | - * @author Kay Kasemir |
| 17 | +import javax.imageio.ImageIO; |
| 18 | +import java.awt.*; |
| 19 | +import java.awt.datatransfer.DataFlavor; |
| 20 | +import java.awt.datatransfer.Transferable; |
| 21 | +import java.awt.datatransfer.UnsupportedFlavorException; |
| 22 | +import java.awt.image.BufferedImage; |
| 23 | +import java.io.File; |
| 24 | + |
| 25 | +/** |
| 26 | + * Create screenshot of a JavaFX scene |
| 27 | + * |
| 28 | + * @author Kay Kasemir |
24 | 29 | */ |
25 | 30 | @SuppressWarnings("nls") |
26 | | -public class Screenshot |
27 | | -{ |
| 31 | +public class Screenshot { |
28 | 32 | private final BufferedImage image; |
29 | 33 |
|
30 | | - /** Initialize screenshot |
| 34 | + /** |
| 35 | + * Initialize screenshot |
| 36 | + * |
| 37 | + * <p>Must be called on UI thread |
31 | 38 | * |
32 | | - * <p>Must be called on UI thread |
33 | | - * @param scene Scene to capture |
| 39 | + * @param scene Scene to capture |
34 | 40 | */ |
35 | | - public Screenshot(final Scene scene) |
36 | | - { |
| 41 | + public Screenshot(final Scene scene) { |
37 | 42 | image = bufferFromNode(scene.getRoot()); |
38 | 43 | } |
39 | 44 |
|
40 | | - public Screenshot(final Node node) |
41 | | - { |
| 45 | + public Screenshot(final Node node) { |
42 | 46 | image = bufferFromNode(node); |
43 | 47 | } |
44 | 48 |
|
45 | | - public Screenshot(final Image image) |
46 | | - { |
| 49 | + public Screenshot(final Image image) { |
47 | 50 | this.image = bufferFromImage(image); |
48 | 51 | } |
49 | 52 |
|
50 | | - /** Get a JavaFX Node Snapshot as a JavaFX Image |
| 53 | + /** |
| 54 | + * Get a JavaFX Node Snapshot as a JavaFX Image |
| 55 | + * |
51 | 56 | * @param node |
52 | 57 | * @return Image |
53 | 58 | */ |
54 | | - public static WritableImage imageFromNode(Node node) |
55 | | - { |
| 59 | + public static WritableImage imageFromNode(Node node) { |
56 | 60 | return node.snapshot(null, null); |
57 | 61 | } |
58 | 62 |
|
59 | | - /** Get a AWT BufferedImage from JavaFX Image |
60 | | - * @param jfx {@link Image} |
61 | | - * @return BufferedImage |
| 63 | + /** |
| 64 | + * Get a AWT BufferedImage from JavaFX Image |
| 65 | + * |
| 66 | + * @param jfx {@link Image} |
| 67 | + * @return BufferedImage |
62 | 68 | */ |
63 | | - public static BufferedImage bufferFromImage(final Image jfx) |
64 | | - { |
65 | | - final BufferedImage img = new BufferedImage((int)jfx.getWidth(), |
66 | | - (int)jfx.getHeight(), |
| 69 | + public static BufferedImage bufferFromImage(final Image jfx) { |
| 70 | + final BufferedImage img = new BufferedImage((int) jfx.getWidth(), |
| 71 | + (int) jfx.getHeight(), |
67 | 72 | BufferedImage.TYPE_INT_ARGB); |
68 | 73 | SwingFXUtils.fromFXImage(jfx, img); |
69 | | - |
70 | 74 | return img; |
71 | 75 | } |
72 | 76 |
|
73 | | - /** Get a JavaFX Node Snapshot as an AWT BufferedImage |
| 77 | + /** |
| 78 | + * Get a JavaFX Node Snapshot as an AWT BufferedImage |
| 79 | + * |
74 | 80 | * @param node |
75 | 81 | * @return BufferedImage |
76 | 82 | */ |
77 | | - public static BufferedImage bufferFromNode(Node node) |
78 | | - { |
| 83 | + public static BufferedImage bufferFromNode(Node node) { |
79 | 84 | return bufferFromImage(imageFromNode(node)); |
80 | 85 | } |
81 | 86 |
|
@@ -104,47 +109,90 @@ public static Image captureScreen() |
104 | 109 | /** |
105 | 110 | * Get an image from the clip board. |
106 | 111 | * <p> Returns null if no image is on the clip board. |
| 112 | + * |
107 | 113 | * @return Image |
108 | 114 | */ |
109 | | - public static Image getImageFromClipboard() |
110 | | - { |
| 115 | + public static Image getImageFromClipboard() { |
111 | 116 | Clipboard clipboard = Clipboard.getSystemClipboard(); |
112 | 117 | return clipboard.getImage(); |
113 | 118 | } |
114 | 119 |
|
115 | | - /** Write to file |
116 | | - * @param file Output file |
117 | | - * @throws Exception on error |
| 120 | + /** |
| 121 | + * Write to file |
| 122 | + * |
| 123 | + * @param file Output file |
| 124 | + * @throws Exception on error |
118 | 125 | */ |
119 | | - public void writeToFile(final File file) throws Exception |
120 | | - { |
121 | | - try |
122 | | - { |
| 126 | + public void writeToFile(final File file) throws Exception { |
| 127 | + try { |
123 | 128 | ImageIO.write(image, "png", file); |
124 | | - } |
125 | | - catch (Exception ex) |
126 | | - { |
| 129 | + } catch (Exception ex) { |
127 | 130 | throw new Exception("Cannot create screenshot " + file.getAbsolutePath(), ex); |
128 | 131 | } |
129 | 132 | } |
130 | 133 |
|
131 | | - /** Write to temp. file |
132 | | - * @param file_prefix File prefix |
133 | | - * @return File that was created |
134 | | - * @throws Exception on error |
| 134 | + /** |
| 135 | + * Write to temp. file |
| 136 | + * |
| 137 | + * @param file_prefix File prefix |
| 138 | + * @return File that was created |
| 139 | + * @throws Exception on error |
135 | 140 | */ |
136 | | - public File writeToTempfile(final String file_prefix) throws Exception |
137 | | - { |
138 | | - try |
139 | | - { |
| 141 | + public File writeToTempfile(final String file_prefix) throws Exception { |
| 142 | + try { |
140 | 143 | final File file = File.createTempFile(file_prefix, ".png"); |
141 | 144 | file.deleteOnExit(); |
142 | 145 | writeToFile(file); |
143 | 146 | return file; |
144 | | - } |
145 | | - catch (Exception ex) |
146 | | - { |
| 147 | + } catch (Exception ex) { |
147 | 148 | throw new Exception("Cannot create tmp. file:\n" + ex.getMessage()); |
148 | 149 | } |
149 | 150 | } |
| 151 | + |
| 152 | + /** |
| 153 | + * Puts the {@link Node} as image data onto the clipboard. |
| 154 | + * |
| 155 | + * <p> |
| 156 | + * <b>NOTE:</b> on Windows calling this will throw an {@link java.io.IOException}, but screenshot will still be available on |
| 157 | + * the clipboard. <a href='https://stackoverflow.com/questions/59140881/error-copying-an-image-object-to-the-clipboard'>This Stackoverflow post</a> |
| 158 | + * suggests the printed stack trace is in fact debug information. |
| 159 | + * </p> |
| 160 | + * |
| 161 | + * @param node Node from which to take a screenshot. |
| 162 | + */ |
| 163 | + public static void copyToClipboard(Node node) { |
| 164 | + BufferedImage bufferedImage = bufferFromNode(node); |
| 165 | + Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new TransferableImage(bufferedImage), null); |
| 166 | + } |
| 167 | + |
| 168 | + /** |
| 169 | + * Minimal implementation to support putting image data on the clipboard |
| 170 | + */ |
| 171 | + private static class TransferableImage implements Transferable { |
| 172 | + |
| 173 | + private final java.awt.Image image; |
| 174 | + |
| 175 | + public TransferableImage(java.awt.Image image) { |
| 176 | + this.image = image; |
| 177 | + } |
| 178 | + |
| 179 | + @Override |
| 180 | + public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException { |
| 181 | + if (flavor.equals(DataFlavor.imageFlavor) && image != null) { |
| 182 | + return image; |
| 183 | + } else { |
| 184 | + throw new UnsupportedFlavorException(flavor); |
| 185 | + } |
| 186 | + } |
| 187 | + |
| 188 | + @Override |
| 189 | + public DataFlavor[] getTransferDataFlavors() { |
| 190 | + return new DataFlavor[]{DataFlavor.imageFlavor}; |
| 191 | + } |
| 192 | + |
| 193 | + @Override |
| 194 | + public boolean isDataFlavorSupported(DataFlavor flavor) { |
| 195 | + return flavor.equals(DataFlavor.imageFlavor); |
| 196 | + } |
| 197 | + } |
150 | 198 | } |
0 commit comments