001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2003-2006, Geotools Project Managment 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: package org.geotools.image.io;
018:
019: import java.awt.Dimension;
020: import java.awt.Transparency;
021: import java.awt.color.ColorSpace;
022: import java.awt.image.BandedSampleModel;
023: import java.awt.image.ColorModel;
024: import java.awt.image.ComponentSampleModel;
025: import java.awt.image.DataBuffer;
026: import java.awt.image.MultiPixelPackedSampleModel;
027: import java.awt.image.PixelInterleavedSampleModel;
028: import java.awt.image.SampleModel;
029: import java.awt.image.SinglePixelPackedSampleModel;
030:
031: import javax.imageio.ImageReadParam;
032: import javax.imageio.ImageTypeSpecifier;
033: import javax.media.jai.ComponentSampleModelJAI;
034: import javax.media.jai.PlanarImage;
035:
036: import org.geotools.resources.image.ComponentColorModelJAI;
037:
038: /**
039: * A class describing how a raw binary stream is to be decoded. In the context of
040: * {@link RawBinaryImageReader}, the stream may not contains enough information
041: * for an optimal decoding. For example the stream may not contains image's
042: * width and height. The {@code RawBinaryImageReadParam} gives a chance
043: * to specify those missing informations.
044: *
045: * @since 2.0
046: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/unsupported/coverageio/src/main/java/org/geotools/image/io/RawBinaryImageReadParam.java $
047: * @version $Id: RawBinaryImageReadParam.java 26708 2007-08-27 19:42:59Z desruisseaux $
048: * @author Martin Desruisseaux
049: *
050: * @todo We should consider to use Sun's RAW decoder provided with "Java Advanced Imaging Image I/O
051: * Tools" instead (download at http://java.sun.com/products/java-media/jai/). This replacement
052: * is not yet done because we need to figure out a way to handle pad values first.
053: */
054: public class RawBinaryImageReadParam extends ImageReadParam {
055: /**
056: * The expected image model, or {@code null} if unknow.
057: */
058: private SampleModel model;
059:
060: /**
061: * The expected image size, or {@code null} if unknow.
062: */
063: private Dimension size;
064:
065: /**
066: * The expected data type, or {@link DataBuffer#TYPE_UNDEFINED} if unknow.
067: */
068: private int dataType = DataBuffer.TYPE_UNDEFINED;
069:
070: /**
071: * The target data type, or {@link DataBuffer#TYPE_UNDEFINED} if not
072: * defined. In the later case, the target data type will be the same
073: * than the raw one.
074: */
075: private int targetDataType = DataBuffer.TYPE_UNDEFINED;
076:
077: /**
078: * The pad value, or {@link Double#NaN} if there is none.
079: */
080: private double padValue = Double.NaN;
081:
082: /**
083: * Constructs a new {@code RawBinaryImageReadParam} with default parameters.
084: */
085: public RawBinaryImageReadParam() {
086: }
087:
088: /**
089: * Specifies the image size in the input stream. Setting the size to {@code null}
090: * reset the default size, which is reader dependent. Most readers will thrown an
091: * exception at reading time if the image size is unspecified.
092: *
093: * @param size The expected image size, or {@code null} if unknow.
094: */
095: public void setStreamImageSize(final Dimension size) {
096: this .size = (size != null) ? new Dimension(size.width,
097: size.height) : null;
098: }
099:
100: /**
101: * Returns the image size in the input stream, or {@code null} if unknow.
102: * Image size is specified by the last call to {@link #setStreamImageSize} or
103: * {@link #setStreamSampleModel}.
104: */
105: public Dimension getStreamImageSize() {
106: return (size != null) ? (Dimension) size.clone() : null;
107: }
108:
109: /**
110: * Checks the validity of the specified data type.
111: *
112: * @param dataType The data type to check.
113: * @throws IllegalArgumentException if {@code dataType} is not one of
114: * the valid enums of {@link DataBuffer}.
115: */
116: private static void checkDataType(final int dataType)
117: throws IllegalArgumentException {
118: if ((dataType < DataBuffer.TYPE_BYTE || dataType > DataBuffer.TYPE_DOUBLE)
119: && dataType != DataBuffer.TYPE_UNDEFINED) {
120: throw new IllegalArgumentException(String.valueOf(dataType));
121: }
122: }
123:
124: /**
125: * Specifies the data type in input stream. Setting data type to
126: * {@link DataBuffer#TYPE_UNDEFINED} reset the default value, which
127: * is reader dependent.
128: *
129: * @param dataType The data type, or {@link DataBuffer#TYPE_UNDEFINED} if unknow.
130: * Know data type should be a constant from {@link DataBuffer}. Common
131: * types are {@link DataBuffer#TYPE_INT}, {@link DataBuffer#TYPE_FLOAT}
132: * and {@link DataBuffer#TYPE_DOUBLE}.
133: */
134: public void setStreamDataType(final int dataType) {
135: checkDataType(dataType);
136: this .dataType = dataType;
137: }
138:
139: /**
140: * Returns the data type in input stream, or {@link DataBuffer#TYPE_UNDEFINED}
141: * if unknow. Data type is specified by the last call to {@link #setStreamDataType}
142: * or {@link #setStreamSampleModel}.
143: */
144: public int getStreamDataType() {
145: return dataType;
146: }
147:
148: /**
149: * Sets the desired image type for the destination image, using one of
150: * {@link DataBuffer} enumeration constant. This setting will override
151: * any previous setting made with {@link #setDestinationType(ImageTypeSpecifier)}
152: * or this {@code setDestinationType(int)} method.
153: *
154: * @param destType The data type. This should be a constant from {@link DataBuffer}.
155: * Common types are {@link DataBuffer#TYPE_INT}, {@link DataBuffer#TYPE_FLOAT}
156: * and {@link DataBuffer#TYPE_DOUBLE}.
157: */
158: public void setDestinationType(final int destType) {
159: checkDataType(destType);
160: targetDataType = destType;
161: setDestinationType(getDestinationType(model != null ? model
162: .getNumBands() : 1));
163: }
164:
165: /**
166: * Creates a destination type with the specified number of bands.
167: * If no such destination type is available, returns {@code null}.
168: */
169: final ImageTypeSpecifier getDestinationType(final int numBands) {
170: if (targetDataType == DataBuffer.TYPE_UNDEFINED) {
171: return null;
172: }
173: final SampleModel sampleModel;
174: final ColorModel colorModel;
175: final ColorSpace colorSpace = getColorSpace(numBands);
176: if (model != null) {
177: /*
178: * Case 1: we know the sample model for data in the
179: * underlying stream. We will use the same
180: * model for the memory image, just changing
181: * the data type.
182: */
183: if (numBands != model.getNumBands()) {
184: throw new IllegalArgumentException(
185: "Number of bands mismatch");
186: }
187: sampleModel = getStreamSampleModel(model, model, size,
188: targetDataType);
189: } else {
190: /*
191: * Case 2: We have to create a sample model from scratch. We
192: * will use a banded sample model with some arbitrary
193: * color space (which may be changed after the image
194: * reading is completed).
195: */
196: final int width, height;
197: if (size != null) {
198: width = size.width;
199: height = size.height;
200: } else {
201: width = height = 1;
202: }
203: final int[] bankIndices = new int[numBands];
204: final int[] bandOffsets = new int[numBands];
205: for (int i = numBands; --i >= 0;)
206: bankIndices[i] = i;
207: if (ContinuousPalette.USE_JAI_MODEL) {
208: sampleModel = new ComponentSampleModelJAI(
209: targetDataType, width, height, 1, width,
210: bankIndices, bandOffsets);
211: } else {
212: return ImageTypeSpecifier.createBanded(colorSpace,
213: bankIndices, bandOffsets, targetDataType,
214: false, false);
215: }
216: }
217: /*
218: * Constructs a color model likely to matches the
219: * sample model, and then finish the type specifier.
220: */
221: if (sampleModel instanceof ComponentSampleModel) {
222: // This is the most common case.
223: colorModel = new ComponentColorModelJAI(
224: getColorSpace(numBands), false, false,
225: Transparency.OPAQUE, sampleModel.getDataType());
226: } else {
227: // Fallback to JAI helper method if we have a less common case.
228: colorModel = PlanarImage.createColorModel(sampleModel);
229: }
230: return new ImageTypeSpecifier(colorModel, sampleModel);
231: }
232:
233: /**
234: * Returns a default color space for the destination sample model.
235: * If no destination image has been specified, then a gray scale
236: * color space will be constructed for values ranging from 0 to 1.
237: */
238: private ColorSpace getColorSpace(int numBands) {
239: if (destination != null) {
240: return destination.getColorModel().getColorSpace();
241: }
242: /*
243: * Overrides the number of source bands if this
244: * parameter block contains enough informations.
245: */
246: if (sourceBands != null) {
247: numBands = sourceBands.length;
248: } else if (model != null) {
249: numBands = model.getNumBands();
250: }
251: /*
252: * Checks the number of destination bands. If 'destinationBands' is
253: * null, then all bands are going to be used. If it is non-null,
254: * then the destination image may have more bands than what we are
255: * going to use. This problem still an open question... As a patch,
256: * current implementation search for the greatest band number.
257: */
258: if (destinationBands != null) {
259: for (int i = 0; i < destinationBands.length; i++) {
260: if (destinationBands[i] >= numBands) {
261: numBands = destinationBands[i] + 1;
262: }
263: }
264: }
265: if (numBands == 1) {
266: return ColorSpace.getInstance(ColorSpace.CS_GRAY);
267: }
268: return new ScaledColorSpace(numBands, 0, 0, 1);
269: }
270:
271: /**
272: * Set the pad value.
273: *
274: * @param padValue The pad value, or {@link Double#NaN} if there is none.
275: */
276: public void setPadValue(final double padValue) {
277: this .padValue = padValue;
278: }
279:
280: /**
281: * Returns the pad value, or {@link Double#NaN} if there is none
282: */
283: public double getPadValue() {
284: return padValue;
285: }
286:
287: /**
288: * Set a sample model indicating the data layout in the input stream.
289: * Indications comprise image size and data type, i.e. calling this
290: * method with a non-null value is equivalent to calling also the
291: * following methods:
292: *
293: * <blockquote><pre>
294: * setStreamImageSize(model.getWidth(), model.getHeight());
295: * setStreamDataType(model.getDataType());
296: * </pre></blockquote>
297: *
298: * Setting the sample model to {@code null} reset
299: * the default model, which is reader dependent.
300: */
301: public void setStreamSampleModel(final SampleModel model) {
302: this .model = model;
303: if (model != null) {
304: size = new Dimension(model.getWidth(), model.getHeight());
305: dataType = model.getDataType();
306: }
307: }
308:
309: /**
310: * Returns a sample model indicating the data layout in the input stream.
311: * The {@link SampleModel}'s width and height should matches the image
312: * size in the input stream.
313: *
314: * @return A sample model indicating the data layout in the input stream,
315: * or {@code null} if unknow.
316: */
317: public SampleModel getStreamSampleModel() {
318: return model = getStreamSampleModel(null);
319: }
320:
321: /**
322: * Returns a sample model indicating the data layout in the input stream.
323: * The {@link SampleModel}'s width and height should matches the image
324: * size in the input stream.
325: *
326: * @param defaultSampleModel A default sample model, or {@code null}
327: * if there is no default. If this {@code RawBinaryImageReadParam}
328: * contains unspecified sample model, image size or data type, values
329: * from {@code defaultSampleModel} will be used.
330: * @return A sample model indicating the data layout in the input stream,
331: * or {@code null} if unknow.
332: */
333: final SampleModel getStreamSampleModel(
334: final SampleModel defaultSampleModel) {
335: return getStreamSampleModel(defaultSampleModel, model, size,
336: dataType);
337: }
338:
339: /**
340: * Returns a sample model indicating the data layout in the input stream.
341: * The {@link SampleModel}'s width and height should matches the image
342: * size in the input stream.
343: *
344: * @param defaultSampleModel A default sample model, or {@code null}
345: * if there is no default. If this {@code RawBinaryImageReadParam}
346: * contains unspecified sample model, image size or data type, values
347: * from {@code defaultSampleModel} will be used.
348: * @param model The sample model in the underlying stream, or {@code null}.
349: * @param size The image size in the underlying stream, or {@code null}.
350: * @param dataType the data type.
351: * @return A sample model indicating the data layout in the input stream,
352: * or {@code null} if unknow.
353: */
354: private static SampleModel getStreamSampleModel(
355: final SampleModel defaultSampleModel, SampleModel model,
356: Dimension size, int dataType) {
357: if (defaultSampleModel != null) {
358: if (model == null) {
359: model = defaultSampleModel;
360: }
361: if (size == null) {
362: size = new Dimension(defaultSampleModel.getWidth(),
363: defaultSampleModel.getHeight());
364: }
365: if (dataType == DataBuffer.TYPE_UNDEFINED) {
366: dataType = defaultSampleModel.getDataType();
367: }
368: }
369: if (model == null || size == null
370: || dataType == DataBuffer.TYPE_UNDEFINED) {
371: return null;
372: }
373: final int width = size.width;
374: final int height = size.height;
375: if (dataType != model.getDataType()) {
376: if (model instanceof ComponentSampleModel) {
377: final ComponentSampleModel cast = (ComponentSampleModel) model;
378: final int pixelStride = cast.getPixelStride();
379: final int scanlineStride = cast.getScanlineStride();
380: final int[] bankIndices = cast.getBankIndices();
381: final int[] bandOffsets = cast.getBandOffsets();
382: if (model instanceof BandedSampleModel) {
383: model = new BandedSampleModel(dataType, width,
384: height, scanlineStride, bankIndices,
385: bandOffsets);
386: } else if (model instanceof PixelInterleavedSampleModel) {
387: model = new PixelInterleavedSampleModel(dataType,
388: width, height, pixelStride, scanlineStride,
389: bandOffsets);
390: } else if (model instanceof ComponentSampleModelJAI) {
391: model = new ComponentSampleModelJAI(dataType,
392: width, height, pixelStride, scanlineStride,
393: bankIndices, bandOffsets);
394: } else {
395: model = new ComponentSampleModel(dataType, width,
396: height, pixelStride, scanlineStride,
397: bankIndices, bandOffsets);
398: }
399: } else if (model instanceof MultiPixelPackedSampleModel) {
400: final MultiPixelPackedSampleModel cast = (MultiPixelPackedSampleModel) model;
401: final int numberOfBits = DataBuffer
402: .getDataTypeSize(dataType);
403: final int scanlineStride = cast.getScanlineStride();
404: final int dataBitOffset = cast.getDataBitOffset();
405: model = new MultiPixelPackedSampleModel(dataType,
406: width, height, numberOfBits, scanlineStride,
407: dataBitOffset);
408: } else if (model instanceof SinglePixelPackedSampleModel) {
409: final SinglePixelPackedSampleModel cast = (SinglePixelPackedSampleModel) model;
410: final int scanlineStride = cast.getScanlineStride();
411: final int[] bitMasks = cast.getBitMasks();
412: model = new SinglePixelPackedSampleModel(dataType,
413: width, height, scanlineStride, bitMasks);
414: } else {
415: throw new IllegalStateException(model.getClass()
416: .getName());
417: }
418: }
419: if (model.getWidth() != width || model.getHeight() != height) {
420: model = model.createCompatibleSampleModel(width, height);
421: }
422: return model;
423: }
424: }
|