Skip to content

JNode Graphics2D

opencode-agent[bot] edited this page Sep 27, 2026 · 2 revisions

JNode Graphics2D

Graphics2D implementation with hardware acceleration support in JNode's AWT peer layer.

Overview

JNode provides two Graphics2D implementations for AWT component rendering:

  • Current: JNodeSurfaceGraphics2D — extends SurfaceGraphics2D, delegates drawing operations to a Surface abstraction
  • Deprecated: JNodeGraphics2D — extends AbstractSurfaceGraphics2D (which extends GNU Classpath's AbstractGraphics2D), uses an internal BufferedImage for software rendering

Both implementations bridge the standard Java 2D API to JNode's video driver layer via the Surface interface.

Class Hierarchy

java.awt.Graphics2D
├── SurfaceGraphics2D (abstract, gui/src/awt/org/jnode/awt/util/)
│   └── JNodeSurfaceGraphics2D (gui/src/awt/org/jnode/awt/)
└── gnu.java.awt.java2d.AbstractGraphics2D (GNU Classpath)
    └── AbstractSurfaceGraphics2D (abstract, deprecated, gui/src/awt/org/jnode/awt/util/)
        └── JNodeGraphics2D (deprecated, gui/src/awt/org/jnode/awt/)

Rendering Pipeline

JNodeSurfaceGraphics2D (Current)

JNodeSurfaceGraphics2D wraps an AbstractSurface (from the video driver layer) and delegates all rendering through it:

  1. getDestinationRaster() lazily creates a BufferedImage (TYPE_INT_ARGB) sized to the component
  2. Drawing operations (fill, draw, drawString) delegate to surface.draw(...), surface.fill(...), or FontManager
  3. updateRaster() blits the BufferedImage back to the surface via drawImage(image, 0, 0, null)

This is a double-buffering approach: rendering writes to an offscreen BufferedImage, and updateRaster flushes it to the physical surface.

JNodeGraphics2D (Deprecated)

JNodeGraphics2D uses the older AbstractSurfaceGraphics2D base:

  1. getDestinationRaster() lazily creates a BufferedImage (TYPE_INT_ARGB)
  2. Drawing operations (rawDrawLine, rawFillRect, rawDrawString) directly call surface methods
  3. updateRaster() calls rawUpdateRaster() to flush the raster to the surface

AbstractSurfaceGraphics2D also manages rendering modes (PAINT_MODE vs XOR_MODE) via Surface.PAINT_MODE/Surface.XOR_MODE.

Surface Integration

The Surface interface (org.jnode.driver.video.Surface) is the core abstraction for paintable 2D regions:

  • FrameBuffer-backed: Physical video memory or memory-mapped framebuffer
  • BufferedImage-backed: In-memory image buffer used as rendering target

Key surface operations used by Graphics2D:

  • surface.drawCompatibleRaster(...) — blit a raster to the surface
  • surface.fillRect(...) / surface.drawLine(...) — primitive drawing
  • surface.copyArea(...) — fast copy for copyArea
  • surface.draw(...) — shape rendering via BasicSurfaceGraphics
  • surface.getColorModel() — get the ColorModel for raster compatibility

Font Rendering

Both Graphics2D implementations route text rendering through JNodeToolkit.getFontManager():

tk.getFontManager().drawText(surface, getClip(), getTransform(), text, font, x, y, getColor());

The FontManager (org.jnode.awt.font.FontManager) dispatches to TrueType (TTFFontData) or BDF (BDFFontContainer) renderers. See Font-Rendering for details.

Rendering Modes

AbstractSurfaceGraphics2D maintains a mode field:

  • Surface.PAINT_MODE — normal opaque paint (set via setPaintMode())
  • Surface.XOR_MODE — XOR composite mode for fast drawing (set via setXORMode(Color))

Only JNodeSurfaceGraphics2D supports full Graphics2D state (AffineTransform, Composite, Stroke, Paint, RenderingHints).

Color Model and Bit-Depth

JNodeToolkit.getColorModel() provides the ColorModel for the current display configuration. Graphics2D classes use this to:

  • Validate raster compatibility (isCompatibleRaster)
  • Convert between pixel formats when blitting images

The video driver layer supports multiple bit-depths (8bpp indexed, 16bpp, 24bpp, 32bpp), and the color model adapts accordingly.

Key Helper Classes

Class Role
BasicSurfaceGraphics Simple Graphics implementation delegating to Surface, handles origin translation, clipping, color, font
SurfaceGraphics2D Full Graphics2D state (transform, composite, stroke, paint, hints) delegating simple operations to BasicSurfaceGraphics
AbstractSurface Base for surface implementations in org.jnode.driver.video.util
BufferedImageSurface In-memory surface wrapping BufferedImage
GraphicsFactory Factory for creating appropriate Graphics2D instances per component type

Window Paint Clipping

When a JInternalFrame is occluded, Swing still repaints its full bounds — JNode has no per-component damage tracking. Without clipping, the lower frame's pixels are painted over the frames stacked above it, which is what users see as windows "bleeding" through each other.

JNode fixes this with a list of visible regions in desktop coordinates, computed per window from the JDesktopPane z-order and intersected with each image blit. null means no restriction (the fast path); an empty list means fully covered — draw nothing.

Computing the Regions — SwingToolkit

gui/src/awt/org/jnode/awt/swingpeers/SwingToolkit.java:437-487:

List<Rectangle> getWindowPaintRegions(JInternalFrame source) {
    if (source == null || source instanceof SwingWindow) return null;   // :438-440
    final JDesktopPane desktop = source.getDesktopPane();
    if (desktop == null) return null;                                    // :441-444
    final int sourceZ = desktop.getComponentZOrder(source);
    if (sourceZ < 0) return null;                                        // :445-448
    final List<Rectangle> occlusions = new ArrayList<Rectangle>();
    for (JInternalFrame frame : desktop.getAllFrames()) {                // :450-459
        if (frame == source || !frame.isVisible() || frame.isIcon()) continue;
        final int frameZ = desktop.getComponentZOrder(frame);
        if (frameZ >= 0 && frameZ < sourceZ) occlusions.add(frame.getBounds());
    }
    ...
}

Swing's z-order is index-based with lower index on top, so frameZ < sourceZ is the "in front of me" test. Iconified and invisible frames are excluded. The source bounds are then clipped to the desktop pane (:460-466); an empty intersection returns an empty list. With no occluders and no desktop clipping the method returns null (:467-476).

private static void subtract(Rectangle region, Rectangle occlusion, List<Rectangle> result) (:489-512) does the region algebra: non-intersecting regions pass through unchanged; an intersecting region is decomposed into at most four rectangles (top band, bottom band, left band, right band) from left/right/top/bottom intersection bounds. Applied iteratively at :477-486:

List<Rectangle> regions = new ArrayList<Rectangle>();
regions.add(sourceBounds);
for (Rectangle occlusion : occlusions) {
    final List<Rectangle> remaining = new ArrayList<Rectangle>();
    for (Rectangle region : regions) { subtract(region, occlusion, remaining); }
    regions = remaining;
}
return regions;

The parameter type is JInternalFrame, not SwingBaseWindow — SwingBaseWindow<awtT, …> extends JInternalFrame (SwingBaseWindow.java:52-53), so the narrower type is legal and lets tests pass a plain JInternalFrame with no display.

Plumbing It In — SwingComponentPeer

gui/src/awt/org/jnode/awt/swingpeers/SwingComponentPeer.java:

  • New private SwingBaseWindow getWindowAncestor() (:156-162) walks peerComponent.getParent() until it hits a SwingBaseWindow.
  • getGraphics() (:164-207) applies the clip after the existing translate/clipRect:
    g.translate(x, y);
    g.clipRect(0, 0, width, height);
    if (g instanceof SurfaceGraphics2D) {
        final SwingBaseWindow window = getWindowAncestor();
        if (window != null) {
            ((SurfaceGraphics2D) g).setPaintClip(toolkit.getWindowPaintRegions(window));
        }
    }
  • The instanceof SurfaceGraphics2D guard matters. getGraphics() builds the graphics as (:182-183):
    Graphics g = SystemProperties.getProperty("gnu.javax.swing.noGraphics2D") == null ?
        new JNodeGraphics2D(this) : GraphicsFactory.getInstance().createGraphics(this);
    JNodeGraphics2D extends AbstractSurfaceGraphics2D extends AbstractGraphics2D — not a SurfaceGraphics2D, so the clip is silently dropped on that branch. But JNode sets res.put("gnu.javax.swing.noGraphics2D", "true") at core/src/core/org/jnode/vm/VmSystem.java:339, so on JNode the factory always returns a JNodeSurfaceGraphics2D and the clip is live in production.
  • DesktopFramePeer.getGraphics() (DesktopFramePeer.java:320-323) is not given a paint clip.

Storing and Consuming It — BasicSurfaceGraphics

gui/src/awt/org/jnode/awt/util/BasicSurfaceGraphics.java:

Member Line Role
private List<Rectangle> paintClip :59 the region list; null = unrestricted
copy constructor BasicSurfaceGraphics(BasicSurfaceGraphics g) :65-75 deep-copies the regions so Graphics.create() preserves the clip
public void setPaintClip(List<Rectangle> regions) :77-88 null → unrestricted; empty list → paint nothing; otherwise defensive copy dropping null/zero-area regions
private List<Rectangle> clipToPaint(Rectangle bounds) :90-103 paintClip == null → [bounds]; else region.intersection(bounds) for each non-empty result

SurfaceGraphics2D.setPaintClip(List<Rectangle>) (:990-992) just forwards. It is applied in exactly two places — drawImage(Image, int, int, Color, ImageObserver) at :513-535 and drawImage(Image, int, int, ImageObserver) at :570-592:

if (!r.isEmpty()) {
    final List<Rectangle> regions = clipToPaint(r);
    for (Rectangle region : regions) {
        surface.drawCompatibleRaster(rast, region.x - tx, region.y - ty,
            region.x, region.y, region.width, region.height, bgcolor);
        surface.update(region.x, region.y, region.width, region.height);
    }
}

tx/ty are the pre-clip transformed origin captured at :519 / :576, so each sub-rectangle's source offset stays correct. All drawImage overloads funnel into these two — the scaled ones at :636 / :684 delegate after AreaAveragingScaleFilter, the sub-rectangle ones at :273 / :336-408 after CropImageFilter.

paintClip is not consulted by clearRect (:125), copyArea (:155), fillRect, or SurfaceGraphics2D's own drawImage/draw/fill. Only image blitting is clipped.

Opaque Color Fast Path in fillRect

SurfaceGraphics2D.fillRect (:1002-1055):

public void fillRect(int x, int y, int width, int height) {
    if (paint == null || (paint instanceof Color && ((Color) paint).getAlpha() == 255)) {
        simpleGraphics.fillRect(x, y, width, height);        // fast: direct surface fillRect
    } else {
        x = x + simpleGraphics.origin.x;
        y = y + simpleGraphics.origin.y;
        BufferedImage img = getFillerImage(x, y, width, height);
        drawImage(img, x, y, background, null);              // slow: PaintContext -> image -> blit
    }
}

Previously only paint == null took the fast path, so any setPaint(new Color(r,g,b)) — which also propagates to simpleGraphics.setColor at :766-774 — took the slow route: getFillerImage (:1057-1081) builds a PaintContext over a hardcoded 800×600 device rect, rasterises per-pixel in Java, allocates a BufferedImage, and blits it. Now an opaque Color is treated as a solid fill. getFillerImage is still used for gradients and TexturePaint.

Gotchas

  • Deprecated path: JNodeGraphics2D / AbstractSurfaceGraphics2D are marked @deprecated and may be removed
  • Missing implementations: JNodeSurfaceGraphics2D has stub implementations for clip(Shape), drawRenderedImage, drawRenderableImage, drawImage(AffineTransform), drawImage(BufferedImageOp), hit(), shear() — all emit debug messages via Unsafe.debug
  • getDeviceConfiguration() returns null in JNodeSurfaceGraphics2D
  • Double buffering overhead: The current implementation renders to a BufferedImage then blits to the surface, adding a copy per update cycle
  • Clip transformation: BasicSurfaceGraphics handles origin translation; JNodeSurfaceGraphics2D applies an AffineTransform from the graphics origin for shape operations
  • Lower z-order index means on top — the opposite of the usual intuition. Every occlusion test in SwingToolkit and WindowBar uses otherZ < frameZ to mean "in front"
  • Paint clipping only applies to SurfaceGraphics2D — the JNodeGraphics2D branch silently drops setPaintClip
  • Paint clipping only applies to drawImage — fillRect, draw, clearRect and copyArea are not clipped
  • getFillerImage assumes 800×600 — a hardcoded device rect that is wrong at any other resolution
  • Unclamped drawPixels/fillRect corrupts neighbouring scanlines — see Video-Driver-Architecture

Related Classes

  • gui/src/awt/org/jnode/awt/swingpeers/SwingToolkit.java — getWindowPaintRegions(), activateWindow(), AWT root repaint
  • gui/src/awt/org/jnode/awt/swingpeers/SwingComponentPeer.java — per-component getGraphics() that installs the paint clip
  • gui/src/awt/org/jnode/awt/util/AbstractBitmapGraphics.java — drawPixels/fillRect clipping that prevents framebuffer corruption
  • gui/src/awt/org/jnode/awt/JNodeGenericPeer.java — the AWT peer base; creates Graphics2D instances via GraphicsFactory
  • gui/src/awt/org/jnode/awt/JNodeToolkit.java — provides getGraphics() returning the AbstractSurface, color model, and font metrics
  • gui/src/awt/org/jnode/awt/util/BasicSurfaceGraphics.java — simple Graphics implementation underlying both Graphics2D classes, and the owner of paintClip
  • gui/src/awt/org/jnode/awt/util/AbstractSurfaceGraphics2D.java — deprecated base with direct surface rendering
  • gui/src/driver/org/jnode/driver/video/ — video driver layer with Surface interface and implementations

Clone this wiki locally