// ********************************************************************** // // <copyright> // // BBN Technologies // 10 Moulton Street // Cambridge, MA 02138 // (617) 873-8000 // // Copyright (C) BBNT Solutions LLC. All rights reserved. // // </copyright> // ********************************************************************** // // $Source: /cvs/distapps/openmap/src/openmap/com/bbn/openmap/image/AbstractImageFormatter.java,v $ // $RCSfile: AbstractImageFormatter.java,v $ // $Revision: 1.11 $ // $Date: 2008/01/29 22:04:13 $ // $Author: dietrick $ // // ********************************************************************** package com.bbn.openmap.image; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GraphicsEnvironment; import java.awt.geom.Point2D; import java.awt.image.BufferedImage; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.Properties; import com.bbn.openmap.Layer; import com.bbn.openmap.MapBean; import com.bbn.openmap.PropertyConsumer; import com.bbn.openmap.proj.Proj; import com.bbn.openmap.proj.Projection; import com.bbn.openmap.util.Debug; /** * The abstract implementation of the ImageFormatter. The ImageFormatter deals * with most of the image meanderings of Java, while letting you create an image * in a specific format. The ImageFormatter's responsibility has grown slightly, * since it now contains the BufferedImage that it will be formatting. Thisis to * make things go smoother for different uses of the formatter - some image * formats, for instance, really need to utilize a special implementation of a * Graphics in order to create the data file they want. The new definition * allows for that. Generally, however, you'll want to either hand the MapBean * to the formatter to get the image bytes, or, as in the case of the * ImageServer, get a Graphics from the formatter, paint the map into it, then * retrieve the image bytes after that. */ public abstract class AbstractImageFormatter implements ImageFormatter, PropertyConsumer, PropertyChangeListener { protected BufferedImage bufferedImage; protected String propertiesPrefix; public AbstractImageFormatter() { } /** Set the properties of the image formatter. */ public void setProperties(String prefix, Properties props) { } /** * Convert a BufferedImage to a image file format... * * @param bi a BufferedImage.. */ public abstract byte[] formatImage(BufferedImage bi); /** * Create a new instance of the same type of formatter. If you are running * in a multi-threaded environment, you'll need to provide a new instance of * the formatter to each thread, since the image and graphics that are being * drawn into for each thread are contained within. * * @return a new instance of this type of formatter, with the same * properties set. */ public abstract ImageFormatter makeClone(); /** * Return true if the image format support fully transparent pixels. The * returned value represent the capability of the image format, not the * current color model. * * @return true of transparent pixels supported */ protected abstract boolean imageFormatSupportTransparentPixel(); /** * Return true if the image format support alpha channel. The returned value * represent the capability of the image format, not the current color * model. * * @return true if alpha supported */ protected abstract boolean imageFormatSupportAlphaChannel(); /** * Take a MapBean, and get the image bytes that represent the current state. * * @param map the MapBean. * @return byte[] representing an image of the map in it's current state. */ public byte[] getImageFromMapBean(MapBean map) { return getImageFromMapBean(map, -1, -1, false); } /** * Take a MapBean, and get the image bytes that represent the current state. * * @param map the MapBean. * @param width the pixel width of the desired image. * @param height the pixel height of the desired image. * @return byte[] representing an image of the map in it's current state. */ public byte[] getImageFromMapBean(MapBean map, int width, int height) { return getImageFromMapBean(map, width, height, true); } /** * Take a MapBean, and get the image bytes that represent the current state. * * @param map the MapBean. * @param width the pixel width of the desired image. * @param height the pixel height of the desired image. * @param scaleImage true to resize image based on scale * @return byte[] representing an image of the map in it's current state. */ public byte[] getImageFromMapBean(MapBean map, int width, int height, boolean scaleImage) { if (map == null) { return new byte[0]; } Proj proj = (Proj) map.getProjection(); boolean needToScale = (width != proj.getWidth() || height != proj.getHeight()); if (Debug.debugging("formatter")) { Debug.output("AIF: called with w:" + width + ", h:" + height + ", need to scale (" + needToScale + ")" + " and scaleImage (" + scaleImage + ")"); } if (width == -1) width = proj.getWidth(); if (height == -1) height = proj.getHeight(); Graphics graphics = getGraphics(width, height); if (!needToScale) { if (Debug.debugging("formatter")) { Debug.output("AIF: don't need to scale, painting normally."); } // This way just paints what the MapBean is displaying. map.paintAll(graphics); } else { // One problem with this approach is that it will // use the ProjectionPainter interface on the layers. So, // you may not get the same image that is on the map. All // layers on the map will get painted in the image - so if // a layer hasn't painted itself on the map window, you // will see it in the image. // This lets us know what the layers are map.addPropertyChangeListener(this); // Layers should be set... Point2D cp = map.getCenter(); double scaleMod = 1f;// scale factor for image scale // If we need to scale the image, // figure out the scale factor. if (scaleImage) { if (Debug.debugging("formatter")) { Debug.output("AIF: scaling image to w:" + width + ", h:" + height); } double area1 = (double) proj.getHeight() * (double) proj.getWidth(); double area2 = (double) height * (double) width; scaleMod = Math.sqrt(area1 / area2); } Proj tp = (Proj) map.getProjectionFactory().makeProjection(map.getProjection().getClass(), cp, map.getScale() * (float) scaleMod, width, height); tp.drawBackground((Graphics2D) graphics, map.getBckgrnd()); if (layers != null) { for (int i = layers.length - 1; i >= 0; i--) { Projection oldProj = layers[i].getProjection(); layers[i].renderDataForProjection(tp, graphics); if (Debug.debugging("formatter")) { Debug.output("AbstractImageFormatter: rendering " + layers[i].getName()); } // Need to set the old Projection object on the // Layer, not the current MapBean Proj object. If // you set the MapBean Proj object, make sure you // clone it first. The Layer will do a check on // the Projection object it has against any new // ones it receives. If it has the original from // the MapBean, the check it does will return a // false negative, and the layer will think it // doesn't have to do anything. if (oldProj != null && oldProj == map.getProjection()) { // Seems like a lot of users are getting // burned by manually setting the same // projection on the MapBean as they are on // the layers, and the layers are freezing up // after they are used to create an image. // I don't see how this problem is manifesting // itself, but this code section is an attempt // to help. oldProj = oldProj.makeClone(); } layers[i].setProjection(oldProj); } } else { Debug.output("AbstractImageFormatter can't get layers from map!"); } map.removePropertyChangeListener(this); layers = null; } return getImageBytes(); } /** * Return the applicable Graphics to use to paint the layers into. If the * internal BufferedImage hasn't been created yet, or has been set to null, * then a new buffered Image is created, set to the size specified by the * height and width. The ImageGenerator extends MapBean. Remember to dispose * of the graphics object when you are done with it. Uses the default * BufferedImage.TYPE_INT_RGB colormodel. * * @param width pixel width of Graphics. * @param height pixel height of Graphics. * @return Graphics object to use. * @see java.awt.image.BufferedImage */ public Graphics getGraphics(int width, int height) { return getGraphics(width, height, BufferedImage.TYPE_INT_RGB); } public java.awt.Graphics getGraphics(int width, int height, boolean alpha) { int imageFormat = BufferedImage.TYPE_INT_RGB; if (alpha && (imageFormatSupportAlphaChannel() || imageFormatSupportTransparentPixel())) { imageFormat = BufferedImage.TYPE_INT_ARGB; } return getGraphics(width, height, imageFormat); } /** * Return the applicable Graphics to use to paint the layers into. If the * internal BufferedImage hasn't been created yet, or has been set to null, * then a new buffered Image is created, set to the size specified by the * height and width. The ImageGenerator extends MapBean. Remember to dispose * of the graphics object when you are done with it. Lets you select the * image type. * * @param width pixel width of Graphics. * @param height pixel height of Graphics. * @param imageType image type - see BufferedImage * @return java.awt.Graphics object to use. * @see java.awt.image.BufferedImage */ public Graphics getGraphics(int width, int height, int imageType) { bufferedImage = new BufferedImage(width, height, imageType); GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); Graphics g = ge.createGraphics(bufferedImage); g.setClip(0, 0, width, height); return g; } /** * Return the BufferedImage contained within the formatter. * * @return the BufferedImage. */ public BufferedImage getBufferedImage() { return bufferedImage; } /** * Return the BufferedImage contained within the formatter. * * @param bi the BufferedImage to use in this formatter. */ public void setBufferedImage(BufferedImage bi) { bufferedImage = bi; } /** * Scale the internal BufferedImage to the pixel dimensions, and then return * it. * * @param scaledWidth the desired pixel width of the image. * @param scaledHeight the desired pixel height of the image. * @return the scaled BufferedImage. */ public BufferedImage getScaledBufferedImage(int scaledWidth, int scaledHeight) { if (bufferedImage == null) { return null; } if (Debug.debugging("formatter")) { Debug.output("Formatter: scaling image to : " + scaledWidth + ", " + scaledHeight); } java.awt.Image image = ImageScaler.getOptimalScalingImage(bufferedImage, scaledWidth, scaledHeight); if (Debug.debugging("formatter")) { Debug.output("Formatter: creating scaled image..."); } try { BufferedImage buffi = BufferedImageHelper.getBufferedImage(image, 0, 0, -1, -1); // Do this here, in case something bad happens in the // buffered image creation, so at least the original image // is retained. bufferedImage = buffi; } catch (InterruptedException ie) { Debug.error("Formatter: Something bad happened during scaling! \n" + ie); } if (Debug.debugging("formatter")) { Debug.output("Formatter: image successfully scaled"); } return bufferedImage; } /** * Return the image bytes of the formatted image. * * @return byte[] representing the image. */ public byte[] getImageBytes() { BufferedImage bi = getBufferedImage(); if (bi == null) { return new byte[0]; } else { Debug.message("formatter", "Formatter: creating formatted image bytes..."); return formatImage(bi); } } /** * Scale the internal BufferedImage, then return the image bytes of the * formatted image. * * @param scaledWidth the desired pixel width of the image. * @param scaledHeight the desired pixel height of the image. * @return byte[] representing the image. */ public byte[] getScaledImageBytes(int scaledWidth, int scaledHeight) { BufferedImage bi = getScaledBufferedImage(scaledWidth, scaledHeight); if (bi == null) { return new byte[0]; } else { Debug.message("formatter", "Formatter: creating formatted image bytes..."); return formatImage(bi); } } /** * Set the layers and image type in the properties. */ public void setProperties(Properties props) { setProperties((String) null, props); } /** * Part of the PropertyConsumer interface. Doesn't do anything yet. */ public Properties getProperties(Properties props) { if (props == null) { props = new Properties(); } return props; } /** * Part of the PropertyConsumer interface. */ public Properties getPropertyInfo(Properties list) { if (list == null) { list = new Properties(); } return list; } /** * Part of the PropertyConsumer interface. Set the Properties prefix to use * to scope the relevant properties passed into the setProperties method. */ public void setPropertyPrefix(String prefix) { propertiesPrefix = prefix; } /** * Part of the PropertyConsumer interface. Get the Properties prefix used to * scope the relevant properties passed into the setProperties method. */ public String getPropertyPrefix() { return propertiesPrefix; } /** * Used when the layers from the MapBean are needed, in order to use the * renderDataForProjection method. */ protected Layer[] layers = null; /** * Used when the layers from the MapBean are needed, in order to use the * renderDataForProjection method. Sets the Layer[] by adding the formatter * as a PropertyChangeListener to the MapBean. Remember to remove the * formatter from the MapBean as a PropertyChangeListener. */ public void propertyChange(PropertyChangeEvent pce) { String propName = pce.getPropertyName(); if (propName == MapBean.LayersProperty) { layers = (Layer[]) pce.getNewValue(); } } }