I'm using JavaFX [21] and want to precisely capture a snapshot of my entire app window.
So, from the primary stage I get the scene and from the scene get the window:
Scene scene = primaryStage.getScene();
Window appWindow = scene.getWindow();
As far as I'm aware there is no JavaFX method for taking a snapshot of a Window in a similar way to the Scene.snapshot() method. And so I then resort to using the Robot.getScreenCapture() method:
Scene scene = primaryStage.getScene();
Window appWindow = scene.getWindow();
double screenX = appWindow.getX();
double screenY = appWindow.getY();
double appW = appWindow.getWidth();
double appH = appWindow.getHeight();
Rectangle2D rectangle = new Rectangle2D(screenX, screenY, appW, appH);
Robot robot = new Robot();
WritableImage image = robot.getScreenCapture(null, rectangle, true);
All makes sense apart from the resulting image being slightly wider and higher than my app window. Also, it is clear that the screen (x,y) do not coincide exactly with the top left corner of the app.
It's as though the dimensions of an unknown border/inset are being added but I see no mention of this in the JavaDoc.
It's a simple request to obtain a snapshot of my entire app and nothing else, but I can't seem to solve this request using JavaFX. Has anyone else encountered this issue and found a solution?
Thanks.
I'm using JavaFX [21] and want to precisely capture a snapshot of my entire app window.
So, from the primary stage I get the scene and from the scene get the window:
Scene scene = primaryStage.getScene();
Window appWindow = scene.getWindow();
As far as I'm aware there is no JavaFX method for taking a snapshot of a Window in a similar way to the Scene.snapshot() method. And so I then resort to using the Robot.getScreenCapture() method:
Scene scene = primaryStage.getScene();
Window appWindow = scene.getWindow();
double screenX = appWindow.getX();
double screenY = appWindow.getY();
double appW = appWindow.getWidth();
double appH = appWindow.getHeight();
Rectangle2D rectangle = new Rectangle2D(screenX, screenY, appW, appH);
Robot robot = new Robot();
WritableImage image = robot.getScreenCapture(null, rectangle, true);
All makes sense apart from the resulting image being slightly wider and higher than my app window. Also, it is clear that the screen (x,y) do not coincide exactly with the top left corner of the app.
It's as though the dimensions of an unknown border/inset are being added but I see no mention of this in the JavaDoc.
It's a simple request to obtain a snapshot of my entire app and nothing else, but I can't seem to solve this request using JavaFX. Has anyone else encountered this issue and found a solution?
Thanks.
Share Improve this question asked Feb 14 at 14:13 Graham SeedGraham Seed 8843 gold badges12 silver badges27 bronze badges 3 |1 Answer
Reset to default 3The default decorations of a window come from the operating system, so the Javadoc will not give precise details. However, the documentation of properties like Window.width
do say:
This value includes any and all decorations which may be added by the Operating System such as resizable frame handles.
JavaFX does not, at least as of version 23, provide an API to query the size of these decorations or to get the bounds of the "true" window. And unfortunately, there's no snapshot
method for Window
or its subtypes like there is for Scene
and Node
.
You'll have to find a way to get the bounds of the window you want. That will likely involve relying on native libraries. Which means you'll need a separate solution for each platform you're having a problem with.
Example - Windows 11
Here's an example that works on Windows 11 with Java 23 and JavaFX 23. It may work on Windows 10 as well, but I'm not sure. Also not sure if it will work with other versions of Java or JavaFX.
This example uses the Foreign Function & Memory API, which means it requires Java 22+. Other options include using JNI or JNA.
The native functions used are GetForegroundWindow (to get the window handle) and DwmGetWindowAttribute (to get the window's bounds without the drop shadow).
Output
The screenshot of the primary Stage
is being displayed in a Dialog
. Unfortunately, the corners of the screenshot are square despite the corners of the window being round. Though note this is the same behavior seen by Window's Snipping Tool which is what I used to take the screenshot of the Dialog
. The other unfortunate thing is that the screenshot taken by JavaFX's Robot
is of noticeably lower quality than the screenshot taken by Window's Snipping Tool. Not sure if there's a way to fix that.
For contrast, here's what it looks like when getting the window's bounds from the x
, y
, width
, and height
properties of the Window
.
Source Code
Native.java
package com.example.
import static java.lang.foreign.ValueLayout.ADDRESS;
import static java.lang.foreign.ValueLayout.JAVA_INT;
import static java.lang.foreign.ValueLayout.JAVA_LONG;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.Linker;
import java.lang.foreign.MemoryLayout;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.SymbolLookup;
import java.lang.invoke.MethodHandle;
// Note: not a great class name
public final class Native {
public static long GetForegroundWindow() {
try {
return (long) GetForegroundWindow.invokeExact();
} catch (Throwable t) {
throw new RuntimeException(t);
}
}
public static int DwmGetWindowAttribute(
long hwnd, WindowAttribute dwAttribute, MemorySegment pvAttribute, MemoryLayout cbAttribute) {
try {
return (int) DwmGetWindowAttribute.invokeExact(
hwnd, dwAttribute.flag(), pvAttribute, Math.toIntExact(cbAttribute.byteSize()));
} catch (Throwable t) {
throw new RuntimeException(t);
}
}
private Native() {}
/* **************************************************************************
* *
* FFM Handling *
* *
****************************************************************************/
private static final MethodHandle GetForegroundWindow;
private static final MethodHandle DwmGetWindowAttribute;
static {
System.loadLibrary("user32");
System.loadLibrary("dwmapi");
var symbols = SymbolLookup.loaderLookup();
var linker = Linker.nativeLinker();
GetForegroundWindow = linker.downcallHandle(
symbols.findOrThrow("GetForegroundWindow"), FunctionDescriptor.of(JAVA_LONG));
DwmGetWindowAttribute = linker.downcallHandle(
symbols.findOrThrow("DwmGetWindowAttribute"),
FunctionDescriptor.of(JAVA_INT, JAVA_LONG, JAVA_INT, ADDRESS, JAVA_INT));
}
}
Rect.java
package com.example;
import static java.lang.foreign.MemoryLayout.PathElement.groupElement;
import static java.lang.foreign.MemoryLayout.structLayout;
import static java.lang.foreign.ValueLayout.JAVA_INT;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.SegmentAllocator;
import java.lang.foreign.StructLayout;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
public final class Rect {
private final MemorySegment segment;
public Rect(SegmentAllocator allocator) {
this.segment = allocator.allocate(LAYOUT).asReadOnly();
}
public MemorySegment segment() {
return segment;
}
public int left() {
return (int) LEFT.get(segment);
}
public int top() {
return (int) TOP.get(segment);
}
public int right() {
return (int) RIGHT.get(segment);
}
public int bottom() {
return (int) BOTTOM.get(segment);
}
public int width() {
return right() - left();
}
public int height() {
return bottom() - top();
}
/* **************************************************************************
* *
* FFM Handling *
* *
****************************************************************************/
public static final StructLayout LAYOUT = structLayout(
JAVA_INT.withName("left"),
JAVA_INT.withName("top"),
JAVA_INT.withName("right"),
JAVA_INT.withName("bottom"))
.withName("RECT");
private static final VarHandle LEFT = handle("left");
private static final VarHandle TOP = handle("top");
private static final VarHandle RIGHT = handle("right");
private static final VarHandle BOTTOM = handle("bottom");
private static VarHandle handle(String groupElementName) {
var handle = LAYOUT.varHandle(groupElement(groupElementName));
return MethodHandles.insertCoordinates(handle, 1, 0L);
}
}
WindowAttribute.java
package com.example;
public enum WindowAttribute {
EXTENDED_FRAME_BOUNDS(9);
private final int flag;
WindowAttribute(int flag) {
this.flag = flag;
}
public int flag() {
return flag;
}
}
Main.java
package com.example;
import static com.example.WindowAttribute.EXTENDED_FRAME_BOUNDS;
import java.lang.foreign.Arena;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Rectangle2D;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Dialog;
import javafx.scene.image.ImageView;
import javafx.scene.layout.StackPane;
import javafx.scene.robot.Robot;
import javafx.stage.Stage;
import javafx.stage.Window;
public class Main extends Application {
public static void main(String[] args) {
launch(Main.class);
}
@Override
public void start(Stage primaryStage) {
var button = new Button("Take screenshot");
button.setOnAction(e -> {
e.consume();
takeAndShowScreenshot(primaryStage);
});
var root = new StackPane(button);
primaryStage.setScene(new Scene(root, 500, 300));
primaryStage.setTitle("Window Screenshot Demo");
primaryStage.show();
}
private void takeAndShowScreenshot(Window window) {
var area = getWindowBoundsForScreenshot(window);
var image = new Robot().getScreenCapture(null, area);
var content = new StackPane(new ImageView(image));
content.setPadding(new Insets(10));
var dialog = new Dialog<Void>();
dialog.initOwner(window);
dialog.setTitle("Screenshot");
dialog.getDialogPane().getButtonTypes().add(ButtonType.CLOSE);
dialog.getDialogPane().setContent(content);
dialog.setResizable(true);
dialog.show();
}
private Rectangle2D getWindowBoundsForScreenshot(Window window) {
if (!window.isFocused()) throw new IllegalStateException();
long hwnd = Native.GetForegroundWindow();
try (var arena = Arena.ofConfined()) {
var rect = new Rect(arena);
int hresult =
Native.DwmGetWindowAttribute(hwnd, EXTENDED_FRAME_BOUNDS, rect.segment(), Rect.LAYOUT);
if (hresult < 0) {
throw new RuntimeException("DwmGetWindowAttribute returned error code: " + hresult);
}
// The RECT returned by DWM is already scaled, but JavaFX's API works
// on the unscaled values.
double x = rect.left() / window.getOutputScaleX();
double y = rect.top() / window.getOutputScaleY();
double w = rect.width() / window.getOutputScaleX();
double h = rect.height() / window.getOutputScaleY();
return new Rectangle2D(x, y, w, h);
}
}
}
Window.width
do say, "This value includes any and all decorations which may be added by the Operating System such as resizable frame handles". – Slaw Commented Feb 14 at 20:32