<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
</head>
<body>
<p>Hi list,</p>
<p>I'm exploring whether there is interest in exposing a (Canvas)
GraphicsContext-like interface for WritableImage backed by the
existing software rendering pipeline. Currently, JavaFX offers
two main choices for drawing and pixel manipulation, each with
different trade-offs:</p>
<p>- Canvas provides rich drawing primitives via GraphicsContext,
but offers no direct pixel access—requiring costly GPU readbacks
(e.g., snapshot()).<br>
- WritableImage, on the other hand, allows direct pixel
manipulation via PixelReader/PixelWriter, but has no built-in
support for drawing operations like shapes, fills, or blending.</p>
<p>My proposal would combine the strengths of both:<br>
</p>
<p>- Expose drawing operations (shapes, fills, etc.) for
WritableImage <br>
- Direct access to image data before or after rendering without
GPU readbacks / snapshots<br>
- Reuse of JavaFX’s software rendering stack (SWGraphics,
PiscesRenderer, etc.) without activating the software pipeline
globally<br>
</p>
<p>I’ve successfully tested this approach in a non-modular FX
application by accessing internal APIs from com.sun.prism.sw. With
minor adjustments (--add-exports), it may also work in modular
environments.<br>
<br>
Work needed to support this as a public API might include:</p>
<p>- Creating a new GraphicsContext-like interface ("DrawingContext"
?)<br>
- Exposing a method on WritableImage to obtain such a context<br>
- Optionally, refactoring Canvas' GraphicsContext to implement
this new interface (method signatures are likely compatible)<br>
- Implementing the new interface on top of the software renderer</p>
<p>See the end of the post for a working example (assuming you place
the code in the "com.sun.prism.sw" package and deal with the
module restrictions). Note that you do not need to enable the
software pipeline (and you don't want to either, as the whole
point is to remain GPU accelerated but have software renderer
backed drawing primitives for images).<br>
</p>
<p>Any feedback appreciated!</p>
<p>--John<br>
<br>
</p>
<div style="background-color:#ffffff;padding:0px 0px 0px 2px;">
<div
style="color:#000000;background-color:#ffffff;font-family:"Consolas";font-size:11pt;white-space:pre;"><p
style="margin:0;"><span style="color:#0000a0;font-weight:bold;">package</span><span
style="color:#000000;"> com.sun.prism.sw;</span></p><p
style="margin:0;">
</p><p style="margin:0;"><span style="color:#0000a0;font-weight:bold;">import</span><span
style="color:#000000;"> com.sun.glass.ui.Screen;</span></p><p
style="margin:0;"><span style="color:#0000a0;font-weight:bold;">import</span><span
style="color:#000000;"> com.sun.glass.utils.NativeLibLoader;</span></p><p
style="margin:0;"><span style="color:#0000a0;font-weight:bold;">import</span><span
style="color:#000000;"> com.sun.pisces.JavaSurface;</span></p><p
style="margin:0;"><span style="color:#0000a0;font-weight:bold;">import</span><span
style="color:#000000;"> com.sun.pisces.PiscesRenderer;</span></p><p
style="margin:0;"><span style="color:#0000a0;font-weight:bold;">import</span><span
style="color:#000000;"> com.sun.pisces.RendererBase;</span></p><p
style="margin:0;"><span style="color:#0000a0;font-weight:bold;">import</span><span
style="color:#000000;"> com.sun.prism.paint.Color;</span></p><p
style="margin:0;">
</p><p style="margin:0;"><span style="color:#0000a0;font-weight:bold;">import</span><span
style="color:#000000;"> java.nio.IntBuffer;</span></p><p
style="margin:0;">
</p><p style="margin:0;"><span style="color:#0000a0;font-weight:bold;">import</span><span
style="color:#000000;"> javafx.application.Application;</span></p><p
style="margin:0;"><span style="color:#0000a0;font-weight:bold;">import</span><span
style="color:#000000;"> javafx.scene.Scene;</span></p><p
style="margin:0;"><span style="color:#0000a0;font-weight:bold;">import</span><span
style="color:#000000;"> javafx.scene.image.ImageView;</span></p><p
style="margin:0;"><span style="color:#0000a0;font-weight:bold;">import</span><span
style="color:#000000;"> javafx.scene.image.PixelWriter;</span></p><p
style="margin:0;"><span style="color:#0000a0;font-weight:bold;">import</span><span
style="color:#000000;"> javafx.scene.image.WritableImage;</span></p><p
style="margin:0;"><span style="color:#0000a0;font-weight:bold;">import</span><span
style="color:#000000;"> javafx.scene.layout.StackPane;</span></p><p
style="margin:0;"><span style="color:#0000a0;font-weight:bold;">import</span><span
style="color:#000000;"> javafx.stage.Stage;</span></p><p
style="margin:0;">
</p><p style="margin:0;"><span style="color:#0000a0;font-weight:bold;">public</span><span
style="color:#000000;"> </span><span
style="color:#0000a0;font-weight:bold;">class</span><span
style="color:#000000;"> SWRendererExample {</span></p><p
style="margin:0;">
</p><p style="margin:0;"><span style="color:#000000;"> </span><span
style="color:#0000a0;font-weight:bold;">static</span><span
style="color:#000000;"> {</span></p><p style="margin:0;"><span
style="color:#000000;"> NativeLibLoader.</span><span
style="color:#000000;font-style:italic;">loadLibrary</span><span
style="color:#000000;">(</span><span style="color:#2a00ff;">"prism_sw"</span><span
style="color:#000000;">);</span></p><p style="margin:0;"><span
style="color:#000000;"> }</span></p><p style="margin:0;">
</p><p style="margin:0;"><span style="color:#000000;"> </span><span
style="color:#0000a0;font-weight:bold;">public</span><span
style="color:#000000;"> </span><span
style="color:#0000a0;font-weight:bold;">static</span><span
style="color:#000000;"> </span><span
style="color:#0000a0;font-weight:bold;">void</span><span
style="color:#000000;"> main(String[] args) {</span></p><p
style="margin:0;"><span style="color:#000000;"> Application.</span><span
style="color:#000000;font-style:italic;">launch</span><span
style="color:#000000;">(App.</span><span
style="color:#0000a0;font-weight:bold;">class</span><span
style="color:#000000;">);</span></p><p style="margin:0;"><span
style="color:#000000;"> }</span></p><p style="margin:0;">
</p><p style="margin:0;"><span style="color:#000000;"> </span><span
style="color:#0000a0;font-weight:bold;">public</span><span
style="color:#000000;"> </span><span
style="color:#0000a0;font-weight:bold;">static</span><span
style="color:#000000;"> </span><span
style="color:#0000a0;font-weight:bold;">class</span><span
style="color:#000000;"> App </span><span
style="color:#0000a0;font-weight:bold;">extends</span><span
style="color:#000000;"> Application {</span></p><p
style="margin:0;"><span style="color:#000000;"> </span><span
style="color:#646464;">@Override</span></p><p style="margin:0;"><span
style="color:#000000;"> </span><span
style="color:#0000a0;font-weight:bold;">public</span><span
style="color:#000000;"> </span><span
style="color:#0000a0;font-weight:bold;">void</span><span
style="color:#000000;"> start(Stage primaryStage) {</span></p><p
style="margin:0;"><span style="color:#000000;"> WritableImage writableImage = </span><span
style="color:#000000;font-style:italic;">createImageWithSWPipeline</span><span
style="color:#000000;">();</span></p><p style="margin:0;">
</p><p style="margin:0;"><span style="color:#000000;"> Scene scene = </span><span
style="color:#0000a0;font-weight:bold;">new</span><span
style="color:#000000;"> Scene(</span><span
style="color:#0000a0;font-weight:bold;">new</span><span
style="color:#000000;"> StackPane(</span><span
style="color:#0000a0;font-weight:bold;">new</span><span
style="color:#000000;"> ImageView(writableImage)));</span></p><p
style="margin:0;">
</p><p style="margin:0;"><span style="color:#000000;"> primaryStage.setScene(scene);</span></p><p
style="margin:0;"><span style="color:#000000;"> primaryStage.show();</span></p><p
style="margin:0;"><span style="color:#000000;"> }</span></p><p
style="margin:0;"><span style="color:#000000;"> }</span></p><p
style="margin:0;">
</p><p style="margin:0;"><span style="color:#000000;"> </span><span
style="color:#0000a0;font-weight:bold;">public</span><span
style="color:#000000;"> </span><span
style="color:#0000a0;font-weight:bold;">static</span><span
style="color:#000000;"> WritableImage createImageWithSWPipeline() {</span></p><p
style="margin:0;"><span style="color:#000000;"> </span><span
style="color:#0000a0;font-weight:bold;">int</span><span
style="color:#000000;"> width = 400;</span></p><p
style="margin:0;"><span style="color:#000000;"> </span><span
style="color:#0000a0;font-weight:bold;">int</span><span
style="color:#000000;"> height = 300;</span></p><p
style="margin:0;"><span style="color:#000000;"> SWResourceFactory resourceFactory = </span><span
style="color:#0000a0;font-weight:bold;">new</span><span
style="color:#000000;"> SWResourceFactory(Screen.</span><span
style="color:#000000;font-style:italic;">getMainScreen</span><span
style="color:#000000;">());</span></p><p style="margin:0;"><span
style="color:#000000;"> SWRTTexture texture = </span><span
style="color:#0000a0;font-weight:bold;">new</span><span
style="color:#000000;"> SWRTTexture(resourceFactory, width, height);</span></p><p
style="margin:0;"><span style="color:#000000;"> SWContext swContext = </span><span
style="color:#0000a0;font-weight:bold;">new</span><span
style="color:#000000;"> SWContext(resourceFactory);</span></p><p
style="margin:0;">
</p><p style="margin:0;"><span style="color:#000000;"> </span><span
style="color:#3f7f5f;">// Set up a surface to draw on and create the renderer:</span></p><p
style="margin:0;"><span style="color:#000000;"> </span><span
style="color:#0000a0;font-weight:bold;">int</span><span
style="color:#000000;">[] backingArray = </span><span
style="color:#0000a0;font-weight:bold;">new</span><span
style="color:#000000;"> </span><span
style="color:#0000a0;font-weight:bold;">int</span><span
style="color:#000000;">[width * height];</span></p><p
style="margin:0;"><span style="color:#000000;"> IntBuffer pixelBuffer = IntBuffer.</span><span
style="color:#000000;font-style:italic;">wrap</span><span
style="color:#000000;">(backingArray);</span></p><p
style="margin:0;"><span style="color:#000000;"> JavaSurface surface = </span><span
style="color:#0000a0;font-weight:bold;">new</span><span
style="color:#000000;"> JavaSurface(backingArray, RendererBase.</span><span
style="color:#0000c0;">TYPE_INT_ARGB_PRE</span><span
style="color:#000000;">, width, height);</span></p><p
style="margin:0;"><span style="color:#000000;"> PiscesRenderer renderer = </span><span
style="color:#0000a0;font-weight:bold;">new</span><span
style="color:#000000;"> PiscesRenderer(surface);</span></p><p
style="margin:0;">
</p><p style="margin:0;"><span style="color:#000000;"> </span><span
style="color:#3f7f5f;">// Create SWGraphics for drawing (software renderer)</span></p><p
style="margin:0;"><span style="color:#000000;"> SWGraphics swGraphics = </span><span
style="color:#0000a0;font-weight:bold;">new</span><span
style="color:#000000;"> SWGraphics(texture, swContext, renderer);</span></p><p
style="margin:0;">
</p><p style="margin:0;"><span style="color:#000000;"> swGraphics.clear(Color.</span><span
style="color:#0000c0;">WHITE</span><span style="color:#000000;">);</span></p><p
style="margin:0;"><span style="color:#000000;"> swGraphics.setPaint(Color.</span><span
style="color:#0000c0;">BLUE</span><span style="color:#000000;">);</span></p><p
style="margin:0;"><span style="color:#000000;"> swGraphics.fillRect(50, 50, 100, 100);</span></p><p
style="margin:0;"><span style="color:#000000;"> swGraphics.setPaint(Color.</span><span
style="color:#0000c0;">RED</span><span style="color:#000000;">);</span></p><p
style="margin:0;"><span style="color:#000000;"> swGraphics.fillEllipse(75, 75, 10, 20);</span></p><p
style="margin:0;">
</p><p style="margin:0;"><span style="color:#000000;"> </span><span
style="color:#3f7f5f;">// Take the result and place it in a writable image:</span></p><p
style="margin:0;"><span style="color:#000000;"> WritableImage writableImage = </span><span
style="color:#0000a0;font-weight:bold;">new</span><span
style="color:#000000;"> WritableImage(width, height);</span></p><p
style="margin:0;"><span style="color:#000000;"> PixelWriter pw = writableImage.getPixelWriter();</span></p><p
style="margin:0;">
</p><p style="margin:0;"><span style="color:#000000;"> pw.setPixels(0, 0, width, height, javafx.scene.image.PixelFormat.</span><span
style="color:#000000;font-style:italic;">getIntArgbPreInstance</span><span
style="color:#000000;">(), pixelBuffer.array(), 0, width);</span></p><p
style="margin:0;">
</p><p style="margin:0;"><span style="color:#000000;"> </span><span
style="color:#7f0055;font-weight:bold;">return</span><span
style="color:#000000;"> writableImage;</span></p><p
style="margin:0;"><span style="color:#000000;"> }</span></p><p
style="margin:0;"><span style="color:#000000;">}</span></p><p
style="margin:0;">
</p></div>
</div>
</body>
</html>