diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/QuantumToolkit.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/QuantumToolkit.java
index 3ce2914e553..7f39d8f69f1 100644
--- a/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/QuantumToolkit.java
+++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/tk/quantum/QuantumToolkit.java
@@ -1506,8 +1506,8 @@ public void factoryReleased() {
@Override
public PlatformImage createPlatformImage(int w, int h) {
- ByteBuffer bytebuf = ByteBuffer.allocate(w * h * 4);
- return com.sun.prism.Image.fromByteBgraPreData(bytebuf, w, h);
+ IntBuffer buf = IntBuffer.allocate(w * h);
+ return com.sun.prism.Image.fromIntArgbPreData(buf, w, h);
}
@Override
diff --git a/modules/javafx.graphics/src/main/java/com/sun/javafx/util/FXCleaner.java b/modules/javafx.graphics/src/main/java/com/sun/javafx/util/FXCleaner.java
new file mode 100644
index 00000000000..53d1840a5e6
--- /dev/null
+++ b/modules/javafx.graphics/src/main/java/com/sun/javafx/util/FXCleaner.java
@@ -0,0 +1,31 @@
+package com.sun.javafx.util;
+
+import java.lang.ref.Cleaner;
+import java.lang.ref.Cleaner.Cleanable;
+
+/**
+ * A module-wide Cleaner utility for registering cleanup actions on objects
+ * that become phantom-reachable. This class maintains a single shared
+ * {@link Cleaner} instance for the module, avoiding multiple daemon threads.
+ *
+ * Usage example:
+ *
+ * FXCleaner.register(resource, () -> resource.dispose());
+ *
+ */
+public class FXCleaner {
+ private static final Cleaner CLEANER = Cleaner.create();
+
+ /**
+ * Registers a cleanup action to be run when {@code obj} becomes
+ * phantom-reachable.
+ *
+ * @param obj the object to monitor, cannot be {@code null}
+ * @param action the cleanup action to run, cannot be {@code null}
+ * @return a {@link Cleanable} that can be used to cancel the cleanup, never {@code null}
+ * @throws NullPointerException when any argument is {@code null}
+ */
+ public static Cleanable register(Object obj, Runnable action) {
+ return CLEANER.register(obj, action);
+ }
+}
diff --git a/modules/javafx.graphics/src/main/java/com/sun/marlin/MarlinUtils.java b/modules/javafx.graphics/src/main/java/com/sun/marlin/MarlinUtils.java
index 55c775a06f7..a4654b659eb 100644
--- a/modules/javafx.graphics/src/main/java/com/sun/marlin/MarlinUtils.java
+++ b/modules/javafx.graphics/src/main/java/com/sun/marlin/MarlinUtils.java
@@ -77,13 +77,4 @@ public static ThreadGroup getRootThreadGroup() {
}
return currentTG;
}
-
- // JavaFX specific Cleaner for Marlin-FX:
- // Module issue with jdk.internal.ref.Cleaner
- private final static java.lang.ref.Cleaner cleaner
- = java.lang.ref.Cleaner.create();
-
- static java.lang.ref.Cleaner getCleaner() {
- return cleaner;
- }
}
diff --git a/modules/javafx.graphics/src/main/java/com/sun/marlin/OffHeapArray.java b/modules/javafx.graphics/src/main/java/com/sun/marlin/OffHeapArray.java
index f655beee2fe..ee6aaec63c3 100644
--- a/modules/javafx.graphics/src/main/java/com/sun/marlin/OffHeapArray.java
+++ b/modules/javafx.graphics/src/main/java/com/sun/marlin/OffHeapArray.java
@@ -26,6 +26,9 @@
package com.sun.marlin;
import static com.sun.marlin.MarlinConst.LOG_OFF_HEAP_MALLOC;
+
+import com.sun.javafx.util.FXCleaner;
+
import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;
@@ -79,7 +82,7 @@ final class OffHeapArray {
if (!global) {
// Register a cleaning function to ensure freeing off-heap memory:
- MarlinUtils.getCleaner().register(parent, this::free);
+ FXCleaner.register(parent, this::free);
}
}
diff --git a/modules/javafx.graphics/src/main/java/com/sun/marlin/RendererStats.java b/modules/javafx.graphics/src/main/java/com/sun/marlin/RendererStats.java
index 23f134e781d..acde99e1a2c 100644
--- a/modules/javafx.graphics/src/main/java/com/sun/marlin/RendererStats.java
+++ b/modules/javafx.graphics/src/main/java/com/sun/marlin/RendererStats.java
@@ -28,6 +28,8 @@
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentLinkedQueue;
+
+import com.sun.javafx.util.FXCleaner;
import com.sun.marlin.ArrayCacheConst.CacheStats;
import static com.sun.marlin.MarlinUtils.logInfo;
import com.sun.marlin.stats.Histogram;
@@ -383,7 +385,7 @@ void add(final Object parent, final RendererStats stats) {
allStats.add(stats);
// Register a cleaning function to ensure removing dead entries:
- MarlinUtils.getCleaner().register(parent, () -> remove(stats));
+ FXCleaner.register(parent, () -> remove(stats));
}
void remove(final RendererStats stats) {
diff --git a/modules/javafx.graphics/src/main/java/com/sun/prism/sw/SWArgbPreTexture.java b/modules/javafx.graphics/src/main/java/com/sun/prism/sw/SWArgbPreTexture.java
index c039e179c7b..9679163e312 100644
--- a/modules/javafx.graphics/src/main/java/com/sun/prism/sw/SWArgbPreTexture.java
+++ b/modules/javafx.graphics/src/main/java/com/sun/prism/sw/SWArgbPreTexture.java
@@ -45,8 +45,14 @@ class SWArgbPreTexture extends SWTexture {
private boolean hasAlpha = true;
SWArgbPreTexture(SWResourceFactory factory, WrapMode wrapMode, int w, int h) {
+ this(factory, wrapMode, w, h, null);
+ }
+
+ SWArgbPreTexture(SWResourceFactory factory, WrapMode wrapMode, int w, int h, int[] data) {
super(factory, wrapMode, w, h);
- offset = 0;
+
+ this.allocated = data != null;
+ this.data = data;
}
SWArgbPreTexture(SWArgbPreTexture sharedTex, WrapMode altMode) {
diff --git a/modules/javafx.graphics/src/main/java/com/sun/prism/sw/SWDrawingContext.java b/modules/javafx.graphics/src/main/java/com/sun/prism/sw/SWDrawingContext.java
new file mode 100644
index 00000000000..2e2e23ab3ef
--- /dev/null
+++ b/modules/javafx.graphics/src/main/java/com/sun/prism/sw/SWDrawingContext.java
@@ -0,0 +1,578 @@
+/*
+ * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.sun.prism.sw;
+
+import com.sun.glass.ui.Screen;
+import com.sun.glass.utils.NativeLibLoader;
+import com.sun.javafx.geom.Arc2D;
+import com.sun.javafx.geom.Path2D;
+import com.sun.javafx.geom.Rectangle;
+import com.sun.javafx.tk.Toolkit;
+import com.sun.javafx.util.FXCleaner;
+import com.sun.prism.BasicStroke;
+import com.sun.prism.CompositeMode;
+import com.sun.prism.Graphics;
+import com.sun.prism.PixelFormat;
+import com.sun.prism.Texture;
+import com.sun.prism.Texture.Usage;
+
+import java.nio.IntBuffer;
+import java.util.Objects;
+import java.util.function.Consumer;
+
+import javafx.scene.effect.BlendMode;
+import javafx.scene.image.DrawingContext;
+import javafx.scene.image.Image;
+import javafx.scene.image.PixelBuffer;
+import javafx.scene.paint.Color;
+import javafx.scene.paint.Paint;
+import javafx.scene.shape.ArcType;
+import javafx.scene.shape.FillRule;
+import javafx.scene.shape.StrokeLineCap;
+import javafx.scene.shape.StrokeLineJoin;
+
+// TODO dashes
+// TODO save/restore
+/**
+ * A software-based drawing context for {@link com.sun.prism.Image} that allows direct rendering of shapes, paths, and
+ * images into a Prism image buffer.
+ *
+ * This class provides a familiar JavaFX-style drawing API for Prism images, enabling modification of the image contents
+ * using lines, rectangles, ovals, rounded rectangles, arcs, polygons, and images without the need for a Canvas or
+ * {@link javafx.scene.image.PixelWriter}.
+ *
+ * It uses Prism's {@link Graphics} and {@link BasicStroke} for rendering, supporting strokes, fills, alpha
+ * transparency, limited blend modes, and automatic dirty-region tracking to efficiently mark affected pixels.
+ *
+ * Features include:
+ *
+ * - Stroke and fill management (line width, line caps, joins, miter limits).
+ * - Global alpha and support for {@link javafx.scene.effect.BlendMode#SRC_OVER} and
+ * {@link javafx.scene.effect.BlendMode#ADD}.
+ * - Drawing of lines, rectangles, rounded rectangles, ovals, arcs, polygons, polylines, and images.
+ * - Automatic dirty-region tracking for efficient pixel updates.
+ *
+ *
+ * Limitations:
+ *
+ * - Dashed strokes and save/restore state are not yet implemented.
TODO
+ * - Only SRC_OVER and ADD blend modes are supported; others will throw an exception.
+ *
+ *
+ * This class is intended for use in contexts where direct drawing into a Prism image is needed, such as the
+ * {@code getDrawingContext()} method in WritableImage, providing a more convenient API than PixelWriter or snapshotting
+ * a Canvas.
+ *
+ * @see DrawingContext
+ * @see com.sun.prism.Image
+ * @since 26
+ */
+public class SWDrawingContext implements DrawingContext {
+ private static final double SQRT2 = Math.sqrt(2);
+
+ static {
+ NativeLibLoader.loadLibrary("prism_sw");
+ }
+
+ private final Graphics graphics;
+ private final SWResourceFactory resourceFactory;
+ private final Consumer pixelsDirty;
+
+ // Common rendering attributes
+ private double globalAlpha = 1.0;
+ private BlendMode globalBlendMode = BlendMode.SRC_OVER;
+
+ // Fill attributes
+ private Paint fill = Color.BLACK;
+
+ // Stroke attributes
+ private Paint stroke = Color.BLACK;
+ private double lineWidth = 1.0;
+ private StrokeLineCap lineCap = StrokeLineCap.SQUARE;
+ private StrokeLineJoin lineJoin = StrokeLineJoin.MITER;
+ private double miterLimit = 10.0;
+
+ // Path attributes
+ private FillRule fillRule = FillRule.NON_ZERO;
+
+ // Image attributes
+ private boolean imageSmoothing = true;
+
+ // Cached prism values
+ private com.sun.prism.paint.Paint prismFillPaint = com.sun.prism.paint.Color.BLACK;
+ private com.sun.prism.paint.Paint prismStrokePaint = com.sun.prism.paint.Color.BLACK;
+ private BasicStroke prismStroke;
+
+ /**
+ * Constructs a new instance.
+ *
+ * The provided image must be backed by a writable {@link PixelBuffer} with
+ * format {@link PixelFormat#INT_ARGB_PRE INT_ARGB_PRE}.
+ *
+ * @param img a prism image, cannot be {@code null}
+ * @param pixelsDirty a consumer of dirty rectangles, cannot be {@code null}
+ * @throws NullPointerException if any argument is {@code null}
+ * @throws IllegalStateException if the image is not backed by a writable pixel buffer
+ * in the correct format.
+ */
+ public SWDrawingContext(com.sun.prism.Image img, Consumer pixelsDirty) {
+ int[] data = switch (img.getPixelBuffer()) {
+ case IntBuffer ib -> ib.array();
+ default -> throw new IllegalStateException("img must contain an accessible int buffer backed by an int array");
+ };
+
+ this.pixelsDirty = Objects.requireNonNull(pixelsDirty, "pixelsDirty");
+ this.resourceFactory = new SWResourceFactory(Screen.getMainScreen()); // Note, actual screen is irrelevant, we just need one
+
+ SWRTTexture texture = new SWRTTexture(resourceFactory, img.getWidth(), img.getHeight(), data);
+
+ this.graphics = texture.createGraphics();
+
+ FXCleaner.register(this, new StateCleaner(resourceFactory, texture));
+ }
+
+ private record StateCleaner(SWResourceFactory resourceFactory, SWRTTexture texture) implements Runnable {
+ @Override
+ public void run() {
+ texture.dispose();
+ resourceFactory.dispose();
+ }
+ }
+
+ @Override
+ public Paint getStroke() {
+ return stroke;
+ }
+
+ @Override
+ public void setStroke(Paint p) {
+ if (p != null) {
+ this.stroke = p;
+ this.prismStrokePaint = (com.sun.prism.paint.Paint)Toolkit.getToolkit().getPaint(p);
+ }
+ }
+
+ @Override
+ public Paint getFill() {
+ return fill;
+ }
+
+ @Override
+ public void setFill(Paint p) {
+ if (p != null) {
+ this.fill = p;
+ this.prismFillPaint = (com.sun.prism.paint.Paint)Toolkit.getToolkit().getPaint(p);
+ }
+ }
+
+ @Override
+ public double getGlobalAlpha() {
+ return globalAlpha;
+ }
+
+ @Override
+ public void setGlobalAlpha(double alpha) {
+ this.globalAlpha = Math.clamp(alpha, 0.0, 1.0);
+
+ graphics.setExtraAlpha((float) globalAlpha);
+ }
+
+ @Override
+ public BlendMode getGlobalBlendMode() {
+ return globalBlendMode;
+ }
+
+ @Override
+ public void setGlobalBlendMode(BlendMode op) {
+ if (op != null) {
+ CompositeMode cm = switch (op) {
+ case SRC_OVER -> CompositeMode.SRC_OVER;
+ case ADD -> CompositeMode.ADD;
+ default -> throw new IllegalArgumentException("Unsupported blend mode: " + op);
+ };
+
+ this.globalBlendMode = op;
+
+ graphics.setCompositeMode(cm);
+ }
+ }
+
+ @Override
+ public FillRule getFillRule() {
+ return fillRule;
+ }
+
+ @Override
+ public void setFillRule(FillRule fillRule) {
+ if(fillRule != null) {
+ this.fillRule = fillRule;
+ }
+ }
+
+ @Override
+ public double getLineWidth() {
+ return lineWidth;
+ }
+
+ @Override
+ public void setLineWidth(double lw) {
+ if(lw > 0 && lw < Double.POSITIVE_INFINITY && lw != lineWidth) {
+ this.lineWidth = lw;
+
+ invalidateStroke();
+ }
+ }
+
+ @Override
+ public StrokeLineCap getLineCap() {
+ return lineCap;
+ }
+
+ @Override
+ public void setLineCap(StrokeLineCap cap) {
+ if(cap != null && cap != lineCap) {
+ this.lineCap = cap;
+
+ invalidateStroke();
+ }
+ }
+
+ @Override
+ public StrokeLineJoin getLineJoin() {
+ return lineJoin;
+ }
+
+ @Override
+ public void setLineJoin(StrokeLineJoin join) {
+ if (join != null && join != lineJoin) {
+ this.lineJoin = join;
+
+ invalidateStroke();
+ }
+ }
+
+ @Override
+ public double getMiterLimit() {
+ return miterLimit;
+ }
+
+ @Override
+ public void setMiterLimit(double ml) {
+ if (ml > 0.0 && ml < Double.POSITIVE_INFINITY && ml != miterLimit) {
+ this.miterLimit = ml;
+
+ invalidateStroke();
+ }
+ }
+
+ @Override
+ public boolean isImageSmoothing() {
+ return imageSmoothing;
+ }
+
+ @Override
+ public void setImageSmoothing(boolean imageSmoothing) {
+ this.imageSmoothing = imageSmoothing;
+ }
+
+ @Override
+ public void strokeLine(double x1, double y1, double x2, double y2) {
+ applyStrokeParameters();
+
+ graphics.drawLine((float)x1, (float)y1, (float)x2, (float)y2);
+
+ markStrokeRectDirty(x1, y1, Math.abs(x2 - x1), Math.abs(y2 - y1));
+ }
+
+ @Override
+ public void strokeRect(double x, double y, double w, double h) {
+ if(w != 0 || h != 0) {
+ applyStrokeParameters();
+
+ graphics.drawRect((float)x, (float)y, (float)w, (float)h);
+
+ markStrokeRectDirty(x, y, w, h);
+ }
+ }
+
+ @Override
+ public void clearRect(double x, double y, double w, double h) {
+ if (w != 0 && h != 0) {
+ graphics.clearQuad((float)x, (float)y, (float)(x + w), (float)(x + h));
+
+ markRectDirty(x, y, w, h);
+ }
+ }
+
+ @Override
+ public void fillRect(double x, double y, double w, double h) {
+ if (w != 0 && h != 0) {
+ graphics.setPaint(prismFillPaint);
+ graphics.fillRect((float)x, (float)y, (float)w, (float)h);
+
+ markRectDirty(x, y, w, h);
+ }
+ }
+
+ @Override
+ public void strokeRoundRect(double x, double y, double w, double h, double arcWidth, double arcHeight) {
+ if (w != 0 || h != 0) {
+ applyStrokeParameters();
+
+ graphics.drawRoundRect((float)x, (float)y, (float)w, (float)h, (float)arcWidth, (float)arcHeight);
+
+ markStrokeRectDirty(x, y, w, h);
+ }
+ }
+
+ @Override
+ public void fillRoundRect(double x, double y, double w, double h, double arcWidth, double arcHeight) {
+ if (w != 0 && h != 0) {
+ graphics.setPaint(prismFillPaint);
+ graphics.fillRoundRect((float)x, (float)y, (float)w, (float)h, (float)arcWidth, (float)arcHeight);
+
+ markRectDirty(x, y, w, h);
+ }
+ }
+
+ @Override
+ public void strokeOval(double x, double y, double w, double h) {
+ if (w != 0 || h != 0) {
+ applyStrokeParameters();
+
+ graphics.drawEllipse((float)x, (float)y, (float)w, (float)h);
+
+ markStrokeRectDirty(x, y, w, h);
+ }
+ }
+
+ @Override
+ public void fillOval(double x, double y, double w, double h) {
+ if (w != 0 && h != 0) {
+ applyStrokeParameters();
+
+ graphics.setPaint(prismFillPaint);
+ graphics.fillEllipse((float)x, (float)y, (float)w, (float)h);
+
+ markRectDirty(x, y, w, h);
+ }
+ }
+
+ @Override
+ public void strokeArc(double x, double y, double w, double h, double startAngle, double arcExtent, ArcType closure) {
+ if (w != 0 && h != 0 && closure != null) {
+ int arcType = switch (closure) {
+ case CHORD -> Arc2D.CHORD;
+ case OPEN -> Arc2D.OPEN;
+ case ROUND -> Arc2D.PIE;
+ };
+
+ applyStrokeParameters();
+
+ graphics.draw(new Arc2D((float)x, (float)y, (float)w, (float)h, (float)startAngle, (float)arcExtent, arcType));
+
+ markStrokeRectDirty(x, y, w, h);
+ }
+ }
+
+ @Override
+ public void fillArc(double x, double y, double w, double h, double startAngle, double arcExtent, ArcType closure) {
+ if (w != 0 && h != 0 && closure != null) {
+ int arcType = switch (closure) {
+ case CHORD -> Arc2D.CHORD;
+ case OPEN -> Arc2D.OPEN;
+ case ROUND -> Arc2D.PIE;
+ };
+
+ graphics.setPaint(prismFillPaint);
+ graphics.fill(new Arc2D((float)x, (float)y, (float)w, (float)h, (float)startAngle, (float)arcExtent, arcType));
+
+ markRectDirty(x, y, w, h);
+ }
+ }
+
+ @Override
+ public void strokePolyline(double[] xPoints, double[] yPoints, int nPoints) {
+ strokePolyline(xPoints, yPoints, nPoints, false);
+ }
+
+ @Override
+ public void strokePolygon(double[] xPoints, double[] yPoints, int nPoints) {
+ strokePolyline(xPoints, yPoints, nPoints, true);
+ }
+
+ private void strokePolyline(double[] xPoints, double[] yPoints, int nPoints, boolean close) {
+ if (xPoints != null && yPoints != null && nPoints >= 2 && xPoints.length >= nPoints && yPoints.length >= nPoints) {
+ Path2D path = new Path2D();
+ double minX = xPoints[0];
+ double maxX = xPoints[0];
+ double minY = yPoints[0];
+ double maxY = yPoints[0];
+
+ path.moveTo((float)xPoints[0], (float)yPoints[0]);
+
+ for (int i = 1; i < nPoints; i++) {
+ path.lineTo((float)xPoints[i], (float)yPoints[i]);
+
+ minX = Math.min(minX, xPoints[i]);
+ minY = Math.min(minY, yPoints[i]);
+ maxX = Math.max(maxX, xPoints[i]);
+ maxY = Math.max(maxY, yPoints[i]);
+ }
+
+ if (close) {
+ path.closePath();
+ }
+
+ applyStrokeParameters();
+
+ graphics.draw(path);
+
+ markStrokeRectDirty(minX, minY, maxX - minX, maxY - minY);
+ }
+ }
+
+ @Override
+ public void fillPolygon(double[] xPoints, double[] yPoints, int nPoints) {
+ if (xPoints != null && yPoints != null && nPoints >= 3 && xPoints.length >= nPoints && yPoints.length >= nPoints) {
+ Path2D path = new Path2D(switch (fillRule) {
+ case EVEN_ODD -> Path2D.WIND_EVEN_ODD;
+ case NON_ZERO -> Path2D.WIND_NON_ZERO;
+ });
+ double minX = xPoints[0];
+ double maxX = xPoints[0];
+ double minY = yPoints[0];
+ double maxY = yPoints[0];
+
+ path.moveTo((float)xPoints[0], (float)yPoints[0]);
+
+ for (int i = 1; i < nPoints; i++) {
+ path.lineTo((float)xPoints[i], (float)yPoints[i]);
+
+ minX = Math.min(minX, xPoints[i]);
+ minY = Math.min(minY, yPoints[i]);
+ maxX = Math.max(maxX, xPoints[i]);
+ maxY = Math.max(maxY, yPoints[i]);
+ }
+
+ path.closePath();
+
+ graphics.setPaint(prismFillPaint);
+ graphics.fill(path);
+
+ markRectDirty(minX, minY, maxX - minX, maxY - minY);
+ }
+ }
+
+ @Override
+ public void drawImage(Image img, double sx, double sy, double sw, double sh, double dx, double dy, double dw, double dh) {
+ if (img == null || img.getProgress() < 1.0) {
+ return;
+ }
+
+ Object platformImage = Toolkit.getImageAccessor().getPlatformImage(img);
+
+ // Ensure it's a Prism image
+ if (!(platformImage instanceof com.sun.prism.Image prismImage)) {
+ throw new IllegalArgumentException("PlatformImage must be a Prism Image");
+ }
+
+ // Create a texture from the Prism image
+ Texture tex = resourceFactory.createTexture(prismImage, Usage.DEFAULT, Texture.WrapMode.CLAMP_TO_EDGE);
+
+ if (tex == null) {
+ throw new IllegalStateException("Unable to draw image, insufficient resources");
+ }
+
+ try {
+ tex.setLinearFiltering(imageSmoothing);
+
+ graphics.drawTexture(tex, (float)dx, (float)dy, (float)(dx + dw), (float)(dy + dh), (float)sx, (float)sy, (float)(sx + sw), (float)(sy + sh));
+
+ markRectDirty(dx, dy, dw, dh);
+ }
+ finally {
+ tex.dispose();
+ }
+ }
+
+ private void applyStrokeParameters() {
+ if(prismStroke == null) {
+ this.prismStroke = new BasicStroke(
+ (float)lineWidth,
+ switch (lineCap) {
+ case BUTT -> BasicStroke.CAP_BUTT;
+ case ROUND -> BasicStroke.CAP_ROUND;
+ case SQUARE -> BasicStroke.CAP_SQUARE;
+ },
+ switch (lineJoin) {
+ case BEVEL -> BasicStroke.JOIN_BEVEL;
+ case ROUND -> BasicStroke.JOIN_ROUND;
+ case MITER -> BasicStroke.JOIN_MITER;
+ },
+ (float)miterLimit
+ );
+ }
+
+ graphics.setStroke(prismStroke);
+ graphics.setPaint(prismStrokePaint);
+ }
+
+ private void invalidateStroke() {
+ this.prismStroke = null;
+ }
+
+ private void markStrokeRectDirty(double x, double y, double w, double h) {
+ // Base half-width expansion
+ double halfWidth = lineWidth * 0.5;
+
+ // Determine additional expansion factor based on caps and joins
+ double expansionFactor = switch (lineJoin) {
+ case MITER -> Math.max(miterLimit, lineCap == StrokeLineCap.SQUARE ? SQRT2 : 1.0);
+ case BEVEL, ROUND -> lineCap == StrokeLineCap.SQUARE ? SQRT2 : 1.0;
+ };
+
+ // Total expansion radius
+ double r = halfWidth * expansionFactor;
+
+ // Expand the rectangle
+ double dirtyX = x - r;
+ double dirtyY = y - r;
+ double dirtyW = w + r * 2.0;
+ double dirtyH = h + r * 2.0;
+
+ markRectDirty(dirtyX, dirtyY, dirtyW, dirtyH);
+ }
+
+ // TODO It seems bufferDirty only remembers the last rect; may need to update this only once per frame
+ // Note: if called multiple times per frame, then it just updates everything (optimize?)
+ private void markRectDirty(double x, double y, double w, double h) {
+ int fx = (int)Math.floor(x);
+ int fy = (int)Math.floor(y);
+
+ pixelsDirty.accept(new Rectangle(fx, fy, (int)Math.ceil(x + w) - fx, (int)Math.ceil(y + h) - fy));
+ }
+}
diff --git a/modules/javafx.graphics/src/main/java/com/sun/prism/sw/SWRTTexture.java b/modules/javafx.graphics/src/main/java/com/sun/prism/sw/SWRTTexture.java
index 75b8828bbd7..6e50cc1898c 100644
--- a/modules/javafx.graphics/src/main/java/com/sun/prism/sw/SWRTTexture.java
+++ b/modules/javafx.graphics/src/main/java/com/sun/prism/sw/SWRTTexture.java
@@ -46,7 +46,12 @@ class SWRTTexture extends SWArgbPreTexture implements RTTexture {
private boolean isOpaque;
SWRTTexture(SWResourceFactory factory, int w, int h) {
- super(factory, WrapMode.CLAMP_TO_ZERO, w, h);
+ this(factory, w, h, null);
+ }
+
+ SWRTTexture(SWResourceFactory factory, int w, int h, int[] data) {
+ super(factory, WrapMode.CLAMP_TO_ZERO, w, h, data);
+
this.allocate();
this.surface = new JavaSurface(getDataNoClone(), RendererBase.TYPE_INT_ARGB_PRE, w, h);
this.dimensions.setBounds(0, 0, w, h);
diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/image/DrawingContext.java b/modules/javafx.graphics/src/main/java/javafx/scene/image/DrawingContext.java
new file mode 100644
index 00000000000..f71ce64ae17
--- /dev/null
+++ b/modules/javafx.graphics/src/main/java/javafx/scene/image/DrawingContext.java
@@ -0,0 +1,816 @@
+/*
+ * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package javafx.scene.image;
+
+import javafx.scene.Node;
+import javafx.scene.Scene;
+import javafx.scene.canvas.Canvas;
+import javafx.scene.effect.BlendMode;
+import javafx.scene.paint.Color;
+import javafx.scene.paint.Paint;
+import javafx.scene.shape.ArcType;
+import javafx.scene.shape.FillRule;
+import javafx.scene.shape.StrokeLineCap;
+import javafx.scene.shape.StrokeLineJoin;
+
+/**
+ * Interface providing basic drawing operations. An instance of this interface
+ * is provided by {@link WritableImage} and {@link Canvas} via {@link WritableImage#getDrawingContext()}
+ * and {@link Canvas#getGraphicsContext2D()}.
+ *
+ * The provider of this interface may be associated with a {@link Node} which may
+ * be attached to a {@link Scene}. If the associated node is not attached to any scene,
+ * then the operations provided here can be used from any thread, as long as it is only
+ * used from one thread at a time. Once the node is attached to a scene, the operations must
+ * be called from the JavaFX Application Thread.
+ *
+ * TODO A {@code DrawingContext} also manages a stack of state objects that can
+ * be saved or restored at anytime.
+ *
+ * The {@code DrawingContext} maintains the following rendering attributes
+ * which affect various subsets of the rendering methods:
+ *
+ * List of Rendering Attributes
+ *
+ * | Attribute |
+ * Save/Restore? |
+ * Default value |
+ * Description |
+ *
+ * | Common Rendering Attributes |
+ *
+ * | {@link #setGlobalAlpha(double) Global Alpha} |
+ * Yes |
+ * {@code 1.0} |
+ *
+ * An opacity value that controls the visibility or fading of each rendering
+ * operation.
+ * |
+ *
+ * | {@link #setGlobalBlendMode(javafx.scene.effect.BlendMode) Global Blend Mode} |
+ * Yes |
+ * {@link BlendMode#SRC_OVER SRC_OVER} |
+ *
+ * A {@link BlendMode} enum value that controls how pixels from each rendering
+ * operation are composited into the existing image.
+ * |
+ * | Fill Attributes |
+ *
+ * | {@link #setFill(javafx.scene.paint.Paint) Fill Paint} |
+ * Yes |
+ * {@link Color#BLACK BLACK} |
+ *
+ * The {@link Paint} to be applied to the interior of shapes in a
+ * fill operation.
+ * |
+ * | Stroke Attributes |
+ *
+ * | {@link #setStroke(javafx.scene.paint.Paint) Stroke Paint} |
+ * Yes |
+ * {@link Color#BLACK BLACK} |
+ *
+ * The {@link Paint} to be applied to the boundary of shapes in a
+ * stroke operation.
+ * |
+ *
+ * | {@link #setLineWidth(double) Line Width} |
+ * Yes |
+ * {@code 1.0} |
+ *
+ * The width of the stroke applied to the boundary of shapes in a
+ * stroke operation.
+ * |
+ *
+ * | {@link #setLineCap(javafx.scene.shape.StrokeLineCap) Line Cap} |
+ * Yes |
+ * {@link StrokeLineCap#SQUARE SQUARE} |
+ *
+ * The style of the end caps applied to the beginnings and ends of each
+ * dash and/or subpath in a stroke operation.
+ * |
+ *
+ * | {@link #setLineJoin(javafx.scene.shape.StrokeLineJoin) Line Join} |
+ * Yes |
+ * {@link StrokeLineJoin#MITER MITER} |
+ *
+ * The style of the joins applied between individual segments in the boundary
+ * paths of shapes in a stroke operation.
+ * |
+ *
+ * | {@link #setMiterLimit(double) Miter Limit} |
+ * Yes |
+ * {@code 10.0} |
+ *
+ * The ratio limit of how far a {@link StrokeLineJoin#MITER MITER} line join
+ * may extend in the direction of a sharp corner between segments in the
+ * boundary path of a shape, relative to the line width, before it is truncated
+ * to a {@link StrokeLineJoin#BEVEL BEVEL} join in a stroke operation.
+ * |
+ * | Path Attributes |
+ *
+ * | {@link #setFillRule(javafx.scene.shape.FillRule) Fill Rule} |
+ * Yes |
+ * {@link FillRule#NON_ZERO NON_ZERO} |
+ *
+ * The method used to determine the interior of paths for a path fill or
+ * clip operation.
+ * |
+ * | Image Attributes |
+ *
+ * | {@link #setImageSmoothing(boolean) Image Smoothing} |
+ * Yes |
+ * {@code true} |
+ *
+ * A boolean state which enables or disables image smoothing for
+ * {@link #drawImage(javafx.scene.image.Image, double, double) drawImage(all forms)}.
+ * |
+ *
+ *
+ *
+ * The various rendering methods on the {@code DrawingContext} use the
+ * following sets of rendering attributes:
+ *
+ *
+ * Rendering Attributes Table
+ *
+ * | Method |
+ * Common Rendering Attributes |
+ * Fill Attributes |
+ * Stroke Attributes |
+ * Path Attributes |
+ * Image Attributes |
+ *
+ * | Basic Shape Rendering |
+ *
+ * |
+ * {@link #fillRect(double, double, double, double) fillRect()},
+ * {@link #fillRoundRect(double, double, double, double, double, double) fillRoundRect()},
+ * {@link #fillOval(double, double, double, double) fillOval()},
+ * {@link #fillArc(double, double, double, double, double, double, javafx.scene.shape.ArcType) fillArc()}
+ * |
+ * Yes |
+ * Yes |
+ * No |
+ * No |
+ * No |
+ *
+ *
+ * |
+ * {@link #strokeLine(double, double, double, double) strokeLine()},
+ * {@link #strokeRect(double, double, double, double) strokeRect()},
+ * {@link #strokeRoundRect(double, double, double, double, double, double) strokeRoundRect()},
+ * {@link #strokeOval(double, double, double, double) strokeOval()},
+ * {@link #strokeArc(double, double, double, double, double, double, javafx.scene.shape.ArcType) strokeArc()}
+ * |
+ * Yes |
+ * No |
+ * Yes |
+ * No |
+ * No |
+ *
+ *
+ * |
+ * {@link #clearRect(double, double, double, double) clearRect()}
+ * |
+ * Yes |
+ * No |
+ * No |
+ * No |
+ * No |
+ *
+ *
+ * |
+ * {@link #fillPolygon(double[], double[], int) fillPolygon()}
+ * |
+ * Yes |
+ * Yes |
+ * No |
+ * Yes [1] |
+ * No |
+ *
+ *
+ * |
+ * {@link #strokePolygon(double[], double[], int) strokePolygon()},
+ * {@link #strokePolyline(double[], double[], int) strokePolyline()}
+ * |
+ * Yes |
+ * No |
+ * Yes |
+ * No |
+ * No |
+ *
+ * |
+ * [1] Only the Fill Rule applies to fillPolygon()
+ * |
+ * | Image Rendering |
+ *
+ * |
+ * {@link #drawImage(javafx.scene.image.Image, double, double) drawImage(all forms)}
+ * |
+ * Yes |
+ * No |
+ * No |
+ * No |
+ * Yes |
+ *
+ *
+ *
+ * Example:
+ *
+ *
+ * import javafx.scene.*;
+ * import javafx.scene.image.*;
+ * import javafx.scene.paint.*;
+ *
+ * WritableImage writableImage = new WritableImage(250,250);
+ * ImageView root = new ImageView(writableImage);
+ * Scene s = new Scene(root, 300, 300, Color.BLACK);
+ *
+ * DrawingContext c = writableImage.getDrawingContext();
+ *
+ * c.setFill(Color.BLUE);
+ * c.fillRect(75,75,100,100);
+ *
+ *
+ * @see Canvas
+ * @see WritableImage
+ * @since 26
+ */
+public interface DrawingContext {
+
+ /**
+ * Gets the current stroke.
+ * The default value is {@link Color#BLACK BLACK}.
+ * The stroke paint is a stroke attribute
+ * used for any of the stroke methods as specified in the
+ * Rendering Attributes Table.
+ *
+ * @return the {@code Paint} to be used as the stroke {@code Paint}.
+ */
+ Paint getStroke();
+
+ /**
+ * Sets the current stroke paint attribute.
+ * The default value is {@link Color#BLACK BLACK}.
+ * The stroke paint is a stroke attribute
+ * used for any of the stroke methods as specified in the
+ * Rendering Attributes Table.
+ * A {@code null} value will be ignored and the current value will remain unchanged.
+ *
+ * @param p The Paint to be used as the stroke Paint or null.
+ */
+ void setStroke(Paint p);
+
+ /**
+ * Gets the current fill paint attribute.
+ * The default value is {@link Color#BLACK BLACK}.
+ * The fill paint is a fill attribute
+ * used for any of the fill methods as specified in the
+ * Rendering Attributes Table.
+ *
+ * @return The {@code Paint} to be used as the fill {@code Paint}.
+ */
+ Paint getFill();
+
+ /**
+ * Sets the current fill paint attribute.
+ * The default value is {@link Color#BLACK BLACK}.
+ * The fill paint is a fill attribute
+ * used for any of the fill methods as specified in the
+ * Rendering Attributes Table.
+ * A {@code null} value will be ignored and the current value will remain unchanged.
+ *
+ * @param p The {@code Paint} to be used as the fill {@code Paint} or null.
+ */
+ void setFill(Paint p);
+
+ /**
+ * Gets the current global alpha.
+ * The default value is {@code 1.0}.
+ * The global alpha is a common attribute
+ * used for nearly all rendering methods as specified in the
+ * Rendering Attributes Table.
+ *
+ * @return the current global alpha.
+ */
+ double getGlobalAlpha();
+
+ /**
+ * Sets the global alpha of the current state.
+ * The default value is {@code 1.0}.
+ * Any valid double can be set, but only values in the range
+ * {@code [0.0, 1.0]} are valid and the nearest value in that
+ * range will be used for rendering.
+ * The global alpha is a common attribute
+ * used for nearly all rendering methods as specified in the
+ * Rendering Attributes Table.
+ *
+ * @param alpha the new alpha value, clamped to {@code [0.0, 1.0]}
+ * during actual use.
+ */
+ void setGlobalAlpha(double alpha);
+
+ /**
+ * Gets the global blend mode.
+ * The default value is {@link BlendMode#SRC_OVER SRC_OVER}.
+ * The blend mode is a common attribute
+ * used for nearly all rendering methods as specified in the
+ * Rendering Attributes Table.
+ *
+ * @return the global {@code BlendMode} of the current state.
+ */
+ BlendMode getGlobalBlendMode();
+
+ /**
+ * Sets the global blend mode.
+ * The default value is {@link BlendMode#SRC_OVER SRC_OVER}.
+ * A {@code null} value will be ignored and the current value will remain unchanged.
+ * The blend mode is a common attribute
+ * used for nearly all rendering methods as specified in the
+ * Rendering Attributes Table.
+ *
+ * @param op the {@code BlendMode} that will be set or null.
+ */
+ void setGlobalBlendMode(BlendMode op);
+
+ /**
+ * Get the filling rule attribute for determining the interior of paths
+ * in fill and clip operations.
+ * The default value is {@code FillRule.NON_ZERO}.
+ * The fill rule is a path attribute
+ * used for any of the fill or clip path methods as specified in the
+ * Rendering Attributes Table.
+ *
+ * @return current fill rule.
+ */
+ FillRule getFillRule();
+
+ /**
+ * Set the filling rule attribute for determining the interior of paths
+ * in fill or clip operations.
+ * The default value is {@code FillRule.NON_ZERO}.
+ * A {@code null} value will be ignored and the current value will remain unchanged.
+ * The fill rule is a path attribute
+ * used for any of the fill or clip path methods as specified in the
+ * Rendering Attributes Table.
+ *
+ * @param fillRule {@code FillRule} with a value of Even_odd or Non_zero or null.
+ */
+ void setFillRule(FillRule fillRule);
+
+ /**
+ * Gets the current line width.
+ * The default value is {@code 1.0}.
+ * The line width is a stroke attribute
+ * used for any of the stroke methods as specified in the
+ * Rendering Attributes Table.
+ *
+ * @return value between 0 and infinity.
+ */
+ double getLineWidth();
+
+ /**
+ * Sets the current line width.
+ * The default value is {@code 1.0}.
+ * The line width is a stroke attribute
+ * used for any of the stroke methods as specified in the
+ * Rendering Attributes Table.
+ * An infinite or non-positive value outside of the range {@code (0, +inf)}
+ * will be ignored and the current value will remain unchanged.
+ *
+ * @param lw value in the range {0-positive infinity}, with any other value
+ * being ignored and leaving the value unchanged.
+ */
+ void setLineWidth(double lw);
+
+ /**
+ * Gets the current stroke line cap.
+ * The default value is {@link StrokeLineCap#SQUARE SQUARE}.
+ * The line cap is a stroke attribute
+ * used for any of the stroke methods as specified in the
+ * Rendering Attributes Table.
+ *
+ * @return {@code StrokeLineCap} with a value of Butt, Round, or Square.
+ */
+ StrokeLineCap getLineCap();
+
+ /**
+ * Sets the current stroke line cap.
+ * The default value is {@link StrokeLineCap#SQUARE SQUARE}.
+ * The line cap is a stroke attribute
+ * used for any of the stroke methods as specified in the
+ * Rendering Attributes Table.
+ * A {@code null} value will be ignored and the current value will remain unchanged.
+ *
+ * @param cap {@code StrokeLineCap} with a value of Butt, Round, or Square or null.
+ */
+ void setLineCap(StrokeLineCap cap);
+
+ /**
+ * Gets the current stroke line join.
+ * The default value is {@link StrokeLineJoin#MITER}.
+ * The line join is a stroke attribute
+ * used for any of the stroke methods as specified in the
+ * Rendering Attributes Table.
+ *
+ * @return {@code StrokeLineJoin} with a value of Miter, Bevel, or Round.
+ */
+ StrokeLineJoin getLineJoin();
+
+ /**
+ * Sets the current stroke line join.
+ * The default value is {@link StrokeLineJoin#MITER}.
+ * The line join is a stroke attribute
+ * used for any of the stroke methods as specified in the
+ * Rendering Attributes Table.
+ * A {@code null} value will be ignored and the current value will remain unchanged.
+ *
+ * @param join {@code StrokeLineJoin} with a value of Miter, Bevel, or Round or null.
+ */
+ void setLineJoin(StrokeLineJoin join);
+
+ /**
+ * Gets the current miter limit.
+ * The default value is {@code 10.0}.
+ * The miter limit is a stroke attribute
+ * used for any of the stroke methods as specified in the
+ * Rendering Attributes Table.
+ *
+ * @return the miter limit value in the range {@code 0.0-positive infinity}
+ */
+ double getMiterLimit();
+
+ /**
+ * Sets the current miter limit.
+ * The default value is {@code 10.0}.
+ * The miter limit is a stroke attribute
+ * used for any of the stroke methods as specified in the
+ * Rendering Attributes Table.
+ * An infinite or non-positive value outside of the range {@code (0, +inf)}
+ * will be ignored and the current value will remain unchanged.
+ *
+ * @param ml miter limit value between 0 and positive infinity with
+ * any other value being ignored and leaving the value unchanged.
+ */
+ void setMiterLimit(double ml);
+
+ /**
+ * Gets the current image smoothing state.
+ *
+ * @defaultValue {@code true}
+ * @return image smoothing state
+ */
+ boolean isImageSmoothing();
+
+ /**
+ * Sets the image smoothing state.
+ * Image smoothing is an Image attribute
+ * used to enable or disable image smoothing for
+ * {@link #drawImage(javafx.scene.image.Image, double, double) drawImage(all forms)}
+ * as specified in the Rendering Attributes Table.
+ * If image smoothing is {@code true}, images will be scaled using a higher
+ * quality filtering when transforming or scaling the source image to fit
+ * in the destination rectangle.
+ * If image smoothing is {@code false}, images will be scaled without filtering
+ * (or by using a lower quality filtering) when transforming or scaling the
+ * source image to fit in the destination rectangle.
+ *
+ * @defaultValue {@code true}
+ * @param imageSmoothing {@code true} to enable or {@code false} to disable smoothing
+ */
+ void setImageSmoothing(boolean imageSmoothing);
+
+ /**
+ * Strokes a line using the current stroke paint.
+ *
+ * This method will be affected by any of the
+ * global common
+ * or stroke
+ * attributes as specified in the
+ * Rendering Attributes Table.
+ *
+ *
+ * @param x1 the X coordinate of the starting point of the line.
+ * @param y1 the Y coordinate of the starting point of the line.
+ * @param x2 the X coordinate of the ending point of the line.
+ * @param y2 the Y coordinate of the ending point of the line.
+ */
+ void strokeLine(double x1, double y1, double x2, double y2);
+
+ /**
+ * Strokes a rectangle using the current stroke paint.
+ *
+ * This method will be affected by any of the
+ * global common
+ * or stroke
+ * attributes as specified in the
+ * Rendering Attributes Table.
+ *
+ *
+ * @param x the X position of the upper left corner of the rectangle.
+ * @param y the Y position of the upper left corner of the rectangle.
+ * @param w the width of the rectangle.
+ * @param h the height of the rectangle.
+ */
+ void strokeRect(double x, double y, double w, double h);
+
+ /**
+ * Clears a portion of the canvas with a transparent color value.
+ *
+ * This method will be affected only by the current transform, clip,
+ * and effect.
+ *
+ *
+ * @param x X position of the upper left corner of the rectangle.
+ * @param y Y position of the upper left corner of the rectangle.
+ * @param w width of the rectangle.
+ * @param h height of the rectangle.
+ */
+ void clearRect(double x, double y, double w, double h);
+
+ /**
+ * Fills a rectangle using the current fill paint.
+ *
+ * This method will be affected by any of the
+ * global common
+ * or fill
+ * attributes as specified in the
+ * Rendering Attributes Table.
+ *
+ *
+ * @param x the X position of the upper left corner of the rectangle.
+ * @param y the Y position of the upper left corner of the rectangle.
+ * @param w the width of the rectangle.
+ * @param h the height of the rectangle.
+ */
+ void fillRect(double x, double y, double w, double h);
+
+ /**
+ * Strokes a rounded rectangle using the current stroke paint.
+ *
+ * This method will be affected by any of the
+ * global common
+ * or stroke
+ * attributes as specified in the
+ * Rendering Attributes Table.
+ *
+ *
+ * @param x the X coordinate of the upper left bound of the oval.
+ * @param y the Y coordinate of the upper left bound of the oval.
+ * @param w the width at the center of the oval.
+ * @param h the height at the center of the oval.
+ * @param arcWidth the arc width of the rectangle corners.
+ * @param arcHeight the arc height of the rectangle corners.
+ */
+ void strokeRoundRect(double x, double y, double w, double h, double arcWidth, double arcHeight);
+
+ /**
+ * Fills a rounded rectangle using the current fill paint.
+ *
+ * This method will be affected by any of the
+ * global common
+ * or fill
+ * attributes as specified in the
+ * Rendering Attributes Table.
+ *
+ *
+ * @param x the X coordinate of the upper left bound of the oval.
+ * @param y the Y coordinate of the upper left bound of the oval.
+ * @param w the width at the center of the oval.
+ * @param h the height at the center of the oval.
+ * @param arcWidth the arc width of the rectangle corners.
+ * @param arcHeight the arc height of the rectangle corners.
+ */
+ void fillRoundRect(double x, double y, double w, double h, double arcWidth, double arcHeight);
+
+ /**
+ * Strokes an oval using the current stroke paint.
+ *
+ * This method will be affected by any of the
+ * global common
+ * or stroke
+ * attributes as specified in the
+ * Rendering Attributes Table.
+ *
+ *
+ * @param x the X coordinate of the upper left bound of the oval.
+ * @param y the Y coordinate of the upper left bound of the oval.
+ * @param w the width at the center of the oval.
+ * @param h the height at the center of the oval.
+ */
+ void strokeOval(double x, double y, double w, double h);
+
+ /**
+ * Fills an oval using the current fill paint.
+ *
+ * This method will be affected by any of the
+ * global common
+ * or fill
+ * attributes as specified in the
+ * Rendering Attributes Table.
+ *
+ *
+ * @param x the X coordinate of the upper left bound of the oval.
+ * @param y the Y coordinate of the upper left bound of the oval.
+ * @param w the width at the center of the oval.
+ * @param h the height at the center of the oval.
+ */
+ void fillOval(double x, double y, double w, double h);
+
+ /**
+ * Strokes an Arc using the current stroke paint. A {@code null} ArcType or
+ * non positive width or height will cause the render command to be ignored.
+ *
+ * This method will be affected by any of the
+ * global common
+ * or stroke
+ * attributes as specified in the
+ * Rendering Attributes Table.
+ *
+ *
+ * @param x the X coordinate of the arc.
+ * @param y the Y coordinate of the arc.
+ * @param w the width of the arc.
+ * @param h the height of the arc.
+ * @param startAngle the starting angle of the arc in degrees.
+ * @param arcExtent arcExtent the angular extent of the arc in degrees.
+ * @param closure closure type (Round, Chord, Open) or null
+ */
+ void strokeArc(double x, double y, double w, double h, double startAngle, double arcExtent, ArcType closure);
+
+ /**
+ * Fills an arc using the current fill paint. A {@code null} ArcType or
+ * non positive width or height will cause the render command to be ignored.
+ *
+ * This method will be affected by any of the
+ * global common
+ * or fill
+ * attributes as specified in the
+ * Rendering Attributes Table.
+ *
+ *
+ * @param x the X coordinate of the arc.
+ * @param y the Y coordinate of the arc.
+ * @param w the width of the arc.
+ * @param h the height of the arc.
+ * @param startAngle the starting angle of the arc in degrees.
+ * @param arcExtent the angular extent of the arc in degrees.
+ * @param closure closure type (Round, Chord, Open) or null.
+ */
+ void fillArc(double x, double y, double w, double h, double startAngle, double arcExtent, ArcType closure);
+
+ /**
+ * Strokes a polyline with the given points using the currently set stroke
+ * paint attribute.
+ * A {@code null} value for any of the arrays will be ignored and nothing will be drawn.
+ *
+ * This method will be affected by any of the
+ * global common
+ * or stroke
+ * attributes as specified in the
+ * Rendering Attributes Table.
+ *
+ *
+ * @param xPoints array containing the x coordinates of the polyline's points or null.
+ * @param yPoints array containing the y coordinates of the polyline's points or null.
+ * @param nPoints the number of points that make the polyline.
+ */
+ void strokePolyline(double xPoints[], double yPoints[], int nPoints);
+
+ /**
+ * Strokes a polygon with the given points using the currently set stroke paint.
+ * A {@code null} value for any of the arrays will be ignored and nothing will be drawn.
+ *
+ * This method will be affected by any of the
+ * global common
+ * or stroke
+ * attributes as specified in the
+ * Rendering Attributes Table.
+ *
+ *
+ * @param xPoints array containing the x coordinates of the polygon's points or null.
+ * @param yPoints array containing the y coordinates of the polygon's points or null.
+ * @param nPoints the number of points that make the polygon.
+ */
+ void strokePolygon(double[] xPoints, double[] yPoints, int nPoints);
+
+ /**
+ * Fills a polygon with the given points using the currently set fill paint.
+ * A {@code null} value for any of the arrays will be ignored and nothing will be drawn.
+ *
+ * This method will be affected by any of the
+ * global common,
+ * fill,
+ * or Fill Rule
+ * attributes as specified in the
+ * Rendering Attributes Table.
+ *
+ *
+ * @param xPoints array containing the x coordinates of the polygon's points or null.
+ * @param yPoints array containing the y coordinates of the polygon's points or null.
+ * @param nPoints the number of points that make the polygon.
+ */
+ void fillPolygon(double xPoints[], double yPoints[], int nPoints);
+
+ /**
+ * Draws an image at the given x, y position using the width
+ * and height of the given image.
+ * A {@code null} image value or an image still in progress will be ignored.
+ *
+ * This method will be affected by any of the
+ * global common
+ * or image
+ * attributes as specified in the
+ * Rendering Attributes Table.
+ *
+ *
+ * @param img the image to be drawn or null.
+ * @param x the X coordinate on the destination for the upper left of the image.
+ * @param y the Y coordinate on the destination for the upper left of the image.
+ */
+ default void drawImage(Image img, double x, double y) {
+ if (img == null || img.getProgress() < 1.0) {
+ return;
+ }
+
+ drawImage(img, x, y, img.getWidth(), img.getHeight());
+ }
+
+ /**
+ * Draws an image into the given destination rectangle of the canvas. The
+ * Image is scaled to fit into the destination rectangle.
+ * A {@code null} image value or an image still in progress will be ignored.
+ *
+ * This method will be affected by any of the
+ * global common
+ * or image
+ * attributes as specified in the
+ * Rendering Attributes Table.
+ *
+ *
+ * @param img the image to be drawn or null.
+ * @param x the X coordinate on the destination for the upper left of the image.
+ * @param y the Y coordinate on the destination for the upper left of the image.
+ * @param w the width of the destination rectangle.
+ * @param h the height of the destination rectangle.
+ */
+ default void drawImage(Image img, double x, double y, double w, double h) {
+ if (img == null || img.getProgress() < 1.0) {
+ return;
+ }
+
+ drawImage(img, 0, 0, img.getWidth(), img.getHeight(), x, y, w, h);
+ }
+
+ /**
+ * Draws the specified source rectangle of the given image to the given
+ * destination rectangle of the Canvas.
+ * A {@code null} image value or an image still in progress will be ignored.
+ *
+ * This method will be affected by any of the
+ * global common
+ * or image
+ * attributes as specified in the
+ * Rendering Attributes Table.
+ *
+ *
+ * @param img the image to be drawn or null.
+ * @param sx the source rectangle's X coordinate position.
+ * @param sy the source rectangle's Y coordinate position.
+ * @param sw the source rectangle's width.
+ * @param sh the source rectangle's height.
+ * @param dx the destination rectangle's X coordinate position.
+ * @param dy the destination rectangle's Y coordinate position.
+ * @param dw the destination rectangle's width.
+ * @param dh the destination rectangle's height.
+ */
+ void drawImage(
+ Image img,
+ double sx, double sy, double sw, double sh,
+ double dx, double dy, double dw, double dh
+ );
+
+}
diff --git a/modules/javafx.graphics/src/main/java/javafx/scene/image/WritableImage.java b/modules/javafx.graphics/src/main/java/javafx/scene/image/WritableImage.java
index 6b1440f75b7..4be7db4f5f1 100644
--- a/modules/javafx.graphics/src/main/java/javafx/scene/image/WritableImage.java
+++ b/modules/javafx.graphics/src/main/java/javafx/scene/image/WritableImage.java
@@ -29,15 +29,18 @@
import com.sun.javafx.tk.ImageLoader;
import com.sun.javafx.tk.PlatformImage;
import com.sun.javafx.tk.Toolkit;
-import javafx.beans.NamedArg;
-import javafx.beans.property.ReadOnlyObjectProperty;
-import javafx.scene.paint.Color;
+import com.sun.prism.sw.SWDrawingContext;
+import java.lang.ref.WeakReference;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.util.Objects;
+import javafx.beans.NamedArg;
+import javafx.beans.property.ReadOnlyObjectProperty;
+import javafx.scene.paint.Color;
+
/**
* The {@code WritableImage} class represents a custom graphical image
* that is constructed from pixels supplied by the application, and possibly
@@ -157,6 +160,28 @@ public WritableImage(@NamedArg("reader") PixelReader reader,
getPixelWriter().setPixels(0, 0, width, height, reader, x, y);
}
+ private WeakReference drawingContextRef;
+
+ /**
+ * Returns the {@link DrawingContext} associated with this image.
+ *
+ * @return the {@link DrawingContext} associated with this image, never {@code null}
+ */
+ public DrawingContext getDrawingContext() {
+ SWDrawingContext context = drawingContextRef == null ? null : drawingContextRef.get();
+
+ if (context == null) {
+ if (!(getWritablePlatformImage() instanceof com.sun.prism.Image img)) {
+ throw new IllegalStateException("platformImage must be a prism image");
+ }
+
+ context = new SWDrawingContext(img, rect -> bufferDirty(rect));
+ drawingContextRef = new WeakReference<>(context);
+ }
+
+ return context;
+ }
+
@Override
boolean isAnimation() {
return true;
diff --git a/tests/manual/graphics/RandomShapesDemo.java b/tests/manual/graphics/RandomShapesDemo.java
new file mode 100644
index 00000000000..910815d1c20
--- /dev/null
+++ b/tests/manual/graphics/RandomShapesDemo.java
@@ -0,0 +1,129 @@
+import java.util.Random;
+
+import javafx.animation.Animation;
+import javafx.animation.Animation.Status;
+import javafx.animation.KeyFrame;
+import javafx.animation.Timeline;
+import javafx.application.Application;
+import javafx.scene.Scene;
+import javafx.scene.canvas.Canvas;
+import javafx.scene.canvas.GraphicsContext;
+import javafx.scene.control.Button;
+import javafx.scene.control.Label;
+import javafx.scene.image.DrawingContext;
+import javafx.scene.image.ImageView;
+import javafx.scene.image.WritableImage;
+import javafx.scene.layout.BorderPane;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.VBox;
+import javafx.scene.paint.Color;
+import javafx.stage.Stage;
+import javafx.util.Duration;
+
+/**
+ * Shows a WritableImage and Canvas side by side performing
+ * the same drawing operations.
+ */
+public class RandomShapesDemo extends Application {
+ private static final int WIDTH = 400;
+ private static final int HEIGHT = 400;
+ private static final int SHAPES = 20;
+
+ private final Random rnd = new Random();
+
+ @Override
+ public void start(Stage primaryStage) {
+ WritableImage wimg = new WritableImage(WIDTH, HEIGHT);
+ DrawingContext dc = wimg.getDrawingContext();
+ ImageView imageView = new ImageView(wimg);
+ Canvas canvas = new Canvas(WIDTH, HEIGHT);
+ GraphicsContext gc = canvas.getGraphicsContext2D();
+
+ gc.setFill(Color.WHITE);
+ gc.fillRect(0, 0, WIDTH, HEIGHT);
+
+ dc.setFill(Color.WHITE);
+ dc.fillRect(0, 0, WIDTH, HEIGHT);
+
+ drawRandomShapes(dc, gc);
+
+ BorderPane root = new BorderPane();
+ Label label = new Label("WritableImage in ImageView");
+ Label label2 = new Label("Canvas");
+
+ label.setStyle("-fx-text-fill: white; -fx-font-weight: bold");
+ label2.setStyle("-fx-text-fill: white; -fx-font-weight: bold");
+
+ HBox hbox = new HBox(10, new VBox(label, imageView), new VBox(label2, canvas));
+ Button button = new Button("Toggle Animation");
+
+ Timeline timeline = new Timeline(
+ new KeyFrame(Duration.ZERO, e -> addRandomShape(dc, gc)),
+ new KeyFrame(Duration.millis(200))
+ );
+
+ timeline.setCycleCount(Animation.INDEFINITE);
+
+ button.setOnAction(e -> {
+ if(timeline.getStatus() == Status.RUNNING) {
+ timeline.stop();
+ }
+ else {
+ timeline.playFromStart();
+ }
+ });
+
+ hbox.setSpacing(10);
+ hbox.setStyle("-fx-background-color: BLACK; -fx-border-width: 10; -fx-border-color: BLACK;");
+
+ root.setTop(button);
+ root.setCenter(hbox);
+
+ Scene scene = new Scene(root);
+
+ primaryStage.setScene(scene);
+ primaryStage.setTitle("Random Shapes: WritableImage vs Canvas");
+ primaryStage.show();
+ }
+
+ private void drawRandomShapes(DrawingContext ctx, GraphicsContext gc) {
+ for (int i = 0; i < SHAPES; i++) {
+ addRandomShape(ctx, gc);
+ }
+ }
+
+ private void addRandomShape(DrawingContext dc, GraphicsContext gc) {
+ Color randomFill = randomColor();
+ Color randomStroke = randomColor();
+ double width = rnd.nextDouble() * 10;
+
+ dc.setFill(randomFill);
+ dc.setStroke(randomStroke);
+ dc.setLineWidth(width);
+ gc.setFill(randomFill);
+ gc.setStroke(randomStroke);
+ gc.setLineWidth(width);
+
+ double x = rnd.nextDouble() * WIDTH;
+ double y = rnd.nextDouble() * HEIGHT;
+ double w = 20 + rnd.nextDouble() * 80;
+ double h = 20 + rnd.nextDouble() * 80;
+
+ if (rnd.nextBoolean()) {
+ dc.fillRect(x, y, w, h);
+ gc.fillRect(x, y, w, h);
+ }
+ else {
+ dc.strokeOval(x, y, w, h);
+ gc.strokeOval(x, y, w, h);
+ }
+ }
+
+ private Color randomColor() {
+ return Color.color(rnd.nextDouble(), rnd.nextDouble(), rnd.nextDouble(), rnd.nextDouble());
+ }
+
+ public static void main(String[] args) {
+ launch(args);
+ }
+}