/* Copyright (c) 2001, 2007 TOPP - www.openplans.org. All rights reserved.
* This code is licensed under the GPL 2.0 license, availible at the root
* application directory.
*/
package org.vfny.geoserver.config;
import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.IndexColorModel;
import java.io.File;
import java.io.FilenameFilter;
import java.util.Iterator;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.stream.ImageInputStream;
import org.geotools.image.palette.InverseColorMapOp;
import org.geotools.util.SoftValueHashMap;
import org.vfny.geoserver.global.GeoserverDataDirectory;
/**
* Allows access to palettes (implemented as {@link IndexColorModel} classes)
*
* @author Andrea Aime - TOPP
* @author Simone Giannecchini - GeoSolutions
*
*/
public class PaletteManager {
private static final Logger LOG = org.geotools.util.logging.Logging.getLogger("PaletteManager");
/**
* Safe palette, a 6x6x6 color cube, followed by a 39 elements gray scale,
* and a final transparent element. See the internet safe color palette for
* a reference <a href="http://www.intuitive.com/coolweb/colors.html">
*/
public static final String SAFE = "SAFE";
public static final IndexColorModel safePalette = buildDefaultPalette();
static SoftValueHashMap paletteCache = new SoftValueHashMap();
private static InverseColorMapOp safePaletteInversion= new InverseColorMapOp(safePalette);
/**
* TODO: we should probably provide the data directory as a constructor
* parameter here
*/
private PaletteManager() {
}
/**
* Loads a PaletteManager
*
* @param name
* @return
* @throws Exception
*/
public static InverseColorMapOp getPalette(String name)
throws Exception {
// check for safe paletteInverter
if ("SAFE".equals(name.toUpperCase())) {
return safePaletteInversion;
}
// check for cached one, making sure it's not stale
final PaletteCacheEntry entry = (PaletteCacheEntry) paletteCache
.get(name);
if (entry != null) {
if (entry.isStale()) {
paletteCache.remove(name);
} else {
return entry.eicm;
}
}
// ok, load it. for the moment we load palettes from .png and .gif
// files, but we may want to extend this ability to other file formats
// (Gimp palettes for example), in this case we'll adopt the classic
// plugin approach using either the Spring context of the SPI
// hum... loading the paletteDir could be done once, but then if the
// users
// adds the paletteInverter dir with a running Geoserver, we won't find it
// anymore...
final File root = GeoserverDataDirectory.getGeoserverDataDirectory();
final File paletteDir = GeoserverDataDirectory.findConfigDir(root,
"palettes");
final String[] names = new String[] { name + ".gif", name + ".png",
name + ".pal", name + ".tif" };
final File[] paletteFiles = paletteDir.listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
for (int i = 0; i < names.length; i++) {
if (name.toLowerCase().equals(names[i])) {
return true;
}
}
return false;
}
});
// scan the files found (we may have multiple files with different
// extensions and return the first paletteInverter you find
for (int i = 0; i < paletteFiles.length; i++) {
final File file = paletteFiles[i];
final String fileName = file.getName();
if (fileName.endsWith("pal")) {
final IndexColorModel icm = new PALFileLoader(file)
.getIndexColorModel();
if (icm != null) {
final InverseColorMapOp eicm = new InverseColorMapOp(
icm);
paletteCache.put(name, new PaletteCacheEntry(file, eicm));
return eicm;
}
} else {
ImageInputStream iis = ImageIO.createImageInputStream(file);
final Iterator it = ImageIO.getImageReaders(iis);
if (it.hasNext()) {
final ImageReader reader = (ImageReader) it.next();
reader.setInput(iis);
final ColorModel cm = ((ImageTypeSpecifier) reader
.getImageTypes(0).next()).getColorModel();
if (cm instanceof IndexColorModel) {
final IndexColorModel icm = (IndexColorModel) cm;
final InverseColorMapOp eicm = new InverseColorMapOp(
icm);
paletteCache.put(name,
new PaletteCacheEntry(file, eicm));
return eicm;
}
}
}
LOG
.warning("Skipping paletteInverter file "
+ file.getName()
+ " since color model is not indexed (no 256 colors paletteInverter)");
}
return null;
}
/**
* Builds the internet safe paletteInverter
*/
static IndexColorModel buildDefaultPalette() {
int[] cmap = new int[256];
// Create the standard 6x6x6 color cube (all elements do cycle
// between 00, 33, 66, 99, CC and FF, the decimal difference is 51)
// The color is made of alpha, red, green and blue, in this order, from
// the most significant bit onwards.
int i = 0;
int opaqueAlpha = 255 << 24;
for (int r = 0; r < 256; r += 51) {
for (int g = 0; g < 256; g += 51) {
for (int b = 0; b < 256; b += 51) {
cmap[i] = opaqueAlpha | (r << 16) | (g << 8) | b;
i++;
}
}
}
// The gray scale. Make sure we end up with gray == 255
int grayIncr = 256 / (255 - i);
int gray = 255 - ((255 - i - 1) * grayIncr);
for (; i < 255; i++) {
cmap[i] = opaqueAlpha | (gray << 16) | (gray << 8) | gray;
gray += grayIncr;
}
// setup the transparent color (alpha == 0)
cmap[255] = (255 << 16) | (255 << 8) | 255;
// create the color model
return new IndexColorModel(8, 256, cmap, 0, true, 255,
DataBuffer.TYPE_BYTE);
}
/**
* An entry in the paletteInverter cache. Can determine wheter it's stale or not,
* too
*/
private static class PaletteCacheEntry {
File file;
long lastModified;
InverseColorMapOp eicm;
public PaletteCacheEntry(File file,
InverseColorMapOp eicm) {
this.file = file;
this.eicm = eicm;
this.lastModified = file.lastModified();
}
/**
* Returns true if the backing file does not exist any more, or has been
* modified
*
* @return
*/
public boolean isStale() {
return !file.exists() || (file.lastModified() != lastModified);
}
}
}