/* JAI-Ext - OpenSource Java Advanced Image Extensions Library
* http://www.geo-solutions.it/
* Copyright 2014 GeoSolutions
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package it.geosolutions.jaiext.lookup;
import it.geosolutions.jaiext.iterators.RandomIterFactory;
import it.geosolutions.jaiext.range.Range;
import it.geosolutions.jaiext.range.RangeFactory;
import java.awt.Rectangle;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.awt.image.SampleModel;
import java.awt.image.WritableRaster;
import java.awt.image.renderable.ParameterBlock;
import java.util.Map;
import javax.media.jai.BorderExtender;
import javax.media.jai.ColormapOpImage;
import javax.media.jai.ImageLayout;
import javax.media.jai.JAI;
import javax.media.jai.PlanarImage;
import javax.media.jai.ROI;
import javax.media.jai.RenderedOp;
import javax.media.jai.iterator.RandomIter;
import com.sun.media.jai.util.ImageUtil;
import com.sun.media.jai.util.JDKWorkarounds;
/**
* The LookupOpImage class performs the lookup operation on an image with integral data type. This operation consist of passing the input pixels
* through a lookupTable(an array) of all the JAI data types. The output pixels are calculated from the table values by simply taking the array value
* associated to the selected index indicated by the input pixel. The table and source data type can be different, and the destination image will have
* the table data type. Even the band number can be different, in this case the destination image number will depend from the source and table band
* numbers. If the destination sample model is not the same as that of the table, another one is created from the table. A ROI object passed to the
* constructor is taken into account by passing to the table the informations extracted from it; an eventual No Data Range is passed to table if
* present. If No Data or ROI are used, then the destination No Data value is passed to the table. The image calculation is performed by calling the
* computeRect() method that selects an image tile, a raster containing Roi data if Roi RasterAccessor is used, and then these parameters are passed
* to the table that executes the lookup operation.
*/
public class LookupOpImage extends ColormapOpImage {
/** Lookup table currently used*/
private LookupTable lookupTable;
/** ROI image*/
private PlanarImage srcROIImage;
/** Boolean indicating if Roi RasterAccessor must be used*/
private boolean useRoiAccessor;
/** Extended ROI image*/
private RenderedOp srcROIImgExt;
/** ROI Border Extender */
private final static BorderExtender roiExtender = BorderExtender
.createInstance(BorderExtender.BORDER_ZERO);
public LookupOpImage(RenderedImage source, ImageLayout layout, Map configuration,
LookupTable lookupTable, double destinationNoData, ROI roi, Range noData,
boolean useRoiAccessor) {
super(source, layout, configuration, true);
// Addition of the lookup table
this.lookupTable = lookupTable;
SampleModel sModel = source.getSampleModel(); // source sample model
if (sampleModel.getTransferType() != lookupTable.getDataType()
|| sampleModel.getNumBands() != lookupTable.getDestNumBands(sModel.getNumBands())) {
/*
* The current SampleModel is not suitable for the supplied source and lookup table. Create a suitable SampleModel and ColorModel for the
* destination image.
*/
sampleModel = lookupTable.getDestSampleModel(sModel, tileWidth, tileHeight);
if (colorModel != null
&& !JDKWorkarounds.areCompatibleDataModels(sampleModel, colorModel)) {
colorModel = ImageUtil.getCompatibleColorModel(sampleModel, configuration);
}
}
// Set flag to permit in-place operation.
permitInPlaceOperation();
// Initialize the colormap if necessary.
initializeColormapOperation();
//Boolean initialization
boolean hasNoData = false;
boolean hasROI = false;
// If ROI is present, then ROI parameters are added
if (roi != null) {
// Roi object
ROI srcROI = roi;
// Creation of a PlanarImage containing the ROI data
srcROIImage = srcROI.getAsImage();
// Source Bounds
Rectangle srcRect = new Rectangle(source.getMinX(), source.getMinY(),
source.getWidth(), source.getHeight());
// Padding of the input ROI image in order to avoid the call of the getExtendedData() method
// ROI bounds are saved
Rectangle roiBounds = srcROIImage.getBounds();
int deltaX0 = (roiBounds.x - srcRect.x);
int leftP = deltaX0 > 0 ? deltaX0 : 0;
int deltaY0 = (roiBounds.y - srcRect.y);
int topP = deltaY0 > 0 ? deltaY0 : 0;
int deltaX1 = (srcRect.x + srcRect.width - roiBounds.x + roiBounds.width);
int rightP = deltaX1 > 0 ? deltaX1 : 0;
int deltaY1 = (srcRect.y + srcRect.height - roiBounds.y + roiBounds.height);
int bottomP = deltaY1 > 0 ? deltaY1 : 0;
// Extend the ROI image
ParameterBlock pb = new ParameterBlock();
pb.setSource(srcROIImage, 0);
pb.set(leftP, 0);
pb.set(rightP, 1);
pb.set(topP, 2);
pb.set(bottomP, 3);
pb.set(roiExtender, 4);
srcROIImgExt = JAI.create("border", pb);
// Boolean indicating if roi is present
hasROI = true;
// The useRoiAccessor parameter is set
this.useRoiAccessor = useRoiAccessor;
// Then all the ROI informations are passed to the table
lookupTable.setROIparams(roiBounds, srcROIImage, useRoiAccessor);
} else {
//If no ROI is present then all the ROI information are set to null.
this.useRoiAccessor = false;
lookupTable.unsetROI();
}
// If no Data are present, then a No Data Range is added to the table
if (noData != null) {
hasNoData = true;
// Control if the range data type is the same of the source image
if (noData.getDataType().getDataType() != source.getSampleModel().getDataType()) {
// Convert NoData
noData = RangeFactory.convert(noData, source.getSampleModel().getDataType());
}
lookupTable.setNoDataRange(noData);
} else {
// if no data range is not present the table no data range is set to null.
lookupTable.unsetNoData();
}
// If no data Range or ROI are present, then the destination no data value is added.
if (hasNoData || hasROI) {
lookupTable.setDestinationNoData(destinationNoData);
}
}
/**
* Performs the table lookup operation within the specified bounds.
*/
protected void computeRect(Raster[] sources, WritableRaster dest, Rectangle destRect) {
// ROI bounds calculations
Raster tile = sources[0];
Rectangle rect = tile.getBounds();
// ROI calculation if roiAccessor is used
if (useRoiAccessor) {
// Note that the getExtendedData() method is not called because the input images are padded.
// For each image there is a check if the rectangle is contained inside the source image;
// if this not happen, the data is taken from the padded image.
Raster roi = null;
if (srcROIImage.getBounds().contains(rect)) {
roi = srcROIImage.getData(rect);
} else {
roi = srcROIImgExt.getData(rect);
}
lookupTable.lookup(sources[0], dest, destRect, roi);
} else {
lookupTable.lookup(sources[0], dest, destRect, null);
}
}
/**
* Transform the colormap via the lookup table. NoData values are not considered.
*/
protected void transformColormap(byte[][] colormap) {
for (int b = 0; b < 3; b++) {
byte[] map = colormap[b];
int mapSize = map.length;
int band = lookupTable.getNumBands() < 3 ? 0 : b;
for (int i = 0; i < mapSize; i++) {
// Lookup operation for every table element
int result = lookupTable.lookup(band, map[i] & 0xFF);
map[i] = ImageUtil.clampByte(result);
}
}
}
@Override
public synchronized void dispose() {
if(srcROIImgExt != null) {
srcROIImgExt.dispose();
}
super.dispose();
}
}