<!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>