Java2D under SWT: coercing Graphics2D functionality into SWT

Context (or skip to solution ūüôā )

I am currently working on a medium-scale personal project in Electronics Design Automation. I am usually very keen on building scripts and programs to make board-level design tasks faster for me and others. This time around, I am designing a Package Generator program to generate footprints for different CAD packages according to IPC7351A specifications or user-provided rules. The goal over time is to support EAGLE and Altium DXP at the very least.

During the design phase, I decided early on to use the Eclipse’s SWT (Standard Widget Toolkit) instead of Swing for the GUI of my application. The reasons for this will be covered in a later entry about SWT.

My application calls for a significant amount of custom rendering so that a footprint preview control is available. Since footprints are 2D CAD drawings with a lot of detail and geometrical transforms apply, I needed a graphical framework that would make it easy.

Basic screenshot of Java2D control in PackageGen

Issues with SWT’s graphics package

While browsing tutorials and books for SWT, I realized that SWT’s org.eclipse.swt.graphics package is really immature compared to the rest of the framework. Here is a short list of the shortcomings I have found that affected my application:

  • Affine transforms for arbitrary shape drawing are barely discussed and poorly documented.
  • Paths in the drawing system (equivalent to the Shape class of Java2D) are awkward to use.
  • Transformed (rotated/scaled) fonts are affected by explicit issues in SWT’s font API.
    • Fractionnal point sizes are not supported.
    • String drawing uses the “point size” as the user-space transformed size.
It was obvious at that point that if I could find an easy way to use Java2D under SWT, it was the way to go. It did take several days of research and trials to evaluate all the possibilities, but I have found my way ūüôā

Solution: methods of using Java2D under SWT

During my trials, I found four different methods of using Java2D under SWT, each with its advantages and shortcomings. I list them here in the order in which I implemented them:

  1. BufferedImage rendering on SWT graphics context (GC)
  2. JFreeChart.org’s experimental wrapping of the GC into a Graphics2D object
  3. JPanel rendering under the AWT/SWT bridge with helper classes
  4. J2D4SWT rendering package from Holongate.org

In the end, I implemented J2D4SWT and I am really satisfied.

Method 1: BufferedImage rendering on SWT graphics context (GC)

This method is probably the simplest idea for integrating Java2D functionnality under SWT. It is well described¬†in “Java 2D imaging for the Standard Widget Toolkit” at IBM DeveloperWorks. The article also contains a sample helper class (Graphics2DRenderer) to start using the method immediately.

The idea behind this method is simple: we use an AWT offscreen BufferedImage for rendering and then transfer the pixels to an SWT GC. The BufferedImage class in the java.awt.image package contains a createGraphics() method that returns a Graphics2D context on which to draw. The buffered image also allows access to its raster and the data is copied pixel-by-pixel to the SWT Image. The Graphics2DRenderer class automates all of this and wraps the AWT BufferedImage and SWT Image under the hood.

Of course, since this method implies pixel blits entirely in Java, it is extremely slow. The concept is good enough to be reused in a much more optimized way by the J2D4SWT library (see below).

Advantages:

  • Simplest method to use
  • Only a single Java class required (and freely available, sources included)
  • No interference with SWT-only rendering
  • Completely transparent

Shortcomings:

  • Very slow compared to native SWT GC methods¬†

Method 2:¬†JFreeChart.org’s experimental wrapping of the GC into a Graphics2D object

The JFreeChart project develops a full-featured plotting and charting library in Java with Swing controls. Over the years, there has been experimental work to allow some integration with SWT. Part of these efforts were aimed at adapting the Graphics2D class to the¬†SWT GC. The method they have chosen is to reimplement all the main calls of Graphics2D by adapting them to equivalent org.eclipse.swt.graphics.GC. The idea is very good since it only requires a small amount of highly-targeted adapter classes. However, because of some of the shortcomings specific to SWT’s GC (especially fonts), there remains some porting issues.

JFree’s current implementation is still experimental and can be found in the org.jfree.experimental.swt package on SourceForge. It is mostly undocumented, but really easy to use. The adaptation is remarkably elegant, considering that the implementation uses conversions of Fonts, AffineTransforms and Shapes to their equivalent SWT version (Font, Transform and Path).

As an example, with the SWTGraphics2D class, you could fill a canvas with a black rectangle using Java2D as follows:

drawCanvas.addPaintListener(new PaintListener() {
    @Override
    public void paintControl(PaintEvent event) {
        // Get size of source control
        Point controlSize = ((Control) event.getSource()).getSize();

        // create a g2d instance on the GC
        SWTGraphics2D g2d = new SWTGraphics2D(event.gc);

        // Reinit transform
        g2d.getTransform().setToIdentity();

        // Fill background in back
        g2d.setPaint(Color.BLACK);
        g2d.setColor(Color.BLACK);
        g2d.fillRect(0, 0, (controlSize.x, controlSize.y));
    }
});

This method is very quick’and’dirty if you need to do some simple Java2D drawing under SWT or prototype a previous custom control on SWT before porting. It still needs some work but since you have the sources, you can fix the bugs rather easily.

Advantages:

  • Very fast rendering, even with Graphics2D methods
  • Only three Java classes required (and freely available, sources included)
  • No interference with SWT-only rendering
  • Completely transparent

Shortcomings:

  • Very experimental
  • Many Graphics2D methods are not implemented (ie: rotate(double theta, double x, double y))
  • Some glitches in font rendering (sizing issues) and Shape conversions (misalignments)
  • Still buggy and requires changes to Java2D code to adapt to bugs

Method 3: JPanel rendering under the AWT/SWT bridge with helper classes

The AWT/SWT bridge introduced in SWT 3.0 allows one to integrate AWT/Swing widgets into SWT. Performance is much better than the BufferedImage rendering method and native AWt/Swing events are supported. Using the bridge, you can thus add a simple JPanel on any SWT Composite and perform custom painting in the paintComponent() method. At this point, you are actually using Swing directly and SWT handles the rest. There is an excellent article on Swing/SWT Integration by Gordon Hirsch at the Eclipse Corner. This article covers all the specifics of using the SWT/AWT bridge and also provides an integration example with full Javadoc (used in the code below). The following snippet shows how to add a JPanel to your SWT application:

// Native composite creation as child of myComposite

EmbeddedSwingComposite drawCanvas =
        new EmbeddedSwingComposite(myComposite, SWT.NULL) {
    protected JComponent createSwingComponent() {
        return new JPanel() {
            protected void paintComponent(Graphics g) {
                Graphics2D g2d = (Graphics2D)g;
                // ...
                // Your custom paint operations go here
                // ...
            }
        };
    }
};
drawCanvas.populate();

This would seem like the best way to go. However, if you think about it, you are using a full AWT event thread and native controls just to be able to draw using Java2D. If your application already calls for Swing components integration, this is great. If you rather want to be 100% SWT, then this method is overkill because it adds a high level of difficulty in event handling under SWT. When a Swing component is bridged, getting the events under SWT is a nightmare. Most event handling must be done within the Swing event model, which adds complexity (and Swing-ness) to your project.

Advantages:

  • Uses Swing/AWT natively for Java2D (no pixel-blitting/redrawing)
  • Allows the use of composed Swing components
  • Necessitates only small amounts of code

Shortcomings:

  • Event handling within SWT is almost impossible
  • Method is overkill when only Java2D is required and not the rest of Swing
  • Hard to use and riddled with gotchas if you don’t use an integration example

Method 4: J2D4SWT rendering package from Holongate.org

After some research, I found the J2D4SWT package by Christophe Avare. Its architecture is meant to allow the use of Java2D seamlesly with SWT. Internally, it uses the offscreen rendering method. To increase performance, it includes some native accelerations under Win32-x86, GTK-x86 and Motif-x86. Since it is an open-source project, the acceleration code can be recompiled on other targets. 

The J2D4SWT package not only contains the Java2D encapsulation API, but also some optimization schemes for the repainting of controls. When a native acceleration is not present, the implementation uses an SWT-only version that is slower but fully portable accross all platforms.

An IPaintable interface is provided to ease the implementation of Java2D-aware SWT canvases. The main class that can be used to enable Java2D on SWT with J2D4SWT is J2DCanvas which derives from SWT’s Canvas. It is completely native but uses the accelerated drawing primitives and an associated IPaintable interface to render the contents on repaints.

Main interfaces diagram from J2D4SWT
Main interfaces diagram from J2D4SWT

Available canvases for J2D4SWT
Available canvases for J2D4SWT

There are several related libraries on the holongate.org website, including SVG support with the Batik library and a Draw2D/GEF integration to use Java2D as the Draw2D renderer.

Overall, the J2D4SWT library is the easiest to use and fastest method out of the 4 that I have tried. It is very well documented (full Javadoc) and elegant in design, encompassing additionnal detail beyond simple Java2D implementation.

Advantages:

  • Very easy to use
  • Native acceleration on the most popular platforms
  • Unified SWT/Java2D canvas class
  • Completely transparent
  • Highly compatible with Java2D (no glitches)

Shortcomings:

  • Acceleration requires a target-specific library

Conclusion

As you can see, the problem of using Java2D under SWT has been well studied before and there are many ways to make the most of SWT while not leaving Java2D behind. In forthcoming entries, I will discuss solutions to a functional bug in native scrollbars as well as an overview of resources for learning SWT.

One thought on “Java2D under SWT: coercing Graphics2D functionality into SWT

Leave a Reply