/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2001-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.coverage; import java.util.Map; import java.util.HashMap; import java.util.Locale; import java.awt.color.ColorSpace; import java.awt.image.ColorModel; import java.awt.image.DataBuffer; import java.awt.image.IndexColorModel; import java.awt.image.SampleModel; import java.awt.image.SinglePixelPackedSampleModel; import org.opengis.util.InternationalString; import org.opengis.coverage.ColorInterpretation; import org.opengis.coverage.SampleDimensionType; import static org.opengis.coverage.SampleDimensionType.*; import org.geotools.resources.i18n.Errors; import org.geotools.resources.i18n.ErrorKeys; import org.geotools.resources.i18n.Vocabulary; import org.geotools.resources.i18n.VocabularyKeys; import org.geotools.resources.image.ColorUtilities; import org.geotools.util.AbstractInternationalString; import org.geotools.util.Range; import org.geotools.util.SimpleInternationalString; import org.geotools.util.NumberRange; /** * Utility methods for choosing a {@linkplain SampleModel sample model} or a * {@linkplain ColorModel color model} on the basis of a range of values. * This class provides also some methods for mapping {@link SampleDimensionType} * to {@link DataBuffer} types. * * @since 2.1 * * * @source $URL$ * @version $Id$ * @author Martin Desruisseaux (IRD) */ public final class TypeMap { /** * The mapping of {@link SampleDimensionType} to {@link DataBuffer} types. */ private static final TypeMap[] MAP = new TypeMap[SampleDimensionType.values().length]; static { final Map pool = new HashMap(32); final Float M1 = -Float .MAX_VALUE; final Float P1 = Float .MAX_VALUE; final Double M2 = -Double.MAX_VALUE; final Double P2 = Double.MAX_VALUE; // The constructor will register automatically those objects in the above array. new TypeMap( UNSIGNED_1BIT, DataBuffer.TYPE_BYTE, (byte) 1, false, false, pool); new TypeMap( UNSIGNED_2BITS, DataBuffer.TYPE_BYTE, (byte) 2, false, false, pool); new TypeMap( UNSIGNED_4BITS, DataBuffer.TYPE_BYTE, (byte) 4, false, false, pool); new TypeMap( UNSIGNED_8BITS, DataBuffer.TYPE_BYTE, (byte) 8, false, false, pool); new TypeMap( SIGNED_8BITS, DataBuffer.TYPE_BYTE, (byte) 8, true, false, pool); new TypeMap(UNSIGNED_16BITS, DataBuffer.TYPE_USHORT, (byte)16, false, false, pool); new TypeMap( SIGNED_16BITS, DataBuffer.TYPE_SHORT, (byte)16, true, false, pool); new TypeMap(UNSIGNED_32BITS, DataBuffer.TYPE_INT, (byte)32, false, false, pool); new TypeMap( SIGNED_32BITS, DataBuffer.TYPE_INT, (byte)32, true, false, pool); new TypeMap( REAL_32BITS, DataBuffer.TYPE_FLOAT, (byte)32, true, true, M1, P1, pool); new TypeMap( REAL_64BITS, DataBuffer.TYPE_DOUBLE, (byte)64, true, true, M2, P2, pool); }; /** * One of {@link SampleDimensionType} code list. */ private final SampleDimensionType code; /** * The {@link DataBuffer} type. Must be one of the following constants: * {@link DataBuffer#TYPE_BYTE}, {@link DataBuffer#TYPE_USHORT}, * {@link DataBuffer#TYPE_SHORT}, {@link DataBuffer#TYPE_INT}, * {@link DataBuffer#TYPE_FLOAT}, {@link DataBuffer#TYPE_DOUBLE}. */ private final int type; /** * The size in bits. The value range from 1 to 64. This is different than * {@link DataBuffer#getDataTypeSize}, which have values ranging from 8 to 64. */ private final byte size; /** * {@code true} for signed sample type. */ private final boolean signed; /** * {@code true} for floating-point data type. */ private final boolean real; /** * The full range of sample values. */ private final NumberRange range; /** * The range of positive sample values (excluding 0). This range is non-null only for unsigned * type. A range excluding 0 is sometime usefull when the 0 value is reserved for a "no data" * category. */ private final NumberRange positiveRange; /** * The name as an international string. */ private final InternationalString name = new AbstractInternationalString() { public String toString(final Locale locale) { return Vocabulary.getResources(locale).getString(VocabularyKeys.DATA_TYPE_$2, Integer.valueOf(real ? 2 : signed ? 1 : 0), size); } }; /** * Constructs a new mapping with the specified value. */ private TypeMap(final SampleDimensionType code, final int type, final byte size, final boolean signed, final boolean real, final Map pool) { this(code, type, size, signed, real, null, null, pool); } /** * Constructs a new mapping with the specified value. */ private TypeMap(final SampleDimensionType code, final int type, final byte size, final boolean signed, final boolean real, Number lower, Number upper, final Map pool) { Number one = null; if (lower == null) { final long max = (1L << (signed ? size-1 : size)) - 1; final long min = signed ? ~max : 0; // Tild (~), not minus sign (-). if (max <= Byte.MAX_VALUE) { lower = Byte.valueOf((byte) min); upper = Byte.valueOf((byte) max); one = Byte.valueOf((byte) 1); } else if (max <= Short.MAX_VALUE) { lower = Short.valueOf((short) min); upper = Short.valueOf((short) max); one = Short.valueOf((short) 1); } else if (max <= Integer.MAX_VALUE) { lower = Integer.valueOf((int) min); upper = Integer.valueOf((int) max); one = Integer.valueOf(1); } else { lower = Long.valueOf(min); upper = Long.valueOf(max); one = Long.valueOf(1L); } lower = unique(pool, lower); upper = unique(pool, upper); one = unique(pool, one); assert lower.longValue() == min; assert upper.longValue() == max; } assert ((Comparable) lower).compareTo(upper) < 0 : upper; final Class c = upper.getClass(); this.code = code; this.type = type; this.size = size; this.signed = signed; this.real = real; this.range = new NumberRange(c, lower, upper); this.positiveRange = signed ? null : new NumberRange(c, one, upper); final int ordinal = code.ordinal(); assert MAP[ordinal] == null : code; MAP[ordinal] = this; assert code.equals(getSampleDimensionType(range)) : code; } /** * Returns a single instance of the specified number. */ private static Number unique(final Map pool, final Number n) { final Number candidate = pool.put(n, n); if (candidate == null) { return n; } pool.put(candidate, candidate); return candidate; } /** * Returns the smallest sample dimension type capable to hold the specified range of values. * * @param range The range of values. * @return The smallest sample dimension type for the specified range. */ public static SampleDimensionType getSampleDimensionType(final Range range) { final Class type = range.getElementClass(); if (Double.class.isAssignableFrom(type)) { return REAL_64BITS; } if (Float.class.isAssignableFrom(type)) { return REAL_32BITS; } long min = ((Number) range.getMinValue()).longValue(); long max = ((Number) range.getMaxValue()).longValue(); if (!range.isMinIncluded()) min++; if (!range.isMaxIncluded()) max--; return getSampleDimensionType(min, max); } /** * Returns the smallest sample dimension type capable to hold the specified range of values. * An heuristic approach is used for non-integer values. * * @param min The lower value, inclusive. * @param max The upper value, inclusive as well. * @return The smallest sample dimension type for the specified range. */ public static SampleDimensionType getSampleDimensionType(double min, double max) { final long lgMin = (long) min; if (lgMin == min) { final long lgMax = (long) max; if (lgMax == max) { return getSampleDimensionType(lgMin, lgMax); } } min = Math.abs(min); max = Math.abs(max); if (Math.min(min,max) >= Float.MIN_VALUE && Math.max(min,max) <= Float.MAX_VALUE) { return REAL_32BITS; } return REAL_64BITS; } /** * Returns the smallest sample dimension type capable to hold the specified range of values. * * @param min The lower value, inclusive. * @param max The upper value, inclusive as well. * @return The smallest sample dimension type for the specified range. */ public static SampleDimensionType getSampleDimensionType(final long min, final long max) { if (min >= 0) { if (max < (1L << 1)) return UNSIGNED_1BIT; if (max < (1L << 2)) return UNSIGNED_2BITS; if (max < (1L << 4)) return UNSIGNED_4BITS; if (max < (1L << 8)) return UNSIGNED_8BITS; if (max < (1L << 16)) return UNSIGNED_16BITS; if (max < (1L << 32)) return UNSIGNED_32BITS; } else { if (min>=Byte .MIN_VALUE && max<=Byte .MAX_VALUE) return SIGNED_8BITS; if (min>=Short .MIN_VALUE && max<=Short .MAX_VALUE) return SIGNED_16BITS; if (min>=Integer.MIN_VALUE && max<=Integer.MAX_VALUE) return SIGNED_32BITS; } return REAL_32BITS; } /** * Returns the sample dimension type for the specified sample model and band number. If * the sample model use an undefined data type, then this method returns {@code null}. * * @param model The sample model. * @param band The band to query. * @return The sample dimension type for the specified sample model and band number. * @throws IllegalArgumentException if the band number is not in the valid range. */ @SuppressWarnings("fallthrough") public static SampleDimensionType getSampleDimensionType(final SampleModel model, final int band) throws IllegalArgumentException { if (band<0 || band>=model.getNumBands()) { throw new IllegalArgumentException(Errors.format(ErrorKeys.BAD_BAND_NUMBER_$1, band)); } boolean signed = true; switch (model.getDataType()) { case DataBuffer.TYPE_DOUBLE: return REAL_64BITS; case DataBuffer.TYPE_FLOAT: return REAL_32BITS; case DataBuffer.TYPE_USHORT: // Fall through case DataBuffer.TYPE_BYTE: signed=false; // Fall through case DataBuffer.TYPE_INT: case DataBuffer.TYPE_SHORT: { switch (model.getSampleSize(band)) { case 1: return UNSIGNED_1BIT; case 2: return UNSIGNED_2BITS; case 4: return UNSIGNED_4BITS; case 5: return UNSIGNED_8BITS; // for BufferedImages TYPE_USHORT_555_RGB TYPE_USHORT_565_RGB case 8: return signed ? SIGNED_8BITS : UNSIGNED_8BITS; case 16: return signed ? SIGNED_16BITS : UNSIGNED_16BITS; case 32: return signed ? SIGNED_32BITS : UNSIGNED_32BITS; } } } return null; } /** * Returns the sample dimension type name as an international string. For example, the localized * name for {@link SampleDimensionType#UNSIGNED_16BITS} is "16 bits unsigned integer" * in English and "Entier non-signé sur 16 bits" in French. */ public static InternationalString getName(final SampleDimensionType type) { final int ordinal = type.ordinal(); if (ordinal>=0 && ordinal=0 && ordinal getRange(final SampleDimensionType type) { if (type != null) { final int ordinal = type.ordinal(); if (ordinal>=0 && ordinal getPositiveRange(final SampleDimensionType type) { if (type != null) { final int ordinal = type.ordinal(); if (ordinal>=0 && ordinal=0 && ordinal=ColorUtilities.getNumBands(model)) { throw new IllegalArgumentException(Errors.format(ErrorKeys.BAD_BAND_NUMBER_$1, band)); } if (model instanceof IndexColorModel) { return ColorInterpretation.PALETTE_INDEX; } switch (model.getColorSpace().getType()) { case ColorSpace.TYPE_GRAY: { switch (band) { case 0: return ColorInterpretation.GRAY_INDEX; default: return ColorInterpretation.UNDEFINED; } } case ColorSpace.TYPE_RGB: { switch (band) { case 0: return ColorInterpretation.RED_BAND; case 1: return ColorInterpretation.GREEN_BAND; case 2: return ColorInterpretation.BLUE_BAND; case 3: return ColorInterpretation.ALPHA_BAND; default: return ColorInterpretation.UNDEFINED; } } case ColorSpace.TYPE_HSV: { switch (band) { case 0: return ColorInterpretation.HUE_BAND; case 1: return ColorInterpretation.SATURATION_BAND; case 2: return ColorInterpretation.LIGHTNESS_BAND; default: return ColorInterpretation.UNDEFINED; } } case ColorSpace.TYPE_CMY: case ColorSpace.TYPE_CMYK: { switch (band) { case 0: return ColorInterpretation.CYAN_BAND; case 1: return ColorInterpretation.MAGENTA_BAND; case 2: return ColorInterpretation.YELLOW_BAND; case 3: return ColorInterpretation.BLACK_BAND; default: return ColorInterpretation.UNDEFINED; } } default: return ColorInterpretation.UNDEFINED; } } }