Notes On OpenJUMP's Rendering System

From OpenJUMP Wiki
Jump to navigation Jump to search

I wanted to jot down some notes I've taken about JUMP's/OpenJUMP's rendering system. (The rendering system “paints” or renders the feature geometries onto OpenJUMP's layer view. It is basically what draws OpenJUMP's map.) I wanted to share these notes, and some of my remaining questions, with other OpenJUMP developers. I hope that it will allow Java rookies like myself to understand what I think is some of OpenJUMP's most complex code.


A Short Explanation Of OpenJUMP's Rendering System Here is a very simple explanation of OpenJUMP's rendering process *that Jon Aquino gave me* in an e-mail at one time. Here is what he basically told me:

1. A LayerViewPanel contains a LayerManager which contains Layers which each contain a FeatureCollection which contains Features which each contains a JTS Geometry. The LayerViewPanel's RenderingManager calls each Layer's associated LayerRenderer, which for each geometry calls the layer's BasicStyle, which paints the geometry using StyleUtil.

2. The painting process is initiated by LayerViewPanel.paintComponent() method, which, like buttons and other GUI controls, is called by the Java system whenever it needs repainting. For instance, when another window has been dragged over it. When this happens the paintComponent() method calls RenderingManager, and the chain of logic begins as described in (1) above.

The LayerViewPanel.paintComponent() Method I started to retrace the execution of the LayerViewPanel's paintComponent() method, so I could try and understand exactly what was going on. I'm not any where near to being finished, but here is what I have so far:

The Graphics object passed to the paintComponent method is cast to a Graphics2D object. The rendering hints for the Graphics2D object are set. I believe antialiasing is turned on. (Antialiasing makes for prettier curves by partially shading some pixels.)

A call is made to super.paintComponent(). Since the LayerViewPanel extends Jpanel, Jpanels paintCompent() method is called. This would take care of acutally painting the panel itself. (The frame or border for the LayerViewPanel.)

The LayerViewPanel.erase() method is called. The erase() method then calls the LayerViewPanel.fill() method. This basically fills the LayerViewPanel with a solid color. (This solid color is hardcoded as white. I'm sure this could be changed to s solid color of the users choice, or even a pattern if desired.)

A call is made to the copyTo() of the RenderingManager class. (Each LayerViewPanel has a RenderingManager object associated with it. This RenderingManager maintains a list of "things" that can be rendered on the LayerViewPanel, and stores a reference to a Renderer that will be used to paint each "thing". A "thing" might be a Layer, a scale bar, or anything else that can be painted on the LayerViewPanel. These "things" are assigned unique labeled which are called contentIDs, which can be any Java object. Not sure how all of this works just yet...

Common Questions On OpenJUMP's Rendering System

How does OpenJUMP know which Renderer to use with each Layer?

Answer Coming Soon!


How does OpenJUMP determine which Layers should be painted first, and “underneath” the other Layers?

The RenderingManager class in JUMP and OpenJUMP declares to OrderedMaps as private member variables. (java.util.OrderedMap) One is named contentIDToHighRendererFactoryMap and the other is contentIDToLowRendererFactoryMap.

These OrderedMaps are called in the createRenderer() method of the RenderingManager class. I found a clue to their purpose in the putAboveLayerables() method and putBelowLayerables() method, which are also in the RenderingManager class. These two methods accept objects that create Renderers for JUMP and stores them in the two OrderedMaps mentioned above.

When a call is made to the renderAll() method or the copyTo() method of the RenderingManager class the contentIDs() method is called. (I think this method name should be changed to something like "getContentIDs()".)

The contentIDs() method returns a List of objects that need to be rendered by JUMP/OpenJUMP. This list is returned in the order the objects need to be painted in. In other words, the objects that need to be painted first, or "underneath" the other objects are in the top of the List, and the objects that need to be painted last, or "on top" of everything else are in the bottom of this list. As a result, this List really controls the order of rendering or painting in OpenJUMP.

This List is constructed in the contentIDs() method. That means this method contains the logic that determines the order in which items are rendered by JUMP/OpenJUMP. This all ties into the two OrderedMaps mentioned earlier. The items in the contentIDToLowRendererFactoryMap are placed on the top of the List returned by the contentIDs() method. (This is what we want, because those items get painted first or on the "bottom" of everything else.) The contentIDs() method then uses the order of the Layer objects in the LayerManager to place Layer objects in the List. (This means that theLayerManager tracks in which order the Layer objects are rendered. This makes sense becuase the user interacts with the LayerManager to control this order.) Finally, the contentIDs() method takes the items in the contentIDToHighRendererFactoryMap and places them in the List. (This is what we want, because those items need to get painted last, or on top of everything else.)

That is how JUMP/OpenJUMP paints things on the LayerViewPanel in the right order.

Some more notes

In the FeatureCollectionRenderer.createRunnable() method rendering is delegated to either the SimpleFeatureRenderer or the ImageCachingRenderer. If there are over 100 feature in the Layer visible in the current view of the LayerViewPanel then ImageCachingRenderer is used, if there are less than 100, SimpleFeatureRenderer is used.

SimpleFeatureCollectionRenderer extends SimpleFeatureRenderer. The implementation of createRunnable() in both of these classes is hardwired to return null. The Javadoc comment for the Renderer interface says: "First call #createRunnable. If it returns null, get the image using #copyTo. Otherwise, run the Runnable in a separate thread. You can call #copyTo while it's drawing to get the partially drawn image. Drawing is done when #isRendering returns false."

This code is set-up to use a threaded Renderer when possible, but to do the rendering without threads if no threaded Renderer is available. (For example, in the Javadoc comment listed above the programmer is instructed to call the copyTo() method defined in the Renderer interface. If you look at the implementation of this method in the SimpleRenderer class, it simply forwards a call to the paint() method if the same class. (I think this method is called copyTo() because you are copying the graphics from whatever will be rendered to the Graphics object that represents the Canvas of the LayerViewPanel.)

The Sunburned Surveyor - 5.Mai.2008