/*
* Geotoolkit.org - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2003-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.processing.operation;
import java.awt.image.ColorModel;
import java.awt.image.RenderedImage;
import java.awt.image.IndexColorModel;
import java.awt.image.renderable.ParameterBlock;
import javax.media.jai.JAI;
import javax.media.jai.ImageLayout;
import javax.media.jai.PlanarImage;
import org.opengis.parameter.ParameterValueGroup;
import org.geotoolkit.factory.Hints;
import org.geotoolkit.coverage.GridSampleDimension;
import org.geotoolkit.coverage.grid.GridCoverage2D;
import org.geotoolkit.coverage.processing.OperationJAI;
import org.geotoolkit.image.color.ColorUtilities;
import org.geotoolkit.internal.coverage.CoverageUtilities;
/**
* A grid coverage containing a subset of an other grid coverage's sample dimensions,
* and/or a different {@link ColorModel}. A common reason for changing the color model
* is to select a different visible band. Consequently, the {@code "SelectSampleDimension"}
* operation name still appropriate in this context.
*
* @author Martin Desruisseaux (IRD)
* @author Andrea Aime (TOPP)
* @version 3.00
*
* @since 2.2
* @module
*/
final class BandSelector2D extends GridCoverage2D {
/**
* For cross-version compatibility.
*/
private static final long serialVersionUID = -6572017920456641730L;
/**
* The mapping to bands in the source grid coverage.
* May be {@code null} if all bands were keept.
*/
private final int[] bandIndices;
/**
* Constructs a new {@code BandSelect2D} grid coverage. This grid coverage will use
* the same coordinate reference system and the same geometry than the source grid
* coverage.
*
* @param source The source coverage.
* @param image The image to use.
* @param bands The sample dimensions to use.
* @param bandIndices The mapping to bands in {@code source}. Not used
* by this constructor, but keept for futur reference.
*
* @todo It would be nice if we could use always the "BandSelect" operation
* without the "Null" one. But as of JAI-1.1.1, "BandSelect" does not
* detect by itself the case were no copy is required.
*/
private BandSelector2D(final GridCoverage2D source,
final PlanarImage image,
final GridSampleDimension[] bands,
final int[] bandIndices,
final Hints hints)
{
super(source.getName(), // The grid source name
image, // The underlying data
source.getGridGeometry(), // The grid geometry (unchanged).
bands, // The sample dimensions
new GridCoverage2D[] {source}, // The source grid coverages.
null, hints); // Properties
this.bandIndices = bandIndices;
assert bandIndices == null || bandIndices.length == bands.length;
}
/**
* Applies the band select operation to a grid coverage.
*
* @param parameters List of name value pairs for the parameters.
* @param A set of rendering hints, or {@code null} if none.
* @return The result as a grid coverage.
*/
static GridCoverage2D create(GridCoverage2D source, final ParameterValueGroup parameters, Hints hints) {
/*
* Fetches all parameters, clones them if needed. The "VisibleSampleDimension" parameter
* is Geotk-specific and optional. We get it as an Integer both for catching null value,
* and also because it is going to be stored as an image's property anyway.
*/
int[] bandIndices = parameters.parameter("SampleDimensions").intValueList();
if (bandIndices != null) {
bandIndices = bandIndices.clone();
}
Integer visibleBand = (Integer) parameters.parameter("VisibleSampleDimension").getValue();
/*
* Prepares the informations needed for JAI's "BandSelect" operation. The loop below
* should be executed only once, except if the source grid coverage is itself an instance
* of an other BandSelect2D object, in which case the sources will be extracted
* recursively until a non-BandSelect2D object is found.
*/
int visibleSourceBand;
int visibleTargetBand;
GridSampleDimension[] sourceBands;
GridSampleDimension[] targetBands;
RenderedImage sourceImage;
while (true) {
sourceBands = source.getSampleDimensions();
targetBands = sourceBands;
/*
* Constructs an array of target bands. If the 'bandIndices' parameter contains
* only "identity" indices (0, 1, 2...), then we will work as if no band indices
* were provided. It will allow us to use the "Null" operation rather than
* "BandSelect", which make it possible to avoid to copy raster data.
*/
if (bandIndices != null) {
if (bandIndices.length != sourceBands.length || !isIdentity(bandIndices)) {
targetBands = new GridSampleDimension[bandIndices.length];
for (int i=0; i<bandIndices.length; i++) {
targetBands[i] = sourceBands[bandIndices[i]];
}
} else {
bandIndices = null;
}
}
sourceImage = source.getRenderedImage();
visibleSourceBand = CoverageUtilities.getVisibleBand(sourceImage);
if (visibleBand != null) {
visibleTargetBand = mapSourceToTarget(visibleBand.intValue(), bandIndices);
if (visibleSourceBand < 0) {
// TODO: localize
throw new IllegalArgumentException("Visible sample dimension is " +
"not among the ones specified in SampleDimensions param");
}
} else {
// Try to keep the original one, if it hasn't been selected, fall
// back on the first selected band.
visibleTargetBand = mapSourceToTarget(visibleSourceBand, bandIndices);
if (visibleTargetBand < 0) {
visibleTargetBand = 0;
}
}
if (bandIndices == null && visibleSourceBand == visibleTargetBand) {
return source;
}
if (!(source instanceof BandSelector2D)) {
break;
}
/*
* If the source coverage was the result of an other "BandSelect" operation, go up
* the chain and checks if an existing GridCoverage could fit. We do that in order
* to avoid to create new GridCoverage everytime the user is switching the visible
* band. For example we could change the visible band from 0 to 1, and then come
* back to 0 later.
*/
final int[] parentIndices = ((BandSelector2D) source).bandIndices;
if (parentIndices != null) {
if (bandIndices != null) {
for (int i=0; i<bandIndices.length; i++) {
bandIndices[i] = parentIndices[bandIndices[i]];
}
} else {
bandIndices = parentIndices.clone();
}
}
assert source.getSources().size() == 1 : source;
source = (GridCoverage2D) source.getSources().get(0);
}
/*
* All required information are now know. Creates the GridCoverage resulting from the
* operation. A color model will be defined only if the user didn't specify an explicit
* one.
*/
String operation = "Null";
ImageLayout layout = null;
if (hints != null) {
layout = (ImageLayout) hints.get(JAI.KEY_IMAGE_LAYOUT);
}
if (layout == null) {
layout = new ImageLayout();
}
if (visibleBand!=null || !layout.isValid(ImageLayout.COLOR_MODEL_MASK)) {
ColorModel colors = sourceImage.getColorModel();
if (colors instanceof IndexColorModel &&
sourceBands[visibleSourceBand].equals(targetBands[visibleTargetBand]))
{
/*
* If the source color model was an instance of IndexColorModel, reuse
* its color mapping. It may not matches the category colors if the user
* provided its own color model. We are better to use what the user said.
*/
final IndexColorModel indexed = (IndexColorModel) colors;
final int[] ARGB = new int[indexed.getMapSize()];
indexed.getRGBs(ARGB);
colors = ColorUtilities.getIndexColorModel(ARGB, targetBands.length, visibleTargetBand, -1);
} else {
colors = targetBands[visibleTargetBand]
.getColorModel(visibleTargetBand, targetBands.length);
}
/*
* If we are not able to provide a color model because our sample dimensions
* are very simple, let's JAI do its magic and figure out the best one for us.
*/
if (colors != null) {
layout.setColorModel(colors);
}
if (hints != null) {
hints = hints.clone();
hints.put(JAI.KEY_IMAGE_LAYOUT, layout);
} else {
hints = new Hints(JAI.KEY_IMAGE_LAYOUT, layout);
}
}
if (visibleBand == null) {
visibleBand = visibleTargetBand;
}
ParameterBlock params = new ParameterBlock().addSource(sourceImage);
if (targetBands != sourceBands) {
operation = "BandSelect";
params = params.add(bandIndices);
}
final PlanarImage image = OperationJAI.getJAI(hints).createNS(operation, params, hints);
image.setProperty("GC_VisibleBand", visibleBand);
return new BandSelector2D(source, image, targetBands, bandIndices, hints);
}
/**
* Maps the specified source band number to the target band index after the
* selection/reordering process imposed by targetSampleDimensions is applied.
*
* @param sourceBand The index of a source band.
* @param bandIndices The indices of source bands to be retained for target, or {@code null}.
* @return The target band indices, or {@code -1} if not found.
*/
private static int mapSourceToTarget(final int sourceBand, final int[] bandIndices) {
if (bandIndices == null) {
return sourceBand;
}
for (int i=0; i<bandIndices.length; i++) {
if (bandIndices[i] == sourceBand) {
return i;
}
}
return -1;
}
/**
* Returns {@code true} if the specified array contains increasing values 0, 1, 2...
*/
private static boolean isIdentity(final int[] bands) {
for (int i=0; i<bands.length; i++) {
if (bands[i] != i) {
return false;
}
}
return true;
}
}