001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2005-2006, Geotools Project Managment Committee (PMC)
005: * (C) 2003, Institut de Recherche pour le Développement
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: */
017: package org.geotools.coverage.processing.operation;
018:
019: import java.awt.RenderingHints;
020: import java.awt.image.ColorModel;
021: import java.awt.image.IndexColorModel;
022: import java.awt.image.RenderedImage;
023: import java.awt.image.renderable.ParameterBlock;
024:
025: import javax.media.jai.ImageLayout;
026: import javax.media.jai.JAI;
027: import javax.media.jai.PlanarImage;
028:
029: import org.opengis.parameter.ParameterValueGroup;
030:
031: import org.geotools.coverage.GridSampleDimension;
032: import org.geotools.coverage.grid.GridCoverage2D;
033: import org.geotools.coverage.processing.OperationJAI;
034: import org.geotools.resources.coverage.CoverageUtilities;
035: import org.geotools.resources.image.ColorUtilities;
036:
037: /**
038: * A grid coverage containing a subset of an other grid coverage's sample dimensions,
039: * and/or a different {@link ColorModel}. A common reason for changing the color model
040: * is to select a different visible band. Consequently, the {@code "SelectSampleDimension"}
041: * operation name still appropriate in this context.
042: *
043: * @since 2.2
044: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/coverage/src/main/java/org/geotools/coverage/processing/operation/BandSelector2D.java $
045: * @version $Id: BandSelector2D.java 28889 2008-01-22 17:57:52Z aaime $
046: * @author Martin Desruisseaux
047: */
048: final class BandSelector2D extends GridCoverage2D {
049: /**
050: * The mapping to bands in the source grid coverage.
051: * May be {@code null} if all bands were keept.
052: */
053: private final int[] bandIndices;
054:
055: /**
056: * Constructs a new {@code BandSelect2D} grid coverage. This grid coverage will use
057: * the same coordinate reference system and the same geometry than the source grid
058: * coverage.
059: *
060: * @param source The source coverage.
061: * @param image The image to use.
062: * @param bands The sample dimensions to use.
063: * @param bandIndices The mapping to bands in {@code source}. Not used
064: * by this constructor, but keept for futur reference.
065: *
066: * @todo It would be nice if we could use always the "BandSelect" operation
067: * without the "Null" one. But as of JAI-1.1.1, "BandSelect" do not
068: * detect by itself the case were no copy is required.
069: */
070: private BandSelector2D(final GridCoverage2D source,
071: final PlanarImage image, final GridSampleDimension[] bands,
072: final int[] bandIndices) {
073: super (source.getName(), // The grid source name
074: image, // The underlying data
075: (org.geotools.coverage.grid.GridGeometry2D) source
076: .getGridGeometry(), // The grid geometry (unchanged).
077: bands, // The sample dimensions
078: new GridCoverage2D[] { source }, // The source grid coverages.
079: null); // Properties
080:
081: this .bandIndices = bandIndices;
082: assert bandIndices == null
083: || bandIndices.length == bands.length;
084: }
085:
086: /**
087: * Applies the band select operation to a grid coverage.
088: *
089: * @param parameters List of name value pairs for the parameters.
090: * @param A set of rendering hints, or {@code null} if none.
091: * @return The result as a grid coverage.
092: */
093: static GridCoverage2D create(final ParameterValueGroup parameters,
094: RenderingHints hints) {
095: /*
096: * Fetch all parameters, clone them if needed. The "VisibleSampleDimension" parameter is
097: * Geotools-specific and optional. We get it has an Integer both for catching null value,
098: * and also because it is going to be stored as an image's property anyway.
099: */
100: GridCoverage2D source = (GridCoverage2D) parameters.parameter(
101: "Source").getValue();
102: int[] bandIndices = parameters.parameter("SampleDimensions")
103: .intValueList();
104: if (bandIndices != null) {
105: bandIndices = (int[]) bandIndices.clone();
106: }
107: Integer visibleBand = (Integer) parameters.parameter(
108: "VisibleSampleDimension").getValue();
109: /*
110: * Prepares the informations needed for JAI's "BandSelect" operation. The loop below
111: * should be executed only once, except if the source grid coverage is itself an instance
112: * of an other BandSelect2D object, in which case the sources will be extracted
113: * recursively until a non-BandSelect2D object is found.
114: */
115: int visibleSourceBand;
116: int visibleTargetBand;
117: GridSampleDimension[] sourceBands;
118: GridSampleDimension[] targetBands;
119: RenderedImage sourceImage;
120: while (true) {
121: sourceBands = source.getSampleDimensions();
122: targetBands = sourceBands;
123: /*
124: * Constructs an array of target bands. If the 'bandIndices' parameter contains
125: * only "identity" indices (0, 1, 2...), then we will work as if no band indices
126: * were provided. It will allow us to use the "Null" operation rather than
127: * "BandSelect", which make it possible to avoid to copy raster data.
128: */
129: if (bandIndices != null) {
130: if (bandIndices.length != sourceBands.length
131: || !isIdentity(bandIndices)) {
132: targetBands = new GridSampleDimension[bandIndices.length];
133: for (int i = 0; i < bandIndices.length; i++) {
134: targetBands[i] = sourceBands[bandIndices[i]];
135: }
136: } else {
137: bandIndices = null;
138: }
139: }
140: sourceImage = source.getRenderedImage();
141: visibleSourceBand = CoverageUtilities
142: .getVisibleBand(sourceImage);
143: if (visibleBand != null) {
144: visibleTargetBand = mapSourceToTarget(visibleBand
145: .intValue(), bandIndices);
146: if (visibleSourceBand == -1)
147: throw new IllegalArgumentException(
148: "Visible sample dimension is "
149: + "not among the ones specified in SampleDimensions param");
150: } else {
151: // try to keep the original one, if it hasn't been selected, fall
152: // back on the first selected band
153: visibleTargetBand = mapSourceToTarget(
154: visibleSourceBand, bandIndices);
155: if (visibleTargetBand == -1)
156: visibleTargetBand = 0;
157: }
158: if (bandIndices == null
159: && visibleSourceBand == visibleTargetBand) {
160: return source;
161: }
162: if (!(source instanceof BandSelector2D)) {
163: break;
164: }
165: /*
166: * If the source coverage was the result of an other "BandSelect" operation, go up
167: * the chain and checks if an existing GridCoverage could fit. We do that in order
168: * to avoid to create new GridCoverage everytime the user is switching the visible
169: * band. For example we could change the visible band from 0 to 1, and then come
170: * back to 0 later.
171: */
172: final int[] parentIndices = ((BandSelector2D) source).bandIndices;
173: if (parentIndices != null) {
174: if (bandIndices != null) {
175: for (int i = 0; i < bandIndices.length; i++) {
176: bandIndices[i] = parentIndices[bandIndices[i]];
177: }
178: } else {
179: bandIndices = (int[]) parentIndices.clone();
180: }
181: }
182: assert source.getSources().size() == 1 : source;
183: source = (GridCoverage2D) source.getSources().get(0);
184: }
185: /*
186: * All required information are now know. Creates the GridCoverage resulting from the
187: * operation. A color model will be defined only if the user didn't specify an explicit
188: * one.
189: */
190: String operation = "Null";
191: ImageLayout layout = null;
192: if (hints != null) {
193: layout = (ImageLayout) hints.get(JAI.KEY_IMAGE_LAYOUT);
194: }
195: if (layout == null) {
196: layout = new ImageLayout();
197: }
198: if (visibleBand != null
199: || !layout.isValid(ImageLayout.COLOR_MODEL_MASK)) {
200: ColorModel colors = sourceImage.getColorModel();
201: if (colors instanceof IndexColorModel
202: && sourceBands[visibleSourceBand]
203: .equals(targetBands[visibleTargetBand])) {
204: /*
205: * If the source color model was an instance of IndexColorModel, reuse
206: * its color mapping. It may not matches the category colors if the user
207: * provided its own color model. We are better to use what the user said.
208: */
209: final IndexColorModel indexed = (IndexColorModel) colors;
210: final int[] ARGB = new int[indexed.getMapSize()];
211: indexed.getRGBs(ARGB);
212: colors = ColorUtilities.getIndexColorModel(ARGB,
213: targetBands.length, visibleTargetBand);
214: } else {
215: colors = targetBands[visibleTargetBand].getColorModel(
216: visibleTargetBand, targetBands.length);
217: }
218: layout.setColorModel(colors);
219: if (hints != null) {
220: hints = (RenderingHints) hints.clone();
221: hints.put(JAI.KEY_IMAGE_LAYOUT, layout);
222: } else {
223: hints = new RenderingHints(JAI.KEY_IMAGE_LAYOUT, layout);
224: }
225: }
226: if (visibleBand == null) {
227: visibleBand = new Integer(visibleTargetBand);
228: }
229: ParameterBlock params = new ParameterBlock()
230: .addSource(sourceImage);
231: if (targetBands != sourceBands) {
232: operation = "BandSelect";
233: params = params.add(bandIndices);
234: }
235: final PlanarImage image = OperationJAI.getJAI(hints).createNS(
236: operation, params, hints);
237: image.setProperty("GC_VisibleBand", visibleBand);
238: return new BandSelector2D(source, image, targetBands,
239: bandIndices);
240: }
241:
242: /**
243: * Maps the specified source band number to the target band index after the
244: * selection/reordering process imposed by targetSampleDimensions is applied
245: * @param band
246: * @param targetSampleDimensions
247: * @return
248: */
249: private static int mapSourceToTarget(int band,
250: int targetSampleDimensions[]) {
251: if (targetSampleDimensions == null)
252: return band;
253: for (int i = 0; i < targetSampleDimensions.length; i++) {
254: if (targetSampleDimensions[i] == band)
255: return i;
256: }
257: return -1;
258: }
259:
260: /**
261: * Returns {@code true} if the specified array contains increasing values 0, 1, 2...
262: */
263: private static boolean isIdentity(final int[] bands) {
264: for (int i = 0; i < bands.length; i++) {
265: if (bands[i] != i) {
266: return false;
267: }
268: }
269: return true;
270: }
271: }
|