001: /* Copyright (c) 2001, 2007 TOPP - www.openplans.org. All rights reserved.
002: * This code is licensed under the GPL 2.0 license, availible at the root
003: * application directory.
004: */
005: package org.vfny.geoserver.config;
006:
007: import java.awt.image.ColorModel;
008: import java.awt.image.DataBuffer;
009: import java.awt.image.IndexColorModel;
010: import java.io.File;
011: import java.io.FilenameFilter;
012: import java.util.Iterator;
013: import java.util.logging.Logger;
014:
015: import javax.imageio.ImageIO;
016: import javax.imageio.ImageReader;
017: import javax.imageio.ImageTypeSpecifier;
018: import javax.imageio.stream.ImageInputStream;
019:
020: import org.geotools.util.SoftValueHashMap;
021: import org.vfny.geoserver.global.GeoserverDataDirectory;
022: import org.vfny.geoserver.wms.responses.palette.EfficientInverseColorMapComputation;
023: import org.vfny.geoserver.wms.responses.palette.InverseColorMapOp;
024:
025: /**
026: * Allows access to palettes (implemented as {@link IndexColorModel} classes)
027: *
028: * @author Andrea Aime - TOPP
029: * @author Simone Giannecchini - GeoSolutions
030: *
031: */
032: public class PaletteManager {
033: private static final Logger LOG = org.geotools.util.logging.Logging
034: .getLogger("PaletteManager");
035:
036: /**
037: * Safe palette, a 6x6x6 color cube, followed by a 39 elements gray scale,
038: * and a final transparent element. See the internet safe color palette for
039: * a reference <a href="http://www.intuitive.com/coolweb/colors.html">
040: */
041: public static final String SAFE = "SAFE";
042: public static final IndexColorModel safePalette = buildDefaultPalette();
043: static SoftValueHashMap paletteCache = new SoftValueHashMap();
044:
045: private static InverseColorMapOp safePaletteInversion = new InverseColorMapOp(
046: safePalette);
047:
048: /**
049: * TODO: we should probably provide the data directory as a constructor
050: * parameter here
051: */
052: private PaletteManager() {
053: }
054:
055: /**
056: * Loads a PaletteManager
057: *
058: * @param name
059: * @return
060: * @throws Exception
061: */
062: public static InverseColorMapOp getPalette(String name)
063: throws Exception {
064: // check for safe paletteInverter
065: if ("SAFE".equals(name.toUpperCase())) {
066: return safePaletteInversion;
067: }
068:
069: // check for cached one, making sure it's not stale
070: final PaletteCacheEntry entry = (PaletteCacheEntry) paletteCache
071: .get(name);
072: if (entry != null) {
073: if (entry.isStale()) {
074: paletteCache.remove(name);
075: } else {
076: return entry.eicm;
077: }
078: }
079:
080: // ok, load it. for the moment we load palettes from .png and .gif
081: // files, but we may want to extend this ability to other file formats
082: // (Gimp palettes for example), in this case we'll adopt the classic
083: // plugin approach using either the Spring context of the SPI
084:
085: // hum... loading the paletteDir could be done once, but then if the
086: // users
087: // adds the paletteInverter dir with a running Geoserver, we won't find it
088: // anymore...
089: final File root = GeoserverDataDirectory
090: .getGeoserverDataDirectory();
091: final File paletteDir = GeoserverDataDirectory.findConfigDir(
092: root, "palettes");
093: final String[] names = new String[] { name + ".gif",
094: name + ".png", name + ".pal", name + ".tif" };
095: final File[] paletteFiles = paletteDir
096: .listFiles(new FilenameFilter() {
097: public boolean accept(File dir, String name) {
098: for (int i = 0; i < names.length; i++) {
099: if (name.toLowerCase().equals(names[i])) {
100: return true;
101: }
102: }
103:
104: return false;
105: }
106: });
107:
108: // scan the files found (we may have multiple files with different
109: // extensions and return the first paletteInverter you find
110: for (int i = 0; i < paletteFiles.length; i++) {
111: final File file = paletteFiles[i];
112: final String fileName = file.getName();
113: if (fileName.endsWith("pal")) {
114: final IndexColorModel icm = new PALFileLoader(file)
115: .getIndexColorModel();
116:
117: if (icm != null) {
118: final InverseColorMapOp eicm = new InverseColorMapOp(
119: icm);
120: paletteCache.put(name, new PaletteCacheEntry(file,
121: eicm));
122: return eicm;
123: }
124: } else {
125: ImageInputStream iis = ImageIO
126: .createImageInputStream(file);
127: final Iterator it = ImageIO.getImageReaders(iis);
128: if (it.hasNext()) {
129: final ImageReader reader = (ImageReader) it.next();
130: reader.setInput(iis);
131: final ColorModel cm = ((ImageTypeSpecifier) reader
132: .getImageTypes(0).next()).getColorModel();
133: if (cm instanceof IndexColorModel) {
134: final IndexColorModel icm = (IndexColorModel) cm;
135: final InverseColorMapOp eicm = new InverseColorMapOp(
136: icm);
137: paletteCache.put(name, new PaletteCacheEntry(
138: file, eicm));
139: return eicm;
140: }
141: }
142: }
143: LOG
144: .warning("Skipping paletteInverter file "
145: + file.getName()
146: + " since color model is not indexed (no 256 colors paletteInverter)");
147: }
148:
149: return null;
150: }
151:
152: /**
153: * Builds the internet safe paletteInverter
154: */
155: static IndexColorModel buildDefaultPalette() {
156: int[] cmap = new int[256];
157:
158: // Create the standard 6x6x6 color cube (all elements do cycle
159: // between 00, 33, 66, 99, CC and FF, the decimal difference is 51)
160: // The color is made of alpha, red, green and blue, in this order, from
161: // the most significant bit onwards.
162: int i = 0;
163: int opaqueAlpha = 255 << 24;
164:
165: for (int r = 0; r < 256; r += 51) {
166: for (int g = 0; g < 256; g += 51) {
167: for (int b = 0; b < 256; b += 51) {
168: cmap[i] = opaqueAlpha | (r << 16) | (g << 8) | b;
169: i++;
170: }
171: }
172: }
173:
174: // The gray scale. Make sure we end up with gray == 255
175: int grayIncr = 256 / (255 - i);
176: int gray = 255 - ((255 - i - 1) * grayIncr);
177:
178: for (; i < 255; i++) {
179: cmap[i] = opaqueAlpha | (gray << 16) | (gray << 8) | gray;
180: gray += grayIncr;
181: }
182:
183: // setup the transparent color (alpha == 0)
184: cmap[255] = (255 << 16) | (255 << 8) | 255;
185:
186: // create the color model
187: return new IndexColorModel(8, 256, cmap, 0, true, 255,
188: DataBuffer.TYPE_BYTE);
189: }
190:
191: /**
192: * An entry in the paletteInverter cache. Can determine wheter it's stale or not,
193: * too
194: */
195: private static class PaletteCacheEntry {
196: File file;
197:
198: long lastModified;
199:
200: InverseColorMapOp eicm;
201:
202: public PaletteCacheEntry(File file, InverseColorMapOp eicm) {
203: this .file = file;
204: this .eicm = eicm;
205: this .lastModified = file.lastModified();
206: }
207:
208: /**
209: * Returns true if the backing file does not exist any more, or has been
210: * modified
211: *
212: * @return
213: */
214: public boolean isStale() {
215: return !file.exists()
216: || (file.lastModified() != lastModified);
217: }
218: }
219: }
|