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:
Drawing to a BufferedImage.
- Drawing to a Component (idea later)
Drawing to a VolatileImage.
- Printing. (requires the next-generation of gnome printing, which will be done via cairo though)
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)
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.)
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
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.
- Clean up all the font stuff. This should perhaps be seperated into dedicated AWT1.1 and 1.2 2D classes, because the way AWT1.1 handles fonts is similar to how it handles bitmaps: Very abstract. Whereas 2D does its own font rendering, and provides a great level of control. It's messy to try to reconcile this.
- Graphics2D can be simplified further by having all the drawRect(), fillRect(), drawRoundRect() etc methods use the draw(Shape) or fill(Shape) methods and the java.awt.geom classes.
- Optimization ideas for shape-drawing:
- The java.awt.geom classes cache their path iterators (which they should regardless).
We pull a trick and let the java.awt.geom path iterators wrap a native Cairo path, and so skip having to "translate" the PathIterator into a Cairo path on every draw operation. This should speed up repeated drawings of the same paths, which should be fairly common.