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.text;
018:
019: import java.awt.Point;
020: import java.awt.Rectangle;
021: import java.awt.image.BufferedImage;
022: import java.awt.image.DataBuffer;
023: import java.awt.image.DataBufferFloat;
024: import java.awt.image.Raster;
025: import java.awt.image.SampleModel;
026: import java.awt.image.WritableRaster;
027: import java.io.BufferedReader;
028: import java.io.IOException;
029: import java.text.ParseException;
030: import java.util.Locale;
031: import javax.imageio.IIOException;
032: import javax.imageio.ImageReadParam;
033: import javax.imageio.ImageReader;
034: import javax.imageio.ImageTypeSpecifier;
035: import javax.imageio.spi.ImageReaderSpi;
036: import javax.imageio.metadata.IIOMetadata;
037:
038: import org.geotools.io.LineFormat;
039: import org.geotools.resources.XArray;
040: import org.geotools.resources.i18n.Descriptions;
041: import org.geotools.resources.i18n.DescriptionKeys;
042: import org.geotools.image.io.metadata.GeographicMetadata;
043:
044: /**
045: * An image decoder for matrix of floating-point numbers. The default implementation creates
046: * rasters of {@link DataBuffer#TYPE_FLOAT}. An easy way to change this type is to overwrite
047: * the {@link #getRawDataType} method.
048: *
049: * @since 2.4
050: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/unsupported/coverageio/src/main/java/org/geotools/image/io/text/TextMatrixImageReader.java $
051: * @version $Id: TextMatrixImageReader.java 27629 2007-10-26 09:59:20Z desruisseaux $
052: * @author Martin Desruisseaux
053: */
054: public class TextMatrixImageReader extends TextImageReader {
055: /**
056: * The matrix data loaded by {@link #load} method.
057: */
058: private float[] data;
059:
060: /**
061: * The image width. This number is valid only if {@link #data} is non-null.
062: */
063: private int width;
064:
065: /**
066: * The image height. This number is valid only if {@link #completed} is true.
067: */
068: private int height;
069:
070: /**
071: * The expected height, or 0 if unknow. This number
072: * has no signification if {@link #data} is null.
073: */
074: private int expectedHeight;
075:
076: /**
077: * {@code true} if {@link #data} contains all data, or {@code false}
078: * if {@link #data} contains only the first line. This field has no
079: * signification if {@link #data} is null.
080: */
081: private boolean completed;
082:
083: /**
084: * Constructs a new image reader.
085: *
086: * @param provider the provider that is invoking this constructor, or {@code null} if none.
087: */
088: protected TextMatrixImageReader(final ImageReaderSpi provider) {
089: super (provider);
090: }
091:
092: /**
093: * Load data. No subsampling is performed.
094: *
095: * @param imageIndex the index of the image to be read.
096: * @param all {@code true} to read all data, or {@code false} to read only the first line.
097: * @return {@code true} if reading has been aborted.
098: * @throws IOException If an error occurs reading the width information from the input source.
099: */
100: private boolean load(final int imageIndex, final boolean all)
101: throws IOException {
102: clearAbortRequest();
103: if (all) {
104: processImageStarted(imageIndex);
105: }
106: float[] values = (data != null) ? new float[width] : null;
107: int offset = width * height;
108:
109: final BufferedReader input = getReader();
110: final LineFormat format = getLineFormat(imageIndex);
111: final float padValue = (float) getPadValue(imageIndex);
112: String line;
113: while ((line = input.readLine()) != null) {
114: if (isComment(line)) {
115: continue;
116: }
117: try {
118: format.setLine(line);
119: values = format.getValues(values);
120: for (int i = values.length; --i >= 0;) {
121: if (values[i] == padValue) {
122: values[i] = Float.NaN;
123: }
124: }
125: } catch (ParseException exception) {
126: throw new IIOException(getPositionString(exception
127: .getLocalizedMessage()), exception);
128: }
129: if (data == null) {
130: data = new float[1024];
131: }
132: final int newOffset = offset + (width = values.length);
133: if (newOffset > data.length) {
134: data = XArray.resize(data, newOffset
135: + Math.min(newOffset, 65536));
136: }
137: System.arraycopy(values, 0, data, offset, width);
138: offset = newOffset;
139: height++;
140: /*
141: * If only one line was requested, try to guess the expected height.
142: */
143: if (!all) {
144: final long streamLength = getStreamLength(imageIndex,
145: imageIndex + 1);
146: if (streamLength >= 0) {
147: expectedHeight = (int) (streamLength / (line
148: .length() + 1));
149: }
150: break;
151: }
152: /*
153: * Update progress.
154: */
155: if (height <= expectedHeight) {
156: processImageProgress(height * 100f / expectedHeight);
157: if (abortRequested()) {
158: processReadAborted();
159: return true;
160: }
161: }
162: }
163: if ((completed = all) == true) {
164: data = XArray.resize(data, offset);
165: expectedHeight = height;
166: }
167: if (all) {
168: processImageComplete();
169: }
170: return false;
171: }
172:
173: /**
174: * Returns the width in pixels of the given image within the input source.
175: *
176: * @param imageIndex the index of the image to be queried.
177: * @return Image width.
178: * @throws IOException If an error occurs reading the width information
179: * from the input source.
180: */
181: public int getWidth(final int imageIndex) throws IOException {
182: checkImageIndex(imageIndex);
183: if (data == null) {
184: load(imageIndex, false);
185: }
186: return width;
187: }
188:
189: /**
190: * Returns the height in pixels of the given image within the input source.
191: * Calling this method may force loading of full image.
192: *
193: * @param imageIndex the index of the image to be queried.
194: * @return Image height.
195: * @throws IOException If an error occurs reading the height information
196: * from the input source.
197: */
198: public int getHeight(final int imageIndex) throws IOException {
199: checkImageIndex(imageIndex);
200: if (data == null || !completed) {
201: load(imageIndex, true);
202: }
203: return height;
204: }
205:
206: /**
207: * Returns metadata associated with the given image.
208: * Calling this method may force loading of full image.
209: *
210: * @param imageIndex The image index.
211: * @return The metadata, or {@code null} if none.
212: * @throws IOException If an error occurs reading the data information from the input source.
213: */
214: //@Override
215: public IIOMetadata getImageMetadata(final int imageIndex)
216: throws IOException {
217: checkImageIndex(imageIndex);
218: if (!ignoreMetadata) {
219: if (data == null || !completed) {
220: load(imageIndex, true);
221: }
222: float minimum = Float.POSITIVE_INFINITY;
223: float maximum = Float.NEGATIVE_INFINITY;
224: for (int i = 0; i < data.length; i++) {
225: final float value = data[i];
226: if (value < minimum)
227: minimum = value;
228: if (value > maximum)
229: maximum = value;
230: }
231: if (minimum < maximum) {
232: final GeographicMetadata metadata = new GeographicMetadata(
233: this );
234: metadata.getBand(0).setValidRange(minimum, maximum);
235: return metadata;
236: }
237: }
238: return null;
239: }
240:
241: /**
242: * Reads the image indexed by {@code imageIndex}.
243: *
244: * @param imageIndex The index of the image to be retrieved.
245: * @param param Parameters used to control the reading process, or null.
246: * @return The desired portion of the image.
247: * @throws IOException if an input operation failed.
248: */
249: public BufferedImage read(final int imageIndex,
250: final ImageReadParam param) throws IOException {
251: /*
252: * Parameters check.
253: */
254: final int numSrcBands = 1;
255: final int numDstBands = 1;
256: checkImageIndex(imageIndex);
257: checkReadParamBandSettings(param, numSrcBands, numDstBands);
258: /*
259: * Extract user's parameters.
260: */
261: final int[] sourceBands;
262: final int[] destinationBands;
263: final int sourceXSubsampling;
264: final int sourceYSubsampling;
265: final int subsamplingXOffset;
266: final int subsamplingYOffset;
267: final int destinationXOffset;
268: final int destinationYOffset;
269: if (param != null) {
270: sourceBands = param.getSourceBands();
271: destinationBands = param.getDestinationBands();
272: final Point offset = param.getDestinationOffset();
273: sourceXSubsampling = param.getSourceXSubsampling();
274: sourceYSubsampling = param.getSourceYSubsampling();
275: subsamplingXOffset = param.getSubsamplingXOffset();
276: subsamplingYOffset = param.getSubsamplingYOffset();
277: destinationXOffset = offset.x;
278: destinationYOffset = offset.y;
279: } else {
280: sourceBands = null;
281: destinationBands = null;
282: sourceXSubsampling = 1;
283: sourceYSubsampling = 1;
284: subsamplingXOffset = 0;
285: subsamplingYOffset = 0;
286: destinationXOffset = 0;
287: destinationYOffset = 0;
288: }
289: /*
290: * Compute source region and check for possible optimization.
291: */
292: final Rectangle srcRegion = getSourceRegion(param, width,
293: height);
294: final boolean isDirect = sourceXSubsampling == 1
295: && sourceYSubsampling == 1 && subsamplingXOffset == 0
296: && subsamplingYOffset == 0 && destinationXOffset == 0
297: && destinationYOffset == 0 && srcRegion.x == 0
298: && srcRegion.width == width && srcRegion.y == 0
299: && srcRegion.height == height;
300: /*
301: * Read data if it was not already done.
302: */
303: if (data == null || !completed) {
304: if (load(imageIndex, true)) {
305: return null;
306: }
307: }
308: /*
309: * If a direct mapping is possible, perform it.
310: */
311: if (isDirect
312: && (param == null || param.getDestination() == null)) {
313: final ImageTypeSpecifier type = getRawImageType(imageIndex,
314: param, null); // TODO: use SampleConverter
315: final SampleModel model = type.getSampleModel()
316: .createCompatibleSampleModel(width, height);
317: final DataBuffer buffer = new DataBufferFloat(data,
318: data.length);
319: final WritableRaster raster = Raster.createWritableRaster(
320: model, buffer, null);
321: return new BufferedImage(type.getColorModel(), raster,
322: false, null);
323: }
324: /*
325: * Copy data into a new image.
326: */
327: final int dstBand = 0;
328: final BufferedImage image = getDestination(imageIndex, param,
329: width, height, null); // TODO
330: final WritableRaster dstRaster = image.getRaster();
331: final Rectangle dstRegion = new Rectangle();
332: computeRegions(param, width, height, image, srcRegion,
333: dstRegion);
334: final int dstXMin = dstRegion.x;
335: final int dstYMin = dstRegion.y;
336: final int dstXMax = dstRegion.width + dstXMin;
337: final int dstYMax = dstRegion.height + dstYMin;
338:
339: int srcY = srcRegion.y;
340: for (int y = dstYMin; y < dstYMax; y++) {
341: assert (srcY < srcRegion.y + srcRegion.height);
342: int srcX = srcRegion.x;
343: for (int x = dstXMin; x < dstXMax; x++) {
344: assert (srcX < srcRegion.x + srcRegion.width);
345: final float value = data[srcY * width + srcX];
346: dstRaster.setSample(x, y, dstBand, value);
347: srcX += sourceXSubsampling;
348: }
349: srcY += sourceYSubsampling;
350: }
351: return image;
352: }
353:
354: /**
355: * Closes the input stream and disposes the resources that was specific to that stream.
356: */
357: //@Override
358: public void close() throws IOException {
359: completed = false;
360: data = null;
361: width = 0;
362: height = 0;
363: expectedHeight = 0;
364: super .close();
365: }
366:
367: /**
368: * Service provider interface (SPI) for {@link TextMatrixImageReader}s. This SPI provides
369: * the necessary implementation for creating default {@link TextMatrixImageReader} using
370: * default locale and character set. Subclasses can set some fields at construction time
371: * in order to tune the reader to a particular environment, e.g.:
372: *
373: * <blockquote><pre>
374: * public final class MyCustomSpi extends TextMatrixImageReader.Spi {
375: * public MyCustomSpi() {
376: * {@link #names names} = new String[] {"myformat"};
377: * {@link #MIMETypes MIMETypes} = new String[] {"text/x-mytype"};
378: * {@link #vendorName vendorName} = "Institut de Recherche pour le Développement";
379: * {@link #version version} = "1.0";
380: * {@link #locale locale} = Locale.US;
381: * {@link #charset charset} = Charset.forName("ISO-LATIN-1");
382: * {@link #padValue padValue} = 9999;
383: * }
384: * }
385: * </pre></blockquote>
386: *
387: * (Note: fields {@code vendorName} and {@code version} are only informatives).
388: * There is no need to override any method in this example. However, developers
389: * can gain more control by creating subclasses of {@link TextMatrixImageReader}
390: * and {@code Spi}.
391: *
392: * @since 2.1
393: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/unsupported/coverageio/src/main/java/org/geotools/image/io/text/TextMatrixImageReader.java $
394: * @version $Id: TextMatrixImageReader.java 27629 2007-10-26 09:59:20Z desruisseaux $
395: * @author Martin Desruisseaux
396: */
397: public static class Spi extends TextImageReader.Spi {
398: /**
399: * The format names for the default {@link TextMatrixImageReader} configuration.
400: */
401: private static final String[] NAMES = { "matrix" };
402:
403: /**
404: * The mime types for the default {@link TextMatrixImageReader} configuration.
405: */
406: private static final String[] MIME_TYPES = { "text/x-matrix" };
407:
408: /**
409: * Constructs a default {@code TextMatrixImageReader.Spi}. This constructor
410: * provides the following defaults in addition to the defaults defined in the
411: * {@linkplain TextImageReader.Spi#Spi super-class constructor}:
412: *
413: * <ul>
414: * <li>{@link #names} = {@code "matrix"}</li>
415: * <li>{@link #MIMETypes} = {@code "text/x-matrix"}</li>
416: * <li>{@link #pluginClassName} = {@code "org.geotools.image.io.text.TextMatrixImageReader"}</li>
417: * <li>{@link #vendorName} = {@code "Geotools"}</li>
418: * </ul>
419: *
420: * For efficienty reasons, the above fields are initialized to shared arrays. Subclasses
421: * can assign new arrays, but should not modify the default array content.
422: */
423: public Spi() {
424: names = NAMES;
425: MIMETypes = MIME_TYPES;
426: pluginClassName = "org.geotools.image.io.text.TextMatrixImageReader";
427: vendorName = "Geotools";
428: version = "2.4";
429: }
430:
431: /**
432: * Returns a brief, human-readable description of this service provider
433: * and its associated implementation. The resulting string should be
434: * localized for the supplied locale, if possible.
435: *
436: * @param locale A Locale for which the return value should be localized.
437: * @return A String containing a description of this service provider.
438: */
439: public String getDescription(final Locale locale) {
440: return Descriptions.getResources(locale).getString(
441: DescriptionKeys.CODEC_MATRIX);
442: }
443:
444: /**
445: * Returns an instance of the {@code ImageReader} implementation associated
446: * with this service provider.
447: *
448: * @param extension An optional extension object, which may be null.
449: * @return An image reader instance.
450: * @throws IOException if the attempt to instantiate the reader fails.
451: */
452: public ImageReader createReaderInstance(final Object extension)
453: throws IOException {
454: return new TextMatrixImageReader(this );
455: }
456:
457: /**
458: * Returns {@code true} if the specified row length is valid. The default implementation
459: * returns {@code true} if the row seems "long", where "long" is arbitrary fixed to 10
460: * columns. This is an arbitrary choice, which is why this method is not public. It may
461: * be changed in any future Geotools version.
462: */
463: //@Override
464: boolean isValidColumnCount(final int count) {
465: return count > 10;
466: }
467: }
468: }
|