From ffb5020d4ab154904fdc7706ab4cab3fbf5c7e17 Mon Sep 17 00:00:00 2001 From: Tue Ton <49886739+chirontt@users.noreply.github.com> Date: Sun, 16 Aug 2026 15:58:36 -0400 Subject: [PATCH] add Java2D/Graphics2D example --- examples/graphics2d/README.md | 9 +++ .../graphics2d/java/EventFrameGraphics2D.java | 26 +++++++ examples/graphics2d/java/Example.java | 67 +++++++++++++++++++ .../java/LayerRasterGraphics2D.java | 47 +++++++++++++ .../graphics2d/java/NativeIntDataBuffer.java | 33 +++++++++ script/run.py | 2 +- 6 files changed, 183 insertions(+), 1 deletion(-) create mode 100644 examples/graphics2d/README.md create mode 100644 examples/graphics2d/java/EventFrameGraphics2D.java create mode 100644 examples/graphics2d/java/Example.java create mode 100644 examples/graphics2d/java/LayerRasterGraphics2D.java create mode 100644 examples/graphics2d/java/NativeIntDataBuffer.java diff --git a/examples/graphics2d/README.md b/examples/graphics2d/README.md new file mode 100644 index 00000000..04d31416 --- /dev/null +++ b/examples/graphics2d/README.md @@ -0,0 +1,9 @@ +# Java2D/Graphics2D drawing example + +JWM raster example working with the standard `Java2D/Graphics2D` as a graphics-drawing library. + +This example shows some text and an orange circle at the center of the main window. + +Requires Java 22+ to compile/run, as it uses FFM +([Foreign Function & Memory](https://docs.oracle.com/en/java/javase/25/core/foreign-function-and-memory-api.html)) +to access/share the screen pixels' native memory buffers created by JWM. diff --git a/examples/graphics2d/java/EventFrameGraphics2D.java b/examples/graphics2d/java/EventFrameGraphics2D.java new file mode 100644 index 00000000..4fc7abcb --- /dev/null +++ b/examples/graphics2d/java/EventFrameGraphics2D.java @@ -0,0 +1,26 @@ +package io.github.humbleui.jwm.examples; + +import java.awt.image.BufferedImage; + +import io.github.humbleui.jwm.Event; + +/// Frame event for Java2D/Graphics2D graphics-drawing library. +/// +public class EventFrameGraphics2D implements Event { + + private final BufferedImage bufferedImage; + + public EventFrameGraphics2D(BufferedImage bufferedImage) { + this.bufferedImage = bufferedImage; + } + + public BufferedImage getBufferedImage() { + return bufferedImage; + } + + @Override + public String toString() { + return "EventFrameGraphics2D(bufferedImage=" + System.identityHashCode(bufferedImage) + ")"; + } + +} diff --git a/examples/graphics2d/java/Example.java b/examples/graphics2d/java/Example.java new file mode 100644 index 00000000..fa9acd63 --- /dev/null +++ b/examples/graphics2d/java/Example.java @@ -0,0 +1,67 @@ +package io.github.humbleui.jwm.examples; + +import io.github.humbleui.jwm.*; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.util.function.Consumer; + +/// An example of JWM working with Java2D/Graphics2D as graphics-drawing library. +/// +/// This example shows some text and an orange circle at the center of the main window. +/// +public class Example implements Consumer { + + private Window window; + + public Example(int width, int height) { + window = App.makeWindow(); + window.setEventListener(this); + window.setTitle("JWM with Java2D/Graphics2D Fixed Scale"); + + window.setLayer(new LayerRasterGraphics2D()); + + window.setWindowSize(width, height); + window.setVisible(true); + } + + @Override + public void accept(Event e) { + if (e instanceof EventWindowCloseRequest) { + window.close(); + App.terminate(); + } + else if (e instanceof EventFrameGraphics2D eg) { + var image = eg.getBufferedImage(); + var g2d = image.createGraphics(); + drawJava2DScene(g2d, image.getWidth(), image.getHeight()); + g2d.dispose(); + } + } + + private void drawJava2DScene(Graphics2D g2d, int w, int h) { + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + + // Clear background + g2d.setColor(new Color(30, 30, 30)); + g2d.fillRect(0, 0, w, h); + + // Draw an orange circle + int circleSize = Math.min(w, h) / 2; + int x = (w - circleSize) / 2; + int y = (h - circleSize) / 2; + + g2d.setColor(Color.ORANGE); + g2d.fillOval(x, y, circleSize, circleSize); + + // Draw some text + g2d.setColor(Color.WHITE); + g2d.drawString("Hello from High-DPI Correlated Java2D!", 50, 50); + } + + public static void main(String[] args) { + App.start(() -> new Example(800, 600)); + } + +} diff --git a/examples/graphics2d/java/LayerRasterGraphics2D.java b/examples/graphics2d/java/LayerRasterGraphics2D.java new file mode 100644 index 00000000..a4476176 --- /dev/null +++ b/examples/graphics2d/java/LayerRasterGraphics2D.java @@ -0,0 +1,47 @@ +package io.github.humbleui.jwm.examples; + +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.DataBuffer; +import java.awt.image.Raster; +import java.awt.image.SinglePixelPackedSampleModel; + +import io.github.humbleui.jwm.LayerRaster; + +/// Raster layer implementation for Java2D/Graphics2D graphics-drawing library. +/// +public class LayerRasterGraphics2D extends LayerRaster { + + private BufferedImage renderBuffer; + + @Override + public void frame() { + // re-initialize the raster buffer if needed + initRasterBuffer(getWidth(), getHeight()); + _window.accept(new EventFrameGraphics2D(renderBuffer)); + swapBuffers(); + } + + private void initRasterBuffer(int width, int height) { + if (width <= 0 || height <= 0) return; + + // If the buffer size hasn't changed, skip re-allocation + if (renderBuffer != null && renderBuffer.getWidth() == width && renderBuffer.getHeight() == height) { + return; + } + + // Use custom DataBuffer wrapper around the JWM native pixel buffer + var nativeDataBuffer = new NativeIntDataBuffer(getPixelsPtr(), width * height); + + // Construct a Custom WritableRaster linking to native memory buffer + int[] bandMasks = {0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000}; // RGBA masks + var sampleModel = new SinglePixelPackedSampleModel( + DataBuffer.TYPE_INT, width, height, width, bandMasks + ); + var raster = Raster.createWritableRaster(sampleModel, nativeDataBuffer, null); + + // Build the BufferedImage for rendering + renderBuffer = new BufferedImage(ColorModel.getRGBdefault(), raster, true, null); + } + +} diff --git a/examples/graphics2d/java/NativeIntDataBuffer.java b/examples/graphics2d/java/NativeIntDataBuffer.java new file mode 100644 index 00000000..4a6eee62 --- /dev/null +++ b/examples/graphics2d/java/NativeIntDataBuffer.java @@ -0,0 +1,33 @@ +package io.github.humbleui.jwm.examples; + +import static java.lang.foreign.ValueLayout.JAVA_INT; + +import java.awt.image.DataBuffer; +import java.lang.foreign.MemorySegment; + +/// Custom DataBuffer wrapping a native memory segment +/// as backing data "array". +/// +public class NativeIntDataBuffer extends DataBuffer { + + private final MemorySegment segment; + + public NativeIntDataBuffer(long dataIntPtr, int size) { + super(DataBuffer.TYPE_INT, size); + this.segment = MemorySegment.ofAddress(dataIntPtr) + .reinterpret(size * JAVA_INT.byteSize()); + } + + @Override + public int getElem(int bank, int i) { + // Read directly from native memory space + return segment.getAtIndex(JAVA_INT, i); + } + + @Override + public void setElem(int bank, int i, int val) { + // Write directly to native memory space + segment.setAtIndex(JAVA_INT, i, val); + } + +} diff --git a/script/run.py b/script/run.py index 69e034cc..650a0341 100755 --- a/script/run.py +++ b/script/run.py @@ -56,7 +56,7 @@ def main(): 'examples/dashboard/resources' ] sources = build_utils.files(f'examples/{args.example}/java/**/*.java') - build_utils.javac(sources, f'examples/{args.example}/target/classes', classpath = classpath, release='16') + build_utils.javac(sources, f'examples/{args.example}/target/classes', classpath = classpath, release='22') # run subprocess.check_call([