// **********************************************************************
//
// <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/layer/policy/BufferedImageRenderPolicy.java,v $
// $RCSfile: BufferedImageRenderPolicy.java,v $
// $Revision: 1.8 $
// $Date: 2005/10/26 15:47:42 $
// $Author: dietrick $
//
// **********************************************************************
package com.bbn.openmap.layer.policy;
import java.awt.AlphaComposite;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.util.logging.Level;
import com.bbn.openmap.layer.OMGraphicHandlerLayer;
import com.bbn.openmap.omGraphics.OMGraphicList;
import com.bbn.openmap.omGraphics.OMScalingRaster;
import com.bbn.openmap.proj.Cylindrical;
import com.bbn.openmap.proj.Projection;
/**
* The BufferedImageRenderPolicy is a RenderPolicy that creates and uses an
* image buffer based on the painting times for the layer. If the time to paint
* exceeds the bufferTiggerDelay, an image buffer for the layer is used for
* paints as long as the projection doesn't change. A new buffer is used for a
* projection change because we need the image buffer to be transparent for
* parts of the map that are not used by the layer.
*/
public class BufferedImageRenderPolicy extends RenderingHintsRenderPolicy {
protected ImageBuffer imageBuffer = new ImageBuffer();
/**
* Set the layer at some point before use.
*/
public BufferedImageRenderPolicy() {
super();
}
/**
* Don't pass in a null layer.
*/
public BufferedImageRenderPolicy(OMGraphicHandlerLayer layer) {
super(layer);
}
/**
* Set the ImageBuffer, for subclasses to modify it's particular behavior
*/
protected void setImageBuffer(ImageBuffer iBuffer) {
imageBuffer = iBuffer;
}
/**
* @return the imageBuffer
*/
protected ImageBuffer getImageBuffer() {
return imageBuffer;
}
/**
* Called from the OMGraphicHandlerLayer's projectionChanged() method. This
* method updates the current image buffer so it can be re-projected for the
* current projection before the layer worker goes off to do more work. In
* case of rapid projection changes, the layer should be able to display the
* current buffer in the right place, at least. Ghah! Don't do a lot of work
* in this thread.
*
* @param newProj the newest projection known.
*/
public void prePrepare(Projection newProj) {
getImageBuffer().generate(newProj);
}
public OMGraphicList prepare() {
if (layer != null) {
/*
* Grab a copy of the projection that the list is being made for, so
* the buffer gets placed for the right location. It's really
* important in case the projection height and width don't quite
* match up with the coordinate coverage area. Doesn't sound right,
* but that happens occasionally and the images appear stretched out
* if you don't match up the image to the projection.
*/
Projection proj = layer.getProjection();
OMGraphicList list = layer.prepare();
try {
getImageBuffer().update(list, proj);
} catch (NullPointerException npe) {
logger.fine("Caught NPE creating the image buffer for layer: " + layer.getName());
if (logger.isLoggable(Level.FINE)) {
npe.printStackTrace();
}
getImageBuffer().clear();
}
return list;
} else {
logger.warning("NULL layer, can't do anything.");
}
return null;
}
public void paint(Graphics g) {
if (layer == null) {
logger.warning("NULL layer, skipping...");
return;
}
Projection proj = layer.getProjection();
if (layer.isProjectionOK(proj)) {
Graphics2D g2 = (Graphics2D) g.create();
ImageBuffer imageBuffer = getImageBuffer();
setCompositeOnGraphics(g2);
if (!imageBuffer.paint(g2, proj)) {
OMGraphicList list = layer.getList();
if (list != null) {
layer.getList().render(g2);
}
}
g2.dispose();
} else if (logger.isLoggable(Level.FINE)) {
logger.fine(layer.getName() + " BufferedImageRenderPolicy.paint(): skipping due to projection.");
}
}
protected class ImageBuffer {
BufferedImage imageBuffer;
OMScalingRaster imageRaster;
Projection currentProjection;
protected ImageBuffer() {
}
protected void generate(Projection proj) {
OMScalingRaster lImageRaster = getImageRaster();
if (lImageRaster != null) {
if (proj instanceof Cylindrical) {
lImageRaster.setNeedToReposition(true);
lImageRaster.setNeedToRegenerate(true);
lImageRaster.generate(proj);
} else {
setImageRaster(null);
}
}
}
/**
* Return true if something was rendered.
*
* @param g
* @param proj
* @return true if paint was successful for the OMScaling Raster
*/
public boolean paint(Graphics2D g, Projection proj) {
Projection currentProj = currentProjection;
OMScalingRaster omr = getImageRaster();
if (!proj.equals(currentProj)) {
currentProjection = proj;
if (omr != null) {
omr.generate(proj);
omr.render(g);
return true;
}
}
/*
* We used to have a could of calls here that painted the current
* ImageBuffer if it wasn't null. Turns out, that's not a good idea.
* It causes the layer to paint itself in its old location for a
* flash before updating. So we're just going to let the
* OMScalingRaster handling painting.
*/
return false;
}
/**
* Get the updated BufferedImage, with current OMGraphics rendered into
* it. Called with the results of layer.prepare().
*
* @param list OMGraphicList from layer's prepare method.
* @param proj current projection that has buffer size information.
*/
protected void update(OMGraphicList list, Projection proj) {
BufferedImage currentImageBuffer = null;
if (proj != null && layer != null) {
int w = proj.getWidth();
int h = proj.getHeight();
currentImageBuffer = getImageBuffer();
BufferedImage bufferedImage = scrubOrGetNewBufferedImage(currentImageBuffer, w, h);
// Updated image for projection
if (bufferedImage != null) {
if (currentImageBuffer != null) {
currentImageBuffer.flush();
}
currentImageBuffer = bufferedImage;
}
Graphics2D g2d = (Graphics2D) currentImageBuffer.getGraphics();
setRenderingHints(g2d);
if (list != null) {
list.render(g2d);
}
g2d.dispose();
setImageRaster(updateRaster(currentImageBuffer, proj));
}
setImageBuffer(currentImageBuffer);
currentProjection = proj;
}
protected OMScalingRaster updateRaster(BufferedImage imageBuffer, Projection proj) {
if (proj instanceof Cylindrical) {
int w = proj.getWidth();
int h = proj.getHeight();
// If a buffer wasn't created, bail
if (imageBuffer == null) {
return null;
}
Point2D llp1 = proj.getUpperLeft();
/*
* The lower right point is w-1, h-1, the actual pixel index,
* starting at 0. The size of the image is one pixel more. Using
* getLowerRight() leaves one pixel on the bottom and right
* blank in the resulting image.
*/
Point2D llp2 = proj.inverse(w, h);
/*
* We're running into a problem here for the OMScalingRaster
* where the projection is providing a bad coordinate situation
* for OMScalingRasters when zoomed way out. The left pixel
* coordinate of the map, at some point in the OMScalingRaster
* calculations, is being placed to the right of the right pixel
* coordinate of the map. It's a 360 degree precision thing, and
* ever so slight. We're going to test for that here, and if
* that x test fails, we're just going to use a standard
* OMRaster for the Buffer.
*/
/*
* The OMScalingRaster is cool because it can respond to the
* projection change immediately, and display what was there
* before while the layers are working.
*/
Point2D pnt1 = proj.forward(llp1);
Point2D pnt2 = proj.forward(llp2);
if (pnt1.getX() < pnt2.getX() && h > 0 && w > 0) {
OMScalingRaster raster = getImageRaster();
if (raster == null) {
raster = new OMScalingRaster(llp1.getY(), llp1.getX(), llp2.getY(), llp2.getX(), imageBuffer);
} else {
raster.setImage(imageBuffer);
raster.setLat(llp1.getY());
raster.setLon(llp1.getX());
raster.setLRLat(llp2.getY());
raster.setLRLon(llp2.getX());
}
raster.generate(proj);
return raster;
}
}
return null;
}
/**
* Given the current image buffer, and the desired width and height of
* the new projection, return a fresh/refreshed image buffer ready for
* layer painting. Should be clear.
*
* @param currentImage the current BufferedImage
* @param width current projection width, pixels.
* @param height current projection height, pixels.
* @return new BufferedImage if one was needed, i.e. if size changed.
*/
protected BufferedImage scrubOrGetNewBufferedImage(BufferedImage currentImage, int width,
int height) {
int cWidth = -1;
int cHeight = -1;
if (currentImage != null) {
cWidth = currentImage.getWidth();
cHeight = currentImage.getHeight();
}
if (currentImage != null && cWidth == width && cHeight == height) {
// scrub it, refresh the pixels.
Graphics2D graphics = (Graphics2D) currentImage.getGraphics();
graphics.setComposite(AlphaComposite.Clear);
graphics.fillRect(0, 0, width, height);
graphics.setComposite(AlphaComposite.SrcOver);
return null;
}
return new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
}
protected void clear() {
BufferedImage bImage = getImageBuffer();
if (bImage != null) {
Graphics2D graphics = (Graphics2D) bImage.getGraphics();
graphics.setComposite(AlphaComposite.Clear);
graphics.fillRect(0, 0, bImage.getWidth(), bImage.getHeight());
graphics.setComposite(AlphaComposite.SrcOver);
}
setImageRaster(null);
}
protected BufferedImage getImageBuffer() {
return imageBuffer;
}
protected void setImageBuffer(BufferedImage bImage) {
if (imageBuffer != null && imageBuffer != bImage) {
imageBuffer.flush();
}
imageBuffer = bImage;
}
/**
* @return the imageRaster
*/
protected OMScalingRaster getImageRaster() {
return imageRaster;
}
/**
* @param imageRaster the imageRaster to set
*/
protected void setImageRaster(OMScalingRaster imageRaster) {
this.imageRaster = imageRaster;
}
}
}