On Mon, 10 Nov 2025 13:59:30 GMT, John Hendrikx <[email protected]> wrote:

> This PR adds a `getDrawingContext` method to `WritableImage`, which works 
> similar to `Canvas::getGraphicsContext` and shares the same signatures. Key 
> features include:
> 
> - **Shape rendering**: `strokeRect`, `fillRect`, `strokeOval`, `fillOval`, 
> `strokeArc`, `fillArc`, `strokePolyline`, `fillPolygon`, etc.
> - **Stroke and fill attributes**: `lineWidth`, `lineCap`, `lineJoin`, 
> `miterLimit`, `fillRule`, `stroke` and `fill` paints.
> - **Global graphics settings**: `globalAlpha` and `globalBlendMode`.
> - **Image drawing**: draw other `Image` instances with scaling and 
> source/destination rectangles.
> - **Text rendering**: render text
> - **Save/Restore**: store current stroke, font, dashes, etc and restore them 
> later
> - **Paths**: begin a path, with lines, curves, etc, then stroke or fill it
> - **Clips**: support rectangular clips in the SW renderer
> 
> This feature enables direct software rendering to `WritableImage` without 
> requiring a `Canvas` + snapshot.
> 
> **Additional notes**:
> 
> - The implementation leverages the software stack consisting of the Marlin 
> rasterizer and Pisces compositor/painter.
> 
> **Example usage**:
> 
> 
> WritableImage img = new WritableImage(400, 400);
> DrawingContext ctx = img.getDrawingContext();
> ctx.setFill(Color.RED);
> ctx.fillRect(50, 50, 100, 100);
> 
> 
> See the sample program `RandomShapesDemo` to see a `WritableImage` and 
> `Canvas` side by side performing the same operations:
> 
> <img width="1249" height="741" alt="image" 
> src="https://github.com/user-attachments/assets/4a0b9dcc-8f96-4faa-99cf-83c66d2c851e";
>  />
> 
> Newer version:
> 
> <img width="1249" height="741" alt="image" 
> src="https://github.com/user-attachments/assets/c0502620-3d02-4fbd-8c33-43bd5138b42c";
>  />
> 
> ---------
> - [x] I confirm that I make this contribution in accordance with the [OpenJDK 
> Interim AI Policy](https://openjdk.org/legal/ai).

quite a few things.  I would suggest adding unit tests for all the discovered 
problematic scenarios, if possible.

modules/javafx.graphics/src/main/java/com/sun/prism/sw/SWDrawingContext.java 
line 201:

> 199:     public SWDrawingContext(com.sun.prism.Image img, Consumer<Rectangle> 
> pixelsDirty) {
> 200:         int[] data = switch (img.getPixelBuffer()) {
> 201:             case IntBuffer ib -> ib.array();

is it possible to get a sliced array at this point?
IntBuffer::array() ignores the arrayOffset so the subsequent operations might 
overwrite wrong elements.

modules/javafx.graphics/src/main/java/com/sun/prism/sw/SWDrawingContext.java 
line 258:

> 256:     @Override
> 257:     public void setGlobalAlpha(double alpha) {
> 258:         this.globalAlpha = Math.clamp(alpha, 0.0, 1.0);

the clamping here violates the contract: "any valid double [value] can be set".
the clamping should be done upon use.

modules/javafx.graphics/src/main/java/com/sun/prism/sw/SWDrawingContext.java 
line 547:

> 545:         double y1 = Math.floor(Math.min(y, y + h));
> 546:         double x2 = Math.ceil(Math.max(x, x + w));
> 547:         double y2 = Math.ceil(Math.max(y, y + h));

the clip coordinates are rounded before transformation (L565), possibly 
repeatedly.  later transformation will change the existing clip.  should it be 
done at the last possible moment?

modules/javafx.graphics/src/main/java/com/sun/prism/sw/SWDrawingContext.java 
line 987:

> 985:         applyStrokeParameters();
> 986: 
> 987:         graphics.setTransform(BaseTransform.IDENTITY_TRANSFORM);

will applying the `IDENTITY_TRANSFORM` prevent scaling of stroke attributes 
like line width and dash lengths?

modules/javafx.graphics/src/main/java/com/sun/prism/sw/SWDrawingContext.java 
line 1153:

> 1151: 
> 1152:             if (stroke) {
> 1153:                 graphics.draw(strike.getOutline(run, 
> BaseTransform.getTranslateInstance(runX, runY)));

a wide stroke will go outside the min/max dirty bounds used later in L1167, 
possibly corrupting the pixels.

modules/javafx.graphics/src/main/java/javafx/scene/canvas/GraphicsContext.java 
line 1409:

> 1407:     public void arcTo(double x1, double y1, double x2, double y2, 
> double radius) {
> 1408:         if (path.getNumCommands() == 0) {
> 1409:             moveTo(x1, y1);

this needs lineTo(): this code will not render the expected dot using a round 
cap.

modules/javafx.graphics/src/main/java/javafx/scene/canvas/GraphicsContext.java 
line 1413:

> 1411:         else {
> 1412:             try {
> 1413:                 path.arcTo(curState.transform, (float) x1, (float) y1, 
> (float) x2, (float) y2, (float) radius);

does it need `markPathDirty()` here?

modules/javafx.graphics/src/main/java/javafx/scene/canvas/GraphicsContext.java 
line 1503:

> 1501:     @Override
> 1502:     public void clipRect(double x, double y, double w, double h) {
> 1503:         beginPath();

will this clobber the current path?

scenario: build a path, call clipRect(), call fill() -> wrong path will be 
filled.

modules/javafx.graphics/src/main/java/javafx/scene/image/WritableImage.java 
line 163:

> 161:     }
> 162: 
> 163:     private WeakReference<SWDrawingContext> drawingContextRef;

is it right to use the weak reference here?
what happens if the gc collects the current context, will the next call to 
getDrawingContext() return one with default paint, transform, etc.?

modules/javafx.graphics/src/main/java/javafx/scene/image/WritableImage.java 
line 178:

> 176:             }
> 177: 
> 178:             context = new SWDrawingContext(img, rect -> 
> bufferDirty(rect));

a single `PixelBuffer` may source multiple `WriteableImage`s, this callback 
invalidates only that owned by the context, leaving the other siblings showing 
stale pixels.

-------------

Changes requested by angorya (Reviewer).

PR Review: https://git.openjdk.org/jfx/pull/1969#pullrequestreview-5080469893
PR Review Comment: https://git.openjdk.org/jfx/pull/1969#discussion_r3906170191
PR Review Comment: https://git.openjdk.org/jfx/pull/1969#discussion_r3907141926
PR Review Comment: https://git.openjdk.org/jfx/pull/1969#discussion_r3907007408
PR Review Comment: https://git.openjdk.org/jfx/pull/1969#discussion_r3907028698
PR Review Comment: https://git.openjdk.org/jfx/pull/1969#discussion_r3907123699
PR Review Comment: https://git.openjdk.org/jfx/pull/1969#discussion_r3907160637
PR Review Comment: https://git.openjdk.org/jfx/pull/1969#discussion_r3906140684
PR Review Comment: https://git.openjdk.org/jfx/pull/1969#discussion_r3906979398
PR Review Comment: https://git.openjdk.org/jfx/pull/1969#discussion_r3906191680
PR Review Comment: https://git.openjdk.org/jfx/pull/1969#discussion_r3906957714

Reply via email to