001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2003-2006, Geotools Project Management 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.coverage;
018:
019: // J2SE dependencies
020: import java.awt.Transparency;
021: import java.awt.color.ColorSpace;
022: import java.awt.image.ColorModel;
023: import java.awt.image.ComponentColorModel;
024: import java.awt.image.DataBuffer;
025: import java.awt.image.RenderedImage;
026: import java.util.Arrays;
027: import java.util.Map;
028:
029: // JAI dependencies
030: import javax.media.jai.FloatDoubleColorModel;
031: import javax.media.jai.RasterFactory;
032:
033: // Geotools dependencies
034: import org.geotools.resources.i18n.Errors;
035: import org.geotools.resources.i18n.ErrorKeys;
036: import org.geotools.resources.image.ColorUtilities;
037: import org.geotools.resources.image.ComponentColorModelJAI;
038: import org.geotools.util.WeakValueHashMap;
039:
040: /**
041: * A factory for {@link ColorModel} objects built from a list of {@link Category} objects.
042: * This factory provides only one public static method: {@link #getColorModel}. Instances
043: * of {@link ColorModel} are shared among all callers in the running virtual machine.
044: *
045: * @since 2.1
046: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/coverage/src/main/java/org/geotools/coverage/ColorModelFactory.java $
047: * @version $Id: ColorModelFactory.java 25421 2007-05-05 16:55:18Z desruisseaux $
048: * @author Martin Desruisseaux
049: */
050: final class ColorModelFactory {
051: /**
052: * A pool of color models previously created by {@link #getColorModel}.
053: *
054: * <b>Note:</b> we use {@linkplain java.lang.ref.WeakReference weak references} instead of
055: * {@linkplain java.lang.ref.SoftReference soft references} because the intend is not to
056: * cache the values. The intend is to share existing instances in order to reduce memory
057: * usage. Rational:
058: *
059: * <ul>
060: * <li>{@link ColorModel} may consume a lot of memory. A 16 bits indexed color model
061: * can consume up to 256 kb. We don't want to retain such large objects longer
062: * than necessary. We want to share existing instances without preventing the
063: * garbage collector to collect them.</li>
064: * <li>{@link #getColorModel()} is reasonably fast if invoked only occasionally, so it
065: * is not worth consuming 256 kb for saving the few milliseconds requirying for
066: * building a new color model. Client code should retains their own reference to a
067: * {@link ColorModel} if they plan to reuse it often in a short period of time.</li>
068: * </ul>
069: */
070: private static final Map/*<ColorModelFactory,ColorModel>*/colors = new WeakValueHashMap();
071:
072: /**
073: * The list of categories for the construction of a single instance of a {@link ColorModel}.
074: */
075: private final Category[] categories;
076:
077: /**
078: * The visible band (usually 0) used for the construction
079: * of a single instance of a {@link ColorModel}.
080: */
081: private final int visibleBand;
082:
083: /**
084: * The number of bands (usually 1) used for the construction
085: * of a single instance of a {@link ColorModel}.
086: */
087: private final int numBands;
088:
089: /**
090: * The color model type. One of {@link DataBuffer#TYPE_BYTE}, {@link DataBuffer#TYPE_USHORT},
091: * {@link DataBuffer#TYPE_FLOAT} or {@link DataBuffer#TYPE_DOUBLE}.
092: *
093: * @task TODO: The user may want to set explicitly the number of bits each pixel occupied.
094: * We need to think about an API to allows that.
095: */
096: private final int type;
097:
098: /**
099: * Construct a new {@code ColorModelFactory}. This object will actually be used
100: * as a key in a {@link Map}, so this is not really a {@code ColorModelFactory}
101: * but a kind of "{@code ColorModelKey}" instead. However, since this constructor
102: * is private, user doesn't need to know that.
103: */
104: private ColorModelFactory(final Category[] categories,
105: final int type, final int visibleBand, final int numBands) {
106: this .categories = categories;
107: this .visibleBand = visibleBand;
108: this .numBands = numBands;
109: this .type = type;
110: if (visibleBand < 0 || visibleBand >= numBands) {
111: throw new IllegalArgumentException(Errors.format(
112: ErrorKeys.BAD_BAND_NUMBER_$1, new Integer(
113: visibleBand)));
114: }
115: }
116:
117: /**
118: * Returns a color model for a category set. This method builds up the color model
119: * from each category's colors (as returned by {@link Category#getColors}).
120: *
121: * @param categories The set of categories.
122: * @param type The color model type. One of {@link DataBuffer#TYPE_BYTE},
123: * {@link DataBuffer#TYPE_USHORT}, {@link DataBuffer#TYPE_FLOAT} or
124: * {@link DataBuffer#TYPE_DOUBLE}.
125: * @param visibleBand The band to be made visible (usually 0). All other bands, if any
126: * will be ignored.
127: * @param numBands The number of bands for the color model (usually 1). The returned color
128: * model will renderer only the {@code visibleBand} and ignore the others, but
129: * the existence of all {@code numBands} will be at least tolerated. Supplemental
130: * bands, even invisible, are useful for processing with Java Advanced Imaging.
131: * @return The requested color model, suitable for {@link RenderedImage} objects with values
132: * in the <code>{@link CategoryList#getRange}</code> range.
133: */
134: public static ColorModel getColorModel(final Category[] categories,
135: final int type, final int visibleBand, final int numBands) {
136: synchronized (colors) {
137: ColorModelFactory key = new ColorModelFactory(categories,
138: type, visibleBand, numBands);
139: ColorModel model = (ColorModel) colors.get(key);
140: if (model == null) {
141: model = key.getColorModel();
142: colors.put(key, model);
143: }
144: return model;
145: }
146: }
147:
148: /**
149: * Constructs the color model.
150: */
151: private ColorModel getColorModel() {
152: final int categoryCount = categories.length;
153: if (type != DataBuffer.TYPE_BYTE
154: && type != DataBuffer.TYPE_USHORT) {
155: // If the requested type is any type not supported by IndexColorModel,
156: // fallback on a generic (but very slow!) color model.
157: double min = 0;
158: double max = 1;
159: if (categoryCount != 0) {
160: min = categories[0].minimum;
161: for (int i = categoryCount; --i >= 0;) {
162: final double val = categories[i].maximum;
163: if (!Double.isNaN(val)) {
164: max = val;
165: break;
166: }
167: }
168: }
169: final int transparency = Transparency.OPAQUE;
170: final ColorSpace colors = new ScaledColorSpace(visibleBand,
171: numBands, min, max);
172: if (false) {
173: // This is the J2SE implementation of color model. It should be our preferred one.
174: // Unfortunatly, as of JAI 1.1 we have to use JAI implementation instead of J2SE's
175: // one because javax.media.jai.iterator.RectIter do not work with J2SE's DataBuffer
176: // when the data type is float or double.
177: return new ComponentColorModel(colors, false, false,
178: transparency, type);
179: }
180: if (false) {
181: // This is the JAI implementation of color model. This implementation work with
182: // JAI's RectIter and should in theory support float and double data buffer.
183: // Unfortunatly, it seems to completly ignore our custom ColorSpace. We end
184: // up basically with all-black or all-white images.
185: return new FloatDoubleColorModel(colors, false, false,
186: transparency, type);
187: }
188: if (true) {
189: // Our patched color model extends J2SE's ComponentColorModel (which work correctly
190: // with our custom ColorSpace), but create JAI's SampleModel instead of J2SE's one.
191: // It make RectIter happy and display colors correctly.
192: return new ComponentColorModelJAI(colors, false, false,
193: transparency, type);
194: }
195: // This factory is not really different from a direct construction of
196: // FloatDoubleColorModel. We provide it here just because we must end
197: // with something.
198: return RasterFactory.createComponentColorModel(type,
199: colors, false, false, transparency);
200: }
201: if (numBands == 1 && categoryCount == 0) {
202: // Construct a gray scale palette.
203: final ColorSpace cs = ColorSpace
204: .getInstance(ColorSpace.CS_GRAY);
205: final int[] nBits = { DataBuffer.getDataTypeSize(type) };
206: return new ComponentColorModel(cs, nBits, false, true,
207: Transparency.OPAQUE, type);
208: }
209: /*
210: * Computes the number of entries required for the color palette.
211: * We take the upper range value of the last category.
212: */
213: final int mapSize = (int) Math
214: .round(categories[categoryCount - 1].maximum) + 1;
215: final int[] ARGB = new int[mapSize];
216: /*
217: * Interpolate the colors in the color palette. Colors that do not fall
218: * in the range of a category will be set to a transparent color.
219: */
220: for (int i = 0; i < categoryCount; i++) {
221: final Category category = categories[i];
222: ColorUtilities.expand(category.getColors(), ARGB,
223: (int) Math.round(category.minimum), (int) Math
224: .round(category.maximum) + 1);
225: }
226: return ColorUtilities.getIndexColorModel(ARGB, numBands,
227: visibleBand);
228: }
229:
230: /**
231: * Returns a hash code.
232: */
233: public int hashCode() {
234: final int categoryCount = categories.length;
235: int code = 962745549 + (numBands * 37 + visibleBand) * 37
236: + categoryCount;
237: for (int i = 0; i < categoryCount; i++) {
238: code += categories[i].hashCode();
239: // Better be independant of categories order.
240: }
241: return code;
242: }
243:
244: /**
245: * Check this object with an other one for equality.
246: */
247: public boolean equals(final Object other) {
248: if (other == this ) {
249: return true;
250: }
251: if (other instanceof ColorModelFactory) {
252: final ColorModelFactory that = (ColorModelFactory) other;
253: return this .numBands == that.numBands
254: && this .visibleBand == that.visibleBand
255: && Arrays.equals(this .categories, that.categories);
256: }
257: return false;
258: }
259: }
|