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.wms.responses;
006:
007: import java.awt.Color;
008: import java.awt.image.DataBuffer;
009: import java.awt.image.IndexColorModel;
010: import java.util.Arrays;
011: import java.util.HashSet;
012: import java.util.Iterator;
013: import java.util.Set;
014:
015: import org.geotools.filter.FilterAttributeExtractor;
016: import org.geotools.styling.AnchorPoint;
017: import org.geotools.styling.ColorMap;
018: import org.geotools.styling.ColorMapEntry;
019: import org.geotools.styling.Displacement;
020: import org.geotools.styling.ExternalGraphic;
021: import org.geotools.styling.FeatureTypeConstraint;
022: import org.geotools.styling.FeatureTypeStyle;
023: import org.geotools.styling.Fill;
024: import org.geotools.styling.Graphic;
025: import org.geotools.styling.Halo;
026: import org.geotools.styling.LinePlacement;
027: import org.geotools.styling.LineSymbolizer;
028: import org.geotools.styling.Mark;
029: import org.geotools.styling.NamedLayer;
030: import org.geotools.styling.PointPlacement;
031: import org.geotools.styling.PointSymbolizer;
032: import org.geotools.styling.PolygonSymbolizer;
033: import org.geotools.styling.RasterSymbolizer;
034: import org.geotools.styling.Rule;
035: import org.geotools.styling.Stroke;
036: import org.geotools.styling.Style;
037: import org.geotools.styling.StyleVisitor;
038: import org.geotools.styling.StyledLayer;
039: import org.geotools.styling.StyledLayerDescriptor;
040: import org.geotools.styling.Symbol;
041: import org.geotools.styling.Symbolizer;
042: import org.geotools.styling.TextSymbolizer;
043: import org.geotools.styling.TextSymbolizer2;
044: import org.geotools.styling.UserLayer;
045: import org.opengis.filter.Filter;
046: import org.opengis.filter.expression.Expression;
047: import org.opengis.filter.expression.Literal;
048:
049: /**
050: * A style visitor whose purpose is to extract a minimal palette for the
051: * provided style. This is to be used when no antialiasing is used, since that
052: * would introduce various colors not included in the style. <br>
053: * At the moment the palette is extracted only if external graphics aren't
054: * referenced (a future version may learn to extract a palette merging the ones
055: * of the external graphics).
056: */
057: public class PaletteExtractor extends FilterAttributeExtractor
058: implements StyleVisitor {
059: public static final Color TRANSPARENT = new Color(255, 255, 255, 0);
060: private static final int TRANSPARENT_CODE = 255 << 16 | 255 << 8
061: | 255;
062:
063: Set/*<Color>*/colors;
064: boolean translucentSymbolizers;
065: boolean externalGraphicsSymbolizers;
066: boolean unknownColors;
067: boolean rasterUsed;
068:
069: /**
070: * Initializes a new palette extractor
071: * @param background background color, or null if transparent
072: */
073: public PaletteExtractor(Color background) {
074: super (null);
075: colors = new HashSet();
076: if (background == null)
077: background = TRANSPARENT;
078: colors.add(background);
079: }
080:
081: public boolean canComputePalette() {
082: // hard fail conditions
083: if (translucentSymbolizers || externalGraphicsSymbolizers
084: || unknownColors || rasterUsed)
085: return false;
086:
087: // impossible to devise a palette (0 shuold never happen, but you never know...)
088: if (colors.size() == 0 || colors.size() > 256)
089: return false;
090:
091: return true;
092: }
093:
094: /**
095: * Returns the palette, or null if it wasn't possible to devise one
096: * @return
097: */
098: public IndexColorModel getPalette() {
099: if (!canComputePalette())
100: return null;
101:
102: int[] cmap = new int[colors.size()];
103: int i = 0;
104: for (Iterator it = colors.iterator(); it.hasNext();) {
105: Color color = (Color) it.next();
106: cmap[i++] = (color.getAlpha() << 24)
107: | (color.getRed() << 16) | (color.getGreen() << 8)
108: | color.getBlue();
109: }
110:
111: // have a nice looking palette
112: Arrays.sort(cmap);
113: int transparentIndex = cmap[cmap.length - 1] == TRANSPARENT_CODE ? cmap.length - 1
114: : -1;
115:
116: // find out the minimum number of bits required to represent the palette, and return it
117: int bits = 8;
118: if (cmap.length <= 2) {
119: bits = 1;
120: } else if (cmap.length <= 4) {
121: bits = 2;
122: } else if (cmap.length <= 16) {
123: bits = 4;
124: }
125:
126: // workaround for GEOS-1341, GEOS-1337 will try to find a solution
127: // int length = (int) Math.pow(2, bits);
128: int length = bits == 1 ? 2 : 256;
129: if (cmap.length < length) {
130: int[] temp = new int[length];
131: System.arraycopy(cmap, 0, temp, 0, cmap.length);
132: cmap = temp;
133: }
134:
135: return new IndexColorModel(bits, cmap.length, cmap, 0, true,
136: transparentIndex, DataBuffer.TYPE_BYTE);
137: }
138:
139: /**
140: * Checks whether translucency is used in the provided expression. Raises the flag
141: * of used translucency unless it's possible to determine it's not.
142: * @param opacity
143: */
144: void handleOpacity(Expression opacity) {
145: if (opacity == null)
146: return;
147: if (opacity instanceof Literal) {
148: Literal lo = (Literal) opacity;
149: double value = ((Double) lo.evaluate(null, Double.class))
150: .doubleValue();
151: translucentSymbolizers = translucentSymbolizers
152: || value != 1;
153: } else {
154: // we cannot know, so we assume some will be non opaque
155: translucentSymbolizers = true;
156: }
157: }
158:
159: /**
160: * Adds a color to the color set, and raises the unknown color flag if the color
161: * is an expression other than a literal
162: * @param color
163: */
164: void handleColor(Expression color) {
165: if (color == null)
166: return;
167: if (color instanceof Literal) {
168: Literal lc = (Literal) color;
169: String rgbColor = (String) lc.evaluate(null, String.class);
170: colors.add(Color.decode(rgbColor));
171: } else {
172: unknownColors = true;
173: }
174: }
175:
176: /**
177: * @see org.geotools.styling.StyleVisitor#visit(org.geotools.styling.Style)
178: */
179: public void visit(Style style) {
180: FeatureTypeStyle[] ftStyles = style.getFeatureTypeStyles();
181:
182: for (int i = 0; i < ftStyles.length; i++) {
183: ftStyles[i].accept(this );
184: }
185: }
186:
187: /**
188: * @see org.geotools.styling.StyleVisitor#visit(org.geotools.styling.Rule)
189: */
190: public void visit(Rule rule) {
191: Filter filter = rule.getFilter();
192:
193: if (filter != null) {
194: filter.accept(this , null);
195: }
196:
197: Symbolizer[] symbolizers = rule.getSymbolizers();
198:
199: if (symbolizers != null) {
200: for (int i = 0; i < symbolizers.length; i++) {
201: Symbolizer symbolizer = symbolizers[i];
202: symbolizer.accept(this );
203: }
204: }
205: }
206:
207: /**
208: * @see org.geotools.styling.StyleVisitor#visit(org.geotools.styling.FeatureTypeStyle)
209: */
210: public void visit(FeatureTypeStyle fts) {
211: Rule[] rules = fts.getRules();
212:
213: for (int i = 0; i < rules.length; i++) {
214: Rule rule = rules[i];
215: rule.accept(this );
216: }
217: }
218:
219: public void visit(StyledLayerDescriptor sld) {
220: StyledLayer[] layers = sld.getStyledLayers();
221:
222: for (int i = 0; i < layers.length; i++) {
223: if (layers[i] instanceof NamedLayer) {
224: ((NamedLayer) layers[i]).accept(this );
225: } else if (layers[i] instanceof UserLayer) {
226: ((UserLayer) layers[i]).accept(this );
227: }
228: }
229: }
230:
231: public void visit(NamedLayer layer) {
232: Style[] styles = layer.getStyles();
233:
234: for (int i = 0; i < styles.length; i++) {
235: styles[i].accept(this );
236: }
237: }
238:
239: public void visit(UserLayer layer) {
240: Style[] styles = layer.getUserStyles();
241:
242: for (int i = 0; i < styles.length; i++) {
243: styles[i].accept(this );
244: }
245: }
246:
247: public void visit(FeatureTypeConstraint ftc) {
248: // nothing to do
249: }
250:
251: /**
252: * @see org.geotools.styling.StyleVisitor#visit(org.geotools.styling.Symbolizer)
253: */
254: public void visit(Symbolizer sym) {
255: if (sym instanceof PointSymbolizer) {
256: visit((PointSymbolizer) sym);
257: }
258:
259: if (sym instanceof LineSymbolizer) {
260: visit((LineSymbolizer) sym);
261: }
262:
263: if (sym instanceof PolygonSymbolizer) {
264: visit((PolygonSymbolizer) sym);
265: }
266:
267: if (sym instanceof TextSymbolizer) {
268: visit((TextSymbolizer) sym);
269: }
270:
271: if (sym instanceof RasterSymbolizer) {
272: visit((RasterSymbolizer) sym);
273: }
274: }
275:
276: /**
277: * @see org.geotools.styling.StyleVisitor#visit(org.geotools.styling.Fill)
278: */
279: public void visit(Fill fill) {
280: handleColor(fill.getBackgroundColor());
281:
282: handleColor(fill.getColor());
283:
284: if (fill.getGraphicFill() != null)
285: fill.getGraphicFill().accept(this );
286:
287: handleOpacity(fill.getOpacity());
288: }
289:
290: /**
291: * @see org.geotools.styling.StyleVisitor#visit(org.geotools.styling.Stroke)
292: */
293: public void visit(Stroke stroke) {
294: handleColor(stroke.getColor());
295:
296: if (stroke.getGraphicFill() != null) {
297: stroke.getGraphicFill().accept(this );
298: }
299:
300: if (stroke.getGraphicStroke() != null) {
301: stroke.getGraphicStroke().accept(this );
302: }
303:
304: handleOpacity(stroke.getOpacity());
305: }
306:
307: public void visit(RasterSymbolizer rs) {
308: rasterUsed = true;
309: }
310:
311: /**
312: * @see org.geotools.styling.StyleVisitor#visit(org.geotools.styling.PointSymbolizer)
313: */
314: public void visit(PointSymbolizer ps) {
315: if (ps.getGraphic() != null) {
316: ps.getGraphic().accept(this );
317: }
318: }
319:
320: /**
321: * @see org.geotools.styling.StyleVisitor#visit(org.geotools.styling.LineSymbolizer)
322: */
323: public void visit(LineSymbolizer line) {
324: if (line.getStroke() != null) {
325: line.getStroke().accept(this );
326: }
327: }
328:
329: /**
330: * @see org.geotools.styling.StyleVisitor#visit(org.geotools.styling.PolygonSymbolizer)
331: */
332: public void visit(PolygonSymbolizer poly) {
333: if (poly.getStroke() != null) {
334: poly.getStroke().accept(this );
335: }
336:
337: if (poly.getFill() != null) {
338: poly.getFill().accept(this );
339: }
340: }
341:
342: /**
343: * @see org.geotools.styling.StyleVisitor#visit(org.geotools.styling.TextSymbolizer)
344: */
345: public void visit(TextSymbolizer text) {
346: if (text instanceof TextSymbolizer2) {
347: if (((TextSymbolizer2) text).getGraphic() != null)
348: ((TextSymbolizer2) text).getGraphic().accept(this );
349: }
350:
351: if (text.getFill() != null) {
352: text.getFill().accept(this );
353: }
354:
355: if (text.getHalo() != null) {
356: text.getHalo().accept(this );
357: }
358: }
359:
360: /**
361: * @see org.geotools.styling.StyleVisitor#visit(org.geotools.styling.Graphic)
362: */
363: public void visit(Graphic gr) {
364: if (gr.getSymbols() != null) {
365: Symbol[] symbols = gr.getSymbols();
366:
367: for (int i = 0; i < symbols.length; i++) {
368: Symbol symbol = symbols[i];
369: symbol.accept(this );
370: }
371: }
372:
373: handleOpacity(gr.getOpacity());
374: }
375:
376: /**
377: * @see org.geotools.styling.StyleVisitor#visit(org.geotools.styling.Mark)
378: */
379: public void visit(Mark mark) {
380: if (mark.getFill() != null) {
381: mark.getFill().accept(this );
382: }
383:
384: if (mark.getStroke() != null) {
385: mark.getStroke().accept(this );
386: }
387: }
388:
389: /**
390: * @see org.geotools.styling.StyleVisitor#visit(org.geotools.styling.ExternalGraphic)
391: */
392: public void visit(ExternalGraphic exgr) {
393: externalGraphicsSymbolizers = true;
394: }
395:
396: /**
397: * @see org.geotools.styling.StyleVisitor#visit(org.geotools.styling.PointPlacement)
398: */
399: public void visit(PointPlacement pp) {
400: // nothing to do
401: }
402:
403: /**
404: * @see org.geotools.styling.StyleVisitor#visit(org.geotools.styling.AnchorPoint)
405: */
406: public void visit(AnchorPoint ap) {
407: // nothing to do
408: }
409:
410: /**
411: * @see org.geotools.styling.StyleVisitor#visit(org.geotools.styling.Displacement)
412: */
413: public void visit(Displacement dis) {
414: // nothing to do
415: }
416:
417: /**
418: * @see org.geotools.styling.StyleVisitor#visit(org.geotools.styling.LinePlacement)
419: */
420: public void visit(LinePlacement lp) {
421: // nothing to do
422: }
423:
424: /**
425: * @see org.geotools.styling.StyleVisitor#visit(org.geotools.styling.Halo)
426: */
427: public void visit(Halo halo) {
428: if (halo.getFill() != null) {
429: halo.getFill().accept(this );
430: }
431: }
432:
433: public void visit(ColorMap map) {
434: // for the moment we don't do anything
435: }
436:
437: public void visit(ColorMapEntry entry) {
438: // nothing to do
439: }
440:
441: }
|