Jimi technical guide

Intent

 

This document is intended as a guide to finding out how Jimi works internally.  This is "under the hood" providing an overview of the internal operation of some fundamental operations: image decoding, image encoding, image export to AWT, and a range of supporting features.

Overview of packages

 

Table 1 shows an overview of the fundamental packages of Jimi.

 

Jimi package

Description

com.sun.jimi.core

Front-end classes for image I/O.

Core code for managing encoders and decoders.

Fundamental interfaces.

com.sun.jimi.core.compat

Adapter classes to support Jimi 1.0-style format modules.

com.sun.jimi.core.component

AWT components to support imaging, heavy- and light- weight versions of the JimiCanvas image rendering component.

com.sun.jimi.core.decoder

Decoder implementations.

com.sun.jimi.core.encoder

Encoder implementations.

com.sun.jimi.core.filters

ImageFilter classes.

com.sun.jimi.core.options

Options classes for managing format-specific parameters.

com.sun.jimi.core.raster

Raster-based interfaces and implementations of JimiImage.

com.sun.jimi.util

Shared utility classes.

com.sun.jimi.core.vmem

Virtual memory paging support classes.

 

An overview of image decoding

 

Image decoding always begins with a call from an application to the Jimi front-end class, which results in the creation of a JimiReader.  JimiReader is the bridge between applications and decoders, providing all access to loading an image, or series of images, from an input source.  For the JimiReader to be initialized, three things are needed: an input source, a decoder type, and an image back-end type.  The input source is ultimately an InputStream, possibly being derived from a filename or URL.  The decoder type is a mime string, such as "image/jpeg" which identifies the decoder required to interpret the image.  This mime type can either be specified explicitly (setMimeType()), or it can be derived from the contents of the input stream, a filename or URL suffix, or a java.net.URL object.  The image back-end object is represented by a JimiImageFactory object, which Jimi chooses based on flags specified by the getImage() call, such as VIRTUAL_MEMORY or ONE_SHOT.

 

When all the initialization information has been given to the JimiReader, JimiControl is used to get an appropriate JimiDecoderFactory for the image format.  A decoder instance is then created with the factory's createDecoder() method, and the JimiReader initializes the decoder to prepare for image requests.  Initialization of the decoder is performed using JimiDecoder’s initDecoding() method, which returns an ImageSeriesDecodingController ("controller object" which can be used to request images from the decoder.

 

The decoder itself runs in a separate thread, asynchronously processing requests made via the controller object and synchronizing at checkpoints with the JimiReader and its clients.  These checkpoints are illustrated in the following figure.

 


When the JimiReader has initialized the decoder, it enters the Ready_To_Decode state, where it waits for a request for an image.  When the JimiReader receives such a request via a getImage() call, the controller object is used to have decoder create a JimiImage object for the next available image, bringing the decoder into the Created_Image state.  The decoder waits there until pixel information is required, which is caused by using the decoder's JimiImage object to deliver image data to an ImageConsumer, or by calling waitFinished() on the JimiImage itself.  To provide pixels, the decoder reads image data from the input source and uses it to populate the JimiImage object, which also sends the information to any ImageConsumers which have registered an interest.  When all data for the image has been set in the JimiImage, the decoder reaches the Image_Completed state, a synchronization point for calls to the JimiImage's waitFinished() method.  From there, the decoder checks whether more images are available from the input source: if so, it enters the Ready_To_Decode state again awaiting a request for the next image, or if there are no more images then it finishes its operation.

An overview of image encoding

 

Encoding in Jimi is similar to decoding, only simplified through synchronous operation.  When Jimi gets a request to save an image, it creates a JimiWriter object, which is the sister class of JimiReader providing image saving operations.  The JimiWriter is initialized with an encoding format mime-type, an OutputStream to write to, and an image or set of images to save.  With this information, JimiWriter uses JimiControl to create an appropriate JimiEncoder object for saving with.  Finally, JimiWriter's putImage() method is called and all images are synchronously saved to the file using the JimiEncoder.

