/
PaletteManager.java
222 lines (192 loc) · 7.37 KB
/
PaletteManager.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
/* (c) 2014 Open Source Geospatial Foundation - all rights reserved
* (c) 2001 - 2013 OpenPlans
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.wms.kvp;
import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.IndexColorModel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.stream.ImageInputStream;
import org.geoserver.platform.GeoServerExtensions;
import org.geoserver.platform.GeoServerResourceLoader;
import org.geoserver.platform.resource.Resource;
import org.geoserver.platform.resource.Resources;
import org.geotools.image.palette.InverseColorMapOp;
import org.geotools.util.SoftValueHashMap;
/**
* 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");
/**
* The key used in the format options to specify what quantizer to use
*/
public static final String QUANTIZER = "quantizer";
/**
* 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<String, PaletteCacheEntry> paletteCache = new SoftValueHashMap<String, PaletteCacheEntry>();
static SoftValueHashMap<IndexColorModel, InverseColorMapOp> opCache = new SoftValueHashMap<IndexColorModel, InverseColorMapOp>();
/**
* 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 IndexColorModel getPalette(String name) throws Exception {
// check for safe paletteInverter
if ("SAFE".equals(name.toUpperCase())) {
return safePalette;
}
// 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.icm;
}
}
// 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...
GeoServerResourceLoader loader = GeoServerExtensions.bean(GeoServerResourceLoader.class);
Resource palettes = loader.get("palettes");
Set<String> names = new HashSet<String>();
names.addAll(Arrays.asList(new String[] { name + ".gif", name + ".png", name + ".pal",
name + ".tif" }));
List<Resource> paletteFiles = new ArrayList<Resource>();
for (Resource item : palettes.list()) {
if (names.contains(item.name().toLowerCase())) {
paletteFiles.add(item);
}
}
// scan the files found (we may have multiple files with different
// extensions and return the first paletteInverter you find
for (Resource resource : paletteFiles) {
final String fileName = resource.name();
if (fileName.endsWith("pal")) {
final IndexColorModel icm = new PALFileLoader(resource).getIndexColorModel();
if (icm != null) {
paletteCache.put(name, new PaletteCacheEntry(resource, icm));
return icm;
}
} else {
ImageInputStream iis = ImageIO.createImageInputStream(resource.in());
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;
paletteCache.put(name, new PaletteCacheEntry(resource, icm));
return icm;
}
}
}
LOG.warning("Skipping paletteInverter file " + fileName
+ " since color model is not indexed (no 256 colors paletteInverter)");
}
return null;
}
public static InverseColorMapOp getInverseColorMapOp(IndexColorModel icm) {
// check for cached one, making sure it's not stale
InverseColorMapOp op = (InverseColorMapOp) opCache.get(icm);
if (op != null) {
return op;
} else {
op = new InverseColorMapOp(icm);
opCache.put(icm, op);
return op;
}
}
/**
* 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 {
Resource file;
long lastModified;
IndexColorModel icm;
public PaletteCacheEntry(Resource file,
IndexColorModel icm) {
this.file = file;
this.icm = icm;
this.lastModified = file.lastmodified();
}
/**
* Returns true if the backing file does not exist any more, or has been
* modified
*
* @return
*/
public boolean isStale() {
return !Resources.exists(file) || (file.lastmodified() != lastModified);
}
}
}