Static image rendering for Swing and JavaFX components#123
Conversation
Add renderers that turn GUI components into still images via display(): - java.awt.Component (Swing/AWT): painted off-screen into a BufferedImage and encoded by the existing Image pipeline. Headless-native. - javafx.scene.Node (JavaFX): snapshotted on the JavaFX Application Thread and converted to a BufferedImage. Done reflectively so jjava-jupyter keeps no compile/runtime dependency on JavaFX (it is optional and user-supplied, e.g. via %maven). Producing a snapshot needs a display; on a headless host run the kernel under Xvfb, otherwise it degrades to a short message. To match a JavaFX node regardless of which class loader loaded it, Renderer gains name-based registration (createRegistration(String) / registerByName), matched against the value's class hierarchy by name.
|
Another question is JavaFX requires JVM Options to run in headless mode. It a bit of a pain to add the Although JavaFx does give smoother interaction then swing. |
|
|
Demonstrates display(component) / display(node) -> static PNG for Swing/AWT and JavaFX (the latter optional via %maven, with an FX-thread build helper).
a547bd1 to
6ef98ba
Compare
|
That means it will not work out of the box. Might want to it to the docs or outputting a warning if someone wants to use javafx if those options are not enabled? |
|
Disclaimer: I need more time to get a better feel of how the whole things works to comment intelligently. But maybe we make the whole thing an optional kernel extension that is loaded explicitly (by adding it as a dependency), and this is where it can complain about missing options. |
Summary
The portable, JupyterLab-independent first slice of #122: render GUI components as static images via
display(...), for both Swing/AWT and JavaFX. No frontend, no comms, no extension — just images(viewable anywhere a PNG is: GitHub, nbviewer, etc.).
What's included
java.awt.Component→ PNG. Painted off-screen into aBufferedImageand encoded by the existingImagepipeline. Headless-native (no native window is created).javafx.scene.Node→ PNG. Snapshotted on the JavaFX Application Thread(
Node.snapshot(...)→SwingFXUtils.fromFXImage→BufferedImage). Done reflectively, sojjava-jupyter keeps no compile- or runtime dependency on JavaFX — it's optional and user-supplied
(e.g.
%maven org.openjfx:javafx-controls:...). If the toolkit can't start, it degrades to a short textmessage instead of failing the cell.
Renderer.createRegistration(String)/registerByName). Auser-supplied JavaFX node is loaded by the JShell class loader, not the kernel's, so
Class-identityregistration can't match it; matching by fully-qualified class name (against the value's class hierarchy)
is class-loader agnostic. This is a small, generic addition useful for any optional runtime type.
So
display(component)/display(node)(and a bare last-expression value) now produce a still image.Headless note
JavaFX
snapshot()needs the JavaFX toolkit, which needs a display. On a headless server/CI, run the kernelunder Xvfb (
xvfb-run). (Monocle is not a viable route on JavaFX 21 — OpenJFX 21 doesn't shipMonoclePlatformFactory.) The Swing renderer needs none of this.Tests
Headless unit tests:
SwingTest(Component → PNG of expected size),RendererTest(name-based dispatch),JavaFxTest(graceful fallback when JavaFX is absent). Fulljjava-jupytermodule green.Relation to #122
This is the portable core split out of #122 per the suggestion to digest it one piece at a time. The
interactive parts (comm-streamed frames + mouse/keyboard, and the JupyterLab extension) stay in #122 as a
draft for a later, separate PR.