This collection of answers to frequently asked questions (FAQ) provides brief answers to many common questions about the Java Advanced Imaging API.
Many of the answers are derived from the jai-interest e-mail archive. For legal reasons, sample code provided by users outside Sun cannot be included in the FAQ. However, links to that sample code in the archives are included.
The Java Advanced Imaging API extends the Java 2 platform by allowing sophisticated, high-performance image processing to be incorporated into Java applets and applications. It is a set of classes providing imaging functionality beyond that of Java 2D and the Java Foundation classes, though it is designed for compatibility with those APIs. This API implements a set of core image processing capabilities including image tiling, regions of interest, deferred execution and a set of core image processing operators, including many common point, area, and frequency domain operators.
The Java Advanced Imaging API is intended to meet the needs of technical (medical, seismological, remote sensing, etc.) as well as commercial imaging (such as document production and photography). The API can benefit all Java developers who want to incorporate imaging into their Java applets and applications.
Yes. Applications written using the Java 2D API are completely forward compatible with the Java Advanced Imaging API.
The Java Advanced Imaging API is part of the Java Media API suite that work together to provide customers with enhanced graphics, imaging and communication capabilities. These APIs include: Java 2D for graphics, text, and fundamental image manipulation; Java 3D; Image I/O; the Java Media Framework (JMF) for components to play and control time-based media such as audio and video; Java Shared Data Toolkit; Java Sound; and Java Speech.
A 1.1.2_01 implementation of the Java Advanced Imaging API is available as a standard extension to the Java 2 platform from the Java Advanced Imaging Website.
The Java Advanced Imaging API may be run without any native code, i.e., without the code which provides native acceleration. When run in this manner, the Java Advanced Imaging API uses only Java code. However, the Java Advanced Imaging API has not yet been certified as meeting the requirements of the "100% Pure Java" Program per se.
If a native implementation is present it is, by default, the preferred implementation. But if the nature of the sources and parameters of the operation are incompatible with the native operation then processing will revert to Java code. In general the following minimum requirements must be adhered to for the mediaLib native implementation of an operation to be executed:
RenderedImage
s. SampleModel
which is a ComponentSampleModel
and ColorModel
which is a ComponentColorModel
or no ColorModel
(i.e., it is null).Further restrictions may be imposed by individual operations but the above are the most common requirements.
The source code for the Java Advanced Imaging (JAI) API is not available at this time. We would however like to hear from customers who might have an interest in obtaining the source code in the future. The exact mechanism by which the source might be made available is at present undefined.
From time to time, in order to prevent unsolicited commercial messages ("spam") from appearing on the list, we may allow postings only from list subscribers. If the email address from which you sent the message does not match your address as it appears in the subscription list, your posting may be disallowed.
The other situation where it may seem that postings to the jai-interest mailing list do not appear, it may be that the subscription options for you are set to not send you a copy of your posting.
Please scan this FAQ and the mail archives to see if your problem has already been addressed. If it hasn't, there are several things you can do to increase the chances that we will be able to help you resolve your problem.
Please supply the simplest possible code that exhibits the problem if at all possible. A self-contained, compilable example is best. Where this is not possible, as much context as you can give will be helpful.
For code that generates errors or exceptions, please use the Java command line option "-Djava.compiler=none" as this will produce a stack trace with line numbers. These line numbers are important for us to locate the root cause of most bugs.
The Java Advanced Imaging Tutorial is available for download here (Click Download)
This tutorial is an interactive Java application that uses the Java Advanced Imaging API to showcase real-world imaging examples, that run as a part of the tutorial. It will help you rapidly create new applications using Java Advanced Imaging.
No. You need the Java 2 platform (a/k/a JDK 1.2). Specifically, Java Advanced Imaging 1.1.2_01 requires the Java 2 platform, version 1.3 or higher.
The Java 2D and Java Advanced Imaging APIs support:
Users may define their own custom SampleModel
classes to support other data types.
The Java Advanced Imaging API supports unsigned byte, signed and unsigned short (Java 2 only partially supports signed short), int, float, and double data types. Complex numbers are represented using two bands, one for the real and one for the imaginary portion of each sample. Certain operators require the use of an integral data type (not float or double), e.g., table lookup and bitwise logical operators.
SampleModel
to use for performance?Fastest processing performance will be achieved when the Raster
is composed of a PixelInterleavedSampleModel
and a DataBuffer
type that is supported by native acceleration for the operations of interest. The BandOffsets
arrays of all sources and the destinations should match.
An optional C-based library for Win32, Solaris and Linux provides performance enhancements for many operators. This library is called mediaLib.
Additional hardware optimization on UltraSPARC running Solaris is achieved using the VIS Instruction Set. Additional hardware optimization on the Intel x86 architecture is achieved using MMX.
In schola desinis. Quis istud, quaeso, nesciebat? Unum est sine dolore esse, alterum cum voluptate. Quid ergo?
mediaLib is a Sun-developed high-performance image processing library. The reference implementation of the Java Advanced Imaging API makes use of a special version of mediaLib which is a subset of mediaLib 2.0 and includes a JNI wrapper.
On those Win32 platforms which support MMX, Java Advanced Imaging uses a version of mediaLib containing MMX instructions for acceleration. For those Win32 and Solaris/Intel platforms where MMX is not supported, Java Advanced Imaging uses a pure C version of mediaLib. On Solaris/SPARC platforms, it uses a version containing VIS instructions for acceleration. On the Linux platform, it uses a pure C version of medialib. The use of mediaLib is not essential to the design of the Java Advanced Imaging API; other native platform-specific libraries could be integrated in the future.
No. Native code is supplied for performance purposes only. A C library is supplied for SPARC, Linux and Intel x86 platforms which accelerates most of the operators. In addition there is a lower level SPARC library (VIS) and a MMX library allowing additional hardware acceleration for many operators. If Java Advanced Imaging code does not find these libraries, pure Java code is used.
OutOfMemoryError
s?There are several possible reasons for an application to produce an OutOfMemoryError
.
A common reason is a failure to specify enough memory for the Java virtual machine (JVM) at application startup time. Regardless of how much physical and virtual memory is available on your system, the Java runtime will only allocate a fixed amount, which is determined when the Java Virtual Machine (JVM) starts up. How to allocate more memory to the JVM is discussed below.
JAI creates a TileCache
object to store computed portions of images. By default, the cache size is allowed to store up to 16 megabytes of image data. Therefore the JVM should generally be started up with a size larger than this. The actual memory requirements depend on many factors such as the sizes of the images being used and how many operations are used to produce an output image. A setting of -Xmx128m
, giving 128 megabytes of (virtual) memory to the JVM is a reasonable starting value. Alternatively, you can specify the size of the tile cache yourself with the TileCache.setMemoryCapacity()
method.
Another possible cause of excessive memory use is the use of tile sizes which are too large. By default, images inherit their tile size from their source, i.e. the previous image in a chain. If the tile size is large, more data may be generated than required by an operation. You can specify a smaller tile size for use by an image by creating an ImageLayout
object, setting its tile size fields and passing it to the JAI.create()
call via a RenderingHints
object.
You might also consider adjusting the capacity of the tile cache being used for your particular instance of Java Advanced Imaging. The cache to use for a specific operation may also be specified via the RenderingHints
. For all operations, a tile is not calculated until a region of the image overlapping the tile is requested. To save re-computation, the resulting tiles are stored in a cache. When the cache runs out of memory it removes tiles until it has enough memory to store the new tiles. For example, if you are scrolling an image you might scroll to one area and then back to the original area. Tiles required in the original area might have been flushed from the cache in the intervening time, thereby necessitating their recalculation. By allowing recalculation to take place, there is no need to store all of the computed tiles permanently.
The most efficient storage of image data in RAM will be accomplished when the images are tiled and those tiles which are not currently needed are flushed from memory. The deferred execution architecture of the operation chain attempts to process only the tiles that it must. The tile cache attempts to flush those tiles which are no longer needed. These two facilities working in tandem attempt to provide a reasonable tradeoff between memory use and computation.
The operation chain itself does not, however, handle the way image data are managed in the source and sink portions of the chain as this cannot always be controlled by the API itself. For example, if the image source is a file which is not tiled, then the entire image will be loaded. If a tiled source image file is used (e.g., tiled TIFF or FlashPIX), then only those tiles required to execute the operation chain will be read from the disk.
Similarly, at the data sink end of a chain, e.g., the display, if the entire image is requested and held in memory then that will provoke computation of all tiles in all intermediate operations in the chain and consequently reading of the entire image from disk. The data sink will be most efficient if it requests only those tiles that it needs and uses some kind of efficient algorithm for disposing of the destination tiles that it does not need, e.g., those neither being displayed nor adjacent to tiles being displayed.
To reduce RAM usage even further you also have the option of implementing your own tile cache which uses an alternate type of backing storage for the tiles in the cache, e.g., a disk file or a database. Such an approach may lower execution speed in some cases but there are always tradeoffs. There is also a mechanism to replace the "Least Recently Used" algorithm with a custom implementation via the tile cache metric and tile comparator features.
When images with non-standard bit depths (e.g., 1 or 2 bits per sample) are processed, some JAI operators may perform temporary expansion to an 8 bit per sample format. This may require excessive memory in some cases. This behavior was particularly pronounced in the 1.0.2 release; the 1.1 and later releases are significantly more efficient when performing scale, rotate, affine, and transpose operations on 1-bit images.
In JAI 1.0.2, tiles were only removed from the Tile Cache when the cache became full. At this point, tiles were released from memory until 25% of the memory was freed. As of JAI 1.1 and later, tiles can also be released if no "hard references" to the tiles remain. This can occur for example, if an operation goes "out of scope". JAI 1.1 also checks for OutOfMemoryErrors when requesting tiles, and if one occurs, tiles will be released from the cache and the compute request will be reissued. This has significantly reduced OutOfMemoryError conditions in applications. Applications should still check for OutOfMemoryError conditions because other portions of a program can trigger these independent of JAI.
Some improvements were made in JAI 1.1.1 to reduce memory related exceptions while loading image files that may have been noticed with previous JAI versions.
You may obtain a reference to the default tile cache and call its setMemoryCapacity
method, supplying the cache capacity in bytes:
TileCache cache =
JAI.getDefaultInstance().getTileCache();
long size = 32*1024*1024L; // 32 megabytes
cache.setMemoryCapacity(size);
It may be necessary to set the maximum size of the memory allocation pool of the Java interpreter using the -Xmx
command line option. This is particularly important if you are attempting to work with large, untiled imagery.
-Xmx
<size>
By default, the size is measured in bytes. To specify the size in either kilobytes or megabytes, append "k" for kilobytes or "m" for megabytes.
-Xms
<size>
-Xmx
option).
By default, the size is measured in bytes. To specify the size in either kilobytes or megabytes, append "k" for kilobytes or "m" for megabytes.
While it is certainly possible that a bug in either the Java runtime or the implementation of the Java Advanced Imaging API itself could cause a memory leak, in most cases what appears to be a memory leak is simply the result of a temporary demand for more memory than is available from the Java runtime. A true memory leak implies that an ever-increasing amount of memory will be required by the application; an application that requests a large amount of memory but will eventually release it may be inefficient but cannot truly be said to exhibit a memory leak. The JAI 1.0.2 implementation of the tile cache allowed the cache to fill completely before releasing tiles, giving the impression of an increasing consumption of memory. JAI 1.1 allows tiles to be released when there are no longer any "hard references" to the operation objects.
In general, the proper use of tiling is the easiest way to limit the amount of work done. Setting the tile sizes will be very application-dependent. The Java Advanced Imaging API will perform work on a per-tile basis. If the final operator in a chain requires a certain source rectangle to be computed, the actual computation may extend past the actual area needed, up to the next set of tile boundaries.
Experiments have shown that tile sizes of 512x512 and larger appear to be the most efficient.
The only place where caching takes place automatically is in the method OpImage.getTile
. So if you subclass from OpImage
and override the computeTile
or computeRect
methods, but not the getTile
method (or you override getTile
but call the superclass method somehow), you get caching automatically. Otherwise, you don't. You can construct your own implementations of the TileCache
interface or use the factory method in the JAI
class and insert tiles manually, or you can construct a NullOpImage
around your image which will perform caching.
If you're using an OpImage
subclass, but don't want caching, you can call:
this.setTileCache(null);
A RenderedImage has a SampleModel
and a ColorModel
. The SampleModel
describes the pixel data the image has and how it is stored in the buffer. The ColorModel
interprets the pixel data in a specific ColorSpace
. The color definition of a particular band of the pixel is dependent on the ColorModel
and its associated ColorSpace
. Without a ColorModel
, the pixel data of an image has no color definition.
Your image may have an IndexColorModel
. In this case, the image data is stored in a 1-banded form, and the ColorModel
is used to determine the red, green, blue, and optional alpha values for each pixel. The javax.media.jai.RasterAccessor
will automatically cause such images to appear to have 3 or 4 bands as appropriate.
Java Advanced Imaging versions previous to JAI 1.1.1 used the AWT toolkit to load GIF and JPEG files. This problem is a manifestation of a JDK bug in which creation of the AWT Toolkit
class results in an attempt to open the X display. To work around this problem in Java Advanced Imaging versions prior to 1.1.1, either make an X display available to the Java runtime using the DISPLAY
environment variable (no windows will appear on the display), or consider running a dummy X server that will satisfy the AWT, such as the Xvfb
utility included with the X11R6.4 distribution.
In the JAI 1.1.1 version, the GIF and JPEG decoders were improved to no longer have a dependency on the X server.
A BufferedImage
may be used directly as a source to any Java Advanced Imaging operator or method that calls for a RenderedImage
or WritableRenderedImage
source or sources.
java.awt.Image
from the AWT in Java Advanced Imaging?This code sample reads a GIF or JPEG file using the java.awt.Toolkit
class into a java.awt.Image
, converts the AWT-Image into a RenderedImage
in JAI, then displays the RenderedImage
.
You can also do this in Java2D. Use a MediaTracker
to ensure the image is loaded, get its width and height and create a BufferedImage
with the right dimensions. Then call createGraphics()
on the BufferedImage
and then call drawImage()
on the returned Graphics
, passing it the java.awt.Image
. Now you have a BufferedImage
containing your image data. A BufferedImage
is an instance of RenderedImage
, so you can pass it to any Java Advanced Imaging interface that calls for a RenderedImage
.
PlanarImage
from an array of data? The steps to do this are:
DataBuffer
from your data array.SampleModel
describing the data layout.Raster
from the DataBuffer
and SampleModel
. You can use methods from the RasterFactory
class to do this.ColorModel
which describes your data. The factory method PlanarImage.createColorModel(sampleModel)
will take care of this for some common cases.TiledImage
with the SampleModel
and ColorModel
.TiledImage
with your data by using the TiledImage.setData()
method to copy your raster into the TiledImage.Only the last step involves any actual processing. The rest is just object creation.
Alternatively, a BufferedImage
may be constructed directly from the Raster
and ColorModel
. The RenderedImageAdapter
class may then be used to produce a PlanarImage
from the BufferedImage
. This approach avoids copying entirely.
Sample code to make a RenderedImage
out of a 2D array of floats can be found here.
The Java 2 platform (JDK 1.2 and later) contains a Kodak color management engine which uses ICC profiles for conversion. You will need to provide the ICC profiles in a form from which you can create a java.awt.color.ICC_Profile
object. The ICC_Profile
can then be used to create a java.awt.color.ICC_Colorspace
, which can be used to create a ColorModel
that can be supplied to the JAI "colorconvert" operation. If you use the "colorconvert" operation in this manner, and the SampleModel
of the source image (and thus the resultant destination image) is not compatible with the ColorModel
being specified to the "colorconvert" operation, then you should also provide an appropriate SampleModel
in an ImageLayout
object passed as a RenderingHint
. The SampleModel
should be compatible with the ColorModel
provided.
ICC profiles for the following color spaces may be downloaded here.
Use one of the dither operators -- either OrderedDither (fast) or ErrorDiffusion (slower, but higher quality).
First, convert the image to a luminance (grayscale) image and then dither it. You can convert to a grayscale space using the ColorConvert
operator:
pb = new ParameterBlock();
pb.addSource(src);
ColorModel cm =
new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY),
new int[] {8},
false,
false,
Transparency.OPAQUE,
DataBuffer.TYPE_BYTE);
pb.add(cm);
PlanarImage dst = JAI.create("ColorConvert", pb);
For simple linear conversions to grayscale, the "bandcombine" operator could also be used.
The dither operation needs to have a lookup table that converts any values less than half the grayscale range to zero and any values above half the range to one. The dither operation will automatically vary the threshold to minimize any contouring effects. Here is some sample code that illustrates using the "errordiffusion" and "ordereddither" operations for dithering a grayscale image to a monochrome (1-bit or bilevel) image.
An alternative to dithering for converting an 8-bit grayscale image to a 1-bit monochrome image is simply to apply a threshold to the image. The "Binarize" operation may be used for this purpose. A threshold may be derived by first creating a Histogram using the "Histogram" operation and then calculating the threshold using one of several methods:
getIterativeThreshold()
getMaxEntropyThreshold()
getMaxVarianceThreshold()
getMean()
getMinErrorThreshold()
getMinFuzzinessThreshold()
getModeThreshold()
getPTileThreshold()
This code example demonstrates how "binarize" operation can be used to apply the threshold.
A RenderedImage
occupies an arbitrary rectangular area in the 2D plane. Note that, as in the Java 2D API, the Y axis values increase from top to bottom.
The coordinates of the upper-left corner of the image appears at the point given by the getMinX()
and getMinY()
methods. The width and height of the image are obtained similarly using the getWidth()
and getHeight()
methods. In the illustration, the image starts at pixel (-100, -90) and has a width of 330 pixels and height of 215 pixels. The lower right pixel of the image is thus (229, 124). As shown here, the image location is not restricted to the positive quadrant.
Overlayed on the image is its tile grid. Each tile contains a rectangular portion of the image; all tiles have the same width and height and tiles do not overlap. Tiles are referenced by a pair of indices, i.e., tile (2, 3) is to the right and below tile (1, 2). Tiles may extend past the edges of the image. The contents of the portion outside the image are undefined. Only tiles that have some overlap with the image bounds can be obtained by calling getTile
. Attempts to retrieve any other tiles will result in a return value of null
.
The semantics of an image never depend on the layout of its tile grid layout, but performance maybe affected by the choice of tile size and offsets. The tile grid is defined by the four methods getTileGridXOffset()
, getTileGridYOffset()
, getTileWidth()
, and getTileHeight()
. The first two methods yield the position of the upper-left corner of a notional tile (0, 0). The image need not actually contain such a tile. For example, the tile grid of the image illustrated above may renumbered so that the upper left tile has index (-1, -3) and the lower right tile has index (2, -1):
The image doesn't contain a (0, 0) tile at all, and the pixel ( getTileGridXOffset()
, getTileGridYOffset()
) does not even fall within the image. Both images should act exactly identically when used as a source for all JAI operations except possibly for the output tile grid layout.
The minimum and maximum tile indices may be derived by determining the tile indices of the upper left and lower right pixels of the image. To determine the tile index of a given point, the following methods (available in the PlanarImage
class) may be used:
static int XToTileX(int x,
int tileGridXOffset,
int tileWidth) {
x -= tileGridXOffset;
if (x < 0) {
// Force round to -infinity
x += 1 - tileWidth;
}
return x/tileWidth;
}
static int YToTileY(int y,
int tileGridYOffset,
int tileHeight) {
y -= tileGridYOffset;
if (y < 0) {
// Force round to -infinity
y += 1 - tileHeight;
}
return y/tileHeight;
}
It is not sufficent to compute (x - tileGridXOffset)/tileWidth
since this will produce the wrong result for negative indices. Instead, it is necessary to compute the equivalent of (int)Math.floor((double)(x - tileGridXOffset)/tileWidth)
. The code above produces the same results using integer arithmetic only.
Most often, images loaded from external sources will have their minimum X and Y and tile grid X and Y offsets all equal to zero. As operations are performed, however, such as scaling, rotation or translation, the coordinates of the resultant images will change.
On a more microscopic level, pixels in a RenderedImage
may be thought of as squares with a 1 x 1 extent centered at (x + 0.5, y + 0.5). Geometric mapping operations may need to take this half pixel shift into account.
Unlike the RenderedImage
coordinate system, which is discrete, the RenderableImage
coordinate system is continuous. The image dimensions are described in terms of floating-point miniumum X and Y coordinates, a height, and an aspect ratio. For the "renderable" operation, you supply the height in the renderable coordinate space and the aspect ratio is derived automatically from the ratio of the source image width to its height (width/height) in the rendered image coordinate system.
When a RenderableImage
is rendered, an AffineTransform
is applied to map its coordinate system into the rendered pixel coordinate system which is appropriate to a target device such as a display, printer, file, etc. You have to specify the appropriate transformation.
At this time ROIs are used in Java Advanced Imaging for three purposes:
TiledImage
set()
and setData()
methods.In the latter case ROIs may be geometrically transformed or "shrunk" by operators in an image chain but otherwise are ignored (this geometric modification of the ROI occurs in parallel with the actual image processing operations). In no case other than the statistics operations does the presence of a ROI affect the operation per se.
One way that you might be able to subtract regions in two images defined by a ROI is as follows:
TiledImage
covering the area of the bounding box.Raster
of data over the bounding box.setData(Raster,ROI)
with the Raster
of the previous step and the bounding box to set the data of the TiledImage
.Note that the only tiles which will be calculated in the difference image are those which overlap the bounding box of the intersection of the two ROIs. Thus if you are using tiled source and destination images computation will be minimized.
Normally, the deferred execution model makes the notion of "completing" the computation of an image meaningless. Images are computed a tile at a time, and tiles may be computed multiple times if the tile cache is unable to reserve storage for them. However, in some cases such as perfomance benchmarking you may wish to force an image to be computed in its entirety.
JAI.create()
is a convenience method which effectively does the following:
OperationDescriptor
.RenderedOp
node into the imaging chain.No actual computation of image data will occur until data is requested from a node in the chain unless the isImmediate()
method of the associated OperationDescriptor
returns true
. In this case, the chain will be evaluated as soon as the node is appended to the chain. Otherwise, you can force a chain to be evaluated up to a given node by invoking getRendering()
on the RenderedOp
of that node. This will force evaluation of the entire chain up to and including that node.
Even at this point, actual tile data will not necessarily have been computed. A loop such as the following may be used to force actual tile computation:
void forceLoad(RenderedImage im) {
int minX = im.getMinTileX();
int minY = im.getMinTileY();
int maxX = minX + im.getNumXTiles();
int maxY = minY + im.getNumYTiles();
for (int j = minY; j < maxY; j++) {
for (int i = minX; i < maxX; i++) {
im.getTile(i, j);
}
}
}
Note that if you wish to measure the execution time of a given node you will need to force computation on its source nodes first to exclude their evaluation time from the measurement. The tile caching should also be disabled.
As indicated in the specification of the "overlay" operator, it puts one RenderedImage
on top of another RenderedImage
. To use this operator, two source RenderedImage
s are needed; call them src1 and src2. Then to create an "overlay" op, you simply do:
RenderedImage dst =
JAI.create("overlay", src1, src2);
The result dst will have src2 on the top and src1 on the bottom. The part of src1 covered by src2 will not be seen. The image layout (width, height, etc.) of dst is copied from src1.
One constraint is that src1 and src2 must have the same data type and number of bands, or the library will throw an IllegalArgumentException
.
The position of src2 is based on its minimum X and Y. If you wish to move src2 to a different location, you must do a "translate" operation on src2 first, and overlay the translated image on src1.
If src2 is bigger than src1, it may cover the entire src1 and you'll only see part of src2 as a result (since dst has the dimensions of src1). To increase the dimensions of an image, the "border" operation may be used.
The most direct approach is to use the "lookup" operator to map your 10 or 12 bit data to 8-bit values. If you just want to do simple range conversion, then, for 12 bit data the lookup table's data can be created by:
byte[] blut = new byte[4096];
for (int i=0; i < 4096; i++) {
blut[i] = (byte)(i >> 4);
}
You can determine the min/max using Extrema operator. You will normally want to choose the min of all bands as the minimum, and likewise for the max. Doing it band by band will cause color shifts. Of course, if the imagery is false color, you may not care.
You then create the table by:
double scale = 255.0/(float)(max-min);
for (int i = min; i <= max; i++) {
blut[i] = (byte)((i - min)*scale);
}
// Clamp any input values outside
// min/max range, just in case
for (int i = 0; i < min; i++) {
blut[i] = 0;
}
for (int i = max; i < 4096; i++) {
blut[i] = (byte)255;
}
You may find that these approaches sacrifice too much detail at the low radiance end of the scale. Frequently a non-linear transformation is needed, usually somewhere between square-root and cube-root.
for (int i = 0; i < 4096; i++) {
double f = i*(1.0/4095.0);
// Take the square root
f = Math.pow(f, 0.5);
// Scale to 8 bits and round
blut[i] = (byte)(f*255.0 + 0.5);
}
This type of image, e.g., one derived from a TIFF palette-color file, will have a ColorModel
that is an instance of IndexColorModel
. In general, if the ColorModel
of the source image is an IndexColorModel
but that of the destination is not, then the source image is temporarily "expanded" during processing to the number of bands indicated by the IndexColorModel
(which is the same as the number returned by OpImage.getExpandedNumBands()
).
The destination ColorModel
is either specified by an ImageLayout
object passed in via the RenderingHints
parameter to JAI.create()
or it is derived from the SampleModel
of the (first) source image: a "best guess" ColorModel
is constructed for the destination by passing the source SampleModel
to PlanarImage.createColorModel()
. If source data are not expanded, it is assumed that the operation in question will apply any special handling needed when it encounters an IndexColorModel
case. If it does not specially handle such a case then the raster data will be treated as grayscale (note that if a ColorModel
is specified via the RenderingHints
it is incumbent on the user to ensure that it will be compatible with the destination SampleModel
).
Some operations perform processing on the colormap, effectively changing the colormap and not performing any pixel manipulation. This behavior is accomplished by the operations being subclasses of javax.media.jai.ColormapOpImage
. The operations which are implemented as subclasses of ColormapOpImage are "addConst", "andConst", "divideIntoConst", "exp", "invert", "log", "lookup", "multiplyConst", "not", "orConst", "piecewise", "rescale", "subtractFromConst", "threshold" and "xorConst".
As of JAI 1.1.2, a new RenderingHint
JAI.KEY_TRANSFORM_ON_COLORMAP
specifies whether processing should take place on the colormap, or on the pixel (index) data for those operations that are implemented as subclasses of javax.media.jai.ColormapOpImage
. By default, a ColormapOpImage
node has a hint with a value of Boolean.TRUE
, which means the processing is done on the colormap, and not on the indices. To suppress this behavior, a hint with a value of Boolean.FALSE
should be attached to the node.
Other operations, like "translate" (with integral translation factors) and "crop" operations do not actually perform any processing of the data: they effectively forward tile requests on to the source image.
As of JAI 1.1.2, a new RenderingHint
JAI.KEY_REPLACE_INDEX_COLOR_MODEL
has been provided that allows for automatic color translation for colormapped imagery in those situations where not doing so would result in unexpected / incorrect results (such as geometric operations). Operations that are implemented as subclasses of javax.media.jai.AreaOpImage
and javax.media.jai.GeometricOpImage
set this RenderingHint to true, such that these operations are performed correctly on the colormapped imagery, not treating the indices into the color map as pixel data. These operations are listed in the The Java Advanced Imaging 1.1.2 README
One of the common uses of the format operator is to cast the pixel values of an image to a given data type. In such a case, since JAI 1.1.2, the format operation adds a RenderingHints
object for JAI.KEY_REPLACE_INDEX_COLOR_MODEL
with the value of Boolean.TRUE
, if the source image provided has an IndexColorModel
. Due to the addition of this new RenderingHint
, using the "format" operation with source(s) that have an IndexColorModel
will cause the destination to have an expanded non- IndexColorModel
ColorModel
. This expansion ensures that the conversion to a different data type, ColorModel
or SampleModel
happens correctly such that the indices into the color map (for IndexColorModel
images) are not treated as pixel data.
One further thing to note is that even if you pass in an IndexColorModel
via the RenderingHints
this will not change the way in which the image data per se are handled, although you might be able to display the result. For those operations that are not subclasses of ColormapOpImage, or where color translation is not being performed through the use of JAI.KEY_REPLACE_INDEX_COLOR_MODEL
, the image data will still be processed as if they represent grayscale data.
Sometimes it might be useful to convert the palette-color images into RGB images at the beginning of the processing. One way to expand the palette-color images is by inserting a "format" operation which changes the ColorModel
, thereby forcing expansion. (Note that as mentioned "crop" and integral translation merely forward the tile requests to the source so you can't expand the data via those operations.) Obviously expanding the palette-color data to RGB will increase the memory footprint but this way you are assured of obtaining a valid result.
Probably the best means of converting a palette-color image to a 3-band RGB image is to use the "lookup" operation. You can construct the lookup table using values returned by the getBlues()/getGreens()/getReds() methods of IndexColorModel
; the method getMapSize()
should tell you the number of elements in the lookup table. This code example shows how to perform the conversion.
Java Advanced Imaging API's "border" operation and BorderExtender
classes are capable of extending an image outward for a specified number of pixels in various ways. This is used mainly for geometric operations where interpolation is needed.
To display an image with a decorative border, the Swing toolkit supports several kinds of borders in javax.swing.border
package. For some applications, you may want to use an IconJAI
(found in the sample directory) to display the image, then add a Swing border to it.
The Java Advanced Imaging API does not have a "drop shadow" or similar special effect operator as found in some imaging software. Such an effect could be created by overlaying an image over a modified version of itself to produce a 3D look.
IndexColorModel
into a grayscale image?A method to convert an CS_sRGB into a grayscale Image (CS_GRAY) can be found here.
If you intend to convolve all bands of a given multi-band image (the common case) with the same kernel then you can use the "convolve" operation as is. If however you would like to convolve each band of a multi-band image with a different kernel then you would need to extract each band, e.g., using "bandselect", apply the appropriate convolution kernel to each band using "convolve", and then merge the separate filtered bands into a resultant multi-band image.
If you are attempting to do some sort of multispectral template matching however, the above scenario might not be what you want. In that case, you could use single-band convolution by, for example, first converting the images to a different color space such as HIS or YCC and perform the template matching using the intensity or luminance band.
TiledImage
?A TiledImage
may not be serialized directly as it does not implement the java.io.Serializable
interface. You may however construct a SerializableRenderedImage
from a TiledImage
. See the javax.media.jai.remote.SerializableRenderedImage
documentation for more information. Alternatively, if a suitable external file format exists the ImageCodec
interfaces may be useful.
In order to use Java Advanced Imaging in a browser environment, your applet HTML file should be converted with the Java Plug-in HTMLConverter (now sits in the JDK 1.3.1 and JDK 1.4 bin directories). The Java Plug-in (now part of JRE/JDK 1.3.1 and 1.4) and JAI configuration requirements are as follows:
$JREHOME/lib/security/java.policy
On Solaris:
$HOME/.java.policy
On Windows (NT, for example)
C:\WINNT\Profiles\$User\.java.policy
The permissions needed to be added are:
grant {
// Necessary for JAI to utilize mediaLib in applets.
permission java.io.FilePermission "/usr/bin/uname", "execute";
// May be necessary for JAI to do encoding in applets.
permission java.io.FilePermission "${java.io.tmpdir}/*", "write, delete";
};
grant {
// May be necessary for JAI to do encoding in applets.
permission java.io.FilePermission "${java.io.tmpdir}/*", "write, delete";
};
If the users encode images in the applet, the applet may write to some temporary file. If the users don't encode images in the applet, no permission is needed on Windows. As mentioned above, on Solaris, the user still needs to grant execute permission to /usr/bin/uname on Solaris.
The above steps will make sure the JAI files are picked up by the Plugin at runtime.
Applets can also utilize the Auto Installation capabilities provided by Java Plugin (JPI) including Java extension deployment to facilitate applet deployment.
Operations which are to be created using JAI.create() must be registered with the default OperationRegistry. They can also be automatically detected and registered by listing it in a registry file (META-INF/registryFile.jai) which is often contained in a jar file with the user's other class files (see the javadoc for OperationRegistry and OperationRegistrySpi classes for more information). Each operation has an OperationDescriptor
(denoted "descriptor" in the registry file) which provides a textual description of the operation and specifies the number and type of its sources and parameters. The OperationDescriptor
also specifies the supported operation modes ("rendered", "renderable", "collection" etc.). Rendered and renderable mode correspond to resolution-dependent and resolution-independent operations, respectively. For more information on these concepts please refer to the Java Advanced Imaging Programmer's Guide available via the Java Advanced Imaging home page.
For each OperationDescriptor
there should also be at least one of either a RenderedImageFactory
(RIF) or ContextualRenderedImageFactory
(CRIF). The RIF is for rendered mode operations only; the CRIF can handle operations which function only in renderable mode or in both rendered and renderable modes. The RIFs and CRIFs have entries in the registry file denoted "rendered" and "renderable", respectively. Preferences for RIFs may also be set in the registry file.
In most of the reference port operation implementations there is also eventually an implementation class which is a descendent of OpImage
. For example, it may be a subclass of PointOpImage
, AreaOpImage
, etc., as appropriate. The RIF or CRIF is responsible for creating an instance of this class. It is possible for a RIF to instantiate different implementation classes depending on the types of the sources and parameters. It is also possible for multiple RIFs to instantiate the same implementation class (e.g., "rotate" and "affine" operators might share a common implementation). A RIF may even instantiate a chain of several connected objects.
What happens when JAI.create()
is invoked is as follows:
OperationDescriptor
for the operation in question will be retrieved from the registry.
OperationDescriptor
.
RenderedOp
is created for the given operation. The RenderedOp
contains the name of the operation, the sources and parameters and any rendering hints, but no actual image data until it is rendered as the result of a call to methods such as getWidth
, getData
or getTile
.When the RenderedOp
is eventually rendered, the following occurs:
create()
method is called using the information stored in the RenderedOp
.
create
method of each RIF in sequence; the first non- null
returned value is the result of the operation.
create
method of a RIF is invoked it is responsible for returning an instance of RenderedImage
(usually an OpImage
subclass) created for this operation.A similar sequence of events occurs for renderable mode operations using JAI.createRenderable()
.
In summary, to create a new operation you need to do the following:
OpImage
subclass for your operation.OperationDescriptor
.OperationRegistry
.It is recommended that the user create a META-INF/registryFile.jai
(and include it in the jar file or the classpath) and add the necessary entries in that file to register the new operator, the image factories for each supported mode and preferences if any. To avoid the hassles involved in creating a separate registryFile.jai
file, it is possible to register OperationDescriptor
s and RIFs/CRIFs, using the OperationRegistry
's registerOperationDescriptor
and registerRIF
/ registerCRIF
methods. After this registering (and setting of preferences, if desired), the new operation can then be invoked through JAI.create()
. Of course, the drawback of this is that the new operator will not be automatically reloaded every time a Java Advanced Imaging program is executed, since it is not present in registryFile.jai
. So, in order to use it, the registry methods would always have to be invoked beforehand.
An example of how to use the OperationRegistry
's methods to register a descriptor and it's image factories :
public static void init() { OperationRegistry or = JAI.getDefaultInstance().getOperationRegistry(); // An OperationDescriptor describing the "fourier" transform // operator OperationDescriptor fd = new FourierDescriptor(); // A RenderedImageFactory which could presumably create // FourierOpImage's RenderedImageFactory rifJava = new FourierRIF(); // A RenderedImageFactory which could create a // NativeFourierOpImage which used a native implementation // of the fourier transform. RenderedImageFactory rifNative = new NativeFourierRIF(); // First register the "fourier" OperationDescriptor or.registerDescriptor(fd); // Now register the two RenderedImageFactory-s for this operator RIFRegistry.register(or, fd.getName(), "com.xxx.yyy", rifJava); RIFRegistry.register(or, fd.getName(), "com.xxx.yyy", rifNative); // Now set a preference such that the Native implementation // is preferred over the java implementation RIFRegistry.setPreference(or, fd.getName(), "com.xxx.yyy", rifNative, rifJava); }
Aids to writing your own codec can be found in the Programmer's Guide, section 14.5 and the PNM code supplied with the sample demo.
java -version-Djava.compiler=none
However, Java Advanced Imaging has only been tested on Win32, Solaris, and Linux (Red Hat 6.1 JVM) Java 2 platforms. Please check the Java 2 Website for the latest porting information. Native performance enhancements for the Java Advanced Imaging API are currently available for Windows, Solaris and Linux. Java Advanced Imaging should work without native performance enhancements for other platforms, however this usage is unsupported.
Please note the codec classes are not a committed part of the Java Advanced Imaging API, and that there is a separate Java Image File I/O package. For more information on Image I/O in JAI please refer to Image I/O in Java Advanced Imaging.
The two file formats which support short integer (16-bit) data in the file I/O package supplied with Java Advanced Imaging 1.1.2_01 are Portable Network Graphics (PNG) and TIFF. There is support in the codec APIs for reading multi-image files, and the TIFF codec was enhanced in 1.1 to support both reading and writing of multi-image files. Below are some answers to common format-specific questions.
The documentation incorrectly showed an example of using the "tiff" operator in renderable mode; all codecs operate in the rendered mode only. You can do a JAI.create("fileload", ...)
followed by a JAI.createRenderable("renderable", ...)
to get a renderable source based on an image file.
Limitations of the TIFF codec in 1.1.2_01:
The TIFF reader reads only the tiles which overlap the data rectangle requested by operations which are "downstream" in the imaging chain. Consequently for tiled TIFF the tiles are not read from the TIFF disk image until such time as their geometric region is needed to complete processing of the chain for which they are the data source. If tiles have been previously loaded but have since been flushed from the cache then they will of course need to be reloaded.
The Windows 95 version of BMP is supported. The bit depth of the output when saving a BMP will be determined by that of the source image.
As of JAI 1.1.1, the JAI GIF decoder is no longer implemented by calling into the AWT Toolkit. The new implementation of the JAI GIF decoder correctly handles images with transparent backgrounds.
GIF encoding is problematic due to active patents. See the jai-interest mail archive for possible solutions.
PNG is fully implemented. The type of the encoded image (RGB, Greyscale, or Palette) is determined by the type of the image being saved, not by the choice of PNGEncodeParam
subclass.
The JPEG support is currently implemented on top of the unofficial JDK classes in the com.sun.image.codec.jpeg
package, which may not exist in all Java 2 environments.
FlashPIX reading is only partially implemented. There is no FlashPIX writer. Tiles of FlashPIX images are read only when required.
The Java Advanced Imaging API includes an extention interface allowing third parties to provide their favorite file format handling. Some of the file formats which have been requested and which we encourage third parties to provide are FITS, NITF, DICOM, EPS, RDF, or other vector formats.
String fileName; // Path of the file to be read
PlanarImage image =
JAI.create("fileload", fileName);
getAsBufferedImageBufferedImage
URL url; // URL of image to be read
PlanarImage pi = JAI.create("URL", url);
BufferedImage bi = pi.getAsBufferedImage();
code sample
RenderedOp op = JAI.create("filestore", image, filename, filetype, encodeParam);
getWidth()getHeight()RenderedImageImageDecoder.decodeAsRenderedImage()getData()getTile()
JSR
// Load the input image.
String inputFile = "image.bmp";
RenderedOp src = JAI.create("fileload", inputFile);
// Store it as a JPEG
String outputFile = "image.jpg";
JAI.create("filestore", src, outputFile, "jpeg",
(ImageEncodeParam)null);
IndexColorModel
and the data is stored 2 pixels packed per byte (using a MultiPixelPackedSampleModel
)
java.awt.Robot
Another approach is to create an AWT image and call the same application paint
method to draw into it.
Next, convert the AWTImage to a PlanarImage:
ParameterBlock pb = new ParameterBlock();
pb.add(tAWTImage);
PlanarImage tPlanarImage =
(PlanarImage) JAI.create("awtImage", pb );
OutputStream tOutput =
new FileOutputStream(tFilename);
ImageEncoder tEncoder =
ImageCodec.createImageEncoder("BMP",
tOutput,
null);
tEncoder.encode(tPlanarImage);
tOutput.close();
As of JAI 1.1.2, applications may no longer need to add an ImageLayout containing a ComponentColorModel to the RenderingHints of certain operations or to convert all IndexColorModel images to 3-band RGB images when they are loaded. While the GIF decoder still produces an image which has an IndexColorModel, those operations that would produce an incorrect result when operating on a source with an IndexColorModel use the JAI.KEY_REPLACE_INDEX_COLOR_MODEL RenderingHint (as of JAI 1.1.2) to cause the resultant image to have a non-IndexColorModel, and do the processing on the color translated pixels, as opposed to performing the operations on the indices into the colormap. A detailed discussion of these issues can be found here.
Most operators allow tile layout to be specified via the ImageLayout
hint. One may retile an image, in the next operator this image is used as a source by specifying a new tileWidth/tileHeight. If you don't want to perform any operations on the image, you can always use the reformat operator, with the same data type.
You can also use the freely available utility "tiffcp" (part of the libtiff distribution) to retile TIFF images. This works well with Java Advanced Imaging. tiffcp is also a bit more tolerant of images with "bad" headers than Java Advanced Imaging and you can use it on a TIFF image that Java Advanced Imaging can't read in order to clean it up.
The utility "tiffsplit" is also in the libtiff distribution. This program splits a multi-image TIFF into single-image TIFFs
A example of a float image with a float kernel, converted to a PlanarImage
for display may be found here.
RenderedImagebgfg
RenderedImage bg; // background image
RenderedImage fg; // foreground (inset) image
TiledImage ti =
new TiledImage(bg.getMinX(),
bg.getMinY(),
bg.getWidth(),
bg.getHeight(),
bg.getTileGridXOffset(),
bg.getTileGridYOffset(),
bg.getSampleModel(),
bg.getColorModel());
ti.set(bg);
Rectangle r =
new Rectangle(fg.getMinX(),
fg.getMinY(),
fg.getWidth(),
fg.getHeight());
ROI roi = new ROIShape(r);
ti.setData(fg.getData(), roi);
fgbgSampleModelti
public void display(Collection imgs) {
Frame window = new Frame(testName);
int n = imgs.size();
window.setLayout(new GridLayout(1, n));
Iterator iter = imgs.iterator();
while (iter.hasNext()) {
RenderedImage img =
(RenderedImage)iter.next();
int w = img.getWidth();
int h = img.getHeight();
ScrollingImagePanel panel =
new ScrollingImagePanel(img, w, h);
window.add(panel);
}
window.pack();
window.show();
}
RenderedImageFrameFrame