// ********************************************************************** // // <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/BufferedMapBean.java,v $ // $RCSfile: BufferedMapBean.java,v $ // $Revision: 1.6 $ // $Date: 2004/10/14 18:05:39 $ // $Author: dietrick $ // // ********************************************************************** package com.bbn.openmap; import java.awt.AlphaComposite; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Rectangle; import java.awt.event.ComponentEvent; import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; import java.util.logging.Level; import java.util.logging.Logger; import com.bbn.openmap.proj.Projection; /** * The BufferedMapBean extends the MapBean by adding (you guessed it) buffering. * <p> * Specifically, the layers are stored in a java.awt.Image so that the frequent * painting done by Swing on lightweight components will not cause the layers to * do unnecessary work re-rendering themselves each time. * <P> * Changing the default clipping area may cause some Layers to not be drawn * completely, depending on what the clipping area is set to and when the layer * is trying to get itself painted. When manually adjusting clipping area, make * sure that when restricted clipping is over that a full repaint occurs if * there is a chance that another layer may be trying to paint itself. */ public class BufferedMapBean extends MapBean { private static Logger logger = Logger.getLogger(BufferedMapBean.class.getName()); protected boolean bufferDirty = true; protected BufferedImage drawingBuffer = null; protected PanHelper panningTransform = null; public BufferedMapBean() { super(); } public BufferedMapBean(boolean useThreadedNotification) { super(useThreadedNotification); } /** * Invoked when component has been resized. Layer buffer is nullified. and * super.componentResized(e) is called. * * @param e ComponentEvent */ public void componentResized(ComponentEvent e) { setBufferDirty(true); super.componentResized(e); } /** * Provide a drawing buffer for the layers based on the projection * parameters. If the currentImageBuffer is the right size, the pixels will * be cleared. * * @param currentImageBuffer the buffer to reuse and return, if the size is * appropriate. Flushed if another BufferedImage is returned. * @param proj the current projection of the map * @return BufferedImage to be used for image buffer. */ protected BufferedImage resetDrawingBuffer(BufferedImage currentImageBuffer, Projection proj) { try { int w = proj.getWidth(); int h = proj.getHeight(); if (currentImageBuffer != null) { int cibWidth = currentImageBuffer.getWidth(); int cibHeight = currentImageBuffer.getHeight(); if (cibWidth == w && cibHeight == h) { Graphics2D graphics = (Graphics2D) currentImageBuffer.getGraphics(); graphics.setComposite(AlphaComposite.Clear); graphics.fillRect(0, 0, w, h); graphics.setComposite(AlphaComposite.SrcOver); return currentImageBuffer; } else { currentImageBuffer.flush(); } } return new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); } catch (java.lang.NegativeArraySizeException nae) { } catch (java.lang.IllegalArgumentException iae) { } return new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB); } /** * Same as paintChildren, but allows you to set a clipping area to paint. Be * careful with this, because if the clipping area is set while some layer * decides to paint itself, that layer may not have all it's objects * painted. Same warnings apply. */ public void paintChildren(Graphics g, Rectangle clip) { // if a layer has requested a render, then we render all of // them into a drawing buffer BufferedImage localDrawingBuffer = drawingBuffer; if (panningTransform == null && bufferDirty) { bufferDirty = false; localDrawingBuffer = resetDrawingBuffer(localDrawingBuffer, getProjection()); // In case it's been resized drawingBuffer = localDrawingBuffer; // draw the old image Graphics gr = getMapBeanRepaintPolicy().modifyGraphicsForPainting(localDrawingBuffer.getGraphics()); if (logger.isLoggable(Level.FINE)) { logger.fine("BufferedMapBean rendering layers to buffer."); } super.paintChildren(gr, null); gr.dispose(); } else if (logger.isLoggable(Level.FINE)) { logger.fine("BufferedMapBean rendering buffer."); } if (panningTransform != null) { panningTransform.render((Graphics2D) g); return; } else if (localDrawingBuffer != null) { RotationHelper rotHelper = getRotHelper(); if (rotHelper != null) { rotHelper.paintChildren(g, clip); rotHelper.paintPainters(g); } else { drawProjectionBackground(g); // draw the buffer to the screen, daImage will be drawingBuffer // without rotation g.drawImage(localDrawingBuffer, 0, 0, null); if (painters != null) { painters.paint(g); } } } } /** * Interface-like method to query if the MapBean is buffered, so you can * control behavior better. Allows the removal of specific instance-like * queries for, say, BufferedMapBean, when all you really want to know is if * you have the data is buffered, and if so, should be buffer be cleared. * For the BufferedMapBean, always true. */ public boolean isBuffered() { return true; } /** * Marks the image buffer as dirty if value is true. On the next * <code>paintChildren()</code>, we will call <code>paint()</code> on all * Layer components. * * @param value boolean */ public void setBufferDirty(boolean value) { bufferDirty = value; } /** * Checks whether the image buffer should be repainted. * * @return boolean whether the layer buffer is dirty */ public boolean isBufferDirty() { return bufferDirty; } /** * Clear out resources for the current drawing buffer. */ protected void disposeDrawingBuffer() { Image localDrawingBuffer = drawingBuffer; drawingBuffer = null; if (localDrawingBuffer != null) { localDrawingBuffer.flush(); } } public void dispose() { disposeDrawingBuffer(); super.dispose(); } public AffineTransform getPanningTransform() { return panningTransform; } /** * Set a panning transform on the buffer for rendering in a different place, * quickly. Sets the buffer to be dirty, so when the panning transform is * removed, it will be recreated. * * @param transform */ public void setPanningTransform(AffineTransform transform) { if (transform != null) { if (panningTransform == null) { panningTransform = new PanHelper(transform); setBufferDirty(true); } else { panningTransform.update(transform); } } else { if (panningTransform != null) { panningTransform.dispose(); } panningTransform = null; } } protected class PanHelper extends AffineTransform { protected Image buffer; protected PanHelper(AffineTransform aft) { super(aft); this.buffer = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB); paintChildren(this.buffer.getGraphics(), null); } protected void update(AffineTransform aft) { super.setTransform(aft); } protected void render(Graphics2D g) { drawProjectionBackground(g); ((Graphics2D) g).setTransform(this); if (buffer != null) { g.drawImage(buffer, 0, 0, null); } RotationHelper rotationHelper = getRotHelper(); if (rotationHelper == null && painters != null) { painters.paint(g); } } protected void dispose() { if (buffer != null) { buffer.flush(); buffer = null; } } } }