Imaging, Shimaging!

An advanced Imaging widget for Java Swing applications

[Home] [Download] [F.A.Q.] [Java API] [General Usage] [Advanced Topics] [SF Project]

Advanced Topics : Getting the most out of Shimaging

Using CompositeSource

CompositeSource composits more than one ImageSource into a final rendering for display by an ImagePanel. It does so by treating each ImageSource as a layer. Layers can be programmaticaly enabled and disabled. By Default, layers are composited over the results of compositing lower layers. However, with the addLayer(ImageSource source, Composite c) method, a specific Composite class can be specified, allowing you to change the Composite behavior. Images are composited off-screen into a newly created buffer of same size as the first (bottom, layer zero) ImageSource added.

Examples:

Composite two layers with the second layer overtop the first.

	CompositeSource cs = new CompositeSource(new ImageIOSource(new File("layerone.png")));
	cs.addLayer(new ImageIOSource(new File("layertwo.png")));
		
Composite two layers with the second layer beneath the first.
	CompositeSource cs = new CompositeSource(new ImageIOSource(new File("layerone.png")));
	cs.addLayer(new ImageIOSource(new File("layertwo.png")), AlphaComposite.DstOver);
		
Composite three layers with the composite of the first two layers being composited over the third
	CompositeSource cs = new CompositeSource(new ImageIOSource(new File("layerone.png")));
	
	// Composite layer 1 and 2 with AlphaComposite.SrcOver
	cs.addLayer(new ImageIOSource(new File("layertwo.png"))); 
	
	/* Layer 3 is composited with the results of compositing the previous 
	 * layers. So the combination (OVER) of layer 1 and 2 is composited overtop
	 * layer 3.
	 */
	cs.addLayer(new ImageIOSource(new File("layerthree.png")), AlphaComposite.DstOver); 
		

Beyond simple Compositing: Extending CompositeSource

CompositeSource has a few methods that can be overridden to change how compositing works. Aside from the obvious possibility of overriding doComposite(), the method protected AffineTransform calculateTransform(BufferedImage src, BufferedImage dst) is invoked prior to doComposite for the given source and destination image. The returned AffineTransform is used when rendering the source image into the destination.

The default implementation in CompositeSource returns an Identity AffineTransform. By overriding this method, ScalingCompositeSource ensures that the source image will fit within the destination's bounds, plus or minus a threashold value. The result is that images of varying sizes end up scaled (ignores aspect ratio) to fit within the destination BufferedImage.

What is a PagelessSource?

A PagelessSource is a type of ImageSource that allows for emulating multi-page behavior with a single-page image. This can be useful in situations where you want to composite multiple images of varying numbers of pages. This class and it's decendents are probably most useful for folks that are familiar with enterprise scanning operations.

As an example, let's say you've got a standard form in a light-red ink, which is scanned with a red dropout in batches of ten pages, and output as a single TIFF file. The result is a TIFF image with ten pages, where only the black text or markings that were filled out on the original form are visible. You'll likely want to composite an image of the form that was dropped out during scanning with the scanned TIFF image that contains the data for each form. You can load the dropped-out form image as a PagelessSource, set it's number of fake pages to that of the TIFF image you're handling (in this case, ten), and put the Pageless form source and the TIFF source as layers in a CompositeSource.

This will let the CompositeSource render the dropout layer for every page in the TIFF. Otherwise, if the Dropout layer returned a null image, the layer would not be included in the final rendering.

Hers is an example screenshot of a CMS1500 Medicare form, scanned red dropout (as a PagelessImageIOSource) composited with a test TIFF image.

Sub-Clipping & You. Predictable performance for huge images

In a nutshell: Subclipping is the act of only rendering the visible portion of an Image.

Typically, this results in renderings and scrolling that take between 100ms and 200ms per repaint. This is a dramatic improvement for huge images, which in my benchmarking could take 1600ms or more.

The Problem:

When dealing with large images zooming, rescaling, and rotation can become performance and memory hogs. A 300 x 300 dpi scanned TIFF image can take up 35MB of heap space in the JVM when rendered. Zooming the entire image to 4x and painting the results to a Graphics object can quickly result in OutOfMemoryErrors, even when running with an initial heap size >= 128MB.

The Solution:

What we call sub-clipping. Sub clipping makes the ImageModel aware of its' view (the JViewport in the ImagePanel's JScrollPane) size and location. Using this sub-clipping region, we can dramatically increase the performance of rendering operations (zooming, rescaling, rotating) by only applying the rendering the portion of the image that will be visible as a result of the rendering operations.

By default, when we start sub-clipping, we change our scrolling mode which results in slighly more jittery performance while scrolling, but much better performance overall when rendering the image. During initial development, I was working with 300 x 300 dpi scans of 8.5" x 11" (A4) paper. Performing a 90 degree rotation, rendering, and repainint the results took approximately 1600ms on my Pentium D workstation which was far more powerful than the PC's the end-users would be working on. By applying sub-clipping and then only rotating the visible portion of the image, I could get a 800 x 600 visible region zoomed, rotated, and rescaled in ~160ms. A 160ms lag when scrolling in a JScrollPane, and being able to rotate, zoom, and rescale the visible image nearly instantly was far more acceptable than a 1.6 second lag every time you zoomed, rotated, rescaled, or switched pages with blazing fast scrolling.

Memory usage is reduced dramatically, especially when zooming in on images, or using large images. OutOfMemoryError's become more scarce

Sub-clipping can be enabled or disabled by calling setAllowSubClipping(boolean b) on an ImagePanel's ImageCanvas.

Creating a custom ImageCanvas

ImageCanvas provides two hook functions that can be overridden to add additional paint layers to the ImageCanvas. They are, public void paintBehindImage(Graphics g) and public void paintOverImage(Graphics g). Of course, there are plenty of other methods you could override on ImageCanvas, and as many ways to extend it's behavior as you can dream up.

To create and use a custom ImageCanvas, you'll first need to create a subclass of ImagePanel that overrides protected ImageCanvas createImageCanvas(). Have this method return a new instance of your ImageCanvas subclass. As an example, the following two classes will result in an ImagePanel with a canvas that paints, "Hello World" over top of the Image the ImageCanvas renders.

	/**
	 * Our canvas class extends ImageCanvas, and overrides paintOverImage().
	 */
	class HelloCanvas extends ImageCanvas {
		public HelloCanvas() {
			super();
		}
		
		/**
		 * Draw "Hello World" at 30, 30 in the Graphics context.
		 */
		public void paintOverImage(Graphics g) {
			g.setColor(Color.RED);
			g.drawString("Hello World!", 30, 30);
		}
	}
	
	/**
	 * Creates an anonymous inner class that extends ImagePanel.
	 */
	ImagePanel iPanel = new ImagePanel(source) {
		protected ImageCanvas createImageCanvas() {
			return new HelloCanvas();
		}
	};
		


SourceForge.net Logo