/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2001-2008, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotools.coverage;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.image.RasterFormatException;
import java.awt.image.RenderedImage;
import java.awt.image.WritableRaster;
import java.awt.image.renderable.ParameterBlock;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.LogRecord;
import javax.media.jai.CRIFImpl;
import javax.media.jai.ImageLayout;
import javax.media.jai.JAI;
import javax.media.jai.OperationDescriptorImpl;
import javax.media.jai.OperationRegistry;
import javax.media.jai.PlanarImage;
import javax.media.jai.PointOpImage;
import javax.media.jai.iterator.RectIterFactory;
import javax.media.jai.iterator.WritableRectIter;
import javax.media.jai.registry.RenderedRegistryMode;
import org.geotools.coverage.grid.AbstractGridCoverage;
import org.geotools.resources.i18n.Errors;
import org.geotools.resources.i18n.ErrorKeys;
import org.geotools.resources.i18n.Loggings;
import org.geotools.resources.i18n.LoggingKeys;
import org.geotools.util.logging.Logging;
import org.geotools.image.TransfertRectIter;
/**
* An image that contains transformed samples. It may be sample values after their
* transformation to geophyics values, or the converse. Images are created using the
* {@code SampleTranscoder.CRIF} inner class, where "CRIF" stands for
* {@link java.awt.image.renderable.ContextualRenderedImageFactory}. The image
* operation name is {@code "org.geotools.SampleTranscode"}.
*
* @source $URL$
* @version $Id$
* @author Martin Desruisseaux (IRD)
*
* @since 2.1
*/
final class SampleTranscoder extends PointOpImage {
/**
* The operation name.
* <strong>NOTE:</strong> Class {@link org.geotools.coverage.grid.GridCoverage2D}
* uses this name, but can't refer to this constant since it is in an other package.
*/
public static final String OPERATION_NAME = "org.geotools.SampleTranscode";
/**
* Category lists for each bands.
* The array length must matches the number of bands in source image.
*/
private final CategoryList[] categories;
/**
* Constructs a new {@code SampleTranscoder}.
*
* @param image The source image.
* @param categories The category lists, one for each image's band.
* @param hints The rendering hints.
*/
private SampleTranscoder(final RenderedImage image,
final CategoryList[] categories,
final RenderingHints hints)
{
super(image, (ImageLayout)hints.get(JAI.KEY_IMAGE_LAYOUT), hints, false);
this.categories = categories;
if (categories.length != image.getSampleModel().getNumBands()) {
// Should not happen, since SampleDimension$Descriptor has already checked it.
throw new RasterFormatException(String.valueOf(categories.length));
}
permitInPlaceOperation();
}
/**
* Computes one of the destination image tile.
*
* @todo There is two optimisations we could do here:
* <ul>
* <li>If source and destination are the same raster, then a single
* {@link WritableRectIter} object would be more efficient (the
* hard work is to detect if source and destination are the same).</li>
* <li>If the destination image is a single-banded, non-interleaved
* sample model, we could apply the transform directly in the
* {@link java.awt.image.DataBuffer}. We can even avoid to copy
* sample value if source and destination raster are the same.</li>
* </ul>
*
* @param sources An array of length 1 with source image.
* @param dest The destination tile.
* @param destRect the rectangle within the destination to be written.
*/
@Override
protected void computeRect(final PlanarImage[] sources,
final WritableRaster dest,
final Rectangle destRect)
{
final PlanarImage source = sources[0];
final Rectangle bounds = destRect.intersection(source.getBounds());
if (!destRect.equals(bounds)) {
// TODO: Check if this case occurs sometime, and fill pixel values if it does.
// If it happen to occurs, we will need to fix other GeoTools operations
// as well.
Logging.getLogger(SampleTranscoder.class).warning(
"Bounds mismatch: " + destRect + " and " + bounds);
}
WritableRectIter iterator = RectIterFactory.createWritable(dest, bounds);
if (true) {
// TODO: Detect if source and destination rasters are the same. If they are
// the same, we should skip this block. Iteration will then be faster.
iterator = TransfertRectIter.create(RectIterFactory.create(source, bounds), iterator);
}
int band = 0;
if (!iterator.finishedBands()) do {
categories[band++].transform(iterator);
}
while (!iterator.nextBandDone());
assert band == categories.length : band;
}
/////////////////////////////////////////////////////////////////////////////////
//////// ////////
//////// REGISTRATION OF "SampleTranscode" IMAGE OPERATION ////////
//////// ////////
/////////////////////////////////////////////////////////////////////////////////
/**
* The operation descriptor for the "SampleTranscode" operation. This operation can apply the
* {@link GridSampleDimension#getSampleToGeophysics sampleToGeophysics} transform on all pixels
* in all bands of an image. The transformations are supplied as a list of
* {@link GridSampleDimension}s, one for each band. The supplied {@code GridSampleDimension}
* objects describe the categories in the <strong>source</strong> image. The target image
* will matches sample dimension
*
* <code>{@link GridSampleDimension#geophysics geophysics}(!isGeophysics)</code>,
*
* where {@code isGeophysics} is the previous state of the sample dimension.
*/
private static final class Descriptor extends OperationDescriptorImpl {
/**
* For cross-version serialization.
*/
private static final long serialVersionUID = -4204913600785080791L;
/**
* Construct the descriptor.
*/
public Descriptor() {
super(new String[][]{{"GlobalName", OPERATION_NAME},
{"LocalName", OPERATION_NAME},
{"Vendor", "Geotools 2"},
{"Description", "Transformation from sample to geophysics values"},
{"DocURL", "http://www.geotools.org/"},
{"Version", "1.0"}},
new String[] {RenderedRegistryMode.MODE_NAME}, 1,
new String[] {"sampleDimensions"}, // Argument names
new Class [] {GridSampleDimension[].class}, // Argument classes
new Object[] {NO_PARAMETER_DEFAULT}, // Default values for parameters,
null // No restriction on valid parameter values.
);
}
/**
* Returns {@code true} if the parameters are valids. This implementation check
* that the number of bands in the source image is equals to the number of supplied
* sample dimensions, and that all sample dimensions has categories.
*
* @param modeName The mode name (usually "Rendered").
* @param param The parameter block for the operation to performs.
* @param message A buffer for formatting an error message if any.
*/
@Override
protected boolean validateParameters(final String modeName,
final ParameterBlock param,
final StringBuffer message)
{
if (!super.validateParameters(modeName, param, message)) {
return false;
}
final RenderedImage source = (RenderedImage) param.getSource(0);
final GridSampleDimension[] bands = (GridSampleDimension[]) param.getObjectParameter(0);
final int numBands = source.getSampleModel().getNumBands();
if (numBands != bands.length) {
message.append(Errors.format(ErrorKeys.NUMBER_OF_BANDS_MISMATCH_$3,
numBands, bands.length, "SampleDimension"));
return false;
}
for (int i=0; i<numBands; i++) {
if (bands[i].categories == null) {
message.append(Errors.format(ErrorKeys.BAD_PARAMETER_$2,
"sampleDimensions["+i+"].categories", null));
return false;
}
}
return true;
}
}
/**
* The {@link java.awt.image.renderable.RenderedImageFactory}
* for the {@code "SampleTranscode"} operation.
*/
private static final class CRIF extends CRIFImpl {
/**
* Creates a {@link RenderedImage} representing the results of an imaging
* operation for a given {@link ParameterBlock} and {@link RenderingHints}.
*/
public RenderedImage create(final ParameterBlock param,
final RenderingHints hints)
{
final RenderedImage image = (RenderedImage) param.getSource(0);
final GridSampleDimension[] bands = (GridSampleDimension[]) param.getObjectParameter(0);
final CategoryList[] categories = new CategoryList[bands.length];
for (int i=0; i<categories.length; i++) {
categories[i] = bands[i].categories;
}
if (image instanceof SampleTranscoder) {
final SampleTranscoder other = (SampleTranscoder) image;
if (isInverse(categories, other.categories)) {
return other.getSourceImage(0);
}
}
return new SampleTranscoder(image, categories, hints);
}
/**
* Checks if all categories in {@code categories1} are
* equals to the inverse of {@code categories2}.
*/
private static boolean isInverse(final CategoryList[] categories1,
final CategoryList[] categories2)
{
if (categories1.length != categories2.length) {
return false;
}
for (int i=0; i<categories1.length; i++) {
if (!categories1[i].equals(categories2[i].inverse)) {
return false;
}
}
return true;
}
}
/**
* Register the "SampleTranscode" image operation to the operation registry of
* the specified JAI instance. This method is invoked by the static initializer
* of {@link GridSampleDimension}.
*/
public static void register(final JAI jai) {
final OperationRegistry registry = jai.getOperationRegistry();
try {
registry.registerDescriptor(new Descriptor());
registry.registerFactory(RenderedRegistryMode.MODE_NAME, OPERATION_NAME,
"geotools.org", new CRIF());
} catch (IllegalArgumentException exception) {
final LogRecord record = Loggings.format(Level.SEVERE,
LoggingKeys.CANT_REGISTER_JAI_OPERATION_$1, OPERATION_NAME);
// Note: GridSampleDimension is the public class that use this transcoder.
record.setSourceClassName(GridSampleDimension.class.getName());
record.setSourceMethodName("<classinit>");
record.setThrown(exception);
final Logger logger = AbstractGridCoverage.LOGGER;
record.setLoggerName(logger.getName());
logger.log(record);
}
}
}