Image production

 

How a JimiImage's ImageProducer operates is flexible and handled by the JimiImage implementation.  In Jimi, there is one standard implementation shared by all the JimiRasterImage types.  The standard implementation, supported by JimiRasterImageSupport, is designed to wait until image information has actually been requested before processing the image, to allow for optimizations such as multicasting pixel information from the decoder between the pixel storage and the first ImageConsumer.  The pixel storage is managed by subclasses of JimiRasterImageSupport for dealing with in-memory arrays, swap file paging, or "one-shot" storage-less operation.

 

One of the key benefits of this image production model is allowance for highly optimized image delivery.  By deferring the decoding of pixel data until there is an ImageConsumer interested in receiving it, the pixels can be multicast from the decoder to the pixel storage and the ImageConsumer.  The greatest speed benefit provided here is when decoders are using one-bit-per-pixel images but sending their data in pixel-per-byte arrays.  In this case, pixel data from the decoder will need to be packed into 8 pixel bytes, and then expanded during delivery to subsequent ImageConsumer requests.  By multicasting to the first consumer, the unpacked information sent by the decoder can sent directly without an extra pixel-unpacking step.

 

This production model also enables one-shot storage-less operation.  This is a mode of operation where the pixel-storage is removed, providing no persistence and simply routing pixel data from the decoder directly to the first ImageConsumer registered.  This is ideal for handling the case when users simply want to create an Image object to display, and have no further need for the image.

 

Virtual memory based images benefit similarly.  The image delivery optimization adds extra value when virtual memory is being used, because the image can be sent to the first ImageConsumer without having to read from the swap file.  As pixels are received from the decoder, they are sent directly to the first consumer and also written into the image swap file.  Only in subsequent ImageConsumer requests will the pixel information have to be read from secondary storage.

 

Because the responsibility of image production rests with the JimiImage implementation, decoders are also able to use their own implementations.  For example, a decoder could choose to provide a JimiImage object which routes all ImageConsumer requests back to itself, scanning the input file for the required region of data instead of storing it in memory.

 

Format-specific options

 

Jimi includes support for format-specific options using the classes in the com.sun.jimi.options package.  FormatOptionSet objects are used to represent a set of options associated with an image, either set by a decoder from options used in the source file, or set by an application to specify specific options for image encoding.  All FormatOptionSet objects have methods for describing the options they make available, in a JavaBeans-like way that can be used for interactive forms.  In addition to this, each format supporting a custom option set provides a specialization of FormatOptionSet which defines methods for programmatic option manipulation.  For instance, GIF includes methods for querying and specifying frame-delays for animation, interlacing, whether each frame should use an independent color map, etc.

The JimiCanvas component

 

Jimi includes an image-rendering component called JimiCanvas.  JimiCanvas is an image viewing component which supports a range of rendering modes, including several types of scaling and scrolling.  JimiCanvas also handles image loading, given a filename or URL it will load and render an image in the mode it is configured for.  It also supports paging through multiple frames of a multi-page image file.

 

A useful feature of JimiCanvas is "smart-scrolling".  This is a mode of scrolling that is used to take advantage of JimiImage's virtual memory feature.  It operates by paging only the pieces of an image which are actually in the visible scrolling area in Image objects, and using these to render the viewable part of the image.  As the user scrolls, the buffers are updated quickly to provide smooth scrolling through large images using very little physical memory.

Additional features

 

Jimi provides some additional supporting features for handling images beyond what has been described here.  Color reduction, either to a fixed number of colors or coercion to a standard color map, is handled for all images in Jimi.  Serialization is available for all images, be they Jimi-based or from the AWT, providing simple support for persistence for graphics in GUI applications and JavaBeans, or seamless marshalling of images over I/O streams for client/server systems.  ImageFilter implementations for common operations such as rotation, brightness adjustment, edge-detection, and more are also provided, and work equally well with Jimi's own images as well as normal AWT images.