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.image.*;
021: import java.awt.Dimension;
022: import java.lang.ref.Reference;
023: import java.lang.ref.WeakReference;
024: import java.io.IOException;
025: import java.io.FileNotFoundException;
026: import javax.imageio.IIOException;
027: import javax.imageio.ImageTypeSpecifier;
028: import javax.swing.JFrame;
029:
030: import org.geotools.resources.Utilities;
031: import org.geotools.resources.i18n.Errors;
032: import org.geotools.resources.i18n.ErrorKeys;
033:
034: /**
035: * A set of RGB colors created by a {@linkplain PaletteFactory palette factory} from
036: * a {@linkplain #name}. A palette can creates a {@linkplain ColorModel color model}
037: * (often {@linkplain IndexColorModel indexed}) or an {@linkplain ImageTypeSpecifier
038: * image type specifier} from the RGB colors. The color model is retained by the palette
039: * as a {@linkplain WeakReference weak reference} (<strong>not</strong> as a {@linkplain
040: * java.lang.ref.SoftReference soft reference}) because it may consume up to 256 kilobytes.
041: * The purpose of the weak reference is to share existing instances in order to reduce
042: * memory usage; the purpose is not to provide caching.
043: *
044: * @since 2.4
045: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/unsupported/coverageio/src/main/java/org/geotools/image/io/Palette.java $
046: * @version $Id: Palette.java 26708 2007-08-27 19:42:59Z desruisseaux $
047: * @author Antoine Hnawia
048: * @author Martin Desruisseaux
049: */
050: public abstract class Palette {
051: /**
052: * The originating factory.
053: */
054: final PaletteFactory factory;
055:
056: /**
057: * The name of this palette.
058: */
059: protected final String name;
060:
061: /**
062: * The number of bands in the {@linkplain ColorModel color model}.
063: * The value is 1 in the vast majority of cases.
064: */
065: protected final int numBands;
066:
067: /**
068: * The band to display, in the range 0 inclusive to {@link #numBands} exclusive.
069: * This is used when an image contains more than one band but only one band can
070: * be used for computing the colors to display. For example {@link IndexColorModel}
071: * works on only one band.
072: */
073: protected final int visibleBand;
074:
075: /**
076: * The sample model to be given to {@link ImageTypeSpecifier}.
077: */
078: private transient SampleModel samples;
079:
080: /**
081: * A weak reference to the color model. This color model may consume a significant
082: * amount of memory (up to 256 kb). Consequently, we will prefer {@link WeakReference}
083: * over {@link java.lang.ref.SoftReference}. The purpose of this weak reference is to
084: * share existing instances, not to cache it since it is cheap to rebuild.
085: */
086: private transient Reference/*<ColorModel>*/colors;
087:
088: /**
089: * A weak reference to the image specifier to be returned by {@link #getImageTypeSpecifier}.
090: * We use weak reference because the image specifier contains a reference to the color model
091: * and we don't want to prevent it to be garbage collected. See {@link #colors} for an
092: * explanation about why we use weak instead of soft references.
093: */
094: private transient Reference/*<ImageTypeSpecifier>*/specifier;
095:
096: /**
097: * Creates a palette with the specified name.
098: *
099: * @param factory The originating factory.
100: * @param name The palette name.
101: * @param numBands The number of bands (usually 1) to assign to {@link #numBands}.
102: * @param visibleBand The visible band (usually 0) to assign to {@link #visibleBand}.
103: */
104: protected Palette(final PaletteFactory factory, final String name,
105: final int numBands, final int visibleBand) {
106: if (factory == null) {
107: // Can't use factory.getErrorResources() here.
108: throw new IllegalArgumentException(Errors.format(
109: ErrorKeys.NULL_ARGUMENT_$1, "factory"));
110: }
111: if (name == null) {
112: throw new IllegalArgumentException(factory
113: .getErrorResources().getString(
114: ErrorKeys.NULL_ARGUMENT_$1, "name"));
115: }
116: ensureInsideBounds(numBands, 0, 255); // This maximal value is somewhat arbitrary.
117: ensureInsideBounds(visibleBand, 0, numBands - 1);
118: this .factory = factory;
119: this .name = name.trim();
120: this .numBands = numBands;
121: this .visibleBand = visibleBand;
122: }
123:
124: /**
125: * Ensures that the specified values in inside the expected bounds (inclusives).
126: *
127: * @throws IllegalArgumentException if the specified values are outside the bounds.
128: */
129: final void ensureInsideBounds(final int value, final int min,
130: final int max) throws IllegalArgumentException {
131: if (value < min || value > max) {
132: throw new IllegalArgumentException(factory
133: .getErrorResources().getString(
134: ErrorKeys.VALUE_OUT_OF_BOUNDS_$3,
135: new Integer(value), new Integer(min),
136: new Integer(max)));
137: }
138: }
139:
140: /**
141: * Returns the scale from <cite>normalized values</cite> (values in the range [0..1])
142: * to values in the range of this palette.
143: */
144: double getScale() {
145: return 1;
146: }
147:
148: /**
149: * Returns the offset from <cite>normalized values</cite> (values in the range [0..1])
150: * to values in the range of this palette.
151: */
152: double getOffset() {
153: return 0;
154: }
155:
156: /**
157: * Returns the color model for this palette. This method tries to reuse existing
158: * color model if possible, since it may consume a significant amount of memory.
159: *
160: * @throws FileNotFoundException If the RGB values need to be read from a file and this file
161: * (typically inferred from {@link #name}) is not found.
162: * @throws IOException If an other find of I/O error occured.
163: * @throws IIOException If an other kind of error prevent this method to complete.
164: */
165: public synchronized ColorModel getColorModel() throws IOException {
166: if (colors != null) {
167: final ColorModel candidate = (ColorModel) colors.get();
168: if (candidate != null) {
169: return candidate;
170: }
171: }
172: return getImageTypeSpecifier().getColorModel();
173: }
174:
175: /**
176: * Returns the image type specifier for this palette.
177: *
178: * @throws FileNotFoundException If the RGB values need to be read from a file and this file
179: * (typically inferred from {@link #name}) is not found.
180: * @throws IOException If an other find of I/O error occured.
181: * @throws IIOException If an other kind of error prevent this method to complete.
182: */
183: public abstract ImageTypeSpecifier getImageTypeSpecifier()
184: throws IOException;
185:
186: /**
187: * Returns the image type specifier from the cache, or {@code null}.
188: */
189: final ImageTypeSpecifier queryCache() {
190: if (specifier != null) {
191: final ImageTypeSpecifier candidate = (ImageTypeSpecifier) specifier
192: .get();
193: if (candidate != null) {
194: return candidate;
195: }
196: }
197: if (samples != null && colors != null) {
198: final ColorModel candidate = (ColorModel) colors.get();
199: if (candidate != null) {
200: final ImageTypeSpecifier its = new ImageTypeSpecifier(
201: candidate, samples);
202: specifier = new WeakReference(its);
203: return its;
204: }
205: }
206: return null;
207: }
208:
209: /**
210: * Puts the specified image specifier in the cache.
211: */
212: final void cache(final ImageTypeSpecifier its) {
213: samples = its.getSampleModel();
214: colors = new PaletteDisposer.Reference(this , its
215: .getColorModel());
216: specifier = new WeakReference/*<ImageTypeSpecifier>*/(its);
217: }
218:
219: /**
220: * Returns the color palette as an image of the specified size.
221: * This is useful for looking visually at a color palette.
222: *
223: * @param size The image size. The palette will be vertical if
224: * <code>size.{@linkplain Dimension#height height}</code> >
225: * <code>size.{@linkplain Dimension#width width }</code>
226: *
227: * @throws IOException if the color values can't be read.
228: */
229: public RenderedImage getImage(final Dimension size)
230: throws IOException {
231: final IndexColorModel colors;
232: final BufferedImage image;
233: final WritableRaster raster;
234: colors = (IndexColorModel) getColorModel();
235: raster = colors.createCompatibleWritableRaster(size.width,
236: size.height);
237: image = new BufferedImage(colors, raster, false, null);
238: int xmin = raster.getMinX();
239: int ymin = raster.getMinY();
240: int width = raster.getWidth();
241: int height = raster.getHeight();
242: final boolean horizontal = size.width >= size.height;
243: // Computation will be performed as if the image were horizontal.
244: // If it is not, interchanges x and y values.
245: if (!horizontal) {
246: int tmp;
247: tmp = xmin;
248: xmin = ymin;
249: ymin = tmp;
250: tmp = width;
251: width = height;
252: height = tmp;
253: }
254: final int xmax = xmin + width;
255: final int ymax = ymin + height;
256: final double scale = getScale() / width;
257: final double offset = getOffset();
258: for (int x = xmin; x < xmax; x++) {
259: final double value = offset + scale * (x - xmin);
260: for (int y = ymin; y < ymax; y++) {
261: if (horizontal) {
262: raster.setSample(x, y, 0, value);
263: } else {
264: raster.setSample(y, x, 0, value);
265: }
266: }
267: }
268: return image;
269: }
270:
271: /**
272: * Shows the palette in a windows. This is mostly for debugging purpose.
273: *
274: * @throws IOException if the color values can't be read.
275: */
276: public void show() throws IOException {
277: final JFrame frame = new JFrame(toString());
278: frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
279: frame.add(new javax.media.jai.widget.ImageCanvas(
280: getImage(new Dimension(256, 32))));
281: frame.pack();
282: frame.setVisible(true);
283: }
284:
285: /**
286: * Returns a hash value for this palette.
287: */
288: //@Override
289: public int hashCode() {
290: return name.hashCode() + 37 * numBands + 17 * visibleBand;
291: }
292:
293: /**
294: * Compares this palette with the specified object for equality.
295: */
296: //@Override
297: public boolean equals(final Object object) {
298: if (object != null && getClass().equals(object.getClass())) {
299: final Palette that = (Palette) object;
300: return this .numBands == that.numBands
301: && this .visibleBand == that.visibleBand
302: && Utilities.equals(this .name, that.name);
303: /*
304: * Note: we do not compare PaletteFactory on purpose, since two instances could be
305: * identical except for the locale to use for formatting error messages. Because
306: * Palettes are used as keys in the PaletteFactory.palettes pool, we don't want to
307: * get duplicated palettes only because they format error messages differently.
308: */
309: }
310: return false;
311: }
312: }
|