001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2003-2006, Geotools Project Managment 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.image.io;
018:
019: import java.awt.Color;
020: import java.awt.image.DataBuffer;
021: import java.awt.image.IndexColorModel;
022: import java.io.*;
023: import java.net.URL;
024: import java.nio.charset.Charset;
025: import java.text.ParseException;
026: import javax.imageio.ImageReader; // For javadoc
027: import javax.imageio.IIOException;
028: import javax.imageio.spi.ServiceRegistry;
029: import java.util.*;
030:
031: import org.geotools.io.DefaultFileFilter;
032: import org.geotools.io.LineFormat;
033: import org.geotools.resources.Utilities;
034: import org.geotools.resources.i18n.Errors;
035: import org.geotools.resources.i18n.ErrorKeys;
036: import org.geotools.resources.IndexedResourceBundle;
037: import org.geotools.resources.image.ColorUtilities;
038: import org.geotools.util.logging.Logging;
039: import org.geotools.util.CanonicalSet;
040:
041: /**
042: * A factory for {@linkplain IndexColorModel index color models} created from RGB values listed
043: * in files. The palette definition files are text files containing an arbitrary number of lines,
044: * each line containing RGB components ranging from 0 to 255 inclusive. An optional fourth column
045: * may be provided for alpha components. Empty lines and lines starting with the {@code '#'}
046: * character are ignored. Example:
047: *
048: * <blockquote><pre>
049: * # RGB codes for SeaWiFs images
050: * # (chlorophylle-a concentration)
051: *
052: * 033 000 096
053: * 032 000 097
054: * 031 000 099
055: * 030 000 101
056: * 029 000 102
057: * 028 000 104
058: * 026 000 106
059: * 025 000 107
060: * <i>etc...</i>
061: * </pre></blockquote>
062: *
063: * The number of RGB codes doesn't have to match the target {@linkplain IndexColorModel#getMapSize
064: * color map size}. RGB codes will be automatically interpolated as needed.
065: *
066: * @since 2.1
067: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/unsupported/coverageio/src/main/java/org/geotools/image/io/PaletteFactory.java $
068: * @version $Id: PaletteFactory.java 27848 2007-11-12 13:10:32Z desruisseaux $
069: * @author Martin Desruisseaux
070: */
071: public class PaletteFactory {
072: /**
073: * The file which contains a list of available color palettes. This file is optional
074: * and used only in last resort, since scanning a directory content is more reliable.
075: * If such file exists in the same directory than the one that contains the palettes,
076: * this file will be used by {@link #getAvailableNames}.
077: */
078: private static final String LIST_FILE = "list.txt";
079:
080: /**
081: * The default palette factory.
082: */
083: private static PaletteFactory defaultFactory;
084:
085: /**
086: * The fallback factory, or {@code null} if there is none. The fallback factory
087: * will be queried if a palette was not found in current factory.
088: * <p>
089: * This field should be considered as final. It is modified by {@link #addDefault} only.
090: */
091: private PaletteFactory fallback;
092:
093: /**
094: * The class loader from which to load the palette definition files. If {@code null} and
095: * {@link #loader} is null as well, then loading will occurs from the system current
096: * working directory.
097: */
098: private final ClassLoader classloader;
099:
100: /**
101: * An alternative to {@link #classloader} for loading resources. At most one of
102: * {@code classloader} and {@code loader} can be non-null. If both are {@code null},
103: * then loading will occurs from the system current working directory.
104: */
105: private final Class loader;
106:
107: /**
108: * The base directory from which to search for palette definition files.
109: * If {@code null}, then the working directory ({@code "."}) is assumed.
110: */
111: private final File directory;
112:
113: /**
114: * The file extension.
115: */
116: private final String extension;
117:
118: /**
119: * The charset to use for parsing files, or {@code null} for the current default.
120: */
121: private final Charset charset;
122:
123: /**
124: * The locale to use for parsing files, or {@code null} for the current default.
125: */
126: private final Locale locale;
127:
128: /**
129: * The locale to use for formatting error messages, or {@code null} for the current default.
130: * This locale is informative only; there is no garantee that this locale will be really used.
131: */
132: private transient ThreadLocal/*<Locale>*/warningLocales;
133:
134: /**
135: * The set of palettes already created.
136: */
137: private final CanonicalSet/*<Palette>*/palettes = new CanonicalSet();
138:
139: /**
140: * The set of palettes protected from garbage collection. We protect a palette as long as it
141: * holds a reference to a color model - this is necessary in order to prevent multiple creation
142: * of the same {@link IndexColorModel}. The references are cleaned by {@link PaletteDisposer}.
143: */
144: final Set protectedPalettes/*<Palette>*/= new HashSet();
145:
146: /**
147: * Gets the default palette factory. This method creates a default instance looking for
148: * {@code org/geotools/image/io/colors/*.pal} files where {@code '*'} is a palette name.
149: * Next, this method {@linkplain #scanForPlugins scan for plugins} using the default
150: * class loader. The result is cached for subsequent calls to this {@code getDefault()}
151: * method.
152: */
153: public synchronized static PaletteFactory getDefault() {
154: if (defaultFactory == null) {
155: defaultFactory = new PaletteFactory(
156: /* fallback factory */null,
157: /* class loader */PaletteFactory.class,
158: /* root directory */new File("colors"),
159: /* extension */".pal",
160: /* character set */Charset.forName("ISO-8859-1"),
161: /* locale */Locale.US);
162: scanForPlugins(null);
163: }
164: return defaultFactory;
165: }
166:
167: /**
168: * Lookups for additional palette factories on the classpath. The palette factories shall
169: * be declared in {@code META-INF/services/org.geotools.image.io.PaletteFactory} files.
170: * <p>
171: * Palette factories found are added to the chain of default factories. The next time that
172: * a <code>{@linkplain #getDefault()}.getPalette(...)</code> method will be invoked, the
173: * scanned factories will be tried first. If they can't create a given palette, then the
174: * Geotools default factory will be tried last.
175: * <p>
176: * It is usually not needed to invoke this method directly since it is invoked automatically
177: * by {@link #getDefault()} when first needed. This method may be useful when a specific class
178: * loader need to be used, or when the classpath content changed.
179: *
180: * @param loader The class loader to use, or {@code null} for the default one.
181: *
182: * @since 2.4
183: */
184: public synchronized static void scanForPlugins(
185: final ClassLoader loader) {
186: final Set/*<Class>*/existings = new HashSet/*<Class>*/();
187: for (PaletteFactory p = getDefault(); p != null; p = p.fallback) {
188: existings.add(p.getClass());
189: }
190: final Iterator it = (loader == null) ? ServiceRegistry
191: .lookupProviders(PaletteFactory.class)
192: : ServiceRegistry.lookupProviders(PaletteFactory.class,
193: loader);
194: while (it.hasNext()) {
195: /*
196: * Adds the scanned factory to the chain. There is no public method for doing that
197: * because PaletteFactory is quasi-immutable except for this method which modifies
198: * the fallback field. It is okay in this context since we just created the factory
199: * instance.
200: */
201: final PaletteFactory factory = (PaletteFactory) it.next();
202: if (existings.add(factory.getClass())) {
203: factory.fallback = defaultFactory;
204: defaultFactory = factory;
205: }
206: }
207: }
208:
209: /**
210: * Constructs a palette factory using an optional {@linkplain ClassLoader class loader}
211: * for loading palette definition files.
212: *
213: * @param fallback An optional fallback factory, or {@code null} if there is none. The fallback
214: * factory will be queried if a palette was not found in the current factory.
215: * @param loader An optional class loader to use for loading the palette definition files.
216: * If {@code null}, loading will occurs from the system current working
217: * directory.
218: * @param directory The base directory for palette definition files. It may be a Java package
219: * if a {@code loader} were specified. If {@code null}, then {@code "."} is
220: * assumed.
221: * @param extension File name extension, or {@code null} if there is no extension
222: * to add to filename. If non-null, this extension will be automatically
223: * appended to filename. It should starts with the {@code '.'} character.
224: * @param charset The charset to use for parsing files, or {@code null} for the default.
225: * @param locale The locale to use for parsing files, or {@code null} for the default.
226: */
227: public PaletteFactory(final PaletteFactory fallback,
228: final ClassLoader loader, final File directory,
229: String extension, final Charset charset, final Locale locale) {
230: if (extension != null && !extension.startsWith(".")) {
231: extension = '.' + extension;
232: }
233: this .fallback = fallback;
234: this .classloader = loader;
235: this .loader = null;
236: this .directory = directory;
237: this .extension = extension;
238: this .charset = charset;
239: this .locale = locale;
240: }
241:
242: /**
243: * Constructs a palette factory using an optional {@linkplain Class class} for loading
244: * palette definition files. Using a {@linkplain Class class} instead of a {@linkplain
245: * ClassLoader class loader} can avoid security issue on some platforms (some platforms
246: * do not allow to load resources from a {@code ClassLoader} because it can load from the
247: * root package).
248: *
249: * @param fallback An optional fallback factory, or {@code null} if there is none. The fallback
250: * factory will be queried if a palette was not found in the current factory.
251: * @param loader An optional class to use for loading the palette definition files.
252: * If {@code null}, loading will occurs from the system current working
253: * directory.
254: * @param directory The base directory for palette definition files. It may be a Java package
255: * if a {@code loader} were specified. If {@code null}, then {@code "."} is
256: * assumed.
257: * @param extension File name extension, or {@code null} if there is no extension
258: * to add to filename. If non-null, this extension will be automatically
259: * appended to filename. It should starts with the {@code '.'} character.
260: * @param charset The charset to use for parsing files, or {@code null} for the default.
261: * @param locale The locale to use for parsing files. or {@code null} for the default.
262: *
263: * @since 2.2
264: */
265: public PaletteFactory(final PaletteFactory fallback,
266: final Class loader, final File directory, String extension,
267: final Charset charset, final Locale locale) {
268: if (extension != null && !extension.startsWith(".")) {
269: extension = '.' + extension;
270: }
271: this .fallback = fallback;
272: this .classloader = null;
273: this .loader = loader;
274: this .directory = directory;
275: this .extension = extension;
276: this .charset = charset;
277: this .locale = locale;
278: }
279:
280: /**
281: * Sets the locale to use for formatting warning or error messages. This is typically the
282: * {@linkplain ImageReader#getLocale image reader locale}. This locale is informative only;
283: * there is no garantee that this locale will be really used.
284: * <p>
285: * This method sets the locale for the current thread only. It is safe to use this palette
286: * factory concurrently in many threads, each with their own locale.
287: *
288: * @param warningLocale The locale for warning or error messages, or {@code null} for the
289: * default locale
290: *
291: * @since 2.4
292: */
293: public synchronized void setWarningLocale(final Locale warningLocale) {
294: if (warningLocales == null) {
295: if (warningLocale == null) {
296: return;
297: }
298: warningLocales = warningLocales();
299: }
300: // TODO: use 'remove' on warningLocale==null when we will be allowed to compile for J2SE 1.5.
301: warningLocales.set(warningLocale);
302: }
303:
304: /**
305: * Gets the {@linkplain #warningLocales} from the fallback or create a new one. This
306: * method invokes itself recursively in order to assign the same {@link ThreadLocal}
307: * to every factories in the chain.
308: */
309: private synchronized ThreadLocal warningLocales() {
310: if (warningLocales == null) {
311: warningLocales = (fallback != null) ? fallback
312: .warningLocales() : new ThreadLocal();
313: }
314: return warningLocales;
315: }
316:
317: /**
318: * Returns the locale set by the last invocation to {@link #setWarningLocale} in the
319: * current thread.
320: *
321: * @since 2.4
322: */
323: public Locale getWarningLocale() {
324: final ThreadLocal warningLocales = this .warningLocales;
325: // Protected 'warningLocales' from changes so there is no need to synchronize.
326: return (warningLocales != null) ? (Locale) warningLocales.get()
327: : null;
328: }
329:
330: /**
331: * Returns the resources for formatting error messages.
332: */
333: final IndexedResourceBundle getErrorResources() {
334: return Errors.getResources(getWarningLocale());
335: }
336:
337: /**
338: * Returns an input stream for reading the specified resource. The default
339: * implementation delegates to the {@link Class#getResourceAsStream(String) Class} or
340: * {@link ClassLoader#getResourceAsStream(String) ClassLoader} method of the same name,
341: * according the {@code loader} argument type given to the constructor. Subclasses may
342: * override this method if a more elaborated mechanism is wanted for fetching resources.
343: * This is sometime required in the context of applications using particular class loaders.
344: *
345: * @param name The name of the resource to load, constructed as {@code directory} + {@code name}
346: * + {@code extension} where <var>directory</var> and <var>extension</var> were
347: * specified to the constructor, while {@code name} was given to the
348: * {@link #getPalette} method.
349: * @return The input stream, or {@code null} if the resources was not found.
350: *
351: * @since 2.3
352: */
353: protected InputStream getResourceAsStream(final String name) {
354: if (loader != null) {
355: return loader.getResourceAsStream(name);
356: }
357: if (classloader != null) {
358: return classloader.getResourceAsStream(name);
359: }
360: return null;
361: }
362:
363: /**
364: * Returns the list of available palette names. Any item in this list can be specified as
365: * argument to {@link #getPalette}.
366: *
367: * @return The list of available palette name, or {@code null} if this method
368: * is unable to fetch this information.
369: */
370: public String[] getAvailableNames() {
371: final Set names = new TreeSet();
372: PaletteFactory factory = this ;
373: do {
374: factory.getAvailableNames(names);
375: factory = factory.fallback;
376: } while (factory != null);
377: return (String[]) names.toArray(new String[names.size()]);
378: }
379:
380: /**
381: * Adds available palette names to the specified collection.
382: */
383: private void getAvailableNames(final Collection names) {
384: /*
385: * First, parses the content of every "list.txt" files found on the classpath. Those files
386: * are optional. But if they are present, we assume that their content are accurate.
387: */
388: String filename = new File(directory, LIST_FILE).getPath();
389: BufferedReader in = getReader(LIST_FILE, "getAvailableNames");
390: try {
391: if (in != null) {
392: readNames(in, names);
393: }
394: if (classloader != null) {
395: for (final Enumeration it = classloader
396: .getResources(filename); it.hasMoreElements();) {
397: final URL url = (URL) it.nextElement();
398: in = getReader(url.openStream());
399: readNames(in, names);
400: }
401: }
402: } catch (IOException e) {
403: /*
404: * Logs a warning but do not stop. The only consequence is that the names list
405: * will be incomplete. We log the message as if came from getAvailableNames(),
406: * which is the public method that invoked this one.
407: */
408: Logging.unexpectedException("org.geotools.image.io",
409: PaletteFactory.class, "getAvailableNames", e);
410: }
411: /*
412: * After the "list.txt" files, check if the resources can be read as a directory.
413: * It may happen if the classpath point toward a directory of .class files rather
414: * than a JAR file.
415: */
416: File dir = (directory != null) ? directory : new File(".");
417: if (classloader != null) {
418: dir = toFile(classloader.getResource(dir.getPath()));
419: if (dir == null) {
420: // Directory not found.
421: return;
422: }
423: } else if (loader != null) {
424: dir = toFile(loader.getResource(dir.getPath()));
425: if (dir == null) {
426: // Directory not found.
427: return;
428: }
429: }
430: if (!dir.isDirectory()) {
431: return;
432: }
433: final String[] list = dir.list(new DefaultFileFilter(
434: '*' + extension));
435: final int extLg = extension.length();
436: for (int i = 0; i < list.length; i++) {
437: filename = list[i];
438: final int lg = filename.length();
439: if (lg > extLg
440: && filename.regionMatches(true, lg - extLg,
441: extension, 0, extLg)) {
442: names.add(filename.substring(0, lg - extLg));
443: }
444: }
445: }
446:
447: /**
448: * Copies the content of the specified reader to the specified collection.
449: * The reader is closed after this operation.
450: */
451: private static void readNames(final BufferedReader in,
452: final Collection names) throws IOException {
453: String line;
454: while ((line = in.readLine()) != null) {
455: line = line.trim();
456: if (line.length() != 0 && line.charAt(0) != '#') {
457: names.add(line);
458: }
459: }
460: in.close();
461: }
462:
463: /**
464: * Transforms an {@link URL} into a {@link File}. If the URL can't be
465: * interpreted as a file, then this method returns {@code null}.
466: */
467: private static File toFile(final URL url) {
468: if (url != null && url.getProtocol().equalsIgnoreCase("file")) {
469: return new File(url.getPath());
470: }
471: return null;
472: }
473:
474: /**
475: * Returns a buffered reader for the specified palette.
476: *
477: * @param The palette's name to load. This name doesn't need to contains a path
478: * or an extension. Path and extension are set according value specified
479: * at construction time.
480: * @return A buffered reader to read {@code name}, or {@code null} if the resource is not found.
481: */
482: private LineNumberReader getPaletteReader(String name) {
483: if (extension != null && !name.endsWith(extension)) {
484: name += extension;
485: }
486: return getReader(name, "getPalette");
487: }
488:
489: /**
490: * Returns a buffered reader for the specified filename.
491: *
492: * @param The filename. Path and extension are set according value specified
493: * at construction time.
494: * @return A buffered reader to read {@code name}, or {@code null} if the resource is not found.
495: */
496: private LineNumberReader getReader(final String name,
497: final String caller) {
498: final File file = new File(directory, name);
499: final String path = file.getPath().replace(File.separatorChar,
500: '/');
501: InputStream stream;
502: try {
503: stream = getResourceAsStream(path);
504: if (stream == null) {
505: if (file.canRead())
506: try {
507: stream = new FileInputStream(file);
508: } catch (FileNotFoundException e) {
509: /*
510: * Should not occurs, since we checked for file existence. This is not a fatal
511: * error however, since this method is allowed to returns null if the resource
512: * is not available.
513: */
514: Logging.unexpectedException(
515: "org.geotools.image.io",
516: PaletteFactory.class, caller, e);
517: return null;
518: }
519: else {
520: return null;
521: }
522: }
523: } catch (SecurityException e) {
524: Utilities.recoverableException("org.geotools.image.io",
525: PaletteFactory.class, caller, e);
526: return null;
527: }
528: return getReader(stream);
529: }
530:
531: /**
532: * Wraps the specified input stream into a reader.
533: */
534: private LineNumberReader getReader(final InputStream stream) {
535: return new LineNumberReader(
536: (charset != null) ? new InputStreamReader(stream,
537: charset) : new InputStreamReader(stream));
538: }
539:
540: /**
541: * Reads the colors declared in the specified input stream. Colors must be encoded on 3 or 4
542: * columns. If 3 columns, it is assumed RGB values. If 4 columns, it is assumed RGBA values.
543: * Values must be in the 0-255 ranges. Empty lines and lines starting by {@code '#'} are
544: * ignored.
545: *
546: * @param input The stream to read.
547: * @param name The palette name to read. Used for formatting error message only.
548: * @return The colors.
549: * @throws IOException if an I/O error occured.
550: * @throws IIOException if a syntax error occured.
551: */
552: private Color[] getColors(final LineNumberReader input,
553: final String name) throws IOException {
554: int values[] = null;
555: final LineFormat reader = (locale != null) ? new LineFormat(
556: locale) : new LineFormat();
557: final List colors = new ArrayList();
558: String line;
559: while ((line = input.readLine()) != null)
560: try {
561: line = line.trim();
562: if (line.length() == 0)
563: continue;
564: if (line.charAt(0) == '#')
565: continue;
566: if (reader.setLine(line) == 0)
567: continue;
568: values = reader.getValues(values);
569: int A = 255, R, G, B;
570: switch (values.length) {
571: case 4:
572: A = byteValue(values[3]); // fall through
573: case 3:
574: B = byteValue(values[2]);
575: G = byteValue(values[1]);
576: R = byteValue(values[0]);
577: break;
578: default: {
579: throw syntaxError(input, name, null);
580: }
581: }
582: final Color color;
583: try {
584: color = new Color(R, G, B, A);
585: } catch (IllegalArgumentException exception) {
586: /*
587: * Color constructor checks the RGBA value and throws an IllegalArgumentException
588: * if they are not in the 0-255 range. Intercept this exception and rethrows as a
589: * checked IIOException, since we want to notify the user that the palette file is
590: * badly formatted. (additional note: it is somewhat redundant with byteValue(int)
591: * work. Lets keep it as a safety).
592: */
593: throw syntaxError(input, name, exception);
594: }
595: colors.add(color);
596: } catch (ParseException exception) {
597: throw syntaxError(input, name, exception);
598: }
599: return (Color[]) colors.toArray(new Color[colors.size()]);
600: }
601:
602: /**
603: * Prepares an exception for the specified cause, which may be {@code null}.
604: */
605: private IIOException syntaxError(final LineNumberReader input,
606: final String name, final Exception cause) {
607: String message = getErrorResources().getString(
608: ErrorKeys.BAD_LINE_IN_FILE_$2, name,
609: new Integer(input.getLineNumber()));
610: if (cause != null) {
611: message += cause.getLocalizedMessage();
612: }
613: return new IIOException(message, cause);
614: }
615:
616: /**
617: * Load colors from an URL.
618: *
619: * @param url The palette's URL.
620: * @return The set of colors, or {@code null} if the set was not found.
621: * @throws IOException if an error occurs during reading.
622: * @throws IIOException if an error occurs during parsing.
623: *
624: * @deprecated This method should not be defined here since {@code PaletteFactory} is all
625: * about name relative to a directory specified at construction time. If a user
626: * wants the functionality provided by this method, he should consider creating
627: * a new instance of {@code PaletteFactory}.
628: */
629: public Color[] getColors(final URL url) throws IOException {
630: final InputStream stream = url.openStream();
631: final LineNumberReader reader = new LineNumberReader(
632: (charset != null) ? new InputStreamReader(stream,
633: charset) : new InputStreamReader(stream));
634: final Color[] colors = getColors(reader, url.getFile());
635: reader.close();
636: return colors;
637: }
638:
639: /**
640: * Loads colors from a definition file. If no colors were found in the current palette
641: * factory and a fallback was specified at construction time, then the fallback will
642: * be queried.
643: *
644: * @param name The palette's name to load. This name doesn't need to contains a path
645: * or an extension. Path and extension are set according value specified
646: * at construction time.
647: * @return The set of colors, or {@code null} if the set was not found.
648: * @throws IOException if an error occurs during reading.
649: * @throws IIOException if an error occurs during parsing.
650: */
651: public Color[] getColors(final String name) throws IOException {
652: final LineNumberReader reader = getPaletteReader(name);
653: if (reader == null) {
654: return (fallback != null) ? fallback.getColors(name) : null;
655: }
656: final Color[] colors = getColors(reader, name);
657: reader.close();
658: return colors;
659: }
660:
661: /**
662: * Loads an index color model from a definition file.
663: * The returned model will use index from 0 to 255 inclusive.
664: *
665: * @param name The palette's name to load. This name doesn't need to contains a path
666: * or an extension. Path and extension are set according value specified
667: * at construction time.
668: * @return The index color model, or {@code null} if the palettes was not found.
669: *
670: * @throws IOException if an error occurs during reading.
671: * @throws IIOException if an error occurs during parsing.
672: *
673: * @deprecated Replaced by {@link Palette#getColorModel}.
674: */
675: public IndexColorModel getIndexColorModel(final String name)
676: throws IOException {
677: return getIndexColorModel(name, 0, 256);
678: }
679:
680: /**
681: * Loads an index color model from a definition file.
682: * The returned model will use index from {@code lower} inclusive to
683: * {@code upper} exclusive. Other index will have a transparent color.
684: *
685: * @param name The palette's name to load. This name doesn't need to contains a path
686: * or an extension. Path and extension are set according value specified
687: * at construction time.
688: * @param lower Palette's lower index (inclusive).
689: * @param upper Palette's upper index (exclusive).
690: * @return The index color model, or {@code null} if the palettes was not found.
691: *
692: * @throws IOException if an error occurs during reading.
693: * @throws IIOException if an error occurs during parsing.
694: *
695: * @since 2.3
696: *
697: * @deprecated Replaced by {@link Palette#getColorModel}.
698: */
699: public IndexColorModel getIndexColorModel(final String name,
700: final int lower, final int upper) throws IOException {
701: if (lower < 0) {
702: throw new IllegalArgumentException(getErrorResources()
703: .getString(ErrorKeys.ILLEGAL_ARGUMENT_$2, "lower",
704: new Integer(lower)));
705: }
706: if (upper <= lower) {
707: throw new IllegalArgumentException(getErrorResources()
708: .getString(ErrorKeys.BAD_RANGE_$2,
709: new Integer(lower), new Integer(upper)));
710: }
711: final Color[] colors = getColors(name);
712: if (colors == null) {
713: return (fallback != null) ? fallback.getIndexColorModel(
714: name, lower, upper) : null;
715: }
716: final int[] ARGB = new int[1 << ColorUtilities
717: .getBitCount(upper)];
718: ColorUtilities.expand(colors, ARGB, lower, upper);
719: return ColorUtilities.getIndexColorModel(ARGB);
720: }
721:
722: /**
723: * Ensure that the specified valus is inside the {@code [0..255]} range.
724: * If the value is outside that range, a {@link ParseException} is thrown.
725: */
726: private int byteValue(final int value) throws ParseException {
727: if (value >= 0 && value < 256) {
728: return value;
729: }
730: throw new ParseException(getErrorResources().getString(
731: ErrorKeys.RGB_OUT_OF_RANGE_$1, new Integer(value)), 0);
732: }
733:
734: /**
735: * Returns the palette of the specified name and size. The palette's name doesn't need
736: * to contains a directory path or an extension. Path and extension are set according
737: * values specified at construction time.
738: *
739: * @param name The palette's name to load.
740: * @param size The {@linkplain IndexColorModel index color model} size.
741: * @return The palette.
742: *
743: * @since 2.4
744: */
745: public Palette getPalette(final String name, final int size) {
746: return getPalette(name, 0, size, size, 1, 0);
747: }
748:
749: /**
750: * Returns a palette with a <cite>pad value</cite> at index 0.
751: *
752: * @param name The palette's name to load.
753: * @param size The {@linkplain IndexColorModel index color model} size.
754: * @return The palette.
755: *
756: * @since 2.4
757: */
758: public Palette getPalettePadValueFirst(final String name,
759: final int size) {
760: return getPalette(name, 1, size, size, 1, 0);
761: }
762:
763: /**
764: * Returns a palette with <cite>pad value</cite> at the last index.
765: *
766: * @param name The palette's name to load.
767: * @param size The {@linkplain IndexColorModel index color model} size.
768: * @return The palette.
769: *
770: * @since 2.4
771: */
772: public Palette getPalettePadValueLast(final String name,
773: final int size) {
774: return getPalette(name, 0, size - 1, size, 1, 0);
775: }
776:
777: /**
778: * Returns the palette of the specified name and size. The RGB colors will be distributed
779: * in the range {@code lower} inclusive to {@code upper} exclusive. Remaining pixel values
780: * (if any) will be left to a black or transparent color by default.
781: * <p>
782: * The palette's name doesn't need to contains a directory path or an extension.
783: * Path and extension are set according values specified at construction time.
784: *
785: * @param name The palette's name to load.
786: * @param lower Index of the first valid element (inclusive) in the
787: * {@linkplain IndexColorModel index color model} to be created.
788: * @param upper Index of the last valid element (exclusive) in the
789: * {@linkplain IndexColorModel index color model} to be created.
790: * @param size The size of the {@linkplain IndexColorModel index color model} to be created.
791: * This is the value to be returned by {@link IndexColorModel#getMapSize}.
792: * @param numBands The number of bands (usually 1).
793: * @param visibleBand The band to use for color computations (usually 0).
794: * @return The palette.
795: *
796: * @since 2.4
797: */
798: public Palette getPalette(final String name, final int lower,
799: final int upper, final int size, final int numBands,
800: final int visibleBand) {
801: Palette palette = new IndexedPalette(this , name, lower, upper,
802: size, numBands, visibleBand);
803: palette = (Palette) palettes.unique(palette);
804: return palette;
805: }
806:
807: /**
808: * Creates a palette suitable for floating point values.
809: *
810: * @param name The palette name.
811: * @param minimum The minimal sample value expected.
812: * @param maximum The maximal sample value expected.
813: * @param dataType The data type as a {@link DataBuffer#TYPE_FLOAT}
814: * or {@link DataBuffer#TYPE_DOUBLE} constant.
815: * @param numBands The number of bands (usually 1).
816: * @param visibleBand The band to use for color computations (usually 0).
817: *
818: * @since 2.4
819: *
820: * @todo Current implementation ignores the name and builds a gray scale in all cases.
821: * Future version may improve on that.
822: */
823: public Palette getContinuousPalette(final String name,
824: final float minimum, final float maximum,
825: final int dataType, final int numBands,
826: final int visibleBand) {
827: Palette palette = new ContinuousPalette(this , name, minimum,
828: maximum, dataType, numBands, visibleBand);
829: palette = (Palette) palettes.unique(palette);
830: return palette;
831: }
832: }
|