/*
* Geotoolkit.org - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2006-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;
import java.awt.color.ColorSpace;
import java.awt.image.ColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.RenderedImage;
import java.awt.image.IndexColorModel;
import javax.media.jai.OpImage;
import javax.media.jai.ImageLayout;
import javax.media.jai.NullOpImage;
import org.opengis.coverage.Coverage;
import org.opengis.coverage.grid.GridCoverage;
import org.opengis.parameter.ParameterValueGroup;
import org.geotoolkit.factory.Hints;
import org.geotoolkit.coverage.grid.ViewType;
import org.geotoolkit.coverage.GridSampleDimension;
import org.geotoolkit.coverage.grid.GridCoverage2D;
import org.geotoolkit.internal.coverage.CoverageUtilities;
import org.geotoolkit.resources.Errors;
import org.apache.sis.util.Classes;
import org.geotoolkit.image.color.ColorUtilities;
import org.opengis.parameter.ParameterDescriptorGroup;
/**
* Operation applied on the {@link IndexColorModel} of the image backing the source grid coverage.
* The {@link #doOperation doOperation} method extracts the color map of the source coverage as an
* array of ARGB values, passes that array to {@link #transformColormap transformColormap} and
* creates a new grid coverage using the new colors.
*
* @author Martin Desruisseaux (IRD)
* @version 3.14
*
* @since 1.2
* @module
*/
public abstract class IndexColorOperation extends Operation2D {
/**
* Constructs an operation. The operation name will be the same than the
* parameter descriptor name.
*
* @param descriptor The parameters descriptor.
*/
protected IndexColorOperation(final ParameterDescriptorGroup descriptor) {
super(descriptor);
}
/**
* Returns {@link ViewType#RENDERED} as the preferred view for computation purpose.
*/
@Override
protected ViewType getComputationView(final ParameterValueGroup parameters) {
return ViewType.RENDERED;
}
/**
* Returns {@code true} if the given color model is a gray scale.
* This method does not check the case of {@link IndexColorModel}.
*/
private static boolean isGrayScale(final ColorModel cm) {
return (cm instanceof ComponentColorModel) && (cm.getNumComponents() == 1) &&
cm.getColorSpace().getType() == ColorSpace.TYPE_GRAY;
}
/**
* Performs the color transformation. This method invokes the {@link #transformColormap
* transformColormap(...)} method with the ARGB colors found in the source image, its
* {@link GridSampleDimension} and the parameters supplied to this method. The new colors
* returned by {@code transformColormap} are used for creating a grid coverage backed by
* and image using a new {@link IndexColorModel}.
*
* @param parameters The parameters.
* @param hints Rendering hints (ignored in this implementation).
* @return The result as a coverage.
* @throws IllegalArgumentException if the image do not use an {@link IndexColorModel}.
*/
@Override
protected Coverage doOperation(final ParameterValueGroup parameters, final Hints hints)
throws IllegalArgumentException
{
final GridCoverage2D[] sources = new GridCoverage2D[1];
final ViewType targetView = extractSources(parameters, sources);
final GridCoverage2D source = sources[0];
final RenderedImage image = source.getRenderedImage();
final GridSampleDimension[] bands = source.getSampleDimensions();
final int visibleBand = CoverageUtilities.getVisibleBand(image);
ColorModel targetModel = image.getColorModel();
boolean bandChanged = false;
for (int i=0; i<bands.length; i++) {
/*
* Extracts the ARGB codes from the IndexColorModel and invokes the
* transformColormap(...) method, which needs to be defined by subclasses.
*/
GridSampleDimension band = bands[i];
final ColorModel sourceModel = (i == visibleBand) ? image.getColorModel() : band.getColorModel();
final IndexColorModel colors;
final int[] ARGB;
if (sourceModel instanceof IndexColorModel) {
colors = (IndexColorModel) sourceModel;
ARGB = new int[colors.getMapSize()];
colors.getRGBs(ARGB);
} else if (isGrayScale(sourceModel)) {
colors = null;
ARGB = new int[1 << sourceModel.getPixelSize()];
for (int j=0; j<ARGB.length; j++) {
ARGB[j] = j;
}
} else {
throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalClass_2,
Classes.getClass(sourceModel), IndexColorModel.class));
}
band = transformColormap(ARGB, i, band, parameters);
/*
* Checks if there is any change, either as a new GridSampleDimension instance or in
* the ARGB array. Note that if the new GridSampleDimension is equals to the old one,
* then the new one will be discarded since the old one is more likely to be a shared
* instance.
*/
if (!bands[i].equals(band)) {
bands[i] = band;
bandChanged = true;
}
boolean colorChanged = false;
for (int j=0; j<ARGB.length; j++) {
final int old = (colors != null) ? colors.getRGB(j) : j;
if (ARGB[j] != old) {
colorChanged = true;
bandChanged = true;
break;
}
}
/*
* If we changed the color of the visible band, then create immediately a new
* color model for this band. The new color model will be given later to the
* image operator.
*/
if (colorChanged && (i == visibleBand)) {
targetModel = ColorUtilities.getIndexColorModel(ARGB, bands.length, visibleBand, -1);
}
}
if (!bandChanged) {
return source.view(targetView);
}
/*
* Gives the color model to the image layout and creates a new image using the Null
* operation, which merely propagates its first source along the operation chain
* unmodified (except for the ColorModel given in the layout in this case).
*/
final ImageLayout layout = new ImageLayout().setColorModel(targetModel);
final RenderedImage newImage = new NullOpImage(image, layout, null, OpImage.OP_COMPUTE_BOUND);
final GridCoverage2D target = getFactory(hints).create(
source.getName(), newImage,
source.getCoordinateReferenceSystem(),
source.getGridGeometry().getGridToCRS(),
bands, new GridCoverage[] { source }, null);
return target.view(targetView);
}
/**
* Transforms the supplied RGB colors. This method is automatically invoked
* by {@link #doOperation doOperation(...)} for each band in the source
* {@link GridCoverage2D}. The {@code ARGB} array contains the ARGB values
* from the current source and should be overridden with new ARGB values
* for the destination image.
* <p>
* This method is usually invoked only once, since images backed by {@link IndexColorModel}
* normally have only one band. However it may happen that an image contains additional
* "invisible" bands, in which case this method will be invoked for those bands as well.
*
* @param ARGB Alpha, Red, Green and Blue components to transform.
* @param band The band number, from 0 to the number of bands in the image -1.
* @param sampleDimension The sample dimension of band {@code band}.
* @param parameters The user-supplied parameters.
* @return A sample dimension identical to {@code sampleDimension} except for the
* colors. Subclasses may conservatively returns {@code sampleDimension}.
*/
protected abstract GridSampleDimension transformColormap(final int[] ARGB, final int band,
final GridSampleDimension sampleDimension, final ParameterValueGroup parameters);
}