/* * Geotoolkit.org - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2001-2012, Open Source Geospatial Foundation (OSGeo) * (C) 2009-2012, Geomatys * * 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.geotoolkit.image.palette; import java.io.*; import java.util.*; import java.net.URL; import java.nio.charset.Charset; import java.text.ParseException; import java.awt.Color; import java.awt.image.ColorModel; import java.awt.image.IndexColorModel; import javax.imageio.IIOException; import org.geotoolkit.io.LineFormat; import org.geotoolkit.io.DefaultFileFilter; import org.apache.sis.internal.storage.IOUtilities; import org.apache.sis.util.logging.Logging; import org.apache.sis.util.collection.WeakHashSet; import org.geotoolkit.resources.Errors; import org.apache.sis.util.resources.IndexedResourceBundle; /** * A factory for {@linkplain IndexColorModel index color models} created from RGB values listed * in files. The palette definition files are text files containing an arbitrary number of lines, * each line containing RGB components ranging from 0 to 255 inclusive. An optional fourth column * may be provided for alpha components. Empty lines and lines starting with the {@code '#'} * character are ignored. Example: * * {@preformat text * # RGB codes for SeaWiFs images * # (chlorophylle-a concentration) * 033 000 096 * 032 000 097 * 031 000 099 * 030 000 101 * 029 000 102 * 028 000 104 * 026 000 106 * 025 000 107 * etc... * } * * The number of RGB codes doesn't have to match the target {@linkplain IndexColorModel#getMapSize * color map size}. RGB codes will be automatically interpolated as needed. For example it is legal * to declare only 2 colors, {@linkplain Color#BLUE blue} and {@linkplain Color#RED red} for instance. * If an image needs 256 colors, then all needed colors will be interpolated from blue to red, with * blue at index 0 and red at index 255. * <p> * The {@linkplain #getDefault() default instance} provides the following color palettes. More * details about those palettes are provided in <a href="doc-files/palettes.html">this page</a>. * <p> * <blockquote><table border="1" cellpadding="0" cellspacing="0"> * <tr><td><img width="128" height="24" alt="palette" title="grayscale" src="doc-files/grayscale.png"></td> * <td><img width="128" height="24" alt="palette" title="green-yellow-red" src="doc-files/green-yellow-red.png"></td> * <td><img width="128" height="24" alt="palette" title="white-cyan-red" src="doc-files/white-cyan-red.png"></td> * <td><img width="128" height="24" alt="palette" title="dem" src="doc-files/dem.png"></td></tr> * <tr><td><img width="128" height="24" alt="palette" title="blue" src="doc-files/blue.png"></td> * <td><img width="128" height="24" alt="palette" title="green-beige-red" src="doc-files/green-beige-red.png"></td> * <td><img width="128" height="24" alt="palette" title="bell" src="doc-files/bell.png"></td></tr> * <tr><td><img width="128" height="24" alt="palette" title="cyan-blue" src="doc-files/cyan-blue.png"></td> * <td><img width="128" height="24" alt="palette" title="blue-beige-red" src="doc-files/blue-beige-red.png"></td> * <td><img width="128" height="24" alt="palette" title="rainbow" src="doc-files/rainbow.png"></td></tr> * <tr><td><img width="128" height="24" alt="palette" title="green-inverse" src="doc-files/green-inverse.png"></td> * <td><img width="128" height="24" alt="palette" title="blue-red" src="doc-files/blue-red.png"></td> * <td><img width="128" height="24" alt="palette" title="rainbow-t" src="doc-files/rainbow-t.png"></td></tr> * <tr><td><img width="128" height="24" alt="palette" title="brown-inverse" src="doc-files/brown-inverse.png"></td> * <td><img width="128" height="24" alt="palette" title="red-blue" src="doc-files/red-blue.png"></td> * <td><img width="128" height="24" alt="palette" title="rainbow-c" src="doc-files/rainbow-c.png"></td></tr> * <tr><td><img width="128" height="24" alt="palette" title="red-inverse" src="doc-files/red-inverse.png"></td> * <td><img width="128" height="24" alt="palette" title="yellow-green-blue" src="doc-files/yellow-green-blue.png"></td> * <td><img width="128" height="24" alt="palette" title="SeaWiFS" src="doc-files/SeaWiFS.png"></td></tr> * </table></blockquote> * * {@section Adding custom palettes} * To add custom palettes, create a subclass of {@code PaletteFactory} like below: * * {@preformat java * public class MyPalettes extends PaletteFactory { * public MyPalettes() { * // The call below uses the default options for convenience, * // but we could use an other constructor with more parameters. * super(); * } * } * * // Optionally, we could override getResourceAsStream(String) if we wanted * // to fetch the color codes from a custom source (e.g. from a database). * } * * In the directory that contain the {@code MyPalettes.class} file, create a "{@code colors}" * sub-directory and put the palette definitions (as text files with the "{@code .pal}" suffix) * in that directory. The directory and the file suffix can be changed using one of the * constructors expecting arguments. * * Finally, declare the fully-qualified name of {@code MyPalettes} in the following file: * * {@preformat text * META-INF/services/org.geotoolkit.image.io.PaletteFactory * } * * @author Martin Desruisseaux (IRD, Geomatys) * @version 3.17 * * @since 1.2 * @module */ public class PaletteFactory { /** * The file which contains a list of available color palettes. This file is optional. * If such file exists in the same directory than the one that contains the palettes, * this file will be used by {@link #getAvailableNames()}. */ private static final String LIST_FILE = "list.txt"; /** * The default sub-directory, relative to the {@code PaletteFactory} class directory. */ private static final File DEFAULT_DIRECTORY = new File("colors"); /** * The default palette factory. */ private static PaletteFactory defaultFactory; /** * The fallback factory, or {@code null} if there is none. The fallback factory * will be queried if a palette was not found in current factory. * <p> * This field should be considered as final. It is modified by {@link #scanForPlugins} only. */ private PaletteFactory fallback; /** * The class loader from which to load the palette definition files. If {@code null} and * {@link #loader} is null as well, then loading will occurs from the system current * working directory. */ private final ClassLoader classloader; /** * An alternative to {@link #classloader} for loading resources. At most one of * {@code classloader} and {@code loader} can be non-null. If both are {@code null}, * then loading will occurs from the system current working directory. */ private final Class<?> loader; /** * The base directory from which to search for palette definition files. * If {@code null}, then the working directory ({@code "."}) is assumed. */ private final File directory; /** * The file extension. */ private final String extension; /** * The charset to use for parsing files, or {@code null} for the current default. */ private final Charset charset; /** * The locale to use for parsing files, or {@code null} for the current default. */ private final Locale locale; /** * The locale to use for formatting error messages, or {@code null} for the current default. * This locale is informative only; there is no guarantee that this locale will be really used. */ private transient ThreadLocal<Locale> warningLocales; /** * The set of palettes already created and not yet garbage-collected. The {@code getPalette} * method implementations shall return {@linkplain WeakHashSet#unique unique} {@code Palette} * instances as below: * * {@preformat java * public Palette getPalette(...) { * Palette palette = ...; * palette = palettes.unique(palette); // Ensure that the instance is unique. * return palette; * } * } * * The purpose is to share existing {@link ColorModel} instances when possible, since they * may be big (up to 256 kilobytes for an {@link IndexColorModel} with 16 bits integers). * This mechanism works provided that {@link Palette#createImageTypeSpecifier()} lazily * create the color model only when first invoked. * * @since 3.11 */ protected final WeakHashSet<Palette> palettes = new WeakHashSet<>(Palette.class); /** * The set of palettes protected from garbage collection. We protect a palette as long as it * holds a reference to a color model - this is necessary in order to prevent multiple creation * of the same {@link IndexColorModel}. The references are cleaned by {@link PaletteDisposer}. */ final Set<Palette> protectedPalettes = new HashSet<>(); /** * Gets the default palette factory. The returned factory can provide the palettes listed in * the <a href="#skip-navbar_top">class javadoc</a>, together with the palettes defined by any * custom factories registered in the way defined by the class javadoc. * * {@note The scan for custom factories is performed only when this method is first invoked. If * a new scan is desired (for example because new JAR files are added on the classpath, * then <code>scanForPlugins(null)</code> should be invoked explicitly.} * * If custom factories are found and if they define some palettes of the same name than the * ones provided by the build-in factory, then the custom palettes have precedence over the * build-in ones. * * @return The default palette factory. */ public static synchronized PaletteFactory getDefault() { if (defaultFactory == null) { defaultFactory = new PaletteFactory(); scanForPlugins(null); } return defaultFactory; } /** * Lookups for custom palette factories on the classpath. This method is automatically * invoked by {@link #getDefault()} and doesn't need to be invoked explicitly, unless * new JAR files have been added on the classpath or unless some specific class loader * needs to be used. * <p> * Custom factories shall be declared in the following file: * * {@preformat text * META-INF/services/org.geotoolkit.image.io.PaletteFactory * } * * Newly discovered factories have precedence over the old ones. * * @param loader The class loader to use, or {@code null} for the default one. * * @since 2.4 */ public static synchronized void scanForPlugins(final ClassLoader loader) { final Set<Class<? extends PaletteFactory>> existings = new HashSet<>(); for (PaletteFactory p=getDefault(); p!=null; p=p.fallback) { existings.add(p.getClass()); } final ServiceLoader<PaletteFactory> factories = (loader == null) ? ServiceLoader.load(PaletteFactory.class) : ServiceLoader.load(PaletteFactory.class, loader); for (final PaletteFactory factory : factories) { /* * Adds the scanned factory to the chain. There is no public method for doing that * because PaletteFactory is quasi-immutable except for this method which modifies * the fallback field. It is okay in this context since we just created the factory * instance. */ if (existings.add(factory.getClass())) { PaletteFactory tail = factory; while (tail.fallback != null) { tail = tail.fallback; } tail.fallback = defaultFactory; defaultFactory = factory; } } } /** * Constructs a default palette factory using this {@linkplain #getClass object class} for * loading palette definition files. The default directory is {@code "colors"} relative to * the directory of the subclass extending this class. The character encoding is ISO-8859-1 * and the locale is {@linkplain Locale#US US}. * <p> * This constructor is protected because is it merely a convenience for subclasses registering * themselves as a service in the following file (see <a href="#skip-navbar_top">class javadoc</a> * for more details): * * {@preformat text * META-INF/services/org.geotoolkit.image.io.PaletteFactory * } * * Users should invoke {@link #getDefault} instead, which will return a shared instance * of this class together with any custom factories found on the class path. * * @since 2.5 */ protected PaletteFactory() { this.classloader = null; this.loader = getClass(); this.directory = DEFAULT_DIRECTORY; this.extension = ".pal"; this.charset = Charset.forName("ISO-8859-1"); this.locale = Locale.US; } /** * Constructs a palette factory which will load the palette definition files from the specified * directory. No {@linkplain ClassLoader class loader} is used, i.e. the palettes are read as * ordinary file relative to the current working directory (not the classpath). * * @param directory The base directory for palette definition files relative to current * directory, or {@code null} for {@code "."}. * @param extension File name extension, or {@code null} if there is no extension * to add to filename. If non-null, this extension will be automatically * appended to filename. It should starts with the {@code '.'} character. * @param charset The charset to use for parsing files, or {@code null} for the default. * @param locale The locale to use for parsing files, or {@code null} for the default. * * @since 2.5 */ public PaletteFactory(final File directory, final String extension, final Charset charset, final Locale locale) { this.classloader = null; this.loader = null; this.directory = directory; this.extension = startWithDot(extension); this.charset = charset; this.locale = locale; } /** * Constructs a palette factory using an optional {@linkplain ClassLoader class loader} * for loading palette definition files. If {@code loader} is non-null, the definitions * will be read using {@link ClassLoader#getResource(String)}, which imply that the * {@code directory} argument is relative to the root packages on the classpath. * * @param fallback An optional fallback factory, or {@code null} if there is none. The fallback * factory will be queried if a palette was not found in the current factory. * @param loader An optional class loader to use for loading the palette definition files. * If {@code null}, loading will occurs from the system current working * directory. * @param directory The base directory for palette definition files. It may be a Java package * if a {@code loader} were specified. If {@code null}, then {@code "."} is * assumed. * @param extension File name extension, or {@code null} if there is no extension * to add to filename. If non-null, this extension will be automatically * appended to filename. It should starts with the {@code '.'} character. * @param charset The charset to use for parsing files, or {@code null} for the default. * @param locale The locale to use for parsing files, or {@code null} for the default. */ public PaletteFactory(final PaletteFactory fallback, final ClassLoader loader, final File directory, final String extension, final Charset charset, final Locale locale) { this.fallback = fallback; this.classloader = loader; this.loader = null; this.directory = directory; this.extension = startWithDot(extension); this.charset = charset; this.locale = locale; } /** * Constructs a palette factory using an optional {@linkplain Class class} for loading * palette definition files. If {@code loader} is non-null, the definitions will be read * using {@link Class#getResource(String)}, which imply that the {@code directory} argument * is relative to the package of the {@code PaletteFactory} subclass. * <p> * Using a {@linkplain Class class} instead of a {@linkplain ClassLoader class loader} can * avoid security issue on some platforms (some platforms do not allow to load resources * from a {@code ClassLoader} because it would make possible to load from the root package). * * @param fallback An optional fallback factory, or {@code null} if there is none. The fallback * factory will be queried if a palette was not found in the current factory. * @param loader An optional class to use for loading the palette definition files. * If {@code null}, loading will occurs from the system current working * directory. * @param directory The base directory for palette definition files. It may be a Java package * if a {@code loader} were specified. If {@code null}, then {@code "."} is * assumed. * @param extension File name extension, or {@code null} if there is no extension * to add to filename. If non-null, this extension will be automatically * appended to filename. It should starts with the {@code '.'} character. * @param charset The charset to use for parsing files, or {@code null} for the default. * @param locale The locale to use for parsing files. or {@code null} for the default. * * @since 2.2 */ public PaletteFactory(final PaletteFactory fallback, final Class<?> loader, final File directory, final String extension, final Charset charset, final Locale locale) { this.fallback = fallback; this.classloader = null; this.loader = loader; this.directory = directory; this.extension = startWithDot(extension); this.charset = charset; this.locale = locale; } /** * Ensures that the given string starts with a dot. */ private static String startWithDot(String extension) { if (extension != null && !extension.startsWith(".")) { extension = '.' + extension; } return extension; } /** * Sets the locale to use for formatting warning or error messages. This is typically the * {@linkplain javax.imageio.ImageReader#getLocale image reader locale}. This locale is * informative only; there is no guarantee that this locale will be really used. * <p> * This method sets the locale for the current thread only. It is safe to use this palette * factory concurrently in many threads, each with their own locale. * * @param warningLocale The locale for warning or error messages, or {@code null} for the * {@linkplain Locale#getDefault() default locale}. * * @since 2.4 */ public synchronized void setWarningLocale(final Locale warningLocale) { if (warningLocale != null) { if (warningLocales == null) { warningLocales = warningLocales(); } warningLocales.set(warningLocale); } else if (warningLocales != null) { warningLocales.remove(); } } /** * Gets the {@linkplain #warningLocales} from the fallback or create a new one. This * method invokes itself recursively in order to assign the same {@link ThreadLocal} * to every factories in the chain. */ private synchronized ThreadLocal<Locale> warningLocales() { if (warningLocales == null) { warningLocales = (fallback != null) ? fallback.warningLocales() : new ThreadLocal<Locale>(); } return warningLocales; } /** * Returns the locale set by the last invocation to {@link #setWarningLocale(Locale)} * in the current thread. * * @return The current locale to use for warning messages. * * @since 2.4 */ public synchronized Locale getWarningLocale() { final ThreadLocal<Locale> warningLocales = this.warningLocales; if (warningLocales != null) { return warningLocales.get(); } return null; } /** * Returns the resources for formatting error messages. */ final IndexedResourceBundle getErrorResources() { return Errors.getResources(getWarningLocale()); } /** * Returns an input stream for reading the specified resource. The default * implementation delegates to either {@link Class#getResourceAsStream(String) Class} or * {@link ClassLoader#getResourceAsStream(String) ClassLoader} {@code getResourceAsStream(String)} * method, depending on the type of the {@code loader} argument given to the constructor. * Subclasses may override this method if a more elaborated mechanism is wanted for fetching * resources. This is sometime required in the context of applications using particular class * loaders. * * @param name The name of the resource to load, constructed as {@code directory} + {@code name} * + {@code extension} where <var>directory</var> and <var>extension</var> were * specified to the constructor, while {@code name} was given to the * {@link #getPalette} method. * @return The input stream, or {@code null} if the resources was not found. * * @since 2.3 */ protected InputStream getResourceAsStream(final String name) { if (loader != null) { return loader.getResourceAsStream(name); } if (classloader != null) { return classloader.getResourceAsStream(name); } return null; } /** * Returns the set of available palette names. Any item in this set can be specified as * argument to the {@link #getPalette(String, int)} method. * <p> * If this method can not infer the names of available palettes, then it returns {@code null}. * Note that this is not the same than an empty set, which means "there is no palette". The * null return value means that the set is unknown. * * @return The list of available palette name, or {@code null} if this method * is unable to fetch this information. */ public Set<String> getAvailableNames() { final Set<String> names = new LinkedHashSet<>(); return getAvailableNames(names) ? names : null; } /** * Adds available palette names to the specified collection. * * @param names The collection where to add the name of the palettes. * @return {@code true} if this method has been able to find some informations about palettes. */ private boolean getAvailableNames(final Collection<String> names) { /* * Queries the fallback first, in order to make the "standard" palettes appear first. */ boolean found = (fallback != null) && fallback.getAvailableNames(names); /* * First, parse the content of every "list.txt" files found on the classpath. Those files * are optional. But if they are present, we assume that their content are accurate (this * will not be verified). */ try { BufferedReader in = getReader(LIST_FILE, "getAvailableNames"); if (in != null) { readNames(in, names); found = true; } /* * We can iterate only with ClassLoader because there is no Class.getResources(...) * method. Note that this iteration (when performed) is not completely redundant with * the above call to 'getReader' because the user may have overridden * PaletteFactory.getResourceAsStream(String). */ if (classloader != null) { final String filename = new File(directory, LIST_FILE).getPath().replace(File.separatorChar, '/'); for (final Enumeration<URL> it=classloader.getResources(filename); it.hasMoreElements();) { final URL url = it.nextElement(); in = getReader(url.openStream()); readNames(in, names); found = true; } } } catch (IOException e) { /* * Logs a warning but do not stop. The only consequence is that the names list * will be incomplete. We log the message as if it came from getAvailableNames(), * because it is the public method that invoked this one. */ Logging.unexpectedException(null, PaletteFactory.class, "getAvailableNames", e); } /* * After the "list.txt" files, check if the resources can be read as a directory. * It may happen if the classpath point toward a directory of .class files rather * than a JAR file. */ File dir = (directory != null) ? directory : new File("."); try { if (classloader != null) { dir = IOUtilities.toFile(classloader.getResource(dir.getPath()), null); } else if (loader != null) { dir = IOUtilities.toFile(loader.getResource(dir.getPath()), null); } } catch (IOException e) { /* * The URL to the palette files can not be converted to a File object. * Consequently we can not scan the list of files in the directory. * Returns only the palettes which were explicitly declared. */ return found; } if (dir == null) { return found; } final String[] list = dir.list(new DefaultFileFilter('*' + extension)); if (list == null) { return found; // Not a directory. } final int extLg = extension.length(); for (final String filename : list) { final int lg = filename.length(); if (lg > extLg && filename.regionMatches(true, lg - extLg, extension, 0, extLg)) { names.add(filename.substring(0, lg-extLg)); } } return true; } /** * Copies the content of the specified reader to the specified collection. * The reader is closed after this operation. */ private static void readNames(final BufferedReader in, final Collection<String> names) throws IOException { String line; while ((line = in.readLine()) != null) { line = line.trim(); if (!line.isEmpty() && line.charAt(0) != '#') { names.add(line); } } in.close(); } /** * Returns a buffered reader for the specified palette. * * @param The palette's name to load. This name doesn't need to contains a path * or an extension. Path and extension are set according value specified * at construction time. * @return A buffered reader to read {@code name}, or {@code null} if the resource is not found. */ private LineNumberReader getPaletteReader(String name) { if (extension != null && !name.endsWith(extension)) { name += extension; } return getReader(name, "getPalette"); } /** * Returns a buffered reader for the specified filename. * * @param The filename. Path and extension are set according value specified at construction time. * @return A buffered reader to read {@code name}, or {@code null} if the resource is not found. */ private LineNumberReader getReader(final String name, final String caller) { final File file = new File(directory, name); final String path = file.getPath().replace(File.separatorChar, '/'); InputStream stream; try { stream = getResourceAsStream(path); if (stream == null) { if (file.canRead()) try { stream = new FileInputStream(file); } catch (FileNotFoundException e) { /* * Should not occurs, since we checked for file existence. This is not a fatal * error however, since this method is allowed to returns null if the resource * is not available. */ Logging.unexpectedException(null, PaletteFactory.class, caller, e); return null; } else { return null; } } } catch (SecurityException e) { Logging.recoverableException(null, PaletteFactory.class, caller, e); return null; } return getReader(stream); } /** * Wraps the specified input stream into a reader. */ private LineNumberReader getReader(final InputStream stream) { return new LineNumberReader((charset != null) ? new InputStreamReader(stream, charset) : new InputStreamReader(stream)); } /** * Reads the colors declared in the specified input stream. Colors must be encoded on 3 or 4 * columns. If 3 columns, it is assumed RGB values. If 4 columns, it is assumed RGBA values. * Values must be in the 0-255 ranges. Empty lines and lines starting by {@code '#'} are * ignored. * * @param input The stream to read. * @param name The palette name to read. Used for formatting error message only. * @return The colors. * @throws IOException if an I/O error occurred. * @throws IIOException if a syntax error occurred. */ @SuppressWarnings("fallthrough") private Color[] getColors(final LineNumberReader input, final String name) throws IOException { int[] values = null; final LineFormat reader = (locale!=null) ? new LineFormat(locale) : new LineFormat(); final List<Color> colors = new ArrayList<>(); String line; while ((line=input.readLine()) != null) try { line = line.trim(); if (line.isEmpty()) continue; if (line.charAt(0) == '#') continue; if (reader.setLine(line) == 0) continue; values = reader.getValues(values); int A=255,R,G,B; switch (values.length) { case 4: A = byteValue(values[3]); // fall through case 3: B = byteValue(values[2]); G = byteValue(values[1]); R = byteValue(values[0]); break; default: { throw syntaxError(input, name, null); } } final Color color; try { color = new Color(R, G, B, A); } catch (IllegalArgumentException exception) { /* * Color constructor checks the RGBA value and throws an IllegalArgumentException * if they are not in the 0-255 range. Intercept this exception and rethrows as a * checked IIOException, since we want to notify the user that the palette file is * badly formatted. (additional note: it is somewhat redundant with byteValue(int) * work. Let keep it as a safety). */ throw syntaxError(input, name, exception); } colors.add(color); } catch (ParseException exception) { throw syntaxError(input, name, exception); } return colors.toArray(new Color[colors.size()]); } /** * Prepares an exception for the specified cause, which may be {@code null}. */ private IIOException syntaxError(final LineNumberReader input, final String name, final Exception cause) { String message = getErrorResources().getString(Errors.Keys.IllegalLineInFile_2, name, input.getLineNumber()); if (cause != null) { message += cause.getLocalizedMessage(); } return new IIOException(message, cause); } /** * Loads colors from a definition file. If no colors were found in the current palette * factory, then the fallback (if any fallback was specified to the constructor) will * be queried. * * {@section Special case for single color} * If the given name starts with the {@code '#'} character, then the string is decoded as * a color using the {@link Color#decode(String)} method (i.e., the string is interpreted * as a hexadecimal RGB value) and the decoded color is returned in an array of length 1. * * @param name The name of the palette to load. * @return The set of colors, or {@code null} if no palette was found for the given name. * @throws IOException if an error occurs during reading. */ public Color[] getColors(final String name) throws IOException { final Color[] colors; if (name.startsWith("#")) try { colors = new Color[] {Color.decode(name)}; } catch (NumberFormatException e) { return null; } else { try (LineNumberReader reader = getPaletteReader(name)) { if (reader == null) { return (fallback != null) ? fallback.getColors(name) : null; } colors = getColors(reader, name); } } return colors; } /** * Ensures that the specified value is inside the {@code [0..255]} range. * If the value is outside that range, a {@link ParseException} is thrown. */ private int byteValue(final int value) throws ParseException { if (value>=0 && value<256) { return value; } throw new ParseException(getErrorResources().getString( Errors.Keys.RgbOutOfRange_1, value), 0); } /** * Returns the palette of the specified name and size. * The default implementation is equivalents to: * * {@preformat java * return getPalette(name, 0, size, size, 1, 0); * } * * This method does not test the validity of the {@code name} argument. If there is * no palette for the given name, then a {@link java.io.FileNotFoundException} will * be thrown by the getter methods in the returned palette. * * @param name The name of the palette to load. * @param size The {@linkplain IndexColorModel index color model} size. * @return The palette. * * @since 2.4 */ public Palette getPalette(final String name, final int size) { return getPalette(name, 0, size, size, 1, 0); } /** * Returns a palette with a <cite>pad value</cite> at index 0. * The default implementation is equivalents to: * * {@preformat java * return getPalette(name, 1, size, size, 1, 0); * } * * This method does not test the validity of the {@code name} argument. If there is * no palette for the given name, then a {@link java.io.FileNotFoundException} will * be thrown by the getter methods in the returned palette. * * @param name The name of the palette to load. * @param size The {@linkplain IndexColorModel index color model} size. * @return The palette. * * @since 2.4 */ public Palette getPalettePadValueFirst(final String name, final int size) { return getPalette(name, 1, size, size, 1, 0); } /** * Returns a palette with <cite>pad value</cite> at the last index. * The default implementation is equivalents to: * * {@preformat java * return getPalette(name, 0, size-1, size, 1, 0); * } * * This method does not test the validity of the {@code name} argument. If there is * no palette for the given name, then a {@link java.io.FileNotFoundException} will * be thrown by the getter methods in the returned palette. * * @param name The name of the palette to load. * @param size The {@linkplain IndexColorModel index color model} size. * @return The palette. * * @since 2.4 */ public Palette getPalettePadValueLast(final String name, final int size) { return getPalette(name, 0, size-1, size, 1, 0); } /** * Returns the palette of the specified name and size. The RGB colors will be distributed * in the range {@code lower} inclusive to {@code upper} exclusive. Remaining pixel values * (if any) will be left to a black or transparent color by default. * <p> * This method does not test the validity of the {@code name} argument. If there is * no palette for the given name, then a {@link java.io.FileNotFoundException} will * be thrown by the getter methods in the returned palette. * * @param name The name of the palette to load. * @param lower Index of the first valid element (inclusive) in the * {@linkplain IndexColorModel index color model} to be created. * @param upper Index of the last valid element (exclusive) in the * {@linkplain IndexColorModel index color model} to be created. * @param size The size of the {@linkplain IndexColorModel index color model} to be created. * This is the value to be returned by {@link IndexColorModel#getMapSize()}. * @param numBands The number of bands (usually 1). * @param visibleBand The band to use for color computations (usually 0). * @return The palette. * * @since 2.4 */ public Palette getPalette(final String name, final int lower, final int upper, final int size, final int numBands, final int visibleBand) { Palette palette = new IndexedPalette(this, name, lower, upper, size, numBands, visibleBand); palette = palettes.unique(palette); return palette; } /** * Creates a palette suitable for floating point values. * * @param name The palette name. * @param minimum The minimal sample value expected. * @param maximum The maximal sample value expected. * @param dataType The data type as a {@link java.awt.image.DataBuffer#TYPE_INT}, * {@link java.awt.image.DataBuffer#TYPE_FLOAT} or * {@link java.awt.image.DataBuffer#TYPE_DOUBLE} constant. * @param numBands The number of bands (usually 1). * @param visibleBand The band to use for color computations (usually 0). * @return A palette suitable for floating point values. * * @since 2.4 * * @todo Current implementation ignores the name and builds a gray scale in all cases. * Future version may improve on that. */ public Palette getContinuousPalette(final String name, final float minimum, final float maximum, final int dataType, final int numBands, final int visibleBand) { Palette palette = new ContinuousPalette(this, name, minimum, maximum, dataType, numBands, visibleBand); palette = palettes.unique(palette); return palette; } }