001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2006, GeoTools Project Managment Committee (PMC)
005: * (C) 2006, 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;
010: * version 2.1 of the License.
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: // J2SE dependencies
020: import java.awt.image.Raster; // For javadoc
021: import org.geotools.resources.XMath;
022: import org.geotools.resources.Utilities;
023:
024: /**
025: * Converts samples from the values stored in the image file to the values stored in the
026: * {@linkplain Raster raster}. Some typical conversions are:
027: * <p>
028: * <ul>
029: * <li>Replace "<cite>nodata</cite>" values (typically a fixed value like 9999 or
030: * {@value Short#MAX_VALUE}) by {@link Float#NaN NaN} if the target type is
031: * {@code float} or {@code double}, or 0 if the target type is an integer.</li>
032: * <li>Replace <em>signed</em> integers by <em>unsigned</em> integers, by applying
033: * an offset to the values.</li>
034: * </ul>
035: * <p>
036: * Note that pad values are replaced by 0 in the integer case, not by an arbitrary number,
037: * because 0 is the result of {@code (int) NaN} cast. While not mandatory, this property
038: * make some mathematics faster during conversions between <cite>geophysics</cite> and
039: * <cite>display</cite> views in the coverage module.
040: * <p>
041: * There is no scaling because this class is not for <cite>samples to geophysics values</cite>
042: * conversions (except the replacement of pad values by {@link Double#NaN NaN}). This class is
043: * about the minimal changes needed in order to comply to the contraints of a target
044: * {@linkplain java.awt.image.ColorModel color model}, e.g. for working around negative numbers.
045: *
046: * Sample converters work on {@code int}, {@code float} or {@code double} primitive types,
047: * which match the primitive types expected by the {@link Raster} API.
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/SampleConverter.java $
051: * @version $Id: SampleConverter.java 27583 2007-10-23 11:29:26Z desruisseaux $
052: * @author Martin Desruisseaux
053: */
054: public abstract class SampleConverter {
055: /**
056: * A sample converter that do not performs any conversion.
057: */
058: public static final SampleConverter IDENTITY = new Identity();
059:
060: /**
061: * Constructs a sample converter.
062: */
063: protected SampleConverter() {
064: }
065:
066: /**
067: * Creates a sample converter that replaces a single pad value by {@link Double#NaN NaN}
068: * for floating point numbers, or {@code 0} for integers.
069: *
070: * @param padValue The pad values to replace by {@link Double#NaN NaN} or {@code 0}.
071: */
072: public static SampleConverter createPadValueMask(
073: final double padValue) {
074: return Double.isNaN(padValue) ? IDENTITY : new PadValueMask(
075: padValue);
076: }
077:
078: /**
079: * Creates a sample converter that replaces an arbitrary amount of pad values by
080: * {@link Double#NaN NaN} for floating point numbers, or {@code 0} for integers.
081: *
082: * @param padValue The pad values to replace by {@link Double#NaN NaN} or {@code 0},
083: * or {@code null} if none.
084: */
085: public static SampleConverter createPadValuesMask(
086: final double[] padValues) {
087: if (padValues != null) {
088: switch (padValues.length) {
089: default:
090: return new PadValuesMask(padValues);
091: case 1:
092: return createPadValueMask(padValues[0]);
093: case 0:
094: break;
095: }
096: }
097: return IDENTITY;
098: }
099:
100: /**
101: * Creates a sample converter that replaces a pad value by {@link Double#NaN NaN} or
102: * {@code 0}, and applies an offset on all other values. This is typically used in
103: * order to shift a range of arbitrary (including negative) integer values to a range
104: * of strictly positive values. The later is more manageable by
105: * {@linkplain java.awt.image.IndexColorModel index color model}.
106: *
107: * @param An offset to add to the values to be read, before to store them in the raster. This
108: * is used primarily for transforming <em>signed</em> short into <em>unsigned</em> short.
109: * @param padValue The pad value to replace. This the value before the offset is applied.
110: */
111: public static SampleConverter createOffset(final double offset,
112: final double padValue) {
113: return (offset == 0) ? createPadValueMask(padValue)
114: : new Offset(offset, padValue);
115: }
116:
117: /**
118: * Creates a sample converter that replaces an arbitrary amount of pad values by
119: * {@link Double#NaN NaN} or {@code 0}, and applies an offset on all other values.
120: *
121: * @param An offset to add to the values to be read, before to store them in the raster.
122: * @param padValue The pad values to replace. They the values before the offset is applied.
123: */
124: public static SampleConverter createOffset(final double offset,
125: final double[] padValues) {
126: if (offset == 0) {
127: return createPadValuesMask(padValues);
128: }
129: if (padValues != null) {
130: switch (padValues.length) {
131: default:
132: return new MaskAndOffset(offset, padValues);
133: case 1:
134: return createOffset(offset, padValues[0]);
135: case 0:
136: break;
137: }
138: }
139: return createOffset(offset, Double.NaN);
140: }
141:
142: /**
143: * Converts a double-precision value before to store it in the raster.
144: * Subclasses should override this method if some fixed values need to
145: * be converted into {@link Double#NaN} value.
146: *
147: * @param value The value read from the image file.
148: * @return The value to store in the {@linkplain Raster raster}.
149: */
150: public abstract double convert(double value);
151:
152: /**
153: * Converts a float-precision value before to store it in the raster.
154: * Subclasses should override this method if some fixed values need to
155: * be converted into {@link Float#NaN} value.
156: *
157: * @param value The value read from the image file.
158: * @return The value to store in the {@linkplain Raster raster}.
159: */
160: public abstract float convert(float value);
161:
162: /**
163: * Converts a float-precision value before to store it in the raster.
164: *
165: * @param value The value read from the image file.
166: * @return The value to store in the {@linkplain Raster raster}.
167: */
168: public abstract int convert(int value);
169:
170: /**
171: * If this converter applies an offset, returns the offset. Otherwise returns 0.
172: */
173: public double getOffset() {
174: return 0;
175: }
176:
177: /**
178: * Returns a string representation of this sample converter.
179: * This is mostly for debugging purpose and may change in any future version.
180: */
181: public String toString() {
182: return Utilities.getShortClassName(this ) + "[offset="
183: + getOffset() + ']';
184: }
185:
186: /**
187: * A sample converter that do not performs any conversion.
188: */
189: private static final class Identity extends SampleConverter {
190: public double convert(double value) {
191: return value;
192: }
193:
194: public float convert(float value) {
195: return value;
196: }
197:
198: public int convert(int value) {
199: return value;
200: }
201: }
202:
203: /**
204: * A sample converter that replaces a single pad value by {@link Double#NaN NaN}
205: * for floating point numbers, or {@code 0} for integers.
206: */
207: private static class PadValueMask extends SampleConverter {
208: final double doubleValue;
209: final float floatValue;
210: final int integerValue;
211:
212: PadValueMask(final double padValue) {
213: doubleValue = (double) padValue;
214: floatValue = (float) padValue;
215: final int p = (int) padValue;
216: integerValue = (p == padValue) ? p : 0;
217: }
218:
219: public double convert(final double value) {
220: return (value == doubleValue) ? Double.NaN : value;
221: }
222:
223: public float convert(final float value) {
224: return (value == floatValue) ? Float.NaN : value;
225: }
226:
227: public int convert(final int value) {
228: return (value == integerValue) ? 0 : value;
229: }
230: }
231:
232: /**
233: * A sample converter that replaces a single pad value by 0,
234: * and applies an offset on all other values.
235: */
236: private static final class Offset extends PadValueMask {
237: private final double doubleOffset;
238: private final float floatOffset;
239: private final int integerOffset;
240:
241: Offset(final double offset, final double padValue) {
242: super (padValue);
243: doubleOffset = offset;
244: floatOffset = (float) offset;
245: integerOffset = (int) Math.round(offset);
246: }
247:
248: //@Override (apply everywhere...)
249: public double convert(final double value) {
250: return (value == doubleValue) ? Double.NaN : value
251: + doubleOffset;
252: }
253:
254: public float convert(final float value) {
255: return (value == floatValue) ? Float.NaN : value
256: + floatOffset;
257: }
258:
259: public int convert(final int value) {
260: return (value == integerValue) ? 0 : value + integerOffset;
261: }
262:
263: public double getOffset() {
264: return doubleOffset;
265: }
266: }
267:
268: /**
269: * A sample converter that replaces an arbitrary amount of pad values by
270: * {@link Double#NaN NaN} for floating point numbers, or {@code 0} for integers.
271: */
272: private static class PadValuesMask extends SampleConverter {
273: private final double[] doubleValues;
274: private final float[] floatValues;
275: private final float[] NaNs;
276:
277: PadValuesMask(final double[] padValues) {
278: doubleValues = new double[padValues.length];
279: floatValues = new float[padValues.length];
280: NaNs = new float[padValues.length];
281: for (int i = 0; i < padValues.length; i++) {
282: floatValues[i] = (float) (doubleValues[i] = padValues[i]);
283: NaNs[i] = XMath.toNaN(i);
284: }
285: }
286:
287: public double convert(final double value) {
288: for (int i = 0; i < doubleValues.length; i++) {
289: if (value == doubleValues[i]) {
290: return NaNs[i];
291: }
292: }
293: return value;
294: }
295:
296: public float convert(final float value) {
297: for (int i = 0; i < floatValues.length; i++) {
298: if (value == floatValues[i]) {
299: return NaNs[i];
300: }
301: }
302: return value;
303: }
304:
305: // Do not override: we really need the arithmetic on NaN values.
306: public final int convert(final int value) {
307: return (int) convert((double) value);
308: }
309: }
310:
311: /**
312: * A sample converter that replaces many pad values by 0,
313: * and applies an offset on all other values.
314: */
315: private static final class MaskAndOffset extends PadValuesMask {
316: private final double doubleOffset;
317: private final float floatOffset;
318:
319: MaskAndOffset(final double offset, final double[] padValues) {
320: super (padValues);
321: doubleOffset = (double) offset;
322: floatOffset = (float) offset;
323: }
324:
325: public double convert(final double value) {
326: return super .convert(value) + doubleOffset;
327: }
328:
329: public float convert(final float value) {
330: return super .convert(value) + floatOffset;
331: }
332:
333: public double getOffset() {
334: return doubleOffset;
335: }
336: }
337: }
|