/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2005-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.referencing.piecewise; 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.awt.image.renderable.RenderedImageFactory; import javax.media.jai.CRIFImpl; import javax.media.jai.ColormapOpImage; import javax.media.jai.JAI; import javax.media.jai.OperationDescriptorImpl; import javax.media.jai.PlanarImage; import javax.media.jai.iterator.RectIterFactory; import javax.media.jai.iterator.WritableRectIter; import javax.media.jai.registry.RenderedRegistryMode; import org.geotools.coverage.GridSampleDimension; import org.geotools.image.TransfertRectIter; import org.geotools.image.jai.Registry; import org.geotools.renderer.i18n.ErrorKeys; import org.geotools.renderer.i18n.Errors; import org.opengis.referencing.operation.TransformException; import com.sun.media.jai.opimage.RIFUtil; import com.sun.media.jai.util.ImageUtil; /** * Images are created using the {@code GenericPiecewise.CRIF} inner class, where "CRIF" stands for {@link java.awt.image.renderable.ContextualRenderedImageFactory} . The image operation name is "org.geotools.GenericPiecewise". * * * @source $URL$ * @version $Id$ * @author Simone Giannecchini - GeoSolutions * @since 2.4 */ public class GenericPiecewise<T extends PiecewiseTransform1DElement> extends ColormapOpImage { /** * The operation name. */ public static final String OPERATION_NAME = "org.geotools.GenericPiecewise"; /** * DefaultPiecewiseTransform1D that we'll use to transform this image. We'll apply it ato all of its bands. */ private final PiecewiseTransform1D<T> piecewise; private final boolean isByteData; private byte[][] lut; private double gapsValue = Double.NaN; private boolean hasGapsValue = false; private final boolean useLast; /** * Constructs a new {@code RasterClassifier}. * * @param image * The source image. * @param lic * The DefaultPiecewiseTransform1D. * @param bandIndex * @param hints * The rendering hints. */ private GenericPiecewise(final RenderedImage image, final PiecewiseTransform1D<T> lic, final RenderingHints hints) { super(image, RIFUtil.getImageLayoutHint(hints), hints, false); this.piecewise = lic; // Ensure that the number of sets of breakpoints is either unity // or equal to the number of bands. final int numBands = sampleModel.getNumBands(); // Set the byte data flag. isByteData = sampleModel.getTransferType() == DataBuffer.TYPE_BYTE; // //////////////////////////////////////////////////////////////////// // // Check if we can make good use of a default piecewise element for filling gaps // in the input range // // //////////////////////////////////////////////////////////////////// if (this.piecewise.hasDefaultValue()) { gapsValue=piecewise.getDefaultValue(); hasGapsValue = true; } // //////////////////////////////////////////////////////////////////// // // Check if we can optimize this operation by reusing the last used // piecewise element first. The speed up we get can be substantial since we avoid // an explicit search in the piecewise element list for the fitting piecewise element given // a certain sample value. // // // //////////////////////////////////////////////////////////////////// useLast = piecewise instanceof DefaultDomain1D; // Perform byte-specific initialization. if(isByteData) { // Initialize the lookup table. try { createLUT(numBands); } catch (final TransformException e) { final RuntimeException re= new RuntimeException(e); throw re; } } // Set flag to permit in-place operation. permitInPlaceOperation(); // Initialize the colormap if necessary. initializeColormapOperation(); } /** * Computes one of the destination image tile. * * @todo There are 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. */ protected void computeRect(final PlanarImage[] sources, final WritableRaster dest, final Rectangle destRect) { final PlanarImage source = sources[0]; WritableRectIter iterator = RectIterFactory.createWritable(dest, destRect); 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, destRect), iterator); } PiecewiseTransform1DElement last = null; int bandNumber=0; do { try { iterator.startLines(); if (!iterator.finishedLines()) do { iterator.startPixels(); if (!iterator.finishedPixels()) do { if(isByteData) { final int in=iterator.getSample()&0xff; final int out=0xff&lut[bandNumber][in]; iterator.setSample(out); } else last = domainSearch(iterator, last,bandNumber); } while (!iterator.nextPixelDone()); } while (!iterator.nextLineDone()); } catch (final Exception cause) { final RasterFormatException exception = new RasterFormatException( cause.getLocalizedMessage()); exception.initCause(cause); throw exception; } bandNumber++; } while (iterator.finishedBands()); } private PiecewiseTransform1DElement domainSearch(final WritableRectIter iterator, PiecewiseTransform1DElement last, final int bandNumber) throws TransformException { // // // // get the input value to be transformed // // // final double value = iterator.getSampleDouble(); // // // // get the correct piecewise element for this // transformation // // // final PiecewiseTransform1DElement transformElement; if (useLast) { if (last != null && last.contains(value)) transformElement = last; else { last = transformElement = (PiecewiseTransform1DElement) piecewise .findDomainElement(value); } } else transformElement = (PiecewiseTransform1DElement) piecewise .findDomainElement(value); // // // // in case everything went fine let's apply the // transform. // // // if (transformElement != null) iterator.setSample(transformElement .transform(value)); else { // // // // if we did not find one let's try to use // one of the nodata ones to fill the gaps, // if we are allowed to (see above). // // // if (hasGapsValue) iterator.setSample(gapsValue); else // // // // if we did not find one let's throw a // nice error message // // // throw new IllegalArgumentException(Errors.format(ErrorKeys.ILLEGAL_ARGUMENT_$1, Double.toString(value))); } return last; } // /////////////////////////////////////////////////////////////////////////////// // ////// //////// // ////// 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 piecewise * 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 { /** * */ private static final long serialVersionUID = 7954257625240335874L; /** * Construct the descriptor. */ public Descriptor() { super( new String[][] { { "GlobalName", OPERATION_NAME }, { "LocalName", OPERATION_NAME }, { "Vendor", "Geotools 2" }, { "Description", "Generic Piecewise Transformation" }, { "DocURL", "http://www.geotools.org/" }, { "Version", "1.0" } }, new String[] { RenderedRegistryMode.MODE_NAME }, 1, new String[] { "Domain1D", "bandIndex" }, // Argument // names new Class[] { DefaultPiecewiseTransform1D.class, Integer.class }, // Argument // classes new Object[] { NO_PARAMETER_DEFAULT, new Integer(-1) }, // 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 piecewise. * * @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. */ @SuppressWarnings("unchecked") 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 PiecewiseTransform1D lic = (PiecewiseTransform1D) param.getObjectParameter(0); if (lic == null) return false; final int numBands = source.getSampleModel().getNumBands(); final int bandIndex = param.getIntParameter(1); if (bandIndex == -1) return true; if (bandIndex < 0 || bandIndex >= numBands) { return false; } return true; } } /** * The {@link RenderedImageFactory} for the "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}. */ @SuppressWarnings("unchecked") public RenderedImage create(final ParameterBlock param, final RenderingHints hints) { final RenderedImage image = (RenderedImage) param.getSource(0); final PiecewiseTransform1D lic = (PiecewiseTransform1D) param.getObjectParameter(0); return new GenericPiecewise(image, lic, hints); } } /** * Register the RasterClassifier operation to the operation registry of the * specified JAI instance. This method is invoked by the static initializer * of {@link GridSampleDimension}. * * @param jai * JAI instance in which we want to register the RasterClassifier * operation. * @return <code>true</code> if everything goes fine, <code>false</code> * otherwise. */ public static boolean register(final JAI jai) { return Registry.registerRIF(jai, new Descriptor(), OPERATION_NAME, new CRIF()); } /** * Create a lookup table to be used in the case of byte data. * @param numBands * @throws TransformException */ private void createLUT(final int numBands) throws TransformException { // Allocate memory for the data array references. final byte[][] data = new byte[numBands][]; // Generate the data for each band. for(int band = 0; band < numBands; band++) { // Allocate memory for this band. data[band] = new byte[256]; // Cache the references to avoid extra indexing. final byte[] table = data[band]; // Initialize the lookup table data. PiecewiseTransform1DElement lastPiecewiseElement = null; for(int value = 0; value < 256; value++) { // // // // get the correct piecewise element for this // transformation // // // final PiecewiseTransform1DElement piecewiseElement; if (useLast) { if (lastPiecewiseElement != null && lastPiecewiseElement.contains(value)) piecewiseElement = lastPiecewiseElement; else { lastPiecewiseElement = piecewiseElement = (PiecewiseTransform1DElement) piecewise .findDomainElement(value); } } else piecewiseElement = (PiecewiseTransform1DElement) piecewise .findDomainElement(value); // // // // in case everything went fine let's apply the // transform. // // // if (piecewiseElement != null) table[value] = ImageUtil.clampRoundByte(piecewiseElement .transform(value)); else { // // // // if we did not find one let's try to use // one of the nodata ones to fill the gaps, // if we are allowed to (see above). // // // if (hasGapsValue) table[value] = ImageUtil.clampRoundByte(gapsValue); else // // // // if we did not find one let's throw a // nice error message // // // throw new IllegalArgumentException(Errors.format(ErrorKeys.ILLEGAL_ARGUMENT_$1, Double.toString(value))); } } } // Construct the lookup table. lut = data; } /** * Transform the colormap according to the rescaling parameters. */ protected void transformColormap(final byte[][] colormap) { for(int b = 0; b < 3; b++) { final byte[] map = colormap[b]; final byte[] luTable = lut[b >= lut.length ? 0 : b]; final int mapSize = map.length; for(int i = 0; i < mapSize; i++) { map[i] = luTable[(map[i] & 0xFF)]; } } } }