001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2003-2006, Geotools Project Management Committee (PMC)
005: * (C) 2001, 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: * This package contains documentation from OpenGIS specifications.
018: * OpenGIS consortium's work is fully acknowledged here.
019: */
020: package org.geotools.coverage;
021:
022: // J2SE dependencies
023: import java.awt.Rectangle;
024: import java.awt.RenderingHints;
025: import java.awt.image.RasterFormatException;
026: import java.awt.image.RenderedImage;
027: import java.awt.image.WritableRaster;
028: import java.awt.image.renderable.ParameterBlock;
029: import java.awt.image.renderable.RenderedImageFactory;
030: import java.util.logging.Level;
031: import java.util.logging.LogRecord;
032:
033: // JAI dependencies
034: import javax.media.jai.CRIFImpl;
035: import javax.media.jai.ImageLayout;
036: import javax.media.jai.JAI;
037: import javax.media.jai.OperationDescriptorImpl;
038: import javax.media.jai.OperationRegistry;
039: import javax.media.jai.PlanarImage;
040: import javax.media.jai.PointOpImage;
041: import javax.media.jai.iterator.RectIterFactory;
042: import javax.media.jai.iterator.WritableRectIter;
043: import javax.media.jai.registry.RenderedRegistryMode;
044:
045: // Geotools dependencies
046: import org.geotools.coverage.grid.AbstractGridCoverage;
047: import org.geotools.resources.i18n.Errors;
048: import org.geotools.resources.i18n.ErrorKeys;
049: import org.geotools.resources.i18n.Logging;
050: import org.geotools.resources.i18n.LoggingKeys;
051: import org.geotools.image.TransfertRectIter;
052:
053: /**
054: * An image that contains transformed samples. It may be sample values after their
055: * transformation to geophyics values, or the converse. Images are created using the
056: * {@code SampleTranscoder.CRIF} inner class, where "CRIF" stands for
057: * {@link java.awt.image.renderable.ContextualRenderedImageFactory}. The image
058: * operation name is "org.geotools.SampleTranscode".
059: *
060: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/coverage/src/main/java/org/geotools/coverage/SampleTranscoder.java $
061: * @version $Id: SampleTranscoder.java 23635 2007-01-01 20:58:15Z desruisseaux $
062: * @author Martin Desruisseaux
063: *
064: * @since 2.1
065: */
066: final class SampleTranscoder extends PointOpImage {
067: /**
068: * The operation name.
069: * <strong>NOTE:</strong> Class {@link org.geotools.coverage.grid.GridCoverage2D}
070: * uses this name, but can't refer to this constant since it is in an other package.
071: */
072: public static final String OPERATION_NAME = "org.geotools.SampleTranscode";
073:
074: /**
075: * Category lists for each bands.
076: * The array length must matches the number of bands in source image.
077: */
078: private final CategoryList[] categories;
079:
080: /**
081: * Constructs a new {@code SampleTranscoder}.
082: *
083: * @param image The source image.
084: * @param categories The category lists, one for each image's band.
085: * @param hints The rendering hints.
086: */
087: private SampleTranscoder(final RenderedImage image,
088: final CategoryList[] categories, final RenderingHints hints) {
089: super (image, (ImageLayout) hints.get(JAI.KEY_IMAGE_LAYOUT),
090: hints, false);
091: this .categories = categories;
092: if (categories.length != image.getSampleModel().getNumBands()) {
093: // Should not happen, since SampleDimension$Descriptor has already checked it.
094: throw new RasterFormatException(String
095: .valueOf(categories.length));
096: }
097: permitInPlaceOperation();
098: }
099:
100: /**
101: * Computes one of the destination image tile.
102: *
103: * @todo There is two optimisations we could do here:
104: * <ul>
105: * <li>If source and destination are the same raster, then a single
106: * {@link WritableRectIter} object would be more efficient (the
107: * hard work is to detect if source and destination are the same).</li>
108: * <li>If the destination image is a single-banded, non-interleaved
109: * sample model, we could apply the transform directly in the
110: * {@link java.awt.image.DataBuffer}. We can even avoid to copy
111: * sample value if source and destination raster are the same.</li>
112: * </ul>
113: *
114: * @param sources An array of length 1 with source image.
115: * @param dest The destination tile.
116: * @param destRect the rectangle within the destination to be written.
117: */
118: protected void computeRect(final PlanarImage[] sources,
119: final WritableRaster dest, final Rectangle destRect) {
120: final PlanarImage source = sources[0];
121: WritableRectIter iterator = RectIterFactory.createWritable(
122: dest, destRect);
123: if (true) {
124: // TODO: Detect if source and destination rasters are the same. If they are
125: // the same, we should skip this block. Iteration will then be faster.
126: iterator = TransfertRectIter.create(RectIterFactory.create(
127: source, destRect), iterator);
128: }
129: int band = 0;
130: if (!iterator.finishedBands())
131: do {
132: categories[band].transform(iterator);
133: band++;
134: } while (!iterator.nextBandDone());
135: assert (band == categories.length) : band;
136: }
137:
138: /////////////////////////////////////////////////////////////////////////////////
139: //////// ////////
140: //////// REGISTRATION OF "SampleTranscode" IMAGE OPERATION ////////
141: //////// ////////
142: /////////////////////////////////////////////////////////////////////////////////
143: /**
144: * The operation descriptor for the "SampleTranscode" operation. This operation can apply the
145: * {@link GridSampleDimension#getSampleToGeophysics sampleToGeophysics} transform on all pixels
146: * in all bands of an image. The transformations are supplied as a list of
147: * {@link GridSampleDimension}s, one for each band. The supplied {@code GridSampleDimension}
148: * objects describe the categories in the <strong>source</strong> image. The target image
149: * will matches sample dimension
150: *
151: * <code>{@link GridSampleDimension#geophysics geophysics}(!isGeophysics)</code>,
152: *
153: * where {@code isGeophysics} is the previous state of the sample dimension.
154: */
155: private static final class Descriptor extends
156: OperationDescriptorImpl {
157: /**
158: * Construct the descriptor.
159: */
160: public Descriptor() {
161: super (
162: new String[][] {
163: { "GlobalName", OPERATION_NAME },
164: { "LocalName", OPERATION_NAME },
165: { "Vendor", "Geotools 2" },
166: { "Description",
167: "Transformation from sample to geophysics values" },
168: { "DocURL", "http://www.geotools.org/" },
169: { "Version", "1.0" } },
170: new String[] { RenderedRegistryMode.MODE_NAME }, 1,
171: new String[] { "sampleDimensions" }, // Argument names
172: new Class[] { GridSampleDimension[].class }, // Argument classes
173: new Object[] { NO_PARAMETER_DEFAULT }, // Default values for parameters,
174: null // No restriction on valid parameter values.
175: );
176: }
177:
178: /**
179: * Returns {@code true} if the parameters are valids. This implementation check
180: * that the number of bands in the source image is equals to the number of supplied
181: * sample dimensions, and that all sample dimensions has categories.
182: *
183: * @param modeName The mode name (usually "Rendered").
184: * @param param The parameter block for the operation to performs.
185: * @param message A buffer for formatting an error message if any.
186: */
187: protected boolean validateParameters(final String modeName,
188: final ParameterBlock param, final StringBuffer message) {
189: if (!super .validateParameters(modeName, param, message)) {
190: return false;
191: }
192: final RenderedImage source = (RenderedImage) param
193: .getSource(0);
194: final GridSampleDimension[] bands = (GridSampleDimension[]) param
195: .getObjectParameter(0);
196: final int numBands = source.getSampleModel().getNumBands();
197: if (numBands != bands.length) {
198: message.append(Errors.format(
199: ErrorKeys.NUMBER_OF_BANDS_MISMATCH_$3,
200: new Integer(numBands),
201: new Integer(bands.length), "SampleDimension"));
202: return false;
203: }
204: for (int i = 0; i < numBands; i++) {
205: if (bands[i].categories == null) {
206: message.append(Errors.format(
207: ErrorKeys.BAD_PARAMETER_$2,
208: "sampleDimensions[" + i + "].categories",
209: null));
210: return false;
211: }
212: }
213: return true;
214: }
215: }
216:
217: /**
218: * The {@link RenderedImageFactory} for the "SampleTranscode" operation.
219: */
220: private static final class CRIF extends CRIFImpl {
221: /**
222: * Creates a {@link RenderedImage} representing the results of an imaging
223: * operation for a given {@link ParameterBlock} and {@link RenderingHints}.
224: */
225: public RenderedImage create(final ParameterBlock param,
226: final RenderingHints hints) {
227: final RenderedImage image = (RenderedImage) param
228: .getSource(0);
229: final GridSampleDimension[] bands = (GridSampleDimension[]) param
230: .getObjectParameter(0);
231: final CategoryList[] categories = new CategoryList[bands.length];
232: for (int i = 0; i < categories.length; i++) {
233: categories[i] = bands[i].categories;
234: }
235: if (image instanceof SampleTranscoder) {
236: final SampleTranscoder other = (SampleTranscoder) image;
237: if (isInverse(categories, other.categories)) {
238: return other.getSourceImage(0);
239: }
240: }
241: return new SampleTranscoder(image, categories, hints);
242: }
243:
244: /**
245: * Checks if all categories in {@code categories1} are
246: * equals to the inverse of {@code categories2}.
247: */
248: private static boolean isInverse(
249: final CategoryList[] categories1,
250: final CategoryList[] categories2) {
251: if (categories1.length != categories2.length) {
252: return false;
253: }
254: for (int i = 0; i < categories1.length; i++) {
255: if (!categories1[i].equals(categories2[i].inverse)) {
256: return false;
257: }
258: }
259: return true;
260: }
261: }
262:
263: /**
264: * Register the "SampleTranscode" image operation to the operation registry of
265: * the specified JAI instance. This method is invoked by the static initializer
266: * of {@link GridSampleDimension}.
267: */
268: public static void register(final JAI jai) {
269: final OperationRegistry registry = jai.getOperationRegistry();
270: try {
271: registry.registerDescriptor(new Descriptor());
272: registry.registerFactory(RenderedRegistryMode.MODE_NAME,
273: OPERATION_NAME, "geotools.org", new CRIF());
274: } catch (IllegalArgumentException exception) {
275: final LogRecord record = Logging.format(Level.SEVERE,
276: LoggingKeys.CANT_REGISTER_JAI_OPERATION_$1,
277: OPERATION_NAME);
278: // Note: GridSampleDimension is the public class that use this transcoder.
279: record.setSourceClassName(GridSampleDimension.class
280: .getName());
281: record.setSourceMethodName("<classinit>");
282: record.setThrown(exception);
283: AbstractGridCoverage.LOGGER.log(record);
284: }
285: }
286: }
|