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.AlphaComposite;
008: import java.awt.Color;
009: import java.awt.Graphics2D;
010: import java.awt.RenderingHints;
011: import java.awt.Transparency;
012: import java.awt.image.BufferedImage;
013: import java.awt.image.ColorModel;
014: import java.awt.image.DataBuffer;
015: import java.awt.image.IndexColorModel;
016: import java.awt.image.Raster;
017: import java.awt.image.RenderedImage;
018: import java.awt.image.VolatileImage;
019: import java.awt.image.WritableRaster;
020: import java.util.HashMap;
021: import java.util.Map;
022: import java.util.logging.Level;
023: import java.util.logging.Logger;
024:
025: import javax.media.jai.TiledImage;
026:
027: import org.geotools.image.ImageWorker;
028: import org.vfny.geoserver.wms.WmsException;
029: import org.vfny.geoserver.wms.responses.palette.CustomPaletteBuilder;
030: import org.vfny.geoserver.wms.responses.palette.InverseColorMapOp;
031:
032: /**
033: * Provides utility methods for the shared handling of images by the raster map
034: * and legend producers.
035: *
036: * @author Gabriel Roldan
037: * @version $Id$
038: */
039: public class ImageUtils {
040: private static final Logger LOGGER = org.geotools.util.logging.Logging
041: .getLogger("org.vfny.geoserver.responses.wms.map");
042:
043: /**
044: * Forces the use of the class as a pure utility methods one by declaring a
045: * private default constructor.
046: */
047: private ImageUtils() {
048: // do nothing
049: }
050:
051: /**
052: * Sets up a {@link BufferedImage#TYPE_4BYTE_ABGR} if the paletteInverter is
053: * not provided, or a indexed image otherwise. Subclasses may override this
054: * method should they need a special kind of image
055: *
056: * @param width
057: * the width of the image to create.
058: * @param height
059: * the height of the image to create.
060: * @param paletteInverter
061: * an {@link IndexColorModel} if the image is to be indexed, or
062: * <code>null</code> otherwise.
063: * @return an image of size <code>width x height</code> appropriate for
064: * the given color model, if any, and to be used as a transparent
065: * image or not depending on the <code>transparent</code>
066: * parameter.
067: */
068: public static BufferedImage createImage(final int width,
069: final int height, final IndexColorModel palette,
070: final boolean transparent) {
071: if (palette != null) {
072: // unfortunately we can't use packed rasters because line rendering
073: // gets completely
074: // broken, see GEOS-1312 (http://jira.codehaus.org/browse/GEOS-1312)
075: // final WritableRaster raster =
076: // palette.createCompatibleWritableRaster(width, height);
077: final WritableRaster raster = Raster
078: .createInterleavedRaster(palette.getTransferType(),
079: width, height, 1, null);
080: return new BufferedImage(palette, raster, false, null);
081: }
082:
083: if (transparent) {
084: return new BufferedImage(width, height,
085: BufferedImage.TYPE_4BYTE_ABGR);
086: }
087: // don't use alpha channel if the image is not transparent
088: return new BufferedImage(width, height,
089: BufferedImage.TYPE_3BYTE_BGR);
090:
091: }
092:
093: /**
094: * Sets up and returns a {@link Graphics2D} for the given
095: * <code>preparedImage</code>, which is already prepared with a
096: * transparent background or the given background color.
097: *
098: * @param transparent
099: * whether the graphics is transparent or not.
100: * @param bgColor
101: * the background color to fill the graphics with if its not
102: * transparent.
103: * @param preparedImage
104: * the image for which to create the graphics.
105: * @param extraHints
106: * an optional map of extra rendering hints to apply to the
107: * {@link Graphics2D}, other than
108: * {@link RenderingHints#KEY_ANTIALIASING}.
109: * @return a {@link Graphics2D} for <code>preparedImage</code> with
110: * transparent background if <code>transparent == true</code> or
111: * with the background painted with <code>bgColor</code>
112: * otherwise.
113: */
114: public static Graphics2D prepareTransparency(
115: final boolean transparent, final Color bgColor,
116: final RenderedImage preparedImage, final Map extraHints) {
117: final Graphics2D graphic;
118:
119: if (preparedImage instanceof BufferedImage) {
120: graphic = ((BufferedImage) preparedImage).createGraphics();
121: } else if (preparedImage instanceof TiledImage) {
122: graphic = ((TiledImage) preparedImage).createGraphics();
123: } else if (preparedImage instanceof VolatileImage) {
124: graphic = ((VolatileImage) preparedImage).createGraphics();
125: } else {
126: throw new WmsException("Unrecognized back-end image type");
127: }
128:
129: // fill the background with no antialiasing
130: Map hintsMap;
131: if (extraHints == null) {
132: hintsMap = new HashMap();
133: } else {
134: hintsMap = new HashMap(extraHints);
135: }
136: hintsMap.put(RenderingHints.KEY_ANTIALIASING,
137: RenderingHints.VALUE_ANTIALIAS_OFF);
138: graphic.setRenderingHints(hintsMap);
139: if (transparent) {
140: if (LOGGER.isLoggable(Level.FINE)) {
141: LOGGER.fine("setting to transparent");
142: }
143:
144: int type = AlphaComposite.SRC;
145: graphic.setComposite(AlphaComposite.getInstance(type));
146:
147: Color c = new Color(bgColor.getRed(), bgColor.getGreen(),
148: bgColor.getBlue(), 0);
149: graphic.setBackground(bgColor);
150: graphic.setColor(c);
151: graphic.fillRect(0, 0, preparedImage.getWidth(),
152: preparedImage.getHeight());
153: type = AlphaComposite.SRC_OVER;
154: graphic.setComposite(AlphaComposite.getInstance(type));
155: } else {
156: graphic.setColor(bgColor);
157: graphic.fillRect(0, 0, preparedImage.getWidth(),
158: preparedImage.getHeight());
159: }
160: return graphic;
161: }
162:
163: /**
164: * @param originalImage
165: * @return
166: */
167: public static RenderedImage forceIndexed8Bitmask(
168: RenderedImage originalImage,
169: final InverseColorMapOp invColorMap) {
170: // /////////////////////////////////////////////////////////////////
171: //
172: // Check what we need to do depending on the color model of the image we
173: // are working on.
174: //
175: // /////////////////////////////////////////////////////////////////
176: final ColorModel cm = originalImage.getColorModel();
177: final boolean dataTypeByte = originalImage.getSampleModel()
178: .getDataType() == DataBuffer.TYPE_BYTE;
179: RenderedImage image;
180:
181: // /////////////////////////////////////////////////////////////////
182: //
183: // IndexColorModel and DataBuffer.TYPE_BYTE
184: //
185: // ///
186: //
187: // If we got an image whose color model is already indexed on 8 bits
188: // we have to check if it is bitmask or not.
189: //
190: // /////////////////////////////////////////////////////////////////
191: if ((cm instanceof IndexColorModel) && dataTypeByte) {
192: final IndexColorModel icm = (IndexColorModel) cm;
193:
194: if (icm.getTransparency() != Transparency.TRANSLUCENT) {
195: // //
196: //
197: // The image is indexed on 8 bits and the color model is either
198: // opaque or bitmask. WE do not have to do anything.
199: //
200: // //
201: image = originalImage;
202: } else {
203: // //
204: //
205: // The image is indexed on 8 bits and the color model is
206: // Translucent, we have to perform some color operations in
207: // order to convert it to bitmask.
208: //
209: // //
210: image = new ImageWorker(originalImage)
211: .forceBitmaskIndexColorModel()
212: .getRenderedImage();
213: }
214: } else {
215: // /////////////////////////////////////////////////////////////////
216: //
217: // NOT IndexColorModel and DataBuffer.TYPE_BYTE
218: //
219: // ///
220: //
221: // We got an image that needs to be converted.
222: //
223: // /////////////////////////////////////////////////////////////////
224:
225: if (invColorMap != null) {
226:
227: // make me parametric which means make me work with other image
228: // types
229: image = invColorMap.filterRenderedImage(originalImage);
230: } else {
231: // //
232: //
233: // We do not have a paletteInverter, let's create a palette that
234: // is as good as possible.
235: //
236: // //
237: // make sure we start from a componentcolormodel.
238: image = new ImageWorker(originalImage)
239: .forceComponentColorModel().getRenderedImage();
240:
241: // if (originalImage.getColorModel().hasAlpha()) {
242: // // //
243: // //
244: // // We want to use the CustomPaletteBuilder but to do so we
245: // // have first to reduce the image to either opaque or
246: // // bitmask because otherwise the CustomPaletteBuilder will
247: // // fail to address transparency.
248: // //
249: // // //
250: // // I am exploiting the clamping property of the JAI
251: // // MultiplyCOnst operation.
252: // // TODO make this code parametric since people might want to
253: // // use a different transparency threshold. Right now we are
254: // // thresholding the transparency band using a fixed
255: // // threshold of 255, which means that anything that was not
256: // // transparent will become opaque.
257: // //
258: // ////
259: // final RenderedImage alpha = new ImageWorker(originalImage)
260: // .retainLastBand().multiplyConst(
261: // new double[] { 255.0 }).retainFirstBand()
262: // .getRenderedImage();
263: //
264: // final int numBands = originalImage.getSampleModel()
265: // .getNumBands();
266: // originalImage = new ImageWorker(originalImage).retainBands(
267: // numBands - 1).getRenderedImage();
268: //
269: // final ImageLayout layout = new ImageLayout();
270: //
271: // if (numBands == 4) {
272: // layout.setColorModel(new ComponentColorModel(ColorSpace
273: // .getInstance(ColorSpace.CS_sRGB), true, false,
274: // Transparency.BITMASK, DataBuffer.TYPE_BYTE));
275: // } else {
276: // layout.setColorModel(new ComponentColorModel(ColorSpace
277: // .getInstance(ColorSpace.CS_GRAY), true, false,
278: // Transparency.BITMASK, DataBuffer.TYPE_BYTE));
279: // }
280: //
281: // image = BandMergeDescriptor.create(originalImage, alpha,
282: // new RenderingHints(JAI.KEY_IMAGE_LAYOUT, layout))
283: // .getNewRendering();
284: // } else {
285: // // //
286: // //
287: // // Everything is fine
288: // //
289: // // //
290: // image = originalImage;
291: // }
292:
293: // //
294: //
295: // Build the CustomPaletteBuilder doing some good subsampling.
296: //
297: // //
298: final int subsx = (int) Math.pow(2,
299: image.getWidth() / 256);
300: final int subsy = (int) Math.pow(2,
301: image.getHeight() / 256);
302: image = new CustomPaletteBuilder(image, 256, subsx,
303: subsy, 1).buildPalette().getIndexedImage();
304: }
305: }
306:
307: return image;
308: }
309: }
|