/*
* Geotoolkit.org - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2001-2012, Open Source Geospatial Foundation (OSGeo)
* (C) 2009-2012, Geomatys
*
* 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.geotoolkit.coverage;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.image.DataBuffer;
import java.awt.image.RasterFormatException;
import java.awt.image.RenderedImage;
import java.awt.image.WritableRaster;
import java.awt.image.renderable.ParameterBlock;
import java.util.Vector;
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.geotoolkit.resources.Errors;
import org.geotoolkit.resources.Loggings;
import org.apache.sis.util.logging.Logging;
import org.geotoolkit.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.geotoolkit.SampleTranscode"}.
*
* @author Martin Desruisseaux (IRD, Geomatys)
* @version 3.11
*
* @since 2.1
* @module
*/
final class SampleTranscoder extends PointOpImage {
/**
* The operation name.
* <strong>NOTE:</strong> Class {@link org.geotoolkit.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.geotoolkit.SampleTranscode";
/**
* Category lists for each bands.
* The array length must matches the number of bands in source image.
*/
private final CategoryList[] categories;
/**
* {@code true} if the buffer is of kind {@code TYPE_USHORT} and the sample values
* should be forced to signed integers.
*
* @since 3.11
*/
private final boolean forceSigned;
/**
* 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));
}
boolean forceSigned = false;
if (image.getSampleModel().getDataType() == DataBuffer.TYPE_USHORT) {
for (final CategoryList list : categories) {
if (list.isRangeSigned()) {
forceSigned = true;
break;
}
}
}
this.forceSigned = forceSigned;
permitInPlaceOperation();
}
/**
* Returns the source images.
*/
@Override
@SuppressWarnings("unchecked")
public Vector<RenderedImage> getSources() {
return super.getSources();
}
/**
* Computes one of the destination image tile.
*
* @todo 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.
*
* @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());
assert destRect.equals(bounds) : destRect;
final WritableRectIter iterator;
if (forceSigned) {
iterator = new SignedRectIter(
RectIterFactory.create(source, bounds),
RectIterFactory.createWritable(dest, bounds));
} else {
iterator = TransfertRectIter.create(source, dest, bounds);
}
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", "Geotoolkit.org"},
{"Description", "Transformation from sample to geophysics values"},
{"DocURL", "http://www.geotoolkit.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(Errors.Keys.MismatchedNumberOfBands_3,
numBands, bands.length, "SampleDimension"));
return false;
}
for (int i=0; i<numBands; i++) {
if (bands[i].categories == null) {
message.append(Errors.format(Errors.Keys.IllegalParameterValue_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}.
*/
@Override
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,
org.geotoolkit.image.internal.Setup.PRODUCT_NAME, new CRIF());
} catch (IllegalArgumentException exception) {
final LogRecord record = Loggings.format(Level.SEVERE,
Loggings.Keys.CantRegisterJaiOperation_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 = Logging.getLogger("org.geotoolkit.coverage");
record.setLoggerName(logger.getName());
logger.log(record);
}
}
}