001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2006, GeoTools Project Managment Committee (PMC)
005: * (C) 2005, Institut de Recherche pour le Développement
006: * (C) 2006, Geomatys
007: *
008: * This library is free software; you can redistribute it and/or
009: * modify it under the terms of the GNU Lesser General Public
010: * License as published by the Free Software Foundation;
011: * version 2.1 of the License.
012: *
013: * This library is distributed in the hope that it will be useful,
014: * but WITHOUT ANY WARRANTY; without even the implied warranty of
015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
016: * Lesser General Public License for more details.
017: */
018: package org.geotools.image.io;
019:
020: import java.awt.Color;
021: import java.awt.image.*;
022: import javax.imageio.ImageTypeSpecifier;
023: import javax.imageio.IIOException;
024: import java.io.FileNotFoundException;
025: import java.io.IOException;
026:
027: import org.geotools.resources.i18n.ErrorKeys;
028: import org.geotools.resources.image.ColorUtilities;
029:
030: /**
031: * A set of RGB colors created by a {@linkplain PaletteFactory palette factory} from a name.
032: * A palette can creates an {@linkplain IndexColorModel index color model} or an {@linkplain
033: * ImageTypeSpecifier image type specifier} from the RGB colors. The color model is retained
034: * by the palette as a {@linkplain WeakReference weak reference} (<strong>not</strong> as a
035: * {@linkplain java.lang.ref.SoftReference soft reference}) because it may consume up to 256
036: * kb. The purpose of the weak reference is to share existing instances in order to reduce
037: * memory usage; the purpose is not to provide caching.
038: *
039: * @since 2.4
040: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/unsupported/coverageio/src/main/java/org/geotools/image/io/IndexedPalette.java $
041: * @version $Id: IndexedPalette.java 26708 2007-08-27 19:42:59Z desruisseaux $
042: * @author Antoine Hnawia
043: * @author Martin Desruisseaux
044: */
045: final class IndexedPalette extends Palette {
046: /**
047: * The maximal allowed value, corresponding to the maximum value for unsigned 16 bits integer.
048: * DO NOT EDIT: this value <strong>MUST</strong> be {@code 0xFFFF}.
049: */
050: private static final int MAX_UNSIGNED = 0xFFFF;
051:
052: /**
053: * Index of the first valid element (inclusive) in the {@linkplain IndexColorModel
054: * index color model} to be created. Pixels in the range 0 inclusive to {@code lower}
055: * exclusive will be reserved for "no data" values.
056: * <p>
057: * Strictly speaking, this index should be non-negative because {@link IndexColorModel}
058: * do not supports negative index. However this {@code Palette} implementation accepts
059: * negative values provided that {@link #upper} is not greater than {@value Short#MAX_VALUE}.
060: * If this condition holds, then {@code Palette} will transpose negative values as positive
061: * values in the range {@code 0x80000} to {@code 0xFFFF} inclusive. Be aware that such
062: * approach consume the maximal amount of memory, i.e. 256 kilobytes for each color model.
063: */
064: protected final int lower;
065:
066: /**
067: * Index of the last valid element (exclusive) in the {@linkplain IndexColorModel
068: * index color model} to be created. Pixels in the range {@code upper} inclusive
069: * to {@link #size} exclusive will be reserved for "no data" values. This value
070: * is always greater than {@link #lower} (note that it may be negative).
071: */
072: protected final int upper;
073:
074: /**
075: * The size of the {@linkplain IndexColorModel index color model} to be created.
076: * This is the value to be returned by {@link IndexColorModel#getMapSize}. This
077: * value is always positive.
078: */
079: protected final int size;
080:
081: /**
082: * Creates a palette with the specified name and size. The RGB colors will be distributed
083: * in the range {@code lower} inclusive to {@code upper} exclusive. Remaining pixel values
084: * (if any) will be left to a black or transparent color by default.
085: *
086: * @param factory The originating factory.
087: * @param name The palette name.
088: * @param lower Index of the first valid element (inclusive) in the
089: * {@linkplain IndexColorModel index color model} to be created.
090: * @param upper Index of the last valid element (exclusive) in the
091: * {@linkplain IndexColorModel index color model} to be created.
092: * @param size The size of the {@linkplain IndexColorModel index color model} to be created.
093: * This is the value to be returned by {@link IndexColorModel#getMapSize}.
094: * @param numBands The number of bands (usually 1).
095: * @param visibleBand The band to use for color computations (usually 0).
096: */
097: protected IndexedPalette(final PaletteFactory factory,
098: final String name, final int lower, final int upper,
099: int size, final int numBands, final int visibleBand) {
100: super (factory, name, numBands, visibleBand);
101: final int minAllowed, maxAllowed; // inclusives
102: if (lower < 0) {
103: minAllowed = Short.MIN_VALUE;
104: maxAllowed = Short.MAX_VALUE;
105: size = (size <= 0x100) ? 0x100 : (MAX_UNSIGNED + 1);
106: // 'size' must be FF or FFFF in order to rool negative values.
107: } else {
108: minAllowed = 0;
109: maxAllowed = MAX_UNSIGNED;
110: }
111: ensureInsideBounds(lower, minAllowed, maxAllowed);
112: ensureInsideBounds(upper, minAllowed, maxAllowed + 1);
113: ensureInsideBounds(size, upper, MAX_UNSIGNED + 1);
114: if (lower >= upper) {
115: throw new IllegalArgumentException(factory
116: .getErrorResources().getString(
117: ErrorKeys.BAD_RANGE_$2, new Integer(lower),
118: new Integer(upper)));
119: }
120: this .lower = lower;
121: this .upper = upper;
122: this .size = size;
123: }
124:
125: /**
126: * Returns the scale from <cite>normalized values</cite> (values in the range [0..1])
127: * to values in the range of this palette.
128: */
129: //@Override
130: double getScale() {
131: return upper - lower;
132: }
133:
134: /**
135: * Returns the offset from <cite>normalized values</cite> (values in the range [0..1])
136: * to values in the range of this palette.
137: */
138: //@Override
139: double getOffset() {
140: return lower;
141: }
142:
143: /**
144: * Creates and returns ARGB values for the {@linkplain IndexColorModel index color model} to be
145: * created. This method is invoked automatically the first time the color model is required, or
146: * when it need to be rebuilt.
147: *
148: * @throws FileNotFoundException If the RGB values need to be read from a file and this file
149: * (typically inferred from {@link #name}) is not found.
150: * @throws IOException If an other find of I/O error occured.
151: * @throws IIOException If an other kind of error prevent this method to complete.
152: */
153: private int[] createARGB() throws IOException {
154: final Color[] colors = factory.getColors(name);
155: if (colors == null) {
156: throw new FileNotFoundException(factory.getErrorResources()
157: .getString(ErrorKeys.FILE_DOES_NOT_EXIST_$1, name));
158: }
159: final int[] ARGB = new int[size];
160: if (lower >= 0) {
161: ColorUtilities.expand(colors, ARGB, lower, upper);
162: } else {
163: ColorUtilities.expand(colors, ARGB, 0, upper - lower);
164: final int negativeStart = size + lower;
165: final int negativeCount = -lower;
166: final int[] negatives = new int[negativeCount];
167: System.arraycopy(ARGB, 0, negatives, 0, negativeCount);
168: System.arraycopy(ARGB, negativeCount, ARGB, 0,
169: negativeStart);
170: System.arraycopy(negatives, 0, ARGB, negativeStart,
171: negativeCount);
172: }
173: return ARGB;
174: }
175:
176: /**
177: * Returns the image type specifier for this palette. This method tries to reuse existing
178: * color model if possible, since it may consume a significant amount of memory.
179: *
180: * @throws FileNotFoundException If the RGB values need to be read from a file and this file
181: * (typically inferred from {@linkplain #name name}) is not found.
182: * @throws IOException If an other find of I/O error occured.
183: * @throws IIOException If an other kind of error prevent this method to complete.
184: */
185: public synchronized ImageTypeSpecifier getImageTypeSpecifier()
186: throws IOException {
187: /*
188: * First checks the weak references.
189: */
190: ImageTypeSpecifier its = queryCache();
191: if (its != null) {
192: return its;
193: }
194: /*
195: * Nothing reacheable. Rebuild the specifier.
196: */
197: final int[] ARGB = createARGB();
198: final int bits = ColorUtilities.getBitCount(ARGB.length);
199: final int type = (bits <= 8) ? DataBuffer.TYPE_BYTE
200: : DataBuffer.TYPE_USHORT;
201: final boolean packed = (bits == 1 || bits == 2 || bits == 4);
202: final boolean dense = (packed || bits == 8 || bits == 16);
203: if (dense && (1 << bits) == ARGB.length && numBands == 1) {
204: final byte[] A = new byte[ARGB.length];
205: final byte[] R = new byte[ARGB.length];
206: final byte[] G = new byte[ARGB.length];
207: final byte[] B = new byte[ARGB.length];
208: for (int i = 0; i < ARGB.length; i++) {
209: int code = ARGB[i];
210: B[i] = (byte) ((code) & 0xFF);
211: G[i] = (byte) ((code >>>= 8) & 0xFF);
212: R[i] = (byte) ((code >>>= 8) & 0xFF);
213: A[i] = (byte) ((code >>>= 8) & 0xFF);
214: }
215: its = ImageTypeSpecifier.createIndexed(R, G, B, A, bits,
216: type);
217: } else {
218: /*
219: * The "ImageTypeSpecifier.createIndexed(...)" method is too strict. The IndexColorModel
220: * constructor is more flexible. This block mimic the "ImageTypeSpecifier.createIndexed"
221: * work without the constraints imposed by "createIndexed". Being more flexible consume
222: * less memory for the color palette, since we don't force it to be 64 kb in the USHORT
223: * data type case.
224: */
225: final IndexColorModel colors = ColorUtilities
226: .getIndexColorModel(ARGB, numBands, visibleBand);
227: final SampleModel samples;
228: if (packed) {
229: samples = new MultiPixelPackedSampleModel(type, 1, 1,
230: bits);
231: } else {
232: samples = new PixelInterleavedSampleModel(type, 1, 1,
233: 1, 1, new int[1]);
234: }
235: its = new ImageTypeSpecifier(colors, samples);
236: }
237: cache(its);
238: return its;
239: }
240:
241: /**
242: * Returns a hash value for this palette.
243: */
244: //@Override
245: public int hashCode() {
246: return super .hashCode() + 37
247: * (lower + 37 * (upper + 37 * size));
248: }
249:
250: /**
251: * Compares this palette with the specified object for equality.
252: */
253: //@Override
254: public boolean equals(final Object object) {
255: if (object == this ) {
256: return true;
257: }
258: if (super .equals(object)) {
259: final IndexedPalette that = (IndexedPalette) object;
260: return this .lower == that.lower && this .upper == that.upper
261: && this .size == that.size;
262: }
263: return false;
264: }
265:
266: /**
267: * Returns a string representation of this palette. Used for debugging purpose only.
268: */
269: //@Override
270: public String toString() {
271: return name + " [" + lower + "..." + (upper - 1) + "] size="
272: + size;
273: }
274: }
|