Programming in Java Advanced Imaging
C H A P T E R 7 |
|
Image Enhancement |
THIS chapter describes the basics of improving the visual appearance of images through enhancement operations.
7.1
The JAI API image enhancement operations include:Introduction
- Adding borders
- Cropping an image
- Amplitude rescaling
- Histogram equalization
- Lookup table modification
- Convolution filtering
- Median filtering
- Frequency domain processing
- Pixel point processing
- Thresholding (binary contrast enhancement)
7.2
JAI provides two different ways of adding a border to an image. These two ways are described in the following paragraphs.Adding Borders to Images
7.2.1
TheThe Border Operation
Borderoperation allows you to add a simple filled border around a source image. The border extends the source image's boundaries by a specified number of pixels.The amount of extension may be specified separately for the top, bottom, and left and right sides. The following types of border fill may be specified:
- Zero fill - the border area is extended with zeros (
BORDER_ZERO_FILL).
- Constant fill - the border area is extended with a specified constant value (
BORDER_CONST_FILL). An array of constants must be supplied. The array must have at least one element, in which case this same constant is applied to all destination image bands. Or, it may have a different constant entry for each corresponding band. For all other border types, thisconstantsparameter may benull.
- Extend - the border area is created by copying the edge and corner pixels (
BORDER_COPY).
- Reflection - the border area is created by reflection of the image's outer edge (
BORDER_REFLECT).
- Wrap - the border area is extended by "wrapping" the image plane toroidally, that is, joining opposite edges of the image (
BORDER_WRAP).
Figure 7-1 Image Borders
The image layout (tile width, height, and offsets;
SampleModelandColorModel) is copied from the source. TheBorderoperation takes six parameters:
7.2.2
Some area operations, such as convolve, scale, and rotate, benefit from the addition of an extended border around the source image. The extended border comes into play when the convolution kernel overlaps the source image as the key value is scanned over it.Extending the Edge of an Image
A
BorderExtendermay be applied to an operation using a suitable hint. The hints are defined in Table 7-1.
The
BorderExtenderclass is the superclass for four classes that extend aWritableRasterwith additional pixel data taken from aPlanarImage. Instances ofBorderExtenderare used by thePlanarImage.getExtendedDataandPlanarImage.copyExtendedDatamethods.The
PlanarImage.getExtendedDatamethod returns a copy of an arbitrary rectangular region of the image in aRaster. The portion of the rectangle of interest outside the bounds of the image will be computed by calling the givenBorderExtender. If the region falls entirely within the image, the extender will not be used. Thus it is possible to use anullvalue for theextenderparameter when it is known that no actual extension will be required. The returnedRastershould be considered non-writable. ThecopyExtendedDatamethod should be used if the returnedRasteris to be modified.The
PlanarImage.copyExtendedDatamethod copies an arbitrary rectangular region of theRenderedImageinto a caller-suppliedWritableRaster. The portion of the suppliedWritableRasterthat lies outside the bounds of the image is computed by calling the givenBorderExtender. The suppliedWritableRastermust have aSampleModelthat is compatible with that of the image.Each instance of
BorderExtenderhas anextendmethod that takes aWritableRasterand aPlanarImage. The portion of the raster that intersects the bounds of the image will already contain a copy of the image data. The remaining area is to be filled in according to the policy of theBorderImagesubclass. The subclasses are described in Table 7-1.
API:javax.media.jai.Planarimage
- Raster getExtendedData(Rectangle region, BorderExtender extender)
- returns a copy of an arbitrary rectangular region of this image in a Raster.
Parameters: regionThe region of the image to be returned.
extenderAn instance of BorderExtender, used only if the region exceeds the image bounds.
- void copyExtendedData(WritableRaster dest, BorderExtender extender)
- copies an arbitrary rectangular region of the
RenderedImageinto a caller-suppliedWritableRaster.
Parameters: destA WritableRasterto hold the returned portion of the image.
extenderAn instance of BorderExtender.
API:javax.media.jai.BorderExtender
- static BorderExtender createInstance(int extenderType)
- returns an instance of
BorderExtenderthat implements a given extension policy. The policies understood by this method are:
- abstract void extend(WritableRaster raster, PlanarImage im)
- fills in the portions of a given
Rasterthat lie outside the bounds of a givenPlanarImagewith data derived from thatPlanarImage.7.2.2.1
TheBorderExtenderZero
BorderExtenderZeroclass is a subclass ofBorderExtenderthat implements border extension by filling all pixels outside of the image bounds with zeros. For example, Figure 7-2 shows the result of usingBorderExtenderZeroto extend an image by adding two extra rows to the top and bottom and two extra columns on the left and right sides.
Figure 7-2 BorderExtenderZero Example
API:javax.media.jai.BorderExtenderZero
- final void extend(WritableRaster raster, PlanarImage im)
- fills in the portions of a given
Rasterthat lie outside the bounds of a givenPlanarImagewith zeros. The portion of Raster that lies withinim.getBoundsis not altered.7.2.2.2
TheBorderExtenderConstant
BorderExtenderConstantclass is a subclass ofBorderExtenderthat implements border extension by filling all pixels outside of the image bounds with constant values. For example, Figure 7-3 shows the result of usingBorderExtenderConstantto extend an image by adding two extra rows to the top and bottom and two extra columns on the left and right sides.In the figure, X is the constant fill value. The set of constants is clamped to the range and precision of the data type of the
Rasterbeing filled. The number of constants used is given by the number of bands of theRaster. If theRasterhas b bands, and there are c constants, constants 0 through b - 1 are used when bc. If b > c, zeros are used to fill out the constants array.
Figure 7-3 BorderExtenderConstant Example
API:javax.media.jai.BorderExtenderConstant
- BorderExtenderConstant(double[] constants)
- constructs an instance of
BorderExtenderConstantwith a given set of constants. The constants are specified as an array ofdoubles.
- final void extend(WritableRaster raster, PlanarImage im)
- fills in the portions of a given
Rasterthat lie outside the bounds of a givenPlanarImagewith constant values. The portion ofRasterthat lies withinim.getBoundsis not altered.7.2.2.3
TheBorderExtenderCopy
BorderExtenderCopyclass is a subclass ofBorderExtenderthat implements border extension by filling all pixels outside of the image bounds with copies of the edge pixels. For example, Figure 7-4 shows the result of usingBorderExtenderCopyto extend an image by adding two extra rows to the top and bottom and two extra columns on the left and right sides.Although this type of extension is not particularly visually appealing, it is useful as a way of padding source images prior to area or geometric operations, such as convolution, scaling, or rotation.
Figure 7-4 BorderExtenderCopy Example
API:javax.media.jai.BorderExtenderCopy
- final void extend(WritableRaster raster, PlanarImage im)
- fills in the portions of a given
Rasterthat lie outside the bounds of a givenPlanarImagewith copies of the edge pixels of the image. The portion ofRasterthat lies withinim.getBoundsis not altered.7.2.2.4
TheBorderExtenderWrap
BorderExtenderWrapclass is a subclass ofBorderExtenderthat implements border extension by filling all pixels outside of the image bounds with copies of the whole image. For example, Figure 7-5 shows the result of usingBorderExtenderWrapto extend an image by adding two extra rows to the top and bottom and two extra columns on the left and right sides.This form of extension is appropriate for data that is inherently periodic, such as the Fourier transform of an image or a wallpaper pattern.
Figure 7-5 BorderExtenderWrap Example
API:javax.media.jai.BorderExtenderWrap
- final void extend(WritableRaster raster, PlanarImage im)
- Fills in the portions of a given
Rasterthat lie outside the bounds of a givenPlanarImagewith copies of the entire image. The portion ofRasterthat lies withinim.getBoundsis not altered.7.2.2.5
TheBorderExtenderReflect
BorderExtenderReflectclass is a subclass ofBorderExtenderthat implements border extension by filling all pixels outside the image bounds with reflected copies of the whole image. For example, Figure 7-6 shows the result of usingBorderExtenderReflectto extend an image by adding two extra rows to the top and bottom and one extra column on the left and right sides.This form of extension avoids discontinuities around the edges of the image.
Figure 7-6 BorderExtenderReflect Example
API:javax.media.jai.BorderExtenderReflect
- final void extend(WritableRaster raster, PlanarImage im)
- Fills in the portions of a given
Rasterthat lie outside the bounds of a givenPlanarImagewith suitably reflected copies of the entire image. The portion ofRasterthat lies withinim.getBoundsis not altered.7.3
TheCropping an Image
Cropoperation crops a rendered or renderable image to a specified rectangular area. The x, y, width, and height values are clipped to the source image's bounding box. These values are rounded to typeintfor rendered images.The
Cropoperation takes four parameters. None of the parameters have default values; all must be supplied.
Parameter Type Description x Float The x origin for each band.
y Float The y origin for each band.
width Float The width for each band.
height Float The height for each band.
7.4
Amplitude rescaling provides a linear amplitude transformation of input pixel values to output pixel values. Amplitude rescaling can be used to enhance images that have insufficient contrast between the lightest and darkest values, such as caused by underexposure or overexposure of the original image.Amplitude Rescaling
The full dynamic range of one band of an eight-bit image is 0 to 255. An underexposed image may only contain pixel values from 10 to 180, resulting in an image that does not fully use the dynamic range of the display. Such an image can be greatly improved by linearly stretching the contrast range; mapping the lowest values to 0 and the highest values to 255.
The
rescaleoperation takes a rendered or renderable source image and maps the pixel values of the image from one range to another range by multiplying each pixel value by one of a set of constants and then adding another constant to the result of the multiplication. If the number of constants supplied is less than the number of bands of the destination, the constant from entry 0 is applied to all the bands. Otherwise, a constant from a different entry is applied to each band. There must be at least one entry in each of the constants and offsets arrays.The pixel values of the destination image are defined by the following pseudocode:
constant = (constants.length < dstNumBands) ? constants[0] : constants[b]; offset = (offsets.length < dstNumBands) ? offsets[0] : offsets[b]; dst[x][y][b] = src[x][y][b]*constant + offset;The pixel arithmetic is performed using the data type of the destination image. By default, the destination will have the same data type as the source image unless anImageLayoutcontaining aSampleModelwith a different data type is supplied as a rendering hint.The values of the lowest and highest pixel amplitudes must be known. This information can be acquired through the
Extremaoperation (see Section 9.3, "Finding the Extrema of an Image").The following equations show the relationships between the extrema and the scale and offset factors.
The
- where max(b) and min(b) are the largest and smallest pixel values in the band, respectively.
rescaleoperation takes two parameters:
Parameter Type Description constants double The per-band constants to multiply by.
offsets double The per-band offsets to be added.
7.5
An image histogram is an analytic tool used to measure the amplitude distribution of pixels within an image. For example, a histogram can be used to provide a count of the number of pixels at amplitude 0, the number at amplitude 1, and so on. By analyzing the distribution of pixel amplitudes, you can gain some information about the visual appearance of an image. A high-contrast image contains a wide distribution of pixel counts covering the entire amplitude range. A low contrast image has most of the pixel amplitudes congregated in a relatively narrow range.Histogram Equalization
See Section 9.4, "Histogram Generation," for information on how to generate a histogram for an image. The next two sections describe JAI operations that use an image histogram to enhance an image's appearance.
7.5.1
ThePiecewise Linear Mapping
Piecewiseoperation performs a piecewise linear mapping of an image's pixel values. The piecewise linear mapping is described by a set of breakpoints that are provided as an array of the form:float breakPoints[N][2][numBreakPoints]If N is unity, the same set of breakpoints will be applied to all bands in the image. The abscissas of the supplied breakpoints must be monotonically increasing.
- where the value of N may be either unity or the number of bands in the source image.
The pixel values of the destination image are defined by the following pseudocode:
if(src[x][y][b] < breakPoints[b][0][0]) dst[x][y][b] = breakPoints[b][1][0]); } else if(src[x][y][b] > breakPoints[b][0][numBreakPoints-1]) { dst[x][y][b] = breakPoints[b][1][numBreakPoints-1]); } else { int i = 0; while(breakPoints[b][0][i+1] < src[x][y][b]) { i++; } dst[x][y][b] = breakPoints[b][1][i] + (src[x][y][b] - breakPoints[b][0][i])* (breakPoints[b][1][i+1] - breakPoints[b][1][i])/ (breakPoints[b][0][i+1] - breakPoints[b][0][i]);ThePiecewiseoperation takes one parameter:
Parameter Type Description breakPoints Float The breakpoint array.
Listing 7-1 shows a code sample of a
Piecewiseoperation, showing only the construction of the piecewise-mapped image and the operation. The generation of the source image, fmt, is not shown.
Listing 7-1 Example Piecewise Operation
// Create a piecewise-mapped image emphasizing low values. float[][][] bp = new float[numBands][2][]; for(int b = 0; b < numBands; b++) { bp[b][0] = new float[] {0.0F, 32.0F, 64.0F, 255.0F}; bp[b][1] = new float[] {0.0F, 64.0F, 112.0F, 255.0F}; }// Create the Piecewise operation. RenderedOp pw = JAI.create("piecewise", fmt, bp);
7.5.2
It is sometimes desirable to transform an image so that its histogram matches that of a specified functional form. TheHistogram Matching
MatchCDFoperation performs a piecewise linear mapping of the pixel values of an image such that the cumulative distribution function (CDF) of the destination image matches as closely as possible a specified cumulative distribution function.The CDF of an image is its area-normalized threshold area function. The desired CDF for the
MatchCDFoperation is described by an array of the form:float CDF[numBands][numBins[b]]Each element in the array
- where
numBinsdenotes the number of bins in the histogram of the source image for band b.CDF[b]must be non-negative, the array must represent a non-decreasing sequence, and the last element of the array must be 1.0F. The source image must have aHistogramobject available via itsgetPropertymethod.The
MatchCDFoperation takes one parameter:
Parameter Type Description CDF Float The desired cumulative distribution function.
The operation requires that the image histogram be available.
Listing 7-2 shows a code sample of a
MatchCDFoperation, showing only the histogram operation, construction of two different CDFs, and the operations that use them.
Listing 7-2 Example MatchCDF Operation
// Retrieves a histogram for the image. private static Histogram getHistogram(RenderedOp img, int binCount) {// Get the band count. int numBands = img.getSampleModel().getNumBands();// Allocate histogram memory. int[] numBins = new int[numBands]; double[] lowValue = new double[numBands]; double[] highValue = new double[numBands]; for(int i = 0; i < numBands; i++) { numBins[i] = binCount; lowValue[i] = 0.0; highValue[i] = 255.0; }// Create the Histogram object. Histogram hist = new Histogram(numBins, lowValue, highValue);// Set the ROI to the entire image. ROIShape roi = new ROIShape(img.getBounds());// Create the histogram op. RenderedOp histImage = JAI.create("histogram", img, hist, roi, new Integer(1), new Integer(1));// Retrieve the histogram. hist = (Histogram)histImage.getProperty("histogram");return hist; }// Create an equalization CDF. float[][] CDFeq = new float[numBands][]; for(int b = 0; b < numBands; b++) { CDFeq[b] = new float[binCount]; for(int i = 0; i < binCount; i++) { CDFeq[b][i] = (float)(i+1)/(float)binCount; } }// Create a normalization CDF. double[] mean = new double[] {128.0, 128.0, 128.0}; double[] stDev = new double[] {64.0, 64.0, 64.0}; float[][] CDFnorm = new float[numBands][]; for(int b = 0; b < numBands; b++) { CDFnorm[b] = new float[binCount]; double mu = mean[b]; double twoSigmaSquared = 2.0*stDev[b]*stDev[b]; CDFnorm[b][0] = (float)Math.exp(-mu*mu/twoSigmaSquared); for(int i = 1; i < binCount; i++) { double deviation = i - mu; CDFnorm[b][i] = CDFnorm[b][i-1] + (float)Math.exp(-deviation*deviation/twoSigmaSquared); } } for(int b = 0; b < numBands; b++) { double CDFnormLast = CDFnorm[b][binCount-1]; for(int i = 0; i < binCount; i++) { CDFnorm[b][i] /= CDFnormLast; } }// Create a histogram-equalized image. RenderedOp eq = JAI.create("matchcdf", fmt, CDFeq);// Create a histogram-normalized image. RenderedOp nm = JAI.create("matchcdf", fmt, CDFnorm);
7.6
The lookup table modification provides a non-linear amplitude transformation. Non-linear amplitude transformation is useful if you have a non-linear amplitude response difference between the sensor that captures the image data and the display.Lookup Table Modification
The lookup table modification mechanism allows you to arbitrarily convert between the source image byte, short, or integer pixel value and one or more output values. The output value can be a byte, short, integer, float, or double image pixel.
The input pixel value acts as an address to the lookup table inputs, as shown in Figure 7-8. Each location in the lookup table stores the desired output value for that particular address.
The lookup table is first loaded with the necessary data. Table 7-2 shows a partial listing of an example lookup table. In this example, the input values range from 0 to 255. The output values provide a scaled square root transformation between the input and output, according to the following equation:
This example provides a non-linear amplitude transformation between input and output pixel values, in which the smaller input amplitude values are amplified and the larger input values are attenuated. Other types of lookup values can be used to solve nearly any non-linear amplitude scaling problem.
7.6.1
TheCreating the Lookup Table
LookupTableJAIobject represents a single- or multi-banded table or a color cube of any supported data types. A single- or multi-banded source image of integer data types is passed through the table and transformed into a single- or multi-banded destination image of both integral and float or double data types.The
LookupTableJAIobject is used for theErrorDiffusionoperation, where it describes a color map, and theLookupoperation, where it describes the lookup table. For theLookupoperation, the table data may cover only a subrange of the legal range of the input data type. The subrange is selected by means of an offset parameter that is to be subtracted from the input value before indexing into the table array.The procedures for constructing a lookup table vary slightly, depending on whether the input image is single-banded or multi-banded. For a single-band input image, you construct a single lookup table. For a multi-band image, you construct a single lookup table with entries for each band.
7.6.1.1
The single-banded lookup table contains data for a single channel or image component. To create a lookup table for a single-band input image, use one of the single-band constructors. The constructors take up to three parameters:Creating a Single-band Lookup Table
- A pointer to the data to be stored in the table. The data may be of type
Byte,Short,UShort,Int,Float, orDouble.
- The offset. The offset selects the lookup table subrange. The offset value is subtracted from the input value before indexing into the table array.
Listing 7-3 shows an example of the construction of a single-band byte lookup table.
- A boolean flag that indicates whether Short data is of type Short or UShort.
Listing 7-3 Example Single-band Lookup Table
byte[] tableData = new byte[0x10000]; for (int i = 0; i < 0x10000; i++) { tableData[i] = (byte)(i >> 8); } // Create a LookupTableJAI object to be used with the // "lookup" operator. LookupTableJAI table = new LookupTableJAI(tableData);
API:javax.media.jai.LookupTableJAI
- LookupTableJAI(byte[] data)
- constructs a single-banded byte lookup table with an index offset of 0.
Parameters: dataThe single-banded byte data
- LookupTableJAI(byte[] data, int offset)
- constructs a single-banded byte lookup table with an index offset.
Parameters: dataThe single-banded byte data
offsetThe offset
- LookupTableJAI(short[] data, boolean isUShort)
- constructs a single-banded short or unsigned short lookup table with an index offset of 0.
Parameters: dataThe single-banded short data
isUShortTrue if the data type is DataBuffer.TYPE_USHORT; false if the data type isDataBuffer.TYPE_SHORT.
- LookupTableJAI(short[] data, int offset, boolean isUShort)
- constructs a single-banded short or unsigned short lookup table with an index offset.
Parameters: dataThe single-banded short data
offsetThe offset
isUShortTrue if the data type is DataBuffer.TYPE_USHORT; false if the data type isDataBuffer.TYPE_SHORT.
- LookupTableJAI(int[] data)
- constructs a single-banded int lookup table with an index offset
Parameters: dataThe single-banded int data
- LookupTableJAI(int[] data, int offset)
- constructs a single-banded int lookup table with an index offset
Parameters: dataThe single-banded int data
offsetThe offset
- LookupTableJAI(float[] data)
- constructs a single-banded float lookup table with an index offset of 0
Parameters: dataThe single-banded float data
- LookupTableJAI(float[] data, int offset)
- constructs a single-banded float lookup table with an index offset
Parameters: dataThe single-banded float data
offsetThe offset
- LookupTableJAI(double[] data)
- constructs a single-banded double lookup table with an index offset of 0
Parameters: dataThe single-banded double data
- LookupTableJAI(double[] data, int offset)
- constructs a single-banded double lookup table with an index offset
Parameters: dataThe single-banded double data
offsetThe offset
7.6.1.2
The multi-band lookup table contains data for more than one channels or image components, such as separate arrays for R, G, and B. To create a lookup table for a multi-band input image, use one of the multi-band constructors. Like the single-band constructors, the multi-band constructors take up to three parameters:Creating a Multi-band Lookup Table
- A pointer to the data to be stored in the table. The data may be of type Byte, Short, UShort, Int, Float, or Double.
- The offset. The offset selects the lookup table subrange. The offset value is subtracted from the input value before indexing into the table array. The constructors allow you to specify one offset for all of the bands or separate offsets for each band.
Listing 7-4 shows an example of the construction of a multi-banded byte lookup table.
- A boolean flag that indicates whether Short data is of type Short or UShort.
Listing 7-4 Example Multi-band Lookup Table
// Create the table data. byte[][] tableData = new byte[3][0x10000]; for (int i = 0; i < 0x10000; i++) { tableData[0][i] = (byte)(i >> 8); // this may be different tableData[1][i] = (byte)(i >> 8); // for each band tableData[2][i] = (byte)(i >> 8); }// Create a LookupTableJAI object to be used with the // "lookup" operator. LookupTableJAI table = new LookupTableJAI(tableData);
API:javax.media.jai.LookupTableJAI
- LookupTableJAI(byte[][] data)
- constructs a multi-banded byte lookup table with an index offset for each band of 0.
Parameters: dataThe multi-banded byte data in [band][index] format
- LookupTableJAI(byte[][] data, int offset)
- constructs a multi-banded byte lookup table where all bands have the same index offset.
Parameters: dataThe multi-banded byte data in [band][index] format
offsetThe common offset for all bands
- LookupTableJAI(byte[][] data, int[] offsets)
- constructs a multi-banded byte lookup table where each band has a different index offset.
Parameters: dataThe multi-banded byte data in [band][index] format
offsetsThe offsets for the bands
- LookupTableJAI(short[][] data, boolean isUShort)
- constructs a multi-banded short or unsigned short lookup table. The index offset for each band is 0
Parameters: dataThe multi-banded short data in [band][index] format.
isUShortTrue if the data type is DataBuffer.TYPE_USHORT; false if the data type isDataBuffer.TYPE_SHORT.
- LookupTableJAI(short[][] data, int offset, boolean isUShort)
- constructs a multi-banded short or unsigned short lookup table where all bands have the same index offset
Parameters: dataThe multi-banded short data in [band][index] format
offsetThe common offset for all bands
isUShortTrue if the data type is DataBuffer.TYPE_USHORT; false if the data type isDataBuffer.TYPE_SHORT.
- LookupTableJAI(short[][] data, int[] offsets, boolean isUShort)
- constructs a multi-banded short or unsigned short lookup table where each band has a different index offset
Parameters: dataThe multi-banded short data in [band][index] format
offsetThe offsets for the bands
isUShortTrue if the data type is DataBuffer.TYPE_USHORT; false if the data type isDataBuffer.TYPE_SHORT.
- LookupTableJAI(int[][] data)
- constructs a multi-banded int lookup table. The index offset for each band is 0
Parameters: dataThe multi-banded int data in [band][index] format
- LookupTableJAI(int[][] data, int offset)
- constructs a multi-banded int lookup table where all bands have the same index offset
Parameters: dataThe multi-banded int data in [band][index] format
offsetThe common offset for all bands
- LookupTableJAI(int[][] data, int[] offsets)
- constructs a multi-banded int lookup table where each band has a different index offset
Parameters: dataThe multi-banded int data in [band][index] format
offsetThe offsets for the bands
- LookupTableJAI(float[][] data)
- constructs a multi-banded float lookup table. The index offset for each band is 0
Parameters: dataThe multi-banded float data in [band][index] format
- LookupTableJAI(float[][] data, int offset)
- constructs a multi-banded float lookup table where all bands have the same index offset
Parameters: dataThe multi-banded float data in [band][index] format
offsetThe common offset for all bands
- LookupTableJAI(float[][] data, int[] offsets)
- constructs a multi-banded float lookup table where each band has a different index offset
Parameters: dataThe multi-banded float data in [band][index] format
offsetThe offsets for the bands
- LookupTableJAI(double[][] data)
- constructs a multi-banded double lookup table. The index offset for each band is 0
Parameters: dataThe multi-banded double data in [band][index] format
- LookupTableJAI(double[][] data, int offset)
- constructs a multi-banded double lookup table where all bands have the same index offset
Parameters: dataThe multi-banded double data in [band][index] format
offsetThe common offset for all bands
- LookupTableJAI(double[][] data, int[] offsets)
- constructs a multi-banded double lookup table where each band has a different index offset
Parameters: dataThe multi-banded double data in [band][index] format
offsetsThe offsets for the bands
7.6.1.3
Dithering operations that use a color cube are considerably faster than those that use a generic lookup table. However, the color cube provides less control over the exact contents of the lookup table.Creating a Color-cube Lookup Table
The
ColorCubeclass is a subclass ofLookupTableJAIand represents a color cube lookup table. You create a colorcube using one of theColorCube.createColorCubemethods. Rather than specifying the data to be loaded into the lookup table, you provide an array ofdimensions. Thedimensionsparameter specifies the size (or number of levels) of each band of the image.Although a color cube implies three dimensions, that is not always the case. The color cube has the same number of
dimensionsas the image has bands. For example, a monochrome image requires only onedimensionparameter.The values in the
dimensionsparameter are signed. A positive value indicates that the corresponding color ramp increases. A negative value indicates that the ramp decreases.JAI provides two predefined color cubes, which can be used for the ordered dither operation (see Section 6.6.1, "Ordered Dither"):
These color cubes are specified by the
colorMapparameter that is required by theOrderedDitheroperation.
API:javax.media.jai.ColorCube
- static ColorCube createColorCube(int dataType, int offset, int[] dimensions)
- creates a multi-banded
ColorCubeof a specified data type.
Parameters: dataTypeThe data type of the ColorCube. One ofDataBuffer.TYPE_BYTE,DataBuffer.TYPE_SHORT,DataBuffer.TYPE_USHORT,DataBuffer.TYPE_INT,DataBuffer.TYPE_FLOAT, orDataBuffer.TYPE_DOUBLE.
offsetThe common offset for all bands.
dimensionsThe signed dimensions for each band.
- static ColorCube createColorCube(int dataType, int[] dimensions)
- create a multi-banded
ColorCubeof a specified data type with zero offset for all bands.
Parameters: dataTypeThe data type of the ColorCube. One ofDataBuffer.TYPE_BYTE,DataBuffer.TYPE_SHORT,DataBuffer.TYPE_USHORT,DataBuffer.TYPE_INT,DataBuffer.TYPE_FLOAT, orDataBuffer.TYPE_DOUBLE.
dimensionsThe signed dimensions for each band.
- static ColorCube createColorCubeByte(int[] dimensions)
- constructs a multi-banded byte
ColorCube.
Parameters: dimensionsA list of signed sizes of each side of the color cube.
- static ColorCube createColorCubeByte(int offset, int[] dimensions)
- constructs a multi-banded byte ColorCube with an index offset common to all bands.
Parameters: offsetThe common offset for all bands.
dimensionsA list of signed sizes of each side of the color cube.
- static ColorCube createColorCubeShort(int[] dimensions)
- constructs a multi-banded short
ColorCube.
- static ColorCube createColorCubeShort(int offset, int[] dimensions)
- constructs a multi-banded short
ColorCubewith an index offset common to all bands.
- static ColorCube createColorCubeUShort(int[] dimensions)
- constructs a multi-banded unsigned short
ColorCube.
- static ColorCube createColorCubeUShort(int offset, int[] dimensions)
- constructs a multi-banded unsigned short
ColorCubewith an index offset common to all bands.
- static ColorCube createColorCubeInt(int[] dimensions)
- constructs a multi-banded int
ColorCube.
- static ColorCube createColorCubeInt(int offset, int[] dimensions)
- constructs a multi-banded int
ColorCubewith an index offset common to all bands.
- static ColorCube createColorCubeFloat(int[] dimensions)
- constructs a multi-banded float
ColorCube.
- static ColorCube createColorCubeFloat(int offset, int[] dimensions)
- constructs a multi-banded float ColorCube with an index offset common to all bands.
- static ColorCube createColorCubeDouble(int[] dimensions)
- constructs a multi-banded double
ColorCubewith an index offset common to all bands.
- static ColorCube createColorCubeDouble(int offset, int[] dimensions)
- constructs a multi-banded double
ColorCubewith an index offset common to all bands.7.6.2
ThePerforming the Lookup
lookupoperation performs a general table lookup on a rendered or renderable image. The destination image is obtained by passing the source image through the lookup table. The source image may be single- or multi-banded of data typesbyte,ushort,short, orint. The lookup table may be single- or multi-banded of any JAI-supported data types. The destination image must have the same data type as the lookup table, and its number of bands is determined based on the number of bands of the source and the table. If the source is single-banded, the destination has the same number of bands as the lookup table; otherwise, the destination has the same number of bands as the source.If either the source or the table is single-banded and the other one is multi-banded, the single band is applied to every band of the multi-banded object. If both are multi-banded, their corresponding bands are matched up.
The table may have a set of offset values, one for each band. This value is subtracted from the source pixel values before indexing into the table data array.
It is the user's responsibility to make certain the lookup table supplied is suitable for the source image. Specifically, the table data must cover the entire range of the source data. Otherwise, the result of this operation is undefined.
By the nature of this operation, the destination may have a different number of bands and/or data type from the source. The
SampleModelof the destination is created in accordance with the actual lookup table used in a specific case.There are three specific cases of table lookup that determine the pixel values of the destination image:
- If the source image is single-banded and the lookup table is single- or multi-banded, the destination image has the same number of bands as the lookup table:
for (int h = 0; h < dstHeight; h++) { for (int w = 0; w < dstWidth; w++) { for (int b = 0; b < dstNumBands; b++) { dst[h][w][b] = table[b][src[h][w][0] - offsets[b]] } } }
- If the source image is multi-banded and the lookup table is single-banded, the destination image has the same number of bands as the source image:
for (int h = 0; h < dstHeight; h++) { for (int w = 0; w < dstWidth; w++) { for (int b = 0; b < dstNumBands; b++) { dst[h][w][b] = table[0][src[h][w][b] - offsets[0]] } } }
- If the source image is multi-banded and the lookup table is multi-banded, with the same number of bands as the source image, the destination image will have the same number of bands as the source image:
for (int h = 0; h < dstHeight; h++) { for (int w = 0; w < dstWidth; w++) { for (int b = 0; b < dstNumBands; b++) { dst[h][w][b] = table[b][src[h][w][b] - offsets[b]] } } }Thelookupoperation takes one parameter:
Parameter Type Description table LookupTableJAI The lookup table through which the source image is passed.
See Section 7.6.1, "Creating the Lookup Table" for more information.
For a complete example of the
Lookupoperation, see Listing A-1 on page 397.
7.6.3
Other Lookup Table Operations
7.6.3.1
Several methods are available to read the current contents of the lookup table. The choice of method depends on the data format: byte, short, integer, floating-point, or double floating-point.Reading the Table Data
API:javax.media.jai.LookupTableJAI
- java.awt.image.DataBuffer getData()
- returns the table data as a
DataBuffer.
- byte[][] getByteData()
- returns the byte table data in array format.
- byte[] getByteData(int band)
- returns the byte table data of a specific band in array format.
- short[][] getShortData()
- returns the short table data in array format.
- short[] getShortData(int band)
- returns the short table data of a specific band in array format.
- int[][] getIntData()
- returns the integer table data in array format.
- int[] getIntData(int band)
- returns the integer table data of a specific band in array format.
- float[][] getFloatData()
- returns the float table data in array format.
- float[] getFloatData(int band)
- returns the float table data of a specific band in array format.
- double[][] getDoubleData()
- returns the double table data in array format.
- double[] getDoubleData(int band)
- returns the double table data of a specific band in array format.
7.6.3.2
There are three methods for reading the offset values within the current lookup table.Reading the Table Offsets
API:javax.media.jai.LookupTableJAI
- int[] getOffsets()
- returns the index offsets of entry 0 for all bands.
- int getOffset()
- returns the index offset of entry 0 for the default band.
- int getOffset(int band)
- returns the index offset of entry 0 for a specific band.
Parameters: bandThe band to read
7.6.3.3
A single method is used to read the number of bands in the lookup table.Reading the Number of Bands
API:javax.media.jai.LookupTableJAI
- int getNumBands()
- returns the number of bands of the table.
7.6.3.4
A single method is used to read the number of entries per band in the lookup table.Reading the Number of Entries Per Band
API:javax.media.jai.LookupTableJAI
- int getNumEntries()
- returns the number of entries per band of the table.
7.6.3.5
A single method is used to read the data type of the lookup table.Reading the Data Type
API:javax.media.jai.LookupTableJAI
- int getDataType()
- returns the data type of the table data.
7.6.3.6
Reading the Destination Bands and SampleModel
API:javax.media.jai.LookupTableJAI
- int getDestNumBands(int sourceNumBands)
- returns the number of bands of the destination image, based on the number of bands of the source image and lookup table.
Parameters: sourceNum-BandsThe number of bands of the source image.
- java.awt.image.SampleModel getDestSampleModel(java.awt.image.SampleModel srcSampleModel)
- returns a
SampleModelsuitable for holding the output of a lookup operation on the source data described by a givenSampleModelwith this table. The width and height of the destinationSampleModelare the same as that of the source. This method returns null if the sourceSampleModelhas a non-integral data type.
Parameters: srcSample-ModelThe SampleModelof the source image.
- java.awt.image.SampleModel getDestSampleModel(java.awt.image.SampleModel srcSampleModel, int width, int height)
- returns a
SampleModelsuitable for holding the output of a lookup operation on the source data described by a givenSampleModelwith this table. This method will return null if the sourceSampleModelhas a non-integral data type.
Parameters: srcSample-ModelThe SampleModelof the source image.
widthThe width of the destination SampleModel.
heightThe height of the destination SampleModel.
7.7
Convolution filtering is often used to reduce the effects of noise in images or to sharpen the detail in blurred images. Convolution filtering is a form of spatial filtering that computes each output sample by multiplying elements of a kernel with the samples surrounding a particular source sample.Convolution Filtering
Convolution filtering operates on a group of input pixels surrounding a center pixel. The adjoining pixels provide important information about brightness trends in the area of the pixel being processed.
Convolution filtering moves across the source image, pixel by pixel, placing resulting pixels into the destination image. The resulting brightness of each source pixel depends on the group of pixels surrounding the source pixel. Using the brightness information of the source pixel's neighbors, the convolution process calculates the spatial frequency activity in the area, making it possible to filter the brightness based on the spatial frequency of the area.
Convolution filtering uses a convolve kernel, containing an array of convolution coefficient values, called key elements, as shown in Figure 7-9. The array is not restricted to any particular size, and does not even have to be square. The kernel can be 1 x 1, 3 x 3, 5 x 5,
MxN,and so on. A larger kernel size affords a more precise filtering operation by increasing the number of neighboring pixels used in the calculation. However, the kernel cannot be bigger in any dimension than the image data. Also, the larger the kernel, the more computations that are required to be performed. For example, given a 640 x 480 image and a 3 x 3 kernel, the convolve operation requires over five million total multiplications and additions.The convolution filtering operation computes each output sample by multiplying the key elements of the kernel with the samples surrounding a particular source pixel. For each destination pixel, the kernel is rotated 180 degrees and its key element is placed over the source pixel corresponding with the destination pixel. The key elements are multiplied with the source pixels under them, and the resulting products are summed together to produce the destination sample value.
The selection of the weights for the key elements determines the nature of the filtering action, such as high-pass or low-pass. If the values of the key elements are the reciprocal of the number of key elements in the kernel (for example, 1/9 for a 3 x 3 kernel), the result is a conventional low-pass averaging process. If the weights are altered, certain pixels in the kernel will have an increased or decreased influence in the average. Figure 7-10 shows three example convolve filters, low-pass, high-pass, and Laplacian.
Figure 7-10 Convolve Filter Samples
The low-pass filter, also known as a box filter, attenuates the high-spatial frequency components of an image and has little affect on the low-frequency components. The effect of passing an image through a low-pass filter is a slight blurring. This is caused by attenuating the sharp brightness transitions between edges and makes the image appear to have less detail. See also Section 7.7.2, "Box Filter."
The high-pass filter has the opposite effect of the low-pass filter, accentuating the high-frequency components and offering little affect on the low-frequency components. The effect of passing an image through a high-pass filter is a sharper image with increased detail in the areas of brightness transition.
The Laplacian filter is another image detail sharpening filter that works well for noise-free images. This filter subtracts the brightness values of the four neighboring pixels from the central pixel. The result of applying this filter is to reduce the gray level to zero.
7.7.1
The following example code shows aPerforming the Convolve Operation
convolveoperation on a single sampledst[x][y], which assumes that the kernel is of sizeMxNand has already been rotated through 180 degrees. The kernel's key element is located at position (xKey,yKey).dst[x][y] = 0; for (int i = -xOrigin; i < -xOrigin + width; i++) { for (int j = -yOrigin; j < -yOrigin + height; j++) { dst[x][y] += src[x + i][y + j]*kernel[xOrigin + i][yOrigin + j]; } }Convolution, or any neighborhood operation, leaves a band of pixels around the edges undefined. For example, for a 3 x 3 kernel, only four kernel elements and four source pixels contribute to the destination pixel located at (0,0). Such pixels are not included in the destination image. A border extension may be added via theBorderExtenderclass. The type of border extension can be specified as aRenderingHintto theJAI.createmethod. If no border extension type is provided, a default extension ofBorderExtender.BORDER_COPYwill be used to perform the extension. See Section 3.7.3, "Rendering Hints."The
convolveoperation uses a single parameter:
Parameter Type Description kernel KernelJAI The convolution kernel. See Section 6.9, "Constructing a Kernel."
The default
kernelisnull.Listing 7-5 shows a code sample for a
Convolveoperation.
Listing 7-5 Example Convolve Operation
// Create the kernel. kernel = new KernelJAI float[] = { 0.0F, -1.0F, 0.0F, -1.0F, 5.0F, -1.0F, 0.0F, -1.0F, 0.0F };// Create the convolve operation. im1 = JAI.create("convolve", im, kernel);
7.7.2
TheBox Filter
BoxFilteroperation is a special case of convolve operation in which each source pixel contributes the same weight to the destination pixel. The box filter operation determines the intensity of a pixel in an image by averaging the source pixels within a rectangular area around the pixel. The pixel values of the destination image are defined by the following pseudocode:int count = width * height; // # of pixels in the box for (int b = 0; b < numBands; b++) { int total = 0; for (int j = -yKey; j < -yKey + height; j++) { for (int i = -xKey; i < -xKey + width; i++) { total += src[x+i][y+j][b]; } } dst[x][y][b] = (total + count/2) / count; // round }TheBoxFilteroperation uses a low-pass filter that passes (leaves untouched) the low spatial frequency components of the image and attenuates the high-frequency components. In an area of the image that has constant brightness, the brightness values are passed untouched. When the filter passes over an area of sharp black to white transitions, the brightness range is greatly attenuated.Convolution, like any neighborhood operation, leaves a band of pixels around the edges undefined. For example, for a 3 x 3 kernel, only four kernel elements and four source pixels contribute to the convolution pixel at the corners of the source image. Pixels that do not allow the full kernel to be applied to the source are not included in the destination image. A
Borderoperation (see Section 7.2, "Adding Borders to Images") may be used to add an appropriate border to the source image to avoid shrinkage of the image boundaries.The kernel may not be bigger in any dimension than the image data.
The
BoxFilteroperation takes four parameters:
Parameter Type Description width Integer The width of the box.
height Integer The height of the box.
xKey Integer The x position of the key element.
yKey Integer The y position of the key element.
The
widthparameter is required. The remaining parameters may benulland, if not supplied, default to the following values:Listing 7-6 shows a code sample for a
![]()
BoxFilteroperation.
Listing 7-6 Example BoxFilter Operation
// Read the arguments. String fileName = args.length > 0 ? args[0] : DEFAULT_FILE; int width = args.length > 1 ? Integer.decode(args[1]).intValue() : DEFAULT_SIZE; int height = args.length > 2 ? Integer.decode(args[2]).intValue() : width;new BoxFilterExample(fileName, width, height); } public BoxFilterExample(String fileName, int width, int height)// Load the image. RenderedOp src = JAI.create("fileload", fileName);// Create the BoxFilter operation. RenderedOp dst = JAI.create("boxfilter", src, width, height, width/2, height/2);
7.8
A median filter is used to remove impulse noise spikes from an image and thus smoothing the image. Impulse noise spikes appear as bright or dark pixels randomly distributed throughout the image. Noise spikes are normally significantly brighter or darker than their neighboring pixels and can easily be found by comparing the median value of a group of input pixels.Median Filtering
The median filter is a neighborhood-based ranking filter in which the pixels in the neighborhood are ranked in the order of their levels. The median value of the group is then stored in the output pixel. The resulting image is then free of pixel brightnesses that are at the extremes in each input group of pixels.
The noise-reducing effect that the median filter has on an image depends on two related things: the spatial extent of the neighborhood (the mask) and the number of pixels involved in the computation. The
MedianFilteroperation supports three different mask shapes, a square, a plus, and an X-shape, as shown in Figure 7-11.
Figure 7-11 Median Filter Masks
The
MedianFilteroperation may also be used to compute the separable median of a 3 x 3 or 5 x 5 region of pixels. The separable median is defined as the median of the medians of each row. For example, if the pixel values in a 3 x 3 window are as follows:
the overall (non-separable) median value is 5, while the separable median is equal to the median of the three row medians: median(1, 2, 3) = 2, median(5, 6, 7) = 6, and median(4, 8, 9) = 8, yielding an overall median of 6. The separable median may be obtained by specifying a mask of typeMEDIAN_MASK_SQUARE_SEPARABLE.The
MedianFilteroperation takes two parameters:
The
maskShapeparameter is one of the following:
The
maskSizeparameter must be 1 (1 x 1) or greater. The default value, if one is not provided, is 3 (3 x 3). For large masks, the noise reduction effect of more pixels used in the computation of the median value reaches a point of diminishing returns. Typical mask sizes are 3 x 3 and 5 x 5.
7.9
Images contain spatial details that are seen as brightness transitions, cycling from dark to light and back to dark. The rate at which the transitions occur in an image represent the image's spatial frequency.Frequency Domain Processing
An image's spatial frequency can be measured horizontally, vertically, or at any diagonal in between. An image contains many spatial frequencies that, when combined in the correct magnitude and phase, form the complex details of the image.
A frequency transform decomposes an image from its spatial domain form of brightness into a frequency domain form of fundamental frequency components. Each frequency component contains a magnitude and phase value. An inverse frequency transform converts an image from its frequency form back to its spatial form.
7.9.1
JAI supports the most common type of frequency transform, the discrete Fourier transform and its inverse, the inverse discrete Fourier transform. The discrete Fourier transform of an image is a two-dimensional process. The result of the transform is a two-dimensional array of values, each having two parts: real and imaginary. Each value represents a distinct spatial frequency component. The frequency-transform image has as many values as there are pixels in the source image.Fourier Transform
The real portion of the values can be displayed as an image, visually showing the frequency components of the source image. The result is in "wrap around" order, with the zero-frequency point (also known as "DC" for direct current) at the upper left corner and the high frequencies at the center.
7.9.1.1
TheDiscrete Fourier Transform
DFT(discrete Fourier transform) operation computes the discrete Fourier transform of an image. A negative exponential is used as the basis function for the transform. The operation supports real-to-complex, complex-to-complex, and complex-to-real transforms. A complex image must have an even number of bands, with the even bands (0, 2, etc.) representing the real parts and the odd bands (1, 3, etc.) the imaginary parts of each complex pixel.If an underlying fast Fourier transform (FFT) implementation is used that requires that the image dimensions be powers of 2, the width and height may each be increased to the power of 2 greater than or equal to the original width and height, respectively.
The
dftoperation takes two parameters.
The default parameters for this operation are
SCALING_NONEandREAL_TO_COMPLEX.The
scalingTypeparameter defines how the image dimensions may be scaled, as follows:
The
dataNatureparameter specifies the nature of the source and destination data, as follows.
If the source data are complex, the number of bands in the source image must be a multiple of 2. The number of bands in the destination must match that which would be expected given the number of bands in the source image and the specified nature of the source and destination data. If the source image is real, the number of bands in the destination will be twice that in the source. If the destination image is real, the number of bands in the destination will be half that in the source. Otherwise the number of bands in the source and destination must be equal.
The
DFToperation defines aPropertyGeneratorthat sets theCOMPLEXproperty of the image toFALSEif thedataNatureparameter isCOMPLEX_TO_REALand toTRUEif thedataNatureparameter isREAL_TO_COMPLEXorCOMPLEX_TO_COMPLEX. The value of this property may be retrieved by calling the getProperty() method withCOMPLEXas the property name.Listing 7-7 shows a code sample for a
DFToperation.
Listing 7-7 Example DFT Operation
// Create the ParameterBlock. ParameterBlock pb = new ParameterBlock(); pb.addSource(src) pb.add(DFTDescriptor.SCALING_NONE); pb.add(DFTDescriptor.REAL_TO_COMPLEX);// Create the DFT operation. PlanarImage dft = (PlanarImage)JAI.create("dft", pb, null);// Get the DFT image information. int width = dft.getWidth(); int height = dft.getHeight(); int numBands = dft.getSampleModel().getNumBands(); int dataType = dft.getSampleModel().getDataType();// Calculate the cutoff "frequencies" from the threshold. threshold /= 200.0F; int minX = (int)(width*threshold); int maxX = width - 1 - minX; int minY = (int)(height*threshold); int maxY = height - 1 - minY;// Retrieve the DFT data. Raster dftData = dft.getData(); double[] real = dftData.getSamples(0, 0, width, height, 0, (double[])null); double[] imag = dftData.getSamples(0, 0, width, height, 1, (double[])null);double[] HR = new double[real.length]; double[] HI = new double[imag.length]; double[] LR = new double[real.length]; double[] LI = new double[imag.length];
7.9.1.2
TheInverse Discrete Fourier Transform
IDFT(inverse discrete Fourier transform) operation computes the inverse discrete Fourier transform of an image. A positive exponential is used as the basis function for the transform. The operation supports real-to-complex, complex-to-complex, and complex-to-real transforms. A complex image must have an even number of bands, with the even bands (0, 2, etc.) representing the real parts and the odd bands (1, 3, etc.) the imaginary parts of each complex pixel.If an underlying fast Fourier transform (FFT) implementation is used that requires that the image dimensions be powers of 2, the width and height may each be increased to the power of 2 greater than or equal to the original width and height, respectively.
The
IDFToperation takes two parameters.
The default parameters for this operation are
SCALING_DIMENSIONSandCOMPLEX_TO_REAL.The
scalingTypeparameter defines how the image dimensions may be scaled, as follows:
The
dataNatureparameter specifies the nature of the source and destination data, as follows.
If the source data are complex, the number of bands in the source image must be a multiple of 2. The number of bands in the destination must match that which would be expected given the number of bands in the source image and the specified nature of the source and destination data. If the source image is real, the number of bands in the destination will be twice that in the source. If the destination image is real, the number of bands in the destination will be half that in the source. Otherwise the number of bands in the source and destination must be equal.
The
IDFToperation defines aPropertyGeneratorthat sets theCOMPLEXproperty of the image toFALSEif thedataNatureparameter isCOMPLEX_TO_REALand toTRUEif thedataNatureparameter isREAL_TO_COMPLEXorCOMPLEX_TO_COMPLEX. The value of this property may be retrieved by calling the getProperty() method withCOMPLEXas the property name.
7.9.2
The discrete cosine transform (DCT) is similar to the discrete Fourier transform (see Section 7.9.1.1, "Discrete Fourier Transform"). However, the DCT is better at compactly representing very small images. Like the discrete Fourier transform (DFT), the DCT also has an inverse operation, the inverse discrete cosine transform (IDCT).Cosine Transform
7.9.2.1
TheDiscrete Cosine Transform (DCT)
DCToperation computes the even discrete cosine transform of an image. Each band of the destination image is derived by performing a two-dimensional DCT on the corresponding band of the source image.The
DCToperation does not take any parameters.Listing 7-8 shows a code sample for a DCT operation.
Listing 7-8 Example DCT Operation
// Load the source image. RenderedImage src = (RenderedImage)JAI.create("fileload", fileName);// Calculate a DCT image from the source image. ParameterBlock pb = (new ParameterBlock()).addSource(src); PlanarImage dct = JAI.create("dct", pb, null);// Get the DCT image data. int width = dct.getWidth(); int height = dct.getHeight(); int numBands = dct.getSampleModel().getNumBands(); int dataType = dct.getSampleModel().getDataType(); double[] dctData = dct.getData().getPixels(0, 0, width, height, (double[])null); double[] pixels = new double[dctData.length];
7.9.2.2
TheInverse Discrete Cosine Transform (IDCT)
IDCToperation computes the inverse even discrete cosine transform of an image. Each band of the destination image is derived by performing a two-dimensional inverse DCT on the corresponding band of the source image.The
IDCToperation does not take any parameters.Listing 7-9 shows a code sample for an operation that first takes the discrete cosine transform of an image, then computes the inverse discrete cosine transform.
Listing 7-9 Example IDCT Operation
// Calculate a DCT image from the source image. System.out.println("Creating DCT of source image ..."); ParameterBlock pb = (new ParameterBlock()).addSource(src); PlanarImage dct = JAI.create("dct", pb, null);// Calculate an IDCT image from the DCT image. System.out.println("Creating IDCT of DCT of source image ..."); pb = (new ParameterBlock()).addSource(dct); PlanarImage idct = JAI.create("idct", pb, null);// Create display image for inverse DCT of DCT of source image. System.out.println("Creating display image for IDCT of DCT"); pixels = idct.getData().getPixels(0, 0, width, height, (double[])pixels); BufferedImage bi = createBI(colorImage, width, height, pixels);
7.9.3
TheMagnitude Enhancement
magnitudeoperation computes the magnitude of each pixel of a complex image. The source image must have an even number of bands, with the even bands (0, 2, etc.) representing the real parts and the odd bands (1, 3, etc.) the imaginary parts of each complex pixel. The destination image has at most half the number of bands of the source image with each sample in a pixel representing the magnitude of the corresponding complex source sample.The magnitude values of the destination image are defined by the following pseudocode:
dstPixel[x][y][b] = sqrt(src[x][y][2b]2 + src[x][y][2b + 1]2)For integral image data types, the result is rounded and clamped as needed.
- where the number of bands b varies from zero to one less than the number of bands in the destination image.
The
magnitudeoperation does not take any parameters.Listing 7-10 shows a code sample for a
magnitudeoperation.
Listing 7-10 Example Magnitude Operation
// Calculate a DFT image from the source image. pb = new ParameterBlock(); pb.addSource(src).add(DFTDescriptor.SCALING_NONE); PlanarImage dft = JAI.create("dft", pb, null);// Create the ParameterBlock specifying the source image. pb = new ParameterBlock(); pb.addSource(dft);// Calculate the magnitude. PlanarImage magnitude = JAI.create("magnitude", pb, null);
7.9.4
TheMagnitude-squared Enhancement
MagnitudeSquaredoperation computes the squared magnitude of each pixel of a complex image. The source image must have an even number of bands, with the even bands (0, 2, etc.) representing the real parts and the odd bands (1, 3, etc.) the imaginary parts of each complex pixel. The destination image has at most half the number of bands of the source image with each sample in a pixel representing the magnitude of the corresponding complex source sample.The squared magnitude values of the destination image are defined by the following pseudocode:
dstPixel[x][y][b] = src[x][y][2b]2 + src[x][y][2b + 1]2For integral image data types, the result is rounded and clamped as needed.
- where the number of bands b varies from zero to one less than the number of bands in the destination image.
The
MagnitudeSquaredoperation does not take any parameters.
7.9.5
ThePhase Enhancement
Phaseoperation computes the phase angle of each pixel of a complex image. The source image must have an even number of bands, with the even bands (0, 2, etc.) representing the real parts and the odd bands (1, 3, etc.) the imaginary parts of each complex pixel. The destination image has at most half the number of bands of the source image with each sample in a pixel representing the phase angle of the corresponding complex source sample.The angle values of the destination image are defined by the following pseudocode:
dst[x][y][b] = atan2(src[x][y][2b + 1], src[x][y][2b])For integral image data types, the result is rounded and scaled so the "natural" arctangent range from [-
- where the number of bands b varies from zero to one less than the number of bands in the destination image.
,
) is remapped into the range [0, MAXVALUE). The result for floating point image data types is the value returned by the
atan2()method.The
phaseoperation does not take any parameters.
7.9.6
TheComplex Conjugate
Conjugateoperation computes the complex conjugate of a complex image. The operation negates the imaginary components of a rendered or renderable source image containing complex data. The source image must contain an even number of bands with the even-indexed bands (0, 2, etc.) representing the real and the odd-indexed bands (1, 3, etc.) the imaginary parts of each pixel. The destination image similarly contains an even number of bands with the same interpretation and with contents defined by:dst[x][y][2*k] = src[x][y][2*k]; dst[x][y][2*k+1] = -src[x][y][2*k+1];The
- where the index k varies from zero to one less than the number of complex components in the destination image.
Conjugateoperation does not require any parameters.
7.9.7
ThePeriodic Shift
PeriodicShiftoperation computes the periodic translation of an image. The destination image of thePeriodicShiftoperation is the infinite periodic extension of the source image with horizontal and vertical periods equal to the image width and height, respectively, shifted by a specified amount along each axis and clipped to the bounds of the source image. Thus for each band b the destination image sample at location (x,y) is defined by:if(x < width - shiftX) { if(y < height - shiftY) { dst[x][y][b] = src[x + shiftX][y + shiftY][b]; } else { dst[x][y][b] = src[x + shiftX][y - height + shiftY][b]; } } else { if(y < height - shiftY) { dst[x][y][b] = src[x - width + shiftX][y + shiftY][b]; } else { dst[x][y][b] = src[x - width + shiftX][y - height + shiftY][b]; } }The
- where
shiftXandshiftYdenote the translation factors along the x and y axes, respectively.PeriodicShiftoperation takes two parameters.
Parameter Type Description shiftX Integer The displacement in the x direction.
shiftY Integer The displacement in the y direction.
7.9.8
ThePolar to Complex
PolarToComplexoperation computes a complex image from a magnitude and a phase image. The operation creates an image with complex-valued pixels from two images, the respective pixel values of which represent the magnitude (modulus) and phase of the corresponding complex pixel in the destination image.The source images should have the same number of bands. The first source image contains the magnitude values and the second source image the phase values. The destination will have twice as many bands with the even-indexed bands (0, 2, etc.) representing the real and the odd-indexed bands (1, 3, etc.) the imaginary parts of each pixel.
The pixel values of the destination image are defined for a given complex sample by the following pseudocode:
dst[x][y][2*b] = src0[x][y][b]*Math.cos(src1[x][y][b]) dst[x][y][2*b+1] = src0[x][y][b]*Math.sin(src1[x][y][b])For phase images with integral data type, it is assumed that the actual phase angle is scaled from the range [-
- where the index b varies from zero to one less than the number of bands in the source images.
PI,PI] to the range [0,MAX_VALUE] whereMAX_VALUEis the maximum value of the data type in question.The
PolarToComplexoperation does not require any parameters.
7.9.9
TheImages Based on a Functional Description
ImageFunctionoperation generates an image from a functional description. This operation permits the creation of images on the basis of a functional specification, which is provided by an object that is an instance of a class that implements thejavax.media.jai.ImageFunctioninterface. In other words, to use this operation, a class containing the functional information must be created and this class must implement theImageFunctioninterface.The
ImageFunctioninterface merely defines the minimal set of methods required to represent such a function. The actual implementation of a class implementing this interface is left to the programmer.For example, if the function you wanted to generate was the negative exponential
exp(-|x| - |y|)Thejavax.media.jai.ImageFunctionimplementation would return the following values:
isComplex()would return false
getNumElements()would return 1and the implementation would initialize the array
float[] real = new real[width*height];
getElements(x, y, width, height, real, null);realsuch thator, equivalentlyreal[j*width + i] = exp(-|x + i| - |y + j|)real[k] = exp(-|x + (k % width)]| - |y + (k / width)|)The (x,y) coordinates passed to the
- where 0
k < width*height.
ImageFunction.getElements()methods are derived by applying an optional translation and scaling to the image x and y coordinates. The image x and y coordinates as usual depend on the values of the minimum x and y coordinates of the image, which need not be zero.Specifically, the function coordinates passed to
getElements()are calculated from the image coordinates as:functionX = xScale*imageX + xTrans; functionY = yScale*imageY + yTrans;The number of bands in the destination image will be equal to the value returned by theImageFunction.getNumElements()method unless theImageFunction.isComplex()method returnstrue, in which case it will be twice that. The data type of the destination image is determined by theSampleModelspecified by anImageLayoutobject provided via a hint. If no layout hint is provided, the data type will default to single-precision floating point.The double precision floating point form of the
getElements()method will be invoked if and only if the data type is specified to bedouble. For all other data types the single precision form ofgetElements()will be invoked and the destination sample values will be clamped to the data type of the image.The
ImageFunctionoperation takes seven parameters.
The image width and height are provided explicitly as parameters. These values override the width and height specified by an
ImageLayoutif such is provided.
API:javax.media.jai.ImageFunction
- boolean isComplex();
- returns whether or not each value's elements are complex.
- int getNumElements();
- returns the number of elements per value at each position.
- void getElements(float startX, float startY, float deltaX, float deltaY, int countX, int countY, int element, float[] real, float[] imag);
- returns all values of a given element for a specified set of coordinates.
Parameters: startXThe x coordinate of the upper left location to evaluate.
startYThe y coordinate of the upper left location to evaluate.
deltaXThe horizontal increment.
deltaYThe vertical increment.
countXThe number of points in the horizontal direction.
countYThe number of points in the vertical direction.
elementThe element.
realA pre-allocated float array of length at least countX*countYin which the real parts of all elements will be returned.
imagA pre-allocated float array of length at least countX*countYin which the imaginary parts of all elements will be returned; may be null for real data, i.e., whenisComplex()returns false.
- void getElements(double startX, double startY, double deltaX, double deltaY, int countX, int countY, int element, double[] real, double[] imag);
- returns all values of a given element for a specified set of coordinates.
7.10
Pixel point operations are the most basic, yet necessary image processing operations. The pixel point operations are primarily contrast enhancement operations that alter the gray levels of an image's pixels. One-by-one, the gray level of each pixel in the source image is modified to a new value, usually by a mathematical relationship.Single-image Pixel Point Processing
JAI supports the following single-image pixel point operations:
- Pixel inverting (
Invert)
- Logarithmic enhancement (
Log)7.10.1
ThePixel Inverting
Invertoperation inverts the pixel values of an image. For source images with signed data types, the pixel values of the destination image are defined by the following pseudocode:dst[x][y][b] = -src[x][y][b]For unsigned data types, the destination values are defined by the following pseudocode:dst[x][y][b] = MAX_VALUE - src[x][y][b]The
- where
MAX_VALUEis the maximum value supported by the system of the data type of the source pixel.Invertoperation does not require any parameters.Figure 7-12 shows a simple example of an
Invertoperation.
7.10.2
Occasionally, it is desirable to quantize an image on a logarithmic scale rather than a linear scale. The human eye has a logarithmic intensity response but some images are digitized by equipment that quantizes the samples on a linear scale. To make the image better for use by a human observer, these images may be made to have a logarithmic response by theLogarithmic Enhancement
Logoperation.The
Logoperation takes the logarithm of the pixel values of the source image. The pixel values of the destination image are defined by the following pseudocode:dst[x][y][b] = java.lang.Math.log(src[x][y][b])For integral image data types, the result is rounded and clamped as needed. For all integral data types, the log of 0 is set to 0. For signed integral data types (shortandint), the log of a negative pixel value is set to -1. For all floating point data types (floatanddouble), the log of 0 is set to -Infinity, and the log of a negative pixel value is set toNaN.The
Logoperation does not take any parameters.Listing 7-11 shows a code sample for a
Logoperation.
Listing 7-11 Example Log Operation
// Create the ParameterBlock specifying the source image. pb = new ParameterBlock(); pb.addSource(image);// Create the Log operation. RenderedImage dst = JAI.create("log", pb);
7.11
The previous section described pixel point operations for single images. This section deals with pixel point processing on two images, also known as dual-image point processing. Dual-image point processing maps two pixel brightnesses, one from each image, to an output image.Dual Image Pixel Point Processing
JAI supports the following dual-image pixel point operations:
- Overlay images (
Overlayoperation)
- Image compositing (
Compositeoperation)7.11.1
TheOverlay Images
Overlayoperation takes two rendered or renderable source images, and overlays the second source image on top of the first source image. Usually, the images are identical scenes, but may have been acquired at different times through different spectral filters.The two source images must have the same data type and number of bands. However, their
SampleModeltypes may differ. The destination image will always have the same bounding rectangle as the first source image, that is, the image on the bottom, and the same data type and number of bands as the two source images. If the two source images don't intersect, the destination will be the same as the first source.The
Overlayoperation is defined by the following pseudocode:if (srcs[1] contains the point (x, y)) { dst[x][y][b] = srcs[1][x][y][b]; } else { dst[x][y][b] = srcs[0][x][y][b]; }TheOverlayoperation does not take any parameters.
7.11.2
TheImage Compositing
Compositeoperation merges unrelated objects from two images. The result is a new image that didn't exist before. TheCompositeoperation combines two images based on their alpha values at each pixel. This is done on a per-band basis, and the source images are expected to have the same number of bands and the same data type. The destination image has the same data type as the two sources, but one extra band than the source images, which represents the result alpha channel.The destination pixel values may be viewed as representing a fractional pixel coverage or transparency factor. Specifically, the
Compositeoperation implements the Porter-Duff "over" rule1, in which the output color of a pixel with source value and alpha tuples (A, a) and (B, b) is given by:The output alpha value is given by:
- a*A + (1 - a)*(b*B)
For premultiplied sources tuples (a*A, a) and (b*B, b), the premultiplied output value is simply:
- a + (1 - a)*b
The color channels of the two source images are supplied via
- (a*A) + (1 - a)*(b*B)
source1andsource2. The two sources must either both be pre-multiplied by alpha or not. Alpha channel should not be included insource1andsource2.The
Compositeoperation takes four parameters:
The alpha channel of the first source images must be supplied via the
source1Alphaparameter. This parameter may not be null. The alpha channel of the second source image may be supplied via thesource2Alphaparameter. This parameter may be null, in which case the second source is considered completely opaque. The alpha images should be single-banded, and have the same data type as the source image.The
alphaPremultipliedparameter indicates whether or not the supplied alpha image is premultiplied to both the source images.The destination image is the combination of the two source images. It has the color channels and one additional alpha channel (the band index depends on the
alphaFirstparameter). Whether the alpha value is pre-multiplied to the color channels also depends on the value ofalphaPremultiplied(pre-multiplied if true).Listing 7-12 shows a code sample for a composite operation.
Listing 7-12 Example Composite Operation
// Get the first image. pb = new ParameterBlock(); pb.add(s1); RenderedImage src1 = (RenderedImage)JAI.create("jpeg", pb);// Get the second image pb = new ParameterBlock(); pb.add(s2); RenderedImage src2 = (RenderedImage)JAI.create("jpeg", pb);// Create the ParameterBlock pb = new ParameterBlock(); pb.addSource(src1); pb.addSource(src2); pb.add(new Boolean(false)); pb.add(new Boolean(false));// Create the composite operation. RenderedImage dst = (RenderedImage)JAI.create("composite", pb);
7.12
Thresholding, also known as binary contrast enhancement, provides a simple means of defining the boundaries of objects that appear on a contrasting background. TheThresholding
Thresholdoperation takes one rendered image, and maps all the pixels of this image whose values fall within a specified range to a specified constant. The range is specified by a low value and a high value.The pixel values of the destination image are defined by the following pseudocode:
lowVal = (low.length < dstNumBands) ? low[0] : low[b]; highVal = (high.length < dstNumBands) ? high[0] : high[b]; const = (constants.length < dstNumBands) ? constants[0] : constants[b]; if (src[x][y][b] >= lowVal && src[x][y][b] <= highVal) { dst[x][y][b] = const; } else { dst[x][y][b] = src[x][y][b]; }TheThresholdoperation takes three parameters:
Parameters Type Description low double[] The low value.
high double[] The high value
constants double[] The constant the pixels are mapped to.
If the number of elements supplied via the
high,low, andconstantsarrays are less than the number of bands of the source image, the element from entry 0 is applied to all the bands. Otherwise, the element from a different entry is applied to its corresponding band.The
lowparameter defines the lower bound for thethresholdoperation for each band of the image. The operation will affect only values greater than or equal tolow[0]in band 0, only values greater than or equal tolow[1]in band 1, and so on. Thehighparameter defines the upper bound for thethresholdoperation for each band of the image.A common way to arrive at the optimal values for the
lowandhighparameters is to perform anextremaoperation on the image (see Section 9.3, "Finding the Extrema of an Image").Listing 7-13 shows a code sample for a
thresholdoperation in which the three parameters are passed as arguments to the operation..
Listing 7-13 Example Threshold Operation
// Set up the operation parameters. PlanarImage src, dst; Integer [] low, high, map; int bands;low = new Integer[bands]; high = new Integer[bands]; map = new Integer[bands];for (int i = 0; i < bands; i++) { low[i] = new Integer(args[1]); high[i] = new Integer(args[2]); map[i] = new Integer(args[3]); }// Create the threshold operation. pb = new ParameterBlock(); pb.addSource(src); pb.add(low); pb.add(high); pb.add(map); RenderedImage dst = JAI.create("threshold", pb);
![]()
![]()
![]()
Programming in Java Advanced Imaging
1 See Computer Graphics, July 1984 pp. 253-259.Copyright © 1999, Sun Microsystems, Inc. All rights reserved.
Casa de Bender