Overview A note about examples Structure of a JIMI Format Module Decoders JimiDecoderFactories Encoders JimiEncoderFactories JimiExtensions Multi-image formats Tips for writing good format modules Categories of Image
header:
signature
ascii string "APF"
width
big-endian 32-bit integer value indicating the image width
height
big-endian 32-bit integer value indicating the image height
pixel data:
series of (width * height) big-endian 32-bit
values, each containing four 8-bit channels of image data, ordered alpha,
red, green, blue.
In the following sections we step through each phase of developing a new Format Module.Any number of format decoders Any number of format encoders Factory classes for each encoder and decoder A JimiExtension class for describing the module, and using it to register your module with JIMI
These are the only methods the decoder has to implement.MutableJimiRasterImage doInitDecoding(JimiImageFactory, InputStream)
This method is called to have the decoder read and interpret image headers to set its options and create an appropriate type of JimiImage. This method exits as soon as the decoder is ready to start decoding pixel data.void doImageDecode()
This method, which is called after doInitDecoding, decodes pixel data and sets it in the previously created JimiImage. When this method completes, the image is fully decoded and the decoding process has finished.
Writing your doInitDecoding method
The doInitDecoding method has the following signature:
public MutableJimiRasterImage doInitDecoding(JimiImageFactory
factory, InputStream in)
throws JimiException,
IOException
There are several steps involved in implementing this method. Firstly, headers for the image are parsed to determine the width, height, and type of ColorModel required for the image. How this is done depends on the format, but these are properties of all raster-based images and are the only information required. Optionally, the decoder may also read additional information from the headers for parameters specific to the image format. These parameters typically include things like compression-type, whether the image was interlaced, comments embedded in the image, or anything else supported by the specific file format. The primary purpose of preserving this information with the image is so that the same options can be automatically re-used if the image is later encoded. If you choose to support format-specific options with your decoder, you must write a class implementing com.sun.jimi.core.util.FormatOptionSet, which provides objects representing the options available. By doing this, your options can be inspected in a standard way, allowing a user to interactively modify them when images are being saved. FormatOptionSet objects can be associated with an image by calling the setOptions method.
When these are finished, all the required information about the image has been gathered and it's time to create an object in which to store the pixel data in. JIMI provides 5 different types of these objects, each of which is an implementation of MutableJimiRasterImage. The type to choose depends on what sort of image you are decoding. The following is a guide to which type you should choose for your decoder:
By checking your needs against this list, you can determine which type of JimiRasterImage your decoder should use. Having done this, the desired object can be created using one of the create<Type>() methods of the JimiImageFactory argument. The object created should then be referenced with an instance field to be accessed again by doImageDecode for setting pixel data.IntRasterImage
This type stores pixel values as ints, which provides 32 bits of data for storing each pixel. This is typically used for RGB-based image data where 8 bits are assigned to each color channel. It is appropriate to use this type for any image which requires 32 bit pixel values.ByteRasterImage
This type stores pixel values as bytes for images which only require 8 bits for each pixel. This is typically used in conjunction with an IndexColorModel for palette based images with 256 colors or less. It is appropriate to use this type for any image which requires 8 bit pixel values.BitRasterImage
This type stores pixel values each in a single bit for reduced memory consumption. This is typically used for black and white images, but can be used for any type that requires only 2 different colors.ChanneledIntRasterImage
This is an extension of IntRasterImage which allows image data to be set individually for each byte-channel of the image. This means that, for example, in several passes the alpha, red, green and blue channels of an image could be individually set for formats which store their data one channel at a time. Although this provides all the functionality of an IntRasterImage, it should only be used when its additional functionality is required because IntRasterImage can be more efficient.LongRasterImage
This type uses 64-bit longs to store individual pixels. This is useful for formats which have large color channels which prevent a pixel from being represented by a 32 bit value. These long-based images require use of special ColorModels derived from LongColorModel.
Finally, you should set the "hints" of the image object you created using the setHints method, which it inherits from MutableRasterImage. The values of the hints are exactly the same as the ones in java.awt.image.ImageConsumer, and by default TOPDOWNLEFTRIGHT | COMPLETESCANLINES | SINGLEFRAME | SINGLEPASS will be selected. If you are going to set data in a different order, you must set more appropriate hints. When all this is complete, you simply return a reference to the image object you created and doInitDecoding is finished.
Here is an example decoder.
Writing your doImageDecode method
This method is very straight forward. You simply read data for the
image and incrementally set it in the image object created in doInitDecoding.
Image data should be set in conveniently sized chunks. There is no specific
required chunk size, though it is conventional to buffer one row at a time
and set the entire row of data. If the format does not deal in rows, any
more convenient chunk size can be used.
As data is set, setProgress (a method inherited from the JimiSingleImageRasterDecoder) should be called to inform any interested objects where the operation is up to. This method takes one int as its argument, which should be set to the percentage of the operation which is complete, a number between 0 and 100 inclusive.
After all data has been read and set in the image object, the setFinished() method should be called on the image. When this is done, the decoding process is complete and the method can return.
These classes are very easy to implement, as this example illustrates.
public void doImageEncode(JimiRasterImage image,
OutputStream out)
throws JimiException,
IOException
Though formats can vary greatly, this method is usually implemented in three steps: getting the image into an appropriate form, writing the image file headers, and writing the image pixel data. Getting the image into an appropriate form involves taking the JimiRasterImage parameter and, if necessary, converting it into a form supported by the image format. For example, a format limited to saving 256-color images, like GIF, would convert the JimiRasterImage into a ByteRasterImage that uses an IndexColorModel with <= 256 colors. A format which writes 24-bit RGB or 32-bit ARGB data, such as JPEG, would not make any conversion, since all JimiRasterImages support access in this way. More flexible formats like PNG which can support either type would check whether the image it had been given was already palette-based, and if it was then save it as such, or if not then save it as RGB. JIMI provides several categories of image and the ability to check for conformance, or to convert images to conform to a certain category. These categories include "palette image", "8-bit palette image", "mono-color image", and are easily accessible through the JimiRasterImageCategorizer class. See the Categories of Images section for more information about these image properties.
With the image in a form appropriate for encoding, the rest of the encoding process is format-specific. The encoder must write all headers for the format and the image pixel data, and the encoding process is complete. Like decoders, the encoders also inherit a setProgress(int progress) method which should be used to allow external objects to track their progress.
Here is an example encoder.
JimiControl.addExtension(new MyJimiExtension());
With this done, your custom formats are available through JIMI in the exact same way as the core formats which JIMI handles.
Don't Buffer Decoders
Decoders in JIMI should not use BufferedInputStreams for reading their input data. JIMI will pass streams which are already buffered to the decoder if buffering is appropriate, so a second buffer in the decoder will degrade performance.Finish reading at the end of the image
Decoders should finish decoding at the end of the data relevant to the image file. Assume that there is important data in the stream beyond the image, and that you should read exactly the amount of image data that is there, leaving the stream aligned at the data which follows the image.
<= 8-bit palette image | ByteRasterImage with java.awt.IndexColorModel |
Palette image | Any JimiRasterImage with a java.awt.image.IndexColorModel |
Black and white | BitRasterImage or ByteRasterImage with IndexColorModel containing 2 entries |
Grayscale (any bitsize) | java.awt.image.DirectColorModel with an identical mask for each of the R,G,B channels |
> 32-bits per pixel | LongRasterImage with subclass of com.sun.jimi.core.raster.LongColorModel |
RGB | Any JimiRasterImage with any java.awt.ColorModel |