Sven's thought on the 2D peers.

Splitting up Graphics2D

Graphics2D is currently a big mess, since it does a lot of different (and not wholly related) stuff. Graphics2D is itself an abstract class, which is appropriate since it represents an abstract drawing context. GNU Classpath on the other hand, is at the moment trying to get away with having one big Graphics2D implementation for everything. It not a good idea, since it makes for unclean code and unclear seperations of the different context-specific stuff.

Graphics2D is implemented on Cairo. Cairo has an abstract drawing context object too, cairo_t. So the obvious thing to do here is to have a generic CairoGraphics2D implementation, which draws to a cairo_t and handles the translation of java graphics calls to Cairo calls, handles keeping track of the current brush and color and such generic stuff. This class should then be subclassed for the different specific drawing contexts which exist (and which currently are handled in the one GdkGraphics2D class). These are:

AWT 1.1 imaging

In AWT 1.1 Image was abstract, and the implementation was purposefully hidden from the program in order to support fast native image routines. This unfortunately also meant that useful stuff like random-access to pixel data wasn't possible, and that bugged a lot of people.

Basically, AWT 1.1 imaging works like the following: An Image can be created in two ways, either with Toolkit.createImage() or Component.createImage().

Toolkit.createImage() is for creating and image from a file, URL, ImageProducer or other source. The resulting image is immutable (getGraphics() doesn't work on it.) This corresponds well to a GdkPixbuf (which aren't easy to draw to, but have good routines for drawing and loading from files.), and that is implemented in GtkImage. The image may be in any pixel format.

Component.createImage() creates an image in the native pixel format of the current display, just like GdkPixmap. (an X pixmap). You can draw to it (getGraphics() works). The main purpose of this kind of bitmap is for double-buffers. This is also implemented by GtkImage in Classpath.

Image also has the nifty feature that it supports animated GIFs. This is not yet supported in Classpath, but it's completely doable within the framework of the existing GtkImage class and GdkPixbuf. When an animated GIF is drawn to the screen, a seperate thread will continue to swap frames and send FRAMEBITS notifications to the ImageObserver included in the drawImage call for each frame. (triggering a repaint on components, leading to the repaint of the new frame)

GdkPixbufDecoder

Currently Toolkit.createImage() is implemented on top of GdkPixbufDecoder, this class uses GdkPixbuf and creates either a GtkImage or a BufferedImage, depending on if you're using Graphics2D or not. I regard this as a bit redundant. BufferedImage loading should be handled by ImageIO (which normally returns BufferedImages), and the loading of GtkImages can be handled by that class directly instead. (which is necessary anyway for proper animated-gif support.)

BufferedImage

In 1.2 BufferedImage was introduced. It does allow random pixel-access (although through several layers of abstraction). It always allows drawing, and getGraphics() returns a Graphics2D context. Image files are loaded into BufferedImages, and they are also used for component double-buffering.

We thus need to be able to draw a BufferedImage on a Cairo context quickly, but also to be able to create a Cairo context for drawing _onto_ a BufferedImage. Since the latter case is the trickier one, we should concentrate on what that requires.

Since java arrays are not required to be at a specific adress, and are not necessarily writable from native code, storing the BufferedImage pixels in java arrays will, in the worst case, require you to update by copying the entire pixel buffer from native to java memory after each drawing operation. Also, the cairo_t context will need to be recreated on every drawing operation. That means a lot of overhead.

The only proper solution, as I see it, is to store the pixel data in native memory, and only copy to the 'java side' when accessed through the methods in the DataBuffer classes. (I belive the purpose of these DataBuffer classes is exactly this, to wrap the native memory buffers.)

The java.awt.image classes support a wide number of pixel data formats, as well as allowing for certain user-defined data formats. Cairo does not support user-defined data formats, or all the predefined data formats used by BufferedImage. Optimally, we'd like Cairo to support user-defined pixel formats just like Java does, but that isn't realistic in the near future. What we do need and want is for Cairo to support all the predefined pixel formats which BufferedImage defines, which should cover most cases of use anyway. I view copying and converting pixel data on drawing operations to user-defined buffers to be an acceptable compromise as long as we can draw natively and quickly to the predefined types.

Basically creating a Graphics2D for a BufferedImage should then consist of grabbing the size, pixel format and pointer to its DataBuffer, wrap that with a cairo_t (which perhaps could even be cached) and create a Graphics2D context for that cairo_t.

Component double-buffering and BufferedImages

Idea: Conversely, if we can get a native buffer from a BufferedImage, we can also create a BufferedImage from a GdkPixmap, that is, a Component.

VolatileImage

In 1.4 VolatileImage was introduced. It's a hardware-accelerated bitmap. I think this wants to be a GLitz surface. VolatileImages are used for double-buffering, if they are available, which means great speed increases for Swing applications, since all widgets will be hardware rendered. GTK, on the other hand, plans to leave this server-side. I'm not sure how this should be worked out.

Other stuff

ClasspathGraphicsImagesText (last edited 2005-06-28 01:12:44 by qcplx01)