layout | title | parent | nav_order |
---|---|---|---|
default |
Extending the API |
Programming Guide |
16 |
{:.no_toc}
Describes how the ImageN API may be extended.
- Contents {:toc}
No image processing API can hope to capture the enormous variety of operations that can be performed on a digital image. Although the ImageN API supports a large number of imaging operations, it was designed from the beginning to encourage programmers to write extensions rather than manipulating image data directly. ImageN allows virtually any image processing algorithm to be added to the API and used as if it were a native part of the API.
The mechanism for adding functionality to the API can be presented at multiple levels of encapsulation and complexity. This allows programmers who wish to add simple things to the API to deal with simple concepts, while more complex extensions have complete control over their environment at the lowest levels of abstraction. The API also supports a variety of programming styles, including an immediate mode and a deferred mode of execution for different types of imaging applications.
All extensions to ImageN require the addition of new classes. All new classes are grouped into packages as a convenient means of organizing the new classes and separating the new classes from code libraries provided by others.
All new packages are given a product name. A product name is the
accepted Java method of using your company's reversed Internet
address to name new packages. This product naming convention helps to
guarantee the uniqueness of package names. Supposing that your
company's Internet address is WebStuff.COM
and you wish to create a
new package named prewitt
. A good choice of package name would be
com.webstuff.prewitt
Or, to uniquely identify the package as part of ImageN.
com.webstuff.imagen.prewitt
The above new prewitt
class files must now be placed into a
subdirectory that matches the product name, such as:
com/webstuff/imagen/prewitt
for Linux-based systems
or
com\webstuff\imagen\prewithh
for Windows systems
The Java convention for class naming is to use initial caps for the
name, for multi-word class names use initial caps for each word. For example AddOpImage
.
Vendors are encouraged to use unique product names (by means of the Java programming language convention of reversed internet addresses) to maximize the likelihood of a clean installation.
To extend the ImageN API by creating new operations, you will need to
write a new OpImage
subclass. This may be done by subclassing one or
more existing utility classes to automate some of the details of the
operator you wish to implement. For most operators, you need only
supply a routine that is capable of producing an arbitrary rectangle
of output, given contiguous source data.
Once created, new operators may be made available to users transparently and without user source code changes using the ImageN registry mechanism. Existing applications may be tuned for new hardware platforms by strategic insertion of new implementations of existing operators.
To create a new operator, you need to create the following new classes:
-
A class that extends the
OpImage
class or any of its subclasses. This new class does the actual processing. See Section 14.3.1, "Extending the OpImage Class." -
A class that extends the
OperationDescriptor
class. This new class describes the operation such as name, parameter list, and so on. See Section 14.3.2, "Extending the OperationDescriptor Interface." -
If the operator will function in the Rendered mode only, a class that implements
java.awt.image.renderable.RenderedImageFactory
.
Every new operator being written must be a subclass of OpImage
or
one of its subclasses. The OpImage
class currently has the following
subclasses:
Class | Description |
---|---|
AreaOpImage | An abstract base class for image operators that require only a fixed rectangular source region around a source pixel in order to compute each destination pixel. |
NullOpImage | Extends: PointOpImage A trivial OpImage subclass that simply transmits its source unchanged. Potentially useful when an interface requires an OpImage but another sort of RenderedImage (such as a TiledImage) is to be used. |
PointOpImage | An abstract base class for image operators that require only a single source pixel in order to compute each destination pixel. |
ScaleOpImage | Extends: WarpOpImage An abstract base class for scale-like operations that require rectilinear backwards mapping and padding by the resampling filter dimensions. |
SourcelessOpImage | An abstract base class for image operators that have no image sources. |
StatisticsOpImage | An abstract base class for image operators that compute statistics on a given region of an image, and with a given sampling rate. |
UntiledOpImage | A general class for single-source operations in which the values of all pixels in the source image contribute to the value of each pixel in the destination image. |
WarpOpImage | A general implementation of image warping, and a superclass for other geometric image operations. |
All abstract methods defined in OpImage
must be implemented by any
new OpImage
subclass. Specifically, there are two fundamental
methods that must be implemented:
Method | Description |
---|---|
getTile | Gets a tile for reading. This method is called by the object that has the new operator name as its source with a rectangle as its parameter. The operation is responsible for returning a rectangle filled in with the correct values. |
computeRect | Computes a rectangle of output, given Raster sources. The method is called by getTile to do the actual computation. The extension must override this method. |
First, you have to decide which of the OpImage
subclasses to extend.
To write a new statistics operation, you would most likely extend the
StatisticsOpImage
class. Each subclass has a specific purpose, as
described in Table 14-1.
Operations that are to be created using one of the JAI.create
methods must be defined in the registryFile
, which is included in
the jai_core.jar
. Each operation has an OperationDescriptor (denoted
as "odesc
" in the registryFile
), which provides a textual
description of the operation and specifies the number and type of its
sources and parameters. The OperationDescriptor also specifies whether
the operation supports rendered mode, renderable mode, or both.
Listing 14-1 shows a sample of the
registryFile
contents. Note that this is not the entire
registryFile
, only a small sample showing two operators (absolute
and addconst).
Listing 14-1 registryFile Example
odesc org.eclipse.imagen.operator.AbsoluteDescriptor absolute
odesc org.eclipse.imagen.operator.AddConstDescriptor addconst
rif org.eclipse.imagen.media.opimage.AbsoluteCRIF
org.eclipse.imagen.media absolute sunabsoluterif
rif org.eclipse.imagen.media.mlib.MlibAbsoluteRIF
org.eclipse.imagen.media absolute mlibabsoluterif
rif org.eclipse.imagen.media.opimage.AddConstCRIF
org.eclipse.imagen.media addconst sunaddconstrif
rif org.eclipse.imagen.media.mlib.MlibAddConstRIF
org.eclipse.imagen.media addconst mlibaddconstrif
crif org.eclipse.imagen.media.opimage.AbsoluteCRIF absolute
crif org.eclipse.imagen.media.opimage.AddConstCRIF addconst
All high-level operation names in ImageN (such as Rotate
, Convolve
,
and AddConst
) are mapped to instances of RenderedImageFactory
(RIF) and/or ContextualRenderedImageFactory
(CRIF) that are capable
of instantiating OpImage
chains to perform the named operation. The
RIF is for rendered mode operations only; the CRIF is for operations
that can handle renderable mode or both rendered and renderable modes.
To avoid the problems associated with directly editing the
registryFile
and then repackaging it, you can register
OperationDescriptors and RIFs and CRIFs using the OperationRegistry's
registerOperationDescription
, and registerRIF
and registerCRIF
methods. The only drawback to this method of registration is that the
new operator will not be automatically reloaded every time a JAI
program is executed., since the operation is not actually present in
the registryFile
. This means that to use the new operation, the
operation will always have to be invoked beforehand.``
To temporarily register a new operation:
-
Register the operation name.
The high-level operation name, called an operation descriptor, is registered by calling the
registerOperationByName()
method or theregisterOperationDescriptor()
method. The operation descriptor name must be unique.Once an operation descriptor is registered, it may be obtained by name by calling the
getOperationDescriptor()
method. -
Register the set of rendered image factory objects.
The rendered image factory (RIF) is registered using the
registerRIF
method. Each RIF is registered with a specific operation name and is given a product name. Similar methods exist for registering a contextual image factory (CRIF).
The OperationDescriptor
interface provides a comprehensive
description of a specific image operation. All of the information
regarding the operation, such as the operation name, version, input,
and property, should be listed. Any conditions placed on the
operation, such as its input format and legal parameter range, should
also be included, and the methods to enforce these conditions should
be implemented. A set of PropertyGenerator
s may be specified to be
used as a basis for the operation's property management.
Each family of the image operation in ImageN must have a descriptor that implements this interface. The following basic resource data must be provided:
-
GlobalName - a global operation name that is visible to all and is the same in all
Locale
s -
LocalName - a localized operation name that may be used as a synonym for the global operation name
-
Vendor - the name of the vendor (company name) defining this operation
-
Description - a brief description of this operation
-
DocURL - a URL where additional documentation on this operation may be found (the javadoc for the operation)
-
Version - the version of the operation
-
arg0Desc, arg1Desc, etc. - descriptions of the arguments. There must be a property for each argument.
-
hint0Desc, hint1Desc, etc. - descriptions of the rendering hints. There must be a property for each hint.
Additional information about the operation must be provided when
appropriate. It is also good idea to provide a detailed description of
the operation's functionality in the class comment. When all of the
above data is provided, the operation can be added to an
OperationRegistry
.
Listing 14-2 shows an example of an operation descriptor for the Clamp operation. Note that the descriptor also contains descriptions of the two required operation parameters, but no hints as these aren't required for the operation.
Listing 14-2 Operation Descriptor for Clamp Operation
public class ClampDescriptor extends OperationDescriptorImpl {
/**
* The resource strings that provide the general documentation
* and specify the parameter list for this operation.
*/
private static final String[][] resources = {
{"GlobalName", "Clamp"},
{"LocalName", "Clamp"},
{"Vendor", "com.sun.org.eclipse.imagen"},
{"Description", "Clamps the pixel values of a rendered image"},
{"DocURL", "ImageN Project](https://projects.eclipse.org/projects/technology.imagen/jaiapi/org.eclipse.imagen.operator.ClampDescriptor.html"},
{"Version", "Beta")},
{"arg0Desc", "The lower boundary for each band."},
{"arg1Desc", "The upper boundary for each band."}
};
As described in Section 3.3, "Processing
Graphs," JAI has two image
modes: Rendered and Renderable. An operation supporting the Rendered
mode takes RenderedImages
as its sources, can only be used in a
Rendered op chain, and produces a RenderedImage
. An operation
supporting the Renderable mode takes RenderableImage
s as its
sources, can only be used in a Renderable op chain, and produces a
RenderableImage
. Therefore, the class types of the sources and the
destination of an operation are different between the two modes, but
the parameters must be the same for both modes.
All operations must support the rendered mode and implement those
methods that supply the information for this mode. Those operations
that support the renderable mode must specify this feature using the
isRenderableSupported
method and implement those methods that supply
the additional information for the Renderable mode.
Table 14-2 lists the Rendered mode methods. Table 14-3 lists the Renderable mode methods. Table 14-4 lists the methods relative to operation parameters.
Table 14-2 Rendered Mode Methods
Method | Description |
---|---|
isRenderedSupported | Returns true if the operation supports the Rendered image mode. This must be true for all operations. |
isImmediate | Returns true if the operation should be rendered immediately during the call to JAI.create; that is, the operation is placed in immediate mode. |
getSourceClasses | Returns an array of Classes that describe the types of sources required by this operation in the Rendered image mode. |
getDestClass | Returns a Class that describes the type of destination this operation produces in the Rendered image mode. |
validateArguments | Returns true if this operation is capable of handling the input rendered source(s) and/or parameter(s) specified in the ParameterBlock. |
Table 14-3 Renderable Mode Methods
Method | Description |
---|---|
isRenderableSupported | Returns true if the operation supports the Renderable image mode. |
getRenderableSourceClasses | Returns an array of Classes that describe the types of sources required by this operation in the Renderable image mode. |
getRenderableDestClass | Returns a Class that describes the type of destination this operation produces in the Renderable image mode. |
validateRenderableArguments | Returns true if this operation is capable of handling the input Renderable source(s) and/or parameter(s) specified in the ParameterBlock. |
Method | Description |
---|---|
getNumParameters | Returns the number of parameters (not including the sources) required by this operation. |
getParamClasses | Returns an array of Classes that describe the types of parameters required by this operation. |
getParamNames | Returns an array of Strings that are the localized parameter names of this operation. |
getParamDefaults | Returns an array of Objects that define the default values of the parameters for this operation. |
getParamDefaultValue | Returns the default value of a specified parameter. |
getParamMinValue | Returns the minimum legal value of a specified numeric parameter for this operation. |
getParamMaxValue | Returns the maximum legal value of a specified numeric parameter for this operation. |
API: org.eclipse.imagen.OperationRegistry
void registerOperationDescriptor(OperationDescriptor odesc, String operationName)
void registerOperationByName(String odescClassName, String operationName)
void unregisterOperationDescriptor(String operationName)
void registerRIF(String operationName, String productName, RenderedImageFactory RIF)
void registerRIFByClassName(String operationName, String productName, String RIFClassName)
Iterators are provided to help the programmer who writes extensions to
the JAI API and does not want to use any of the existing API methods
for traversing pixels. Iterators define the manner in which the source
image pixels are traversed for processing. Iterators may be used both
in the implementation of computeRect
methods or getTile
methods of
OpImage subclasses, and for ad-hoc pixel-by-pixel image manipulation.
Iterators provide a mechanism for avoiding the need to cobble sources,
as well as to abstract away the details of source pixel formatting. An
iterator is instantiated to iterate over a specified rectangular area
of a source RenderedImage
or Raster
. The iterator returns pixel
values in int
, float
, or double
format, automatically promoting
integral values smaller than 32 bits to int
when reading, and
performing the corresponding packing when writing.
ImageN offers three different types of iterator, which should cover nearly all of a programmer's needs. However, extenders may wish to build others for more specialized needs.
The most basic iterator is RectIter
, which provides the ability to
move one line or pixel at a time to the right or downwards, and to
step forward in the list of bands. RookIter
offers slightly more
functionality than RectIter
, allowing leftward and upward movement
and backwards motion through the set of bands. Both RectIter
and
RookIter
allow jumping to an arbitrary line or pixel, and reading
and writing of a random band of the current pixel. The RookIter
also
allows jumping back to the first line or pixel, and to the last line
or pixel.
RandomIter
allows an unrelated set of samples to be read by
specifying their x and y coordinates and band offset. The
RandomIter
will generally be slower than either the RectIter
or
RookIter
, but remains useful for its ability to hide pixel formats
and tile boundaries.
Figure 14-1 shows the Iterator package hierarchy. The classes are described in the following paragraphs.
The RectIter
interface represents an iterator for traversing a
read-only image in top-to-bottom, left-to-right order (Figure
14-2). The RectIter traversal will
generally be the fastest style of iterator, since it does not need to
perform bounds checks against the top or left edges of tiles. The
WritableRectIter
interface traverses a read/write image in the same
manner as the RectIter.
The iterator is initialized with a particular rectangle as its bounds.
The initialization takes place in a factory method (the
RectIterFactory
class) and is not a part of the iterator interface
itself. Once initialized, the iterator may be reset to its initial
state by means of the startLines()
, startPixels()
, and
startBands()
methods. Its position may be advanced using the
nextLine()
, jumpLines()
, nextPixel()
, jumpPixels()
, and
nextBand()
methods.
Figure 14-1 Iterator Hierarchy
Figure 14-2 RectIter Traversal Pattern
The WritableRookIter
interface adds the ability to alter the source
pixel values using the various setSample()
and setPixel()
methods.
An instance of RectIter
may be obtained by means of the
RectIterFactory.create()
method, which returns an opaque object
implementing this interface.
API: org.eclipse.imagen.iterator.RectIterFactory
static RectIter create(RenderedImage im, Rectangle bounds)
static RectIter create(Raster ras, Rectangle bounds)
static WritableRectIter createWritable(WritableRenderedImage im, Rectangle bounds)
static WritableRectIter createWritable(WritableRaster ras, Rectangle bounds)
API: org.eclipse.imagen.iterator.RectIter
void startLines()
void startPixels()
void startBands()
void nextLine()
void jumpLines(int num)
void nextPixel()
void jumpPixels(int num)
void nextBand()
The RookIter
interface represents an iterator for traversing a
read-only image using arbitrary up-down and left-right moves (Figure
14-3 shows two of the possibilities for
traversing the pixels). The RookIter traversal will generally be
somewhat slower than a corresponding instance of RectIter
, since it
must perform bounds checks against the top and left edges of tiles in
addition to their bottom and right edges. The WritableRookIter
interface traverses a read/write image in the same manner as the
RookIter.
An instance of RookIter may be obtained by means of the
RookIterFactory.create()
or RookIterFactory.createWritable()
methods, which return an opaque object implementing this interface.
The iterator is initialized with a particular rectangle as its bounds.
This initialization takes place in a factory method (the
RookIterFactory
class) and is not a part of the iterator interface
itself.
Once initialized, the iterator may be reset to its initial state by
means of the startLines()
, startPixels()
, and startBands()
methods. As with RectIter
, its position may be advanced using the
nextLine()
, jumpLines()
, nextPixel()
, jumpPixels()
, and
nextBand()
methods.
Figure 14-3 RookIter Traversal Patterns
API: org.eclipse.imagen.media.iterator.RookIterFactory
static RookIter create(RenderedImage im, Rectangle bounds)
static RookIter create(Raster ras, Rectangle bounds)
static WritableRookIter createWritable(WritableRenderedImage im, Rectangle bounds)
static WritableRookIter createWritable(WritableRaster ras, Rectangle bounds)
The RandomIter
interface represents an iterator that allows random
access to any sample within its bounding rectangle. The flexibility
afforded by this class will generally exact a corresponding price in
speed and setup overhead.
The iterator is initialized with a particular rectangle as its bounds.
This initialization takes place in a factory method (the
RandomIterFactory
class) and is not a part of the iterator interface
itself. An instance of RandomIter
may be obtained by means of the
RandomIterFactory.create()
method, which returns an opaque object
implementing this interface.
The getSample()
, getSampleFloat()
, and getSampleDouble()
methods
are provided to allow read-only access to the source data. The
getPixel()
methods allow retrieval of all bands simultaneously.
API: org.eclipse.imagen.iterator.RandomIterFactory
static RandomIter create(RenderedImage im, Rectangle bounds)
static RandomIter create(Raster ras, Rectangle bounds)
static WritableRandomIter createWritable(WritableRenderedImage im, Rectangle bounds)
static WritableRandomIter createWritable(WritableRaster ras, Rectangle bounds)
Listing 14-3 shows an example of the
construction of a new RectIter
.
```java` % relative-include RectIterTest.java %}
14.5 Writing New Image Decoders and Encoders
-----------------------------------------------------------------
The `sample` directory contains an example of how to create a new
image codec. The example is of a PNM codec, but can be used as a basis
for creating any codec. The PNM codec consists of three files:
| File Name | Description |
| --------- | ----------- |
| SamplePNMCodec.java | Defines a subclass of ImageCodec for handling the PNM family of image files. |
| SamplePNMImageDecoder.java | Defines an ImageDecoder for the PNM family of image files. Necessary for reading PNM files. |
| SamplePNMImageEncoder.java | Defines an ImageEncoder for the PNM family of image files. Necessary for writing PNM files. |
### 14.5.1 Image Codecs
**Note:** The codec classes are provided for the developer as a
convenience for file IO. These classes are not part of the official
Java Advanced Imaging API and are subject to change as a result of the
near future File IO extension API. Until the File IO extension API is
defined, these classes and functions will be supported for ImageN use.
The `ImageCodec` class allows the creation of image decoders and
encoders. Instances of `ImageCodec` may be registered by name. The
`registerCodec` method associates an `ImageCodec` with the given name.
Any codec previously associated with the name is discarded. Once a
codec has been registered, the name associated with it may be used as
the `name` parameter in the `createImageEncoder` and
`createImageDecoder` methods.
The `ImageCodec` class maintains a registry of `FormatRecognizer`
objects that examine an `InputStream` and determine whether it adheres
to the format handled by a particular `ImageCodec`. A
`FormatRecognizer` is added to the registry with the
`registerFormatRecognizer` method. The unregisterFormatRecognizer
method removes a previously registered `FormatRecognizer` from the
registry.
The `getCodec` method returns the `ImageCodec` associated with a given
name. If no codec is registered with the given name, `null` is
returned.``
**API:** `org.eclipse.imagen.media.codec.ImageCodec`
* `static ImageEncoder createImageEncoder(String name, OutputStream dst, ImageEncodeParam param)`
* `static ImageEncoder createImageEncoder(String name, OutputStream dst)`
* `static ImageDecoder createImageDecoder(String name, InputStream src, ImageDecodeParam param)`
* `static ImageDecoder createImageDecoder(String name, InputStream src)`
* `static void registerCodec(String name, ImageCodec codec)`
* `static void unregisterCodec(String name)`
* `static ImageCodec getCodec(String name)`