001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2007, Geotools Project Managment Committee (PMC)
005: * (C) 2007, Geomatys
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.util.Set;
020: import java.util.Arrays;
021: import java.util.Iterator;
022: import java.util.Collections;
023: import java.util.logging.Level;
024: import java.util.logging.Logger;
025: import java.util.logging.LogRecord;
026:
027: import java.awt.Rectangle;
028: import java.awt.image.ColorModel; // For javadoc
029: import java.awt.image.DataBuffer;
030: import java.awt.image.SampleModel; // For javadoc
031: import java.awt.image.BufferedImage;
032: import java.awt.image.IndexColorModel; // For javadoc
033:
034: import java.io.IOException;
035: import javax.imageio.ImageReader;
036: import javax.imageio.ImageReadParam;
037: import javax.imageio.ImageTypeSpecifier;
038: import javax.imageio.spi.ImageReaderSpi;
039: import javax.imageio.metadata.IIOMetadata;
040: import javax.imageio.event.IIOReadWarningListener; // For javadoc
041:
042: import org.geotools.util.NumberRange;
043: import org.geotools.util.logging.Logging;
044: import org.geotools.resources.XArray;
045: import org.geotools.resources.i18n.Locales;
046: import org.geotools.resources.i18n.Errors;
047: import org.geotools.resources.i18n.ErrorKeys;
048: import org.geotools.resources.IndexedResourceBundle;
049: import org.geotools.image.io.metadata.GeographicMetadata;
050: import org.geotools.image.io.metadata.Band;
051:
052: /**
053: * Base class for readers of geographic images. The default implementation assumes that only one
054: * {@linkplain ImageTypeSpecifier image type} is supported (as opposed to the arbitrary number
055: * allowed by the standard {@link ImageReader}). It also provides a default image type built
056: * automatically from a color palette and a range of valid values.
057: * <p>
058: * More specifically, this class provides the following conveniences to implementors:
059: *
060: * <ul>
061: * <li><p>Provides default {@link #getNumImages} and {@link #getNumBands} implementations,
062: * which return 1. This default behavior matches simple image formats like flat binary
063: * files or ASCII files. Those methods need to be overrided for more complex image
064: * formats.</p></li>
065: *
066: * <li><p>Provides {@link #checkImageIndex} and {@link #checkBandIndex} convenience methods.
067: * Those methods are invoked by most implementation of public methods. They perform their
068: * checks based on the informations provided by the above-cited {@link #getNumImages} and
069: * {@link #getNumBands} methods.</p></li>
070: *
071: * <li><p>Provides default implementations of {@link #getImageTypes} and {@link #getRawImageType},
072: * which assume that only one {@linkplain ImageTypeSpecifier image type} is supported. The
073: * default image type is created from the informations provided by {@link #getRawDataType}
074: * and {@link #getImageMetadata}.</p></li>
075: *
076: * <li><p>Provides {@link #getStreamMetadata} and {@link #getImageMetadata} default
077: * implementations, which return {@code null} as authorized by the specification.
078: * Note that subclasses should consider returning {@link GeographicMetadata} instances.</p></li>
079: * </ul>
080: *
081: * Images may be flat binary or ASCII files with no meta-data and no color information.
082: * Their pixel values may be floating point values instead of integers. The default
083: * implementation assumes floating point values and uses a grayscale color space scaled
084: * to fit the range of values. Displaying such an image may be very slow. Consequently,
085: * users who want to display image are encouraged to change data type and color space with
086: * <a href="http://java.sun.com/products/java-media/jai/">Java Advanced Imaging</a>
087: * operators after reading.
088: *
089: * @since 2.4
090: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/unsupported/coverageio/src/main/java/org/geotools/image/io/GeographicImageReader.java $
091: * @version $Id: GeographicImageReader.java 27862 2007-11-12 19:51:19Z desruisseaux $
092: * @author Martin Desruisseaux
093: */
094: public abstract class GeographicImageReader extends ImageReader {
095: /**
096: * The logger to use for events related to this image reader.
097: */
098: static final Logger LOGGER = Logging
099: .getLogger("org.geotools.image.io");
100:
101: /**
102: * Metadata for each images, or {@code null} if not yet created.
103: */
104: private transient GeographicMetadata[] metadata;
105:
106: /**
107: * Constructs a new image reader.
108: *
109: * @param provider The {@link ImageReaderSpi} that is invoking this constructor,
110: * or {@code null} if none.
111: */
112: protected GeographicImageReader(final ImageReaderSpi provider) {
113: super (provider);
114: availableLocales = Locales.getAvailableLocales();
115: }
116:
117: /**
118: * Sets the input source to use.
119: *
120: * @param input The input object to use for future decoding.
121: * @param seekForwardOnly If {@code true}, images and metadata may only be read
122: * in ascending order from this input source.
123: * @param ignoreMetadata If {@code true}, metadata may be ignored during reads.
124: */
125: //@Override
126: public void setInput(final Object input,
127: final boolean seekForwardOnly, final boolean ignoreMetadata) {
128: metadata = null; // Clears the cache
129: super .setInput(input, seekForwardOnly, ignoreMetadata);
130: }
131:
132: /**
133: * Returns the resources for formatting error messages.
134: */
135: final IndexedResourceBundle getErrorResources() {
136: return Errors.getResources(getLocale());
137: }
138:
139: /**
140: * Ensures that the specified image index is inside the expected range.
141: * The expected range is {@link #minIndex minIndex} inclusive (initially 0)
142: * to <code>{@link #getNumImages getNumImages}(false)</code> exclusive.
143: *
144: * @param imageIndex Index to check for validity.
145: * @throws IndexOutOfBoundsException if the specified index is outside the expected range.
146: * @throws IOException If the operation failed because of an I/O error.
147: */
148: protected void checkImageIndex(final int imageIndex)
149: throws IOException, IndexOutOfBoundsException {
150: final int numImages = getNumImages(false);
151: if (imageIndex < minIndex
152: || (imageIndex >= numImages && numImages >= 0)) {
153: throw new IndexOutOfBoundsException(indexOutOfBounds(
154: imageIndex, minIndex, numImages));
155: }
156: }
157:
158: /**
159: * Ensures that the specified band index is inside the expected range. The expected
160: * range is 0 inclusive to <code>{@link #getNumBands getNumBands}(imageIndex)</code>
161: * exclusive.
162: *
163: * @param imageIndex The image index.
164: * @param bandIndex Index to check for validity.
165: * @throws IndexOutOfBoundsException if the specified index is outside the expected range.
166: * @throws IOException If the operation failed because of an I/O error.
167: */
168: protected void checkBandIndex(final int imageIndex,
169: final int bandIndex) throws IOException,
170: IndexOutOfBoundsException {
171: // Call 'getNumBands' first in order to call 'checkImageIndex'.
172: final int numBands = getNumBands(imageIndex);
173: if (bandIndex >= numBands || bandIndex < 0) {
174: throw new IndexOutOfBoundsException(indexOutOfBounds(
175: bandIndex, 0, numBands));
176: }
177: }
178:
179: /**
180: * Formats an error message for an index out of bounds.
181: *
182: * @param index The index out of bounds.
183: * @param lower The lower legal value, inclusive.
184: * @param upper The upper legal value, exclusive.
185: */
186: private String indexOutOfBounds(final int index, final int lower,
187: final int upper) {
188: return getErrorResources().getString(
189: ErrorKeys.VALUE_OUT_OF_BOUNDS_$3, new Integer(index),
190: new Integer(lower), new Integer(upper - 1));
191: }
192:
193: /**
194: * Returns the number of images available from the current input source.
195: * The default implementation returns 1.
196: *
197: * @param allowSearch If true, the number of images will be returned
198: * even if a search is required.
199: * @return The number of images, or -1 if {@code allowSearch}
200: * is false and a search would be required.
201: *
202: * @throws IllegalStateException if the input source has not been set.
203: * @throws IOException if an error occurs reading the information from the input source.
204: */
205: public int getNumImages(final boolean allowSearch)
206: throws IllegalStateException, IOException {
207: if (input != null) {
208: return 1;
209: }
210: throw new IllegalStateException(getErrorResources().getString(
211: ErrorKeys.NO_IMAGE_INPUT));
212: }
213:
214: /**
215: * Returns the number of bands available for the specified image.
216: * The default implementation returns 1.
217: *
218: * @param imageIndex The image index.
219: * @throws IOException if an error occurs reading the information from the input source.
220: */
221: public int getNumBands(final int imageIndex) throws IOException {
222: checkImageIndex(imageIndex);
223: return 1;
224: }
225:
226: /**
227: * Returns metadata associated with the input source as a whole. Since many raw images
228: * can't store metadata, the default implementation returns {@code null}.
229: *
230: * @throws IOException if an error occurs during reading.
231: */
232: public IIOMetadata getStreamMetadata() throws IOException {
233: return null;
234: }
235:
236: /**
237: * Returns metadata associated with the given image. Since many raw images
238: * can't store metadata, the default implementation returns {@code null}.
239: *
240: * @param imageIndex The image index.
241: * @return The metadata, or {@code null} if none.
242: * @throws IOException if an error occurs during reading.
243: */
244: public IIOMetadata getImageMetadata(int imageIndex)
245: throws IOException {
246: checkImageIndex(imageIndex);
247: return null;
248: }
249:
250: /**
251: * Returns a helper parser for metadata associated with the given image. This implementation
252: * invokes <code>{@linkplain #getImageMetadata getImageMetadata}(imageIndex)</code>, wraps
253: * the result in a {@link GeographicMetadata} object if non-null and caches the result.
254: * <p>
255: * Note that this method forces {@link #ignoreMetadata} to {@code false} for the time of
256: * <code>{@linkplain #getImageMetadata getImageMetadata}(imageIndex)</code> execution,
257: * because some image reader implementations need geographic metadata in order to infer
258: * a valid {@linkplain ColorModel color model}.
259: *
260: * @param imageIndex The image index.
261: * @return The geographic metadata, or {@code null} if none.
262: * @throws IOException if an error occurs during reading.
263: */
264: public GeographicMetadata getGeographicMetadata(final int imageIndex)
265: throws IOException {
266: // Checks if a cached instance is available.
267: if (metadata != null && imageIndex >= 0
268: && imageIndex < metadata.length) {
269: final GeographicMetadata parser = metadata[imageIndex];
270: if (parser != null) {
271: return parser;
272: }
273: }
274: // Checks if metadata are availables. If the user set 'ignoreMetadata' to 'true',
275: // we override his setting since we really need metadata for creating a ColorModel.
276: final IIOMetadata candidate;
277: final boolean oldIgnore = ignoreMetadata;
278: try {
279: ignoreMetadata = false;
280: candidate = getImageMetadata(imageIndex);
281: } finally {
282: ignoreMetadata = oldIgnore;
283: }
284: if (candidate == null) {
285: return null;
286: }
287: // Wraps the IIOMetadata into a GeographicMetadata object,
288: // if it was not already of the appropriate type.
289: final GeographicMetadata parser;
290: if (candidate instanceof GeographicMetadata) {
291: parser = (GeographicMetadata) candidate;
292: } else {
293: parser = new GeographicMetadata(this );
294: parser.mergeTree(candidate);
295: }
296: if (metadata == null) {
297: metadata = new GeographicMetadata[Math.max(imageIndex + 1,
298: 4)];
299: }
300: if (imageIndex >= metadata.length) {
301: metadata = (GeographicMetadata[]) XArray.resize(metadata,
302: Math.max(imageIndex + 1, metadata.length * 2));
303: }
304: metadata[imageIndex] = parser;
305: return parser;
306: }
307:
308: /**
309: * Returns a collection of {@link ImageTypeSpecifier} containing possible image types to which
310: * the given image may be decoded. The default implementation returns a singleton containing
311: * <code>{@link #getRawImageType(int) getRawImageType}(imageIndex)</code>.
312: *
313: * @param imageIndex The index of the image to be retrieved.
314: * @return A set of suggested image types for decoding the current given image.
315: * @throws IOException If an error occurs reading the format information from the input source.
316: */
317: public Iterator getImageTypes(final int imageIndex)
318: throws IOException {
319: return Collections.singleton(getRawImageType(imageIndex))
320: .iterator();
321: }
322:
323: /**
324: * Returns an image type specifier indicating the {@link SampleModel} and {@link ColorModel}
325: * which most closely represents the "raw" internal format of the image. The default
326: * implementation delegates to the following:
327: *
328: * <blockquote><code>{@linkplain #getRawImageType(int,ImageReadParam,SampleConverter[])
329: * getRawImageType}(imageIndex, {@linkplain #getDefaultReadParam()}, null);</code></blockquote>
330: *
331: * If this method needs to be overriden, consider overriding the later instead.
332: *
333: * @param imageIndex The index of the image to be queried.
334: * @return The image type (never {@code null}).
335: * @throws IOException If an error occurs reading the format information from the input source.
336: */
337: //@Override
338: public ImageTypeSpecifier getRawImageType(final int imageIndex)
339: throws IOException {
340: return getRawImageType(imageIndex, getDefaultReadParam(), null);
341: }
342:
343: /**
344: * Returns an image type specifier indicating the {@link SampleModel} and {@link ColorModel}
345: * which most closely represents the "raw" internal format of the image. The default
346: * implementation applies the following rules:
347: *
348: * <ol>
349: * <li><p>The {@linkplain Band#getValidRange range of expected values} and the
350: * {@linkplain Band#getNoDataValues no-data values} are extracted from the
351: * {@linkplain #getGeographicMetadata geographic metadata}, if any.</p></li>
352: *
353: * <li><p>If the given {@code parameters} argument is an instance of {@link GeographicImageReadParam},
354: * then the user-supplied {@linkplain GeographicImageReadParam#getPaletteName palette name}
355: * is fetched. Otherwise or if no palette name was explicitly set, then this method default
356: * to {@value org.geotools.image.io.GeographicImageReadParam#DEFAULT_PALETTE_NAME}. The
357: * palette name will be used in order to {@linkplain PaletteFactory#getColors read a
358: * predefined set of colors} (as RGB values) to be given to the
359: * {@linkplain IndexColorModel index color model}.</p></li>
360: *
361: * <li><p>If the {@linkplain #getRawDataType raw data type} is {@link DataBuffer#TYPE_FLOAT
362: * TYPE_FLOAT} or {@link DataBuffer#TYPE_DOUBLE TYPE_DOUBLE}, then this method builds
363: * a {@linkplain PaletteFactory#getContinuousPalette continuous palette} suitable for
364: * the range fetched at step 1. The data are assumed <cite>geophysics</cite> values
365: * rather than some packed values. Consequently, the {@linkplain SampleConverter sample
366: * converters} will replace no-data values by {@linkplain Float#NaN NaN} with no other
367: * changes.</p></li>
368: *
369: * <li><p>Otherwise, if the {@linkplain #getRawDataType raw data type} is a unsigned integer type
370: * like {@link DataBuffer#TYPE_BYTE TYPE_BYTE} or {@link DataBuffer#TYPE_USHORT TYPE_USHORT},
371: * then this method builds an {@linkplain PaletteFactory#getPalette indexed palette} (i.e. a
372: * palette backed by an {@linkplain IndexColorModel index color model}) with just the minimal
373: * {@linkplain IndexColorModel#getMapSize size} needed for containing fully the range and the
374: * no-data values fetched at step 1. The data are assumed <cite>packed</cite> values rather
375: * than geophysics values. Consequently, the {@linkplain SampleConverter sample converters}
376: * will be the {@linkplain SampleConverter#IDENTITY identity converter} except in the
377: * following cases:
378: * <ul>
379: * <li>The {@linkplain Band#getValidRange range of valid values} is outside the range
380: * allowed by the {@linkplain #getRawDataType raw data type} (e.g. the range of
381: * valid values contains negative integers). In this case, the sample converter
382: * will shift the values to a strictly positive range and replace no-data values
383: * by 0.</li>
384: * <li>At least one {@linkplain Band#getNoDataValues no-data value} is outside the range
385: * of values allowed by the {@linkplain #getRawDataType raw data type}. In this case,
386: * this method will try to only replace the no-data values by 0, without shifting
387: * the valid values if this shift can be avoided.</li>
388: * <li>At least one {@linkplain Band#getNoDataValues no-data value} is far away from the
389: * {@linkplain Band#getValidRange range of valid values} (for example 9999 while
390: * the range of valid values is [0..255]). The meaning of "far away" is determined
391: * by the {@link #collapseNoDataValues collapseNoDataValues} method.</li>
392: * </ul>
393: * </p></li>
394: *
395: * <li><p>Otherwise, if the {@linkplain #getRawDataType raw data type} is a signed integer
396: * type like {@link DataBuffer#TYPE_SHORT TYPE_SHORT}, then this method builds an
397: * {@linkplain PaletteFactory#getPalette indexed palette} with the maximal {@linkplain
398: * IndexColorModel#getMapSize size} supported by the raw data type (note that this is
399: * memory expensive - typically 256 kilobytes). Negative values will be stored in their
400: * two's complement binary form in order to fit in the range of positive integers
401: * supported by the {@linkplain IndexColorModel index color model}.</p></li>
402: * </ol>
403: *
404: * <h3>Overriding this method</h3>
405: * Subclasses may override this method when a constant color {@linkplain Palette palette} is
406: * wanted for all images in a series, for example for all <cite>Sea Surface Temperature</cite>
407: * (SST) from the same provider. A constant color palette facilitates the visual comparaison
408: * of different images at different time. The example below creates hard-coded objects:
409: *
410: * <blockquote><code>
411: * int minimum = -2000; // </code>minimal expected value<code><br>
412: * int maximum = +2300; // </code>maximal expected value<code><br>
413: * int fillValue = -9999; // </code>Value for missing data<code><br>
414: * String palette = "SST-Nasa";// </code>Named set of RGB colors<code><br>
415: * converters[0] = {@linkplain SampleConverter#createOffset(double,double)
416: * SampleConverter.createOffset}(1 - minimum, fillValue);<br>
417: * return {@linkplain PaletteFactory#getDefault()}.{@linkplain PaletteFactory#getPalettePadValueFirst
418: * getPalettePadValueFirst}(paletteName, maximum - minimum).{@linkplain Palette#getImageTypeSpecifier
419: * getImageTypeSpecifier}();
420: * </code></blockquote>
421: *
422: * @param imageIndex
423: * The index of the image to be queried.
424: * @param parameters
425: * The user-supplied parameters, or {@code null}. Note: we recommand to supply
426: * {@link #getDefaultReadParam} instead of {@code null} since subclasses may
427: * override the later with default values suitable to a particular format.
428: * @param converters
429: * If non-null, an array where to store the converters created by this method.
430: * Those converters should be used by <code>{@linkplain #read(int,ImageReadParam)
431: * read}(imageIndex, parameters)</code> implementations for converting the values
432: * read in the datafile to values acceptable for the underling {@linkplain
433: * ColorModel color model}.
434: * @return
435: * The image type (never {@code null}).
436: * @throws IOException
437: * If an error occurs while reading the format information from the input source.
438: *
439: * @see #getRawDataType
440: * @see #collapseNoDataValues
441: * @see #getDestination(int, ImageReadParam, int, int, SampleConverter[])
442: */
443: protected ImageTypeSpecifier getRawImageType(final int imageIndex,
444: final ImageReadParam parameters,
445: final SampleConverter[] converters) throws IOException {
446: /*
447: * Gets the minimal and maximal values allowed for the target image type.
448: * Note that this is meanless for floating point types, so the values in
449: * that case are arbitrary.
450: *
451: * The only integer types that are signed are SHORT (not to be confused with
452: * USHORT) and INT. Other types like BYTE and USHORT are treated as unsigned.
453: */
454: final boolean isFloat;
455: final long floor, ceil;
456: final int dataType = getRawDataType(imageIndex);
457: switch (dataType) {
458: case DataBuffer.TYPE_UNDEFINED: // Actually we don't really know what to do for this case...
459: case DataBuffer.TYPE_DOUBLE: // Fall through since we can treat this case as float.
460: case DataBuffer.TYPE_FLOAT: {
461: isFloat = true;
462: floor = Long.MIN_VALUE;
463: ceil = Long.MAX_VALUE;
464: break;
465: }
466: case DataBuffer.TYPE_INT: {
467: isFloat = false;
468: floor = Integer.MIN_VALUE;
469: ceil = Integer.MAX_VALUE;
470: break;
471: }
472: case DataBuffer.TYPE_SHORT: {
473: isFloat = false;
474: floor = Short.MIN_VALUE;
475: ceil = Short.MAX_VALUE;
476: break;
477: }
478: default: {
479: isFloat = false;
480: floor = 0;
481: ceil = (1L << DataBuffer.getDataTypeSize(dataType)) - 1;
482: break;
483: }
484: }
485: /*
486: * Extracts all informations we will need from the user-supplied parameters, if any.
487: */
488: final String paletteName;
489: final int[] sourceBands;
490: final int[] targetBands;
491: final int visibleBand;
492: if (parameters != null) {
493: sourceBands = parameters.getSourceBands();
494: targetBands = parameters.getDestinationBands();
495: } else {
496: sourceBands = null;
497: targetBands = null;
498: }
499: if (parameters instanceof GeographicImageReadParam) {
500: final GeographicImageReadParam geoparam = (GeographicImageReadParam) parameters;
501: paletteName = geoparam.getNonNullPaletteName();
502: visibleBand = geoparam.getVisibleBand();
503: } else {
504: paletteName = GeographicImageReadParam.DEFAULT_PALETTE_NAME;
505: visibleBand = 0;
506: }
507: final int numBands;
508: if (sourceBands != null) {
509: numBands = sourceBands.length;
510: } else if (targetBands != null) {
511: numBands = targetBands.length;
512: } else {
513: numBands = getNumBands(imageIndex);
514: }
515: /*
516: * Computes a range of values for all bands, as the union in order to make sure that
517: * we can stores every sample values. Also creates SampleConverters in the process.
518: * The later is an opportunist action since we gather most of the needed information
519: * during the loop.
520: */
521: NumberRange allRanges = null;
522: NumberRange visibleRange = null;
523: SampleConverter visibleConverter = SampleConverter.IDENTITY;
524: double maximumFillValue = 0; // Only in the visible band, and must be positive.
525: final GeographicMetadata metadata = getGeographicMetadata(imageIndex);
526: if (metadata != null) {
527: final int maxBand = metadata.getNumBands();
528: for (int i = 0; i < numBands; i++) {
529: int bandIndex = (sourceBands != null) ? sourceBands[i]
530: : i;
531: if (bandIndex < 0 || bandIndex >= maxBand) {
532: warningOccurred("getRawImageType",
533: indexOutOfBounds(bandIndex, 0, maxBand));
534: if (sourceBands != null) {
535: continue; // Next bands may be okay.
536: } else {
537: break; // We are sure that next bands will not be better.
538: }
539: }
540: final Band band = metadata.getBand(bandIndex);
541: final double[] nodataValues = band.getNoDataValues();
542: final NumberRange range = band.getValidRange();
543: double minimum, maximum;
544: if (range != null) {
545: minimum = range.getMinimum();
546: maximum = range.getMaximum();
547: if (!isFloat) {
548: // If the metadata do not contain any information about the range,
549: // treat as if we use the maximal range allowed by the data type.
550: if (minimum == Double.NEGATIVE_INFINITY)
551: minimum = floor;
552: if (maximum == Double.POSITIVE_INFINITY)
553: maximum = ceil;
554: }
555: final double extent = maximum - minimum;
556: if (extent >= 0
557: && (isFloat || extent <= (ceil - floor))) {
558: allRanges = (allRanges != null) ? (NumberRange) allRanges
559: .union(range)
560: : range;
561: } else {
562: // Use range.getMin/MaxValue() because they may be integers rather than doubles.
563: warningOccurred("getRawImageType", Errors
564: .format(ErrorKeys.BAD_RANGE_$2, range
565: .getMinValue(), range
566: .getMaxValue()));
567: continue;
568: }
569: } else {
570: minimum = Double.NaN;
571: maximum = Double.NaN;
572: }
573: // Converts the band index from 'source' space to 'destination' space.
574: if (targetBands != null
575: && bandIndex < targetBands.length) {
576: bandIndex = targetBands[bandIndex];
577: }
578: /*
579: * For floating point types, replaces no-data values by NaN because the floating
580: * point numbers are typically used for geophysics data, so the raster is likely
581: * to be a "geophysics" view for GridCoverage2D. All other values are stored "as
582: * is" without any offset.
583: *
584: * For integer types, if the range of values from the source data file fits into
585: * the range of values allowed by the destination raster, we will use an identity
586: * converter. If the only required conversion is a shift from negative to positive
587: * values, creates an offset converter with no-data values collapsed to 0.
588: */
589: final SampleConverter converter;
590: if (isFloat) {
591: converter = SampleConverter
592: .createPadValuesMask(nodataValues);
593: } else {
594: final boolean isZeroValid = (minimum <= 0 && maximum >= 0);
595: boolean collapsePadValues = false;
596: if (nodataValues != null
597: && nodataValues.length != 0) {
598: final double[] sorted = (double[]) nodataValues
599: .clone();
600: Arrays.sort(sorted);
601: double minFill = sorted[0];
602: double maxFill = minFill;
603: int indexMax = sorted.length;
604: while (--indexMax != 0
605: && Double
606: .isNaN(maxFill = sorted[indexMax]))
607: ;
608: assert minFill <= maxFill
609: || Double.isNaN(minFill) : maxFill;
610: if (bandIndex == visibleBand
611: && maxFill > maximumFillValue) {
612: maximumFillValue = maxFill;
613: }
614: if (minFill < floor || maxFill > ceil) {
615: // At least one fill value is outside the range of acceptable values.
616: collapsePadValues = true;
617: } else if (minimum >= 0) {
618: /*
619: * Arbitrary optimization of memory usage: if there is a "large" empty
620: * space between the range of valid values and a no-data value, then we
621: * may (at subclass implementors choice) collapse the no-data values to
622: * zero in order to avoid wasting the empty space. Note that we do not
623: * perform this collapse if the valid range contains negative values
624: * because it would not save any memory. We do not check the no-data
625: * values between 0 and 'minimum' for the same reason.
626: */
627: int k = Arrays
628: .binarySearch(sorted, maximum);
629: if (k >= 0)
630: k++; // We want the first element greater than maximum.
631: else
632: k = ~k; // Really ~ operator, not -
633: if (k <= indexMax) {
634: double unusedSpace = Math.max(sorted[k]
635: - maximum - 1, 0);
636: while (++k <= indexMax) {
637: final double delta = sorted[k]
638: - sorted[k - 1] - 1;
639: if (delta > 0) {
640: unusedSpace += delta;
641: }
642: }
643: final int unused = (int) Math.min(Math
644: .round(unusedSpace),
645: Integer.MAX_VALUE);
646: collapsePadValues = collapseNoDataValues(
647: isZeroValid, sorted, unused);
648: // We invoked 'collapseNoDataValues' inconditionnaly even if
649: // 'unused' is zero because the user may decide on the basis
650: // of other criterions, like 'isZeroValid'.
651: }
652: }
653: }
654: if (minimum < floor || maximum > ceil) {
655: // The range of valid values is outside the range allowed by raw data type.
656: converter = SampleConverter.createOffset(
657: 1 - minimum, nodataValues);
658: } else if (collapsePadValues) {
659: if (isZeroValid) {
660: // We need to collapse the no-data values to 0, but it causes a clash
661: // with the range of valid values. So we also shift the later.
662: converter = SampleConverter.createOffset(
663: 1 - minimum, nodataValues);
664: } else {
665: // We need to collapse the no-data values and there is no clash.
666: converter = SampleConverter
667: .createPadValuesMask(nodataValues);
668: }
669: } else {
670: /*
671: * Do NOT take 'nodataValues' in account if there is no need to collapse
672: * them. This is not the converter's job to transform "packed" values to
673: * "geophysics" values. We just want them to fit in the IndexColorModel,
674: * and they already fit. So the identity converter is appropriate even
675: * in presence of pad values.
676: */
677: converter = SampleConverter.IDENTITY;
678: }
679: }
680: if (converters != null && bandIndex >= 0
681: && bandIndex < converters.length) {
682: converters[bandIndex] = converter;
683: }
684: if (bandIndex == visibleBand) {
685: visibleConverter = converter;
686: visibleRange = range;
687: }
688: }
689: }
690: /*
691: * Creates a color palette suitable for the range of values in the visible band.
692: * The case for floating points is the simpliest: we should not have any offset,
693: * at most a replacement of no-data values. In the case of integer values, we
694: * must make sure that the indexed color map is large enough for containing both
695: * the highest data value and the highest no-data value.
696: */
697: if (visibleRange == null) {
698: visibleRange = (allRanges != null) ? allRanges
699: : new NumberRange(floor, ceil);
700: }
701: final PaletteFactory factory = PaletteFactory.getDefault();
702: factory.setWarningLocale(locale);
703: final Palette palette;
704: if (isFloat) {
705: assert visibleConverter.getOffset() == 0 : visibleConverter;
706: palette = factory.getContinuousPalette(paletteName,
707: (float) visibleRange.getMinimum(),
708: (float) visibleRange.getMaximum(), dataType,
709: numBands, visibleBand);
710: } else {
711: final double offset = visibleConverter.getOffset();
712: final double minimum = visibleRange.getMinimum();
713: final double maximum = visibleRange.getMaximum();
714: long lower, upper;
715: if (minimum == Double.NEGATIVE_INFINITY) {
716: lower = floor;
717: } else {
718: lower = Math.round(minimum + offset);
719: if (!visibleRange.isMinIncluded()) {
720: lower++; // Must be inclusive
721: }
722: }
723: if (maximum == Double.POSITIVE_INFINITY) {
724: upper = ceil;
725: } else {
726: upper = Math.round(maximum + offset);
727: if (visibleRange.isMaxIncluded()) {
728: upper++; // Must be exclusive
729: }
730: }
731: final long size = Math.max(upper, Math
732: .round(maximumFillValue) + 1);
733: /*
734: * The target lower, upper and size parameters are usually in the range of SHORT
735: * or USHORT data type. The Palette class will performs the necessary checks and
736: * throws an exception if those variables are out of range. However, because we
737: * need to cast to int before passing the parameter values, we restrict them to
738: * the 'int' range as a safety in order to avoid results that accidently fall in
739: * the SHORT or USHORT range. Because Integer.MIN_VALUE or MAX_VALUE are out of
740: * range, it doesn't matter if those values are inaccurate since we will get an
741: * exception anyway.
742: */
743: palette = factory.getPalette(paletteName, (int) Math.max(
744: lower, Integer.MIN_VALUE), (int) Math.min(upper,
745: Integer.MAX_VALUE), (int) Math.min(size,
746: Integer.MAX_VALUE), numBands, visibleBand);
747: }
748: return palette.getImageTypeSpecifier();
749: }
750:
751: /**
752: * Returns the data type which most closely represents the "raw" internal data of the image.
753: * It should be one of {@link DataBuffer} constants. The default {@code GeographicImageReader}
754: * implementation works better with the following types:
755: *
756: * {@link DataBuffer#TYPE_BYTE TYPE_BYTE},
757: * {@link DataBuffer#TYPE_SHORT TYPE_SHORT},
758: * {@link DataBuffer#TYPE_USHORT TYPE_USHORT} and
759: * {@link DataBuffer#TYPE_FLOAT TYPE_FLOAT}.
760: *
761: * The default implementation returns {@link DataBuffer#TYPE_FLOAT TYPE_FLOAT} in every cases.
762: * <p>
763: * <h3>Handling of negative integer values</h3>
764: * If the raw internal data contains negative values but this method still declares a unsigned
765: * integer type ({@link DataBuffer#TYPE_BYTE TYPE_BYTE} or {@link DataBuffer#TYPE_USHORT TYPE_USHORT}),
766: * then the values will be translated in order to fit in the range of strictly positive values.
767: * For example if the raw internal data range from -23000 to +23000, then there is a choice:
768: *
769: * <ul>
770: * <li><p>If this method returns {@link DataBuffer#TYPE_SHORT}, then the data will be
771: * stored "as is" without transformation. However the {@linkplain IndexColorModel
772: * index color model} will have the maximal length allowed by 16 bits integers, with
773: * positive values in the [0 .. {@value java.lang.Short#MAX_VALUE}] range and negative
774: * values wrapped in the [32768 .. 65535] range in two's complement binary form. The
775: * results is a color model consuming 256 kilobytes in every cases. The space not used
776: * by the [-23000 .. +23000] range (in the above example) is lost.</p></li>
777: *
778: * <li><p>If this method returns {@link DataBuffer#TYPE_USHORT}, then the data will be
779: * translated to the smallest strictly positive range that can holds the data
780: * ([1..46000] for the above example). Value 0 is reserved for missing data. The
781: * result is a smaller {@linkplain IndexColorModel index color model} than the
782: * one used by untranslated data.</p></li>
783: * </ul>
784: *
785: * @param imageIndex The index of the image to be queried.
786: * @return The data type ({@link DataBuffer#TYPE_FLOAT} by default).
787: * @throws IOException If an error occurs reading the format information from the input source.
788: *
789: * @see #getRawImageType(int, ImageReadParam, SampleConverter[])
790: */
791: protected int getRawDataType(final int imageIndex)
792: throws IOException {
793: checkImageIndex(imageIndex);
794: return DataBuffer.TYPE_FLOAT;
795: }
796:
797: /**
798: * Returns {@code true} if the no-data values should be collapsed to 0 in order to save memory.
799: * This method is invoked automatically by the {@link #getRawImageType(int, ImageReadParam,
800: * SampleConverter[]) getRawImageType} method when it detected some unused space between the
801: * {@linkplain Band#getValidRange range of valid values} and at least one
802: * {@linkplain Band#getNoDataValues no-data value}.
803: * <p>
804: * The default implementation returns {@code false} in all cases, thus avoiding arbitrary
805: * choice. Subclasses can override this method with some arbitrary threashold, as in the
806: * example below:
807: *
808: * <blockquote><pre>
809: * return unusedSpace >= 1024;
810: * </pre></blockquote>
811: *
812: * @param isZeroValid
813: * {@code true} if 0 is a valid value. If this method returns {@code true} while
814: * {@code isZeroValid} is {@code true}, then the {@linkplain SampleConverter sample
815: * converter} to be returned by {@link #getRawImageType(int, ImageReadParam,
816: * SampleConverter[]) getRawImageType} will offset all valid values by 1.
817: * @param nodataValues
818: * The {@linkplain Arrays#sort(double[]) sorted}
819: * {@linkplain Band#getNoDataValues no-data values} (never null and never empty).
820: * @param unusedSpace
821: * The largest amount of unused space outside the range of valid values.
822: */
823: protected boolean collapseNoDataValues(final boolean isZeroValid,
824: final double[] nodataValues, final int unusedSpace) {
825: return false;
826: }
827:
828: /**
829: * Returns the buffered image to which decoded pixel data should be written. The image
830: * is determined by inspecting the supplied parameters if it is non-null, as described
831: * in the {@linkplain #getDestination(ImageReadParam,Iterator,int,int) super-class method}.
832: * In the default implementation, the {@linkplain ImageTypeSpecifier image type specifier}
833: * set is a singleton containing only the {@linkplain #getRawImageType(int,ImageReadParam,
834: * SampleConverter[]) raw image type}.
835: * <p>
836: * Implementations of the {@link #read(int,ImageReadParam)} method should invoke this
837: * method instead of {@link #getDestination(ImageReadParam,Iterator,int,int)}.
838: *
839: * @param imageIndex The index of the image to be retrieved.
840: * @param parameters The parameter given to the {@code read} method.
841: * @param width The true width of the image or tile begin decoded.
842: * @param height The true width of the image or tile being decoded.
843: * @param converters If non-null, an array where to store the converters required
844: * for converting decoded pixel data into stored pixel data.
845: * @return The buffered image to which decoded pixel data should be written.
846: *
847: * @throws IOException If an error occurs reading the format information from the input source.
848: *
849: * @see #getRawImageType(int, ImageReadParam, SampleConverter[])
850: */
851: protected BufferedImage getDestination(final int imageIndex,
852: final ImageReadParam parameters, final int width,
853: final int height, final SampleConverter[] converters)
854: throws IOException {
855: final Set spi = Collections.singleton(getRawImageType(
856: imageIndex, parameters, converters));
857: return getDestination(parameters, spi.iterator(), width, height);
858: }
859:
860: /**
861: * Returns a default parameter object appropriate for this format. The default
862: * implementation constructs and returns a new {@link GeographicImageReadParam}.
863: *
864: * @return An {@code ImageReadParam} object which may be used.
865: *
866: * @todo Replace the return type by {@link GeographicImageReadParam} when we will
867: * be allowed to compile for J2SE 1.5.
868: */
869: //@Override
870: public ImageReadParam getDefaultReadParam() {
871: return new GeographicImageReadParam(this );
872: }
873:
874: /**
875: * Reads the image indexed by {@code imageIndex} using a default {@link ImageReadParam}.
876: * This is a convenience method that calls <code>{@linkplain #read(int,ImageReadParam)
877: * read}(imageIndex, {@linkplain #getDefaultReadParam})</code>.
878: * <p>
879: * The default Java implementation passed a {@code null} parameter. This implementation
880: * passes the default parameter instead in order to improve consistency when a subclass
881: * overrides {@link #getDefaultReadParam}.
882: *
883: * @param imageIndex the index of the image to be retrieved.
884: * @return the desired portion of the image.
885: *
886: * @throws IllegalStateException if the input source has not been set.
887: * @throws IndexOutOfBoundsException if the supplied index is out of bounds.
888: * @throws IOException if an error occurs during reading.
889: */
890: //@Override
891: public BufferedImage read(final int imageIndex) throws IOException {
892: return read(imageIndex, getDefaultReadParam());
893: }
894:
895: /**
896: * Flips the source region vertically. This method should be invoked straight after
897: * {@link #computeRegions computeRegions} when the image to be read will be flipped
898: * vertically, for example when the {@linkplain java.awt.image.Raster raster} sample
899: * values are filled in a "{@code for (y=ymax-1; y>=ymin; y--)}" loop instead of
900: * "{@code for (y=ymin; y<ymax; y++)}".
901: * <p>
902: * This method should be invoked as in the example below:
903: *
904: * <blockquote><pre>
905: * computeRegions(param, srcWidth, srcHeight, image, srcRegion, destRegion);
906: * flipVertically(param, srcHeight, srcRegion);
907: * </pre></blockquote>
908: *
909: * @param param The {@code param} argument given to {@code computeRegions}.
910: * @param srcHeight The {@code srcHeight} argument given to {@code computeRegions}.
911: * @param srcRegion The {@code srcRegion} argument given to {@code computeRegions}.
912: */
913: protected static void flipVertically(final ImageReadParam param,
914: final int srcHeight, final Rectangle srcRegion) {
915: final int spaceLeft = srcRegion.y;
916: srcRegion.y = srcHeight - (srcRegion.y + srcRegion.height);
917: /*
918: * After the flip performed by the above line, we still have 'spaceLeft' pixels left for
919: * a downward translation. We usually don't need to care about if, except if the source
920: * region is very close to the bottom of the source image, in which case the correction
921: * computed below may be greater than the space left.
922: *
923: * We are done if there is no vertical subsampling. But if there is subsampling, then we
924: * need an adjustment. The flipping performed above must be computed as if the source
925: * region had exactly the size needed for reading nothing more than the last line, i.e.
926: * 'srcRegion.height' must be a multiple of 'sourceYSubsampling' plus 1. The "offset"
927: * correction is computed below accordingly.
928: */
929: if (param != null) {
930: int offset = (srcRegion.height - 1)
931: % param.getSourceYSubsampling();
932: srcRegion.y += offset;
933: offset -= spaceLeft;
934: if (offset > 0) {
935: // Happen only if we are very close to image border and
936: // the above translation bring us outside the image area.
937: srcRegion.height -= offset;
938: }
939: }
940: }
941:
942: /**
943: * Invoked when a warning occured. The default implementation make the following choice:
944: * <p>
945: * <ul>
946: * <li>If at least one {@linkplain IIOReadWarningListener warning listener}
947: * has been {@linkplain #addIIOReadWarningListener specified}, then the
948: * {@link IIOReadWarningListener#warningOccurred warningOccurred} method is
949: * invoked for each of them and the log record is <strong>not</strong> logged.</li>
950: *
951: * <li>Otherwise, the log record is sent to the {@code "org.geotools.image.io"} logger.</li>
952: * </ul>
953: *
954: * Subclasses may override this method if more processing is wanted, or for
955: * throwing exception if some warnings should be considered as fatal errors.
956: */
957: public void warningOccurred(final LogRecord record) {
958: if (warningListeners == null) {
959: LOGGER.log(record);
960: } else {
961: processWarningOccurred(IndexedResourceBundle.format(record));
962: }
963: }
964:
965: /**
966: * Convenience method for logging a warning from the given method.
967: */
968: private void warningOccurred(final String method,
969: final String message) {
970: final LogRecord record = new LogRecord(Level.WARNING, message);
971: record
972: .setSourceClassName(GeographicImageReader.class
973: .getName());
974: record.setSourceMethodName(method);
975: warningOccurred(record);
976: }
977:
978: /**
979: * To be overriden and made {@code protected} by {@link StreamImageReader} only.
980: */
981: void close() throws IOException {
982: metadata = null;
983: }
984: }
|