001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2003-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.image.jai;
018:
019: // J2SE dependencies
020: import java.awt.Rectangle;
021: import java.awt.RenderingHints;
022: import java.awt.image.ColorModel;
023: import java.awt.image.RasterFormatException;
024: import java.awt.image.WritableRaster;
025: import java.util.Vector;
026:
027: // JAI and Vecmath dependencies
028: import javax.media.jai.ImageLayout;
029: import javax.media.jai.JAI;
030: import javax.media.jai.PlanarImage;
031: import javax.media.jai.PointOpImage;
032: import javax.media.jai.iterator.RectIter;
033: import javax.media.jai.iterator.RectIterFactory;
034: import javax.media.jai.iterator.WritableRectIter;
035: import javax.media.jai.operator.BandCombineDescriptor;
036: import javax.vecmath.MismatchedSizeException;
037:
038: // Geotools dependencies
039: import org.geotools.resources.XArray;
040: import org.geotools.resources.image.ImageUtilities;
041:
042: /**
043: * Computes a set of arbitrary linear combinations of the bands of many rendered source images,
044: * using a specified matrix. The matrix size ({@code numRows}×{@code numColumns}) must be
045: * equals to the following:
046: * <p>
047: * <ul>
048: * <li>{@code numRows}: the number of desired destination bands.</li>
049: * <li>{@code numColumns}: the total number of source bands (i.e. the
050: * sum of the number of source bands in all source images) plus one.</li>
051: * </ul>
052: * <p>
053: * The number of source bands used to determine the matrix dimensions is given by the
054: * following code regardless of the type of {@link ColorModel} the sources have:
055: *
056: * <blockquote><pre>
057: * int sourceBands = 0;
058: * for (int i=0; i<sources.length; i++) {
059: * sourceBands += sources[i].getSampleModel().getNumBands();
060: * }
061: * </blockquote></pre>
062: *
063: * The extra column in the matrix contains constant values each of which is added to the
064: * respective band of the destination. The transformation is therefore defined by the pseudocode:
065: *
066: * <blockquote><pre>
067: * // s = source pixel (not all from the same source image)
068: * // d = destination pixel
069: * for (int i=0; i<destBands; i++) {
070: * d[i] = matrix[i][sourceBands];
071: * for (int j=0; j<sourceBands; j++) {
072: * d[i] += matrix[i][j]*s[j];
073: * }
074: * }
075: * </blockquote></pre>
076: *
077: * In the special case where there is only one source, this method is equivalent to JAI's
078: * "{@link BandCombineDescriptor BandCombine}" operation.
079: *
080: * @since 2.1
081: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/coverage/src/main/java/org/geotools/image/jai/Combine.java $
082: * @version $Id: Combine.java 23211 2006-12-05 00:38:41Z desruisseaux $
083: * @author Martin Desruisseaux
084: * @author Remi Eve
085: */
086: public class Combine extends PointOpImage {
087: /**
088: * The linear combinaison coefficients as a matrix. This matrix may not be the same
089: * than the one specified to the constructor, in that the zero coefficients may have
090: * been purged (and {@link #sources} and {@link #bands} arrays adjusted accordingly})
091: * for performance reason.
092: */
093: final double[][] matrix;
094:
095: /**
096: * The source to use for each elements in {@link #matrix}.
097: * This matrix size must be the same than {@code matrix}.
098: */
099: final int[][] sources;
100:
101: /**
102: * The band to use for each elements in {@link #matrix}.
103: * This matrix size must be the same than {@code matrix}.
104: */
105: final int[][] bands;
106:
107: /**
108: * The number of source samples. This is the sum of the number of bands in
109: * all source images. Each {@link #matrix} row must have this length plus 1.
110: */
111: final int numSamples;
112:
113: /**
114: * The transform to apply on sample values before the linear combinaison,
115: * or {@code null} if none.
116: */
117: protected final CombineTransform transform;
118:
119: /**
120: * Construct an image with the specified matrix.
121: *
122: * @param images The rendered sources.
123: * @param matrix The linear combinaison coefficients as a matrix.
124: * @param transform The transform to apply on sample values before the linear combinaison,
125: * or {@code null} if none.
126: * @param hints The rendering hints.
127: *
128: * @throws MismatchedSizeException if some rows in the {@code matrix} argument doesn't
129: * have the expected length.
130: */
131: public Combine(final Vector images, double[][] matrix,
132: final CombineTransform transform, final RenderingHints hints)
133: throws MismatchedSizeException {
134: super (images, ImageUtilities.createIntersection(
135: (ImageLayout) hints.get(JAI.KEY_IMAGE_LAYOUT), images),
136: hints, false);
137: final int numRows = matrix.length;
138: this .matrix = matrix = (double[][]) matrix.clone();
139: this .sources = new int[numRows][];
140: this .bands = new int[numRows][];
141: this .transform = transform;
142: int numSamples = 0;
143: for (int i = getNumSources(); --i >= 0;) {
144: numSamples += getSourceImage(i).getNumBands();
145: }
146: this .numSamples = numSamples;
147: final boolean isSeparable = (transform == null)
148: || transform.isSeparable();
149: for (int j = 0; j < numRows; j++) {
150: final double[] row = matrix[j];
151: final int numColumns = row.length;
152: if (numColumns != numSamples + 1) {
153: throw new MismatchedSizeException();
154: }
155: int source = -1;
156: int band = -1;
157: int numBands = 0;
158: int count = 0; // Number of non-zero coefficients.
159: final double[] copy = new double[numColumns];
160: final int[] sources = new int[numColumns - 1];
161: final int[] bands = new int[numColumns - 1];
162: final int numSources = sources.length;
163: for (int i = 0; i < numSources; i++) {
164: if (++band >= numBands) {
165: band = 0;
166: numBands = getSourceImage(++source).getNumBands();
167: }
168: if (row[i] != 0 || !isSeparable) {
169: copy[count] = row[i];
170: sources[count] = source;
171: bands[count] = band;
172: count++;
173: }
174: }
175: copy[count] = row[row.length - 1];
176: this .matrix[j] = XArray.resize(copy, count + 1);
177: this .sources[j] = XArray.resize(sources, count);
178: this .bands[j] = XArray.resize(bands, count);
179: }
180: /*
181: * Set the sample model according the number of destination bands.
182: */
183: if (getNumBands() != numRows) {
184: throw new UnsupportedOperationException(
185: "Automatic derivation of SampleModel not yet implemented.");
186: }
187: permitInPlaceOperation();
188: }
189:
190: /**
191: * Compute one tile.
192: *
193: * @param images An array of PlanarImage sources.
194: * @param dest A WritableRaster to be filled in.
195: * @param destRect The Rectangle within the destination to be written.
196: */
197: public void computeRect(final PlanarImage[] images,
198: final WritableRaster dest, final Rectangle destRect) {
199: /*
200: * Create the iterators. The 'iterRef' array will contains a copy of 'iters' where
201: * the reference to an iterator is duplicated for each band in the source image:
202: *
203: * iterRef[i] = iters[sources[band][i]]
204: */
205: final RectIter[] iters = new RectIter[images.length];
206: final RectIter[] iterRef = new RectIter[numSamples];
207: double[] samples = null;
208: final int length = iters.length;
209: for (int i = 0; i < length; i++) {
210: iters[i] = RectIterFactory.create(images[i], mapDestRect(
211: destRect, i));
212: }
213: final WritableRectIter iTarget = RectIterFactory
214: .createWritable(dest, destRect);
215: /*
216: * Iterates over all destination bands. In many case, the destination image
217: * will have only one band. Consequently, it is more efficient to iterates
218: * through bands in the outer loop rather than the inner loop.
219: */
220: int band = 0;
221: iTarget.startBands();
222: boolean finished = iTarget.finishedBands();
223: while (!finished) {
224: final double[] row = this .matrix[band];
225: final int[] bands = this .bands[band];
226: final int[] sources = this .sources[band];
227: final int numSamples = sources.length;
228: if (numSamples > this .numSamples
229: || numSamples > bands.length
230: || numSamples >= row.length) {
231: // Should not happen if the constructor checks was right. We performs this
232: // check unconditionnaly since it is cheap, and in the hope to help the JIT
233: // to remove some array bound checks later in inner loops.
234: throw new AssertionError(numSamples);
235: }
236: for (int i = 0; i < numSamples; i++) {
237: iterRef[i] = iters[sources[i]];
238: }
239: if (samples == null || samples.length != numSamples) {
240: samples = new double[numSamples];
241: }
242: /*
243: * Iterates over all lines, then over all pixels. The 'finished' flag is reset
244: * to 'nextXXXDone()' at the end of each loop.
245: */
246: iTarget.startLines();
247: finished = iTarget.finishedLines();
248: for (int i = 0; i < iters.length; i++) {
249: iters[i].startLines();
250: if (iters[i].finishedLines() != finished) {
251: // Should not happen, since constructor computed
252: // the intersection of all source images.
253: throw new RasterFormatException("Missing lines");
254: }
255: }
256: while (!finished) {
257: iTarget.startPixels();
258: finished = iTarget.finishedPixels();
259: for (int i = 0; i < iters.length; i++) {
260: iters[i].startPixels();
261: if (iters[i].finishedPixels() != finished) {
262: // Should not happen, since constructor computed
263: // the intersection of all source images.
264: throw new RasterFormatException(
265: "Missing pixels");
266: }
267: }
268: while (!finished) {
269: /*
270: * Computes the sample values.
271: */
272: for (int i = 0; i < numSamples; i++) {
273: samples[i] = iterRef[i]
274: .getSampleDouble(bands[i]);
275: }
276: if (transform != null) {
277: transform.transformSamples(samples);
278: }
279: double value = row[numSamples];
280: for (int i = 0; i < numSamples; i++) {
281: value += row[i] * samples[i];
282: }
283: iTarget.setSample(value);
284: finished = iTarget.nextPixelDone();
285: for (int i = 0; i < iters.length; i++) {
286: if (iters[i].nextPixelDone() != finished) {
287: // Should not happen, since constructor computed
288: // the intersection of all source images.
289: throw new RasterFormatException(
290: "Missing pixels");
291: }
292: }
293: }
294: finished = iTarget.nextLineDone();
295: for (int i = 0; i < iters.length; i++) {
296: if (iters[i].nextLineDone() != finished) {
297: // Should not happen, since constructor computed
298: // the intersection of all source images.
299: throw new RasterFormatException("Missing lines");
300: }
301: }
302: }
303: band++;
304: finished = iTarget.nextBandDone();
305: }
306: }
307:
308: /**
309: * Optimized {@code Combine} operation for dyadic (two sources) image. This operation
310: * performs a linear combinaison of two images ({@code src0} and {@code src1}).
311: * The parameters {@code scale0} and {@code scale1} indicate the scale of source
312: * images {@code src0} and {@code src1}. If we consider pixel at coordinate
313: * (<var>x</var>,<var>y</var>), its value is determinate by the pseudo-code:
314: *
315: * <blockquote><pre>
316: * value = src0[x][y]*scale0 + src1[x][y]*scale1 + offset
317: * </pre></blockquote>
318: *
319: * @version $Id: Combine.java 23211 2006-12-05 00:38:41Z desruisseaux $
320: * @author Remi Eve
321: * @author Martin Desruisseaux
322: */
323: final static class Dyadic extends Combine {
324: /**
325: * The scale of image {@code src0} for each bands.
326: */
327: private final double[] scales0;
328:
329: /**
330: * The scale of image {@code src1} for each bands.
331: */
332: private final double[] scales1;
333:
334: /**
335: * The offset for each bands.
336: */
337: private final double[] offsets;
338:
339: /**
340: * Construct a new instance of {@code Combine.Dyadic}.
341: *
342: * @param images The rendered sources. This vector must contains exactly 2 sources.
343: * @param matrix The linear combinaison coefficients as a matrix.
344: * @param hints The rendering hints.
345: *
346: * @throws MismatchedSizeException if some rows in the {@code matrix} argument doesn't
347: * have the expected length.
348: */
349: public Dyadic(final Vector images, final double[][] matrix,
350: final RenderingHints hints)
351: throws MismatchedSizeException {
352: super (images, matrix, null, hints);
353: if (getNumSources() != 2) {
354: throw new IllegalArgumentException();
355: }
356: final int numBands = getNumBands();
357: scales0 = new double[numBands];
358: scales1 = new double[numBands];
359: offsets = new double[numBands];
360: for (int j = 0; j < numBands; j++) {
361: final double[] row = this .matrix[j];
362: final int[] sources = this .sources[j];
363: final int[] bands = this .bands[j];
364: for (int i = 0; i < sources.length; i++) {
365: final double coeff = row[i];
366: final int band = bands[i];
367: final int source = sources[i];
368: if (band != j) {
369: throw new AssertionError(band); // Should not happen.
370: }
371: switch (source) {
372: case 0:
373: scales0[band] = coeff;
374: break;
375: case 1:
376: scales1[band] = coeff;
377: break;
378: default:
379: throw new AssertionError(source); // Should not happen.
380: }
381: }
382: offsets[j] = row[sources.length];
383: }
384: }
385:
386: /**
387: * Computes one tile.
388: *
389: * @param images An array of PlanarImage sources.
390: * @param dest A WritableRaster to be filled in.
391: * @param destRect The Rectangle within the destination to be written.
392: */
393: public void computeRect(final PlanarImage[] images,
394: final WritableRaster dest, final Rectangle destRect) {
395: final RectIter iSrc0 = RectIterFactory.create(images[0],
396: mapDestRect(destRect, 0));
397: final RectIter iSrc1 = RectIterFactory.create(images[1],
398: mapDestRect(destRect, 1));
399: final WritableRectIter iTarget = RectIterFactory
400: .createWritable(dest, destRect);
401: int band = 0;
402: iSrc0.startBands();
403: iSrc1.startBands();
404: iTarget.startBands();
405: if (!iTarget.finishedBands() && !iSrc0.finishedBands()
406: && !iSrc1.finishedBands()) {
407: final double scale0 = scales0[Math.min(band,
408: scales0.length - 1)];
409: final double scale1 = scales1[Math.min(band,
410: scales1.length - 1)];
411: final double offset = offsets[Math.min(band,
412: offsets.length - 1)];
413: do {
414: iSrc0.startLines();
415: iSrc1.startLines();
416: iTarget.startLines();
417: if (!iTarget.finishedLines()
418: && !iSrc0.finishedLines()
419: && !iSrc1.finishedLines()) {
420: do {
421: iSrc0.startPixels();
422: iSrc1.startPixels();
423: iTarget.startPixels();
424: if (!iTarget.finishedPixels()
425: && !iSrc0.finishedPixels()
426: && !iSrc1.finishedPixels()) {
427: do {
428: iTarget.setSample(iSrc0
429: .getSampleDouble()
430: * scale0
431: + iSrc1.getSampleDouble()
432: * scale1 + offset);
433: } while (!iSrc0.nextPixelDone()
434: && !iSrc1.nextPixelDone()
435: && !iTarget.nextPixelDone());
436: }
437: } while (!iSrc0.nextLineDone()
438: && !iSrc1.nextLineDone()
439: && !iTarget.nextLineDone());
440: }
441: band++;
442: } while (!iSrc0.nextBandDone() && !iSrc1.nextBandDone()
443: && !iTarget.nextBandDone());
444: }
445: }
446: }
447: }
|