/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2006-2008, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotools.image.io; import org.geotools.resources.XMath; import org.geotools.resources.Classes; /** * Converts samples from the values stored in the image file to the values stored in the * {@linkplain java.awt.image.Raster raster}. Some typical conversions are: * <p> * <ul> * <li>Replace "<cite>nodata</cite>" values (typically a fixed value like 9999 or * {@value Short#MAX_VALUE}) by {@link Float#NaN NaN} if the target type is * {@code float} or {@code double}, or 0 if the target type is an integer.</li> * <li>Replace <em>signed</em> integers by <em>unsigned</em> integers, by applying * an offset to the values.</li> * </ul> * <p> * Note that pad values are replaced by 0 in the integer case, not by an arbitrary number, * because 0 is the result of {@code (int) NaN} cast. While not mandatory, this property * make some mathematics faster during conversions between <cite>geophysics</cite> and * <cite>display</cite> views in the coverage module. * <p> * There is no scaling because this class is not for <cite>samples to geophysics values</cite> * conversions (except the replacement of pad values by {@link Double#NaN NaN}). This class is * about the minimal changes needed in order to comply to the contraints of a target * {@linkplain java.awt.image.ColorModel color model}, e.g. for working around negative numbers. * * Sample converters work on {@code int}, {@code float} or {@code double} primitive types, * which match the primitive types expected by the {@link java.awt.image.Raster} API. * * @since 2.4 * * @source $URL$ * @version $Id$ * @author Martin Desruisseaux */ public abstract class SampleConverter { /** * A sample converter that do not performs any conversion. */ public static final SampleConverter IDENTITY = new Identity(); /** * Constructs a sample converter. */ protected SampleConverter() { } /** * Creates a sample converter that replaces a single pad value by {@link Double#NaN NaN} * for floating point numbers, or {@code 0} for integers. * * @param padValue The pad values to replace by {@link Double#NaN NaN} or {@code 0}. */ public static SampleConverter createPadValueMask(final double padValue) { return Double.isNaN(padValue) ? IDENTITY : new PadValueMask(padValue); } /** * Creates a sample converter that replaces an arbitrary amount of pad values by * {@link Double#NaN NaN} for floating point numbers, or {@code 0} for integers. * * @param padValue The pad values to replace by {@link Double#NaN NaN} or {@code 0}, * or {@code null} if none. */ public static SampleConverter createPadValuesMask(final double[] padValues) { if (padValues != null) { switch (padValues.length) { default: return new PadValuesMask(padValues); case 1: return createPadValueMask(padValues[0]); case 0: break; } } return IDENTITY; } /** * Creates a sample converter that replaces a pad value by {@link Double#NaN NaN} or * {@code 0}, and applies an offset on all other values. This is typically used in * order to shift a range of arbitrary (including negative) integer values to a range * of strictly positive values. The later is more manageable by * {@linkplain java.awt.image.IndexColorModel index color model}. * * @param An offset to add to the values to be read, before to store them in the raster. This * is used primarily for transforming <em>signed</em> short into <em>unsigned</em> short. * @param padValue The pad value to replace. This the value before the offset is applied. */ public static SampleConverter createOffset(final double offset, final double padValue) { return (offset == 0) ? createPadValueMask(padValue) : new Offset(offset, padValue); } /** * Creates a sample converter that replaces an arbitrary amount of pad values by * {@link Double#NaN NaN} or {@code 0}, and applies an offset on all other values. * * @param An offset to add to the values to be read, before to store them in the raster. * @param padValue The pad values to replace. They the values before the offset is applied. */ public static SampleConverter createOffset(final double offset, final double[] padValues) { if (offset == 0) { return createPadValuesMask(padValues); } if (padValues != null) { switch (padValues.length) { default: return new MaskAndOffset(offset, padValues); case 1: return createOffset(offset, padValues[0]); case 0: break; } } return createOffset(offset, Double.NaN); } /** * Converts a double-precision value before to store it in the raster. * Subclasses should override this method if some fixed values need to * be converted into {@link Double#NaN} value. * * @param value The value read from the image file. * @return The value to store in the {@linkplain java.awt.image.Raster raster}. */ public abstract double convert(double value); /** * Converts a float-precision value before to store it in the raster. * Subclasses should override this method if some fixed values need to * be converted into {@link Float#NaN} value. * * @param value The value read from the image file. * @return The value to store in the {@linkplain java.awt.image.Raster raster}. */ public abstract float convert(float value); /** * Converts a float-precision value before to store it in the raster. * * @param value The value read from the image file. * @return The value to store in the {@linkplain java.awt.image.Raster raster}. */ public abstract int convert(int value); /** * If this converter applies an offset, returns the offset. Otherwise returns 0. */ public double getOffset() { return 0; } /** * Returns a string representation of this sample converter. * This is mostly for debugging purpose and may change in any future version. */ @Override public String toString() { return Classes.getShortClassName(this) + "[offset=" + getOffset() + ']'; } /** * A sample converter that do not performs any conversion. */ private static final class Identity extends SampleConverter { public double convert(double value) { return value; } public float convert(float value) { return value; } public int convert(int value) { return value; } } /** * A sample converter that replaces a single pad value by {@link Double#NaN NaN} * for floating point numbers, or {@code 0} for integers. */ private static class PadValueMask extends SampleConverter { final double doubleValue; final float floatValue; final int integerValue; PadValueMask(final double padValue) { doubleValue = padValue; floatValue = (float) padValue; final int p = (int) padValue; integerValue = (p == padValue) ? p : 0; } public double convert(final double value) { return (value == doubleValue) ? Double.NaN : value; } public float convert(final float value) { return (value == floatValue) ? Float.NaN : value; } public int convert(final int value) { return (value == integerValue) ? 0 : value; } } /** * A sample converter that replaces a single pad value by 0, * and applies an offset on all other values. */ private static final class Offset extends PadValueMask { private final double doubleOffset; private final float floatOffset; private final int integerOffset; Offset(final double offset, final double padValue) { super(padValue); doubleOffset = offset; floatOffset = (float) offset; integerOffset = (int) Math.round(offset); } @Override public double convert(final double value) { return (value == doubleValue) ? Double.NaN : value + doubleOffset; } @Override public float convert(final float value) { return (value == floatValue) ? Float.NaN : value + floatOffset; } @Override public int convert(final int value) { return (value == integerValue) ? 0 : value + integerOffset; } @Override public double getOffset() { return doubleOffset; } } /** * A sample converter that replaces an arbitrary amount of pad values by * {@link Double#NaN NaN} for floating point numbers, or {@code 0} for integers. */ private static class PadValuesMask extends SampleConverter { private final double[] doubleValues; private final float [] floatValues; private final float [] NaNs; PadValuesMask(final double[] padValues) { doubleValues = new double[padValues.length]; floatValues = new float [padValues.length]; NaNs = new float [padValues.length]; for (int i=0; i<padValues.length; i++) { floatValues[i] = (float) (doubleValues[i] = padValues[i]); NaNs[i] = XMath.toNaN(i); } } public double convert(final double value) { for (int i=0; i<doubleValues.length; i++) { if (value == doubleValues[i]) { return NaNs[i]; } } return value; } public float convert(final float value) { for (int i=0; i<floatValues.length; i++) { if (value == floatValues[i]) { return NaNs[i]; } } return value; } // Do not override: we really need the arithmetic on NaN values. public final int convert(final int value) { return (int) convert((double) value); } } /** * A sample converter that replaces many pad values by 0, * and applies an offset on all other values. */ private static final class MaskAndOffset extends PadValuesMask { private final double doubleOffset; private final float floatOffset; MaskAndOffset(final double offset, final double[] padValues) { super(padValues); doubleOffset = offset; floatOffset = (float) offset; } @Override public double convert(final double value) { return super.convert(value) + doubleOffset; } @Override public float convert(final float value) { return super.convert(value) + floatOffset; } @Override public double getOffset() { return doubleOffset; } } }