//**********************************************************************
//
//<copyright>
//
//BBN Technologies
//10 Moulton Street
//Cambridge, MA 02138
//(617) 873-8000
//
//Copyright (C) BBNT Solutions LLC. All rights reserved.
//
//</copyright>
//**********************************************************************
//
//$Source:
///cvs/darwars/ambush/aar/src/com/bbn/ambush/mission/MissionHandler.java,v
//$
//$RCSfile: MissionHandler.java,v $
//$Revision: 1.10 $
//$Date: 2004/10/21 20:08:31 $
//$Author: dietrick $
//
//**********************************************************************
package com.bbn.openmap.omGraphics.util;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.awt.image.ImageObserver;
import java.awt.image.PixelGrabber;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.bbn.openmap.dataAccess.image.WorldFile;
import com.bbn.openmap.omGraphics.OMRaster;
import com.bbn.openmap.proj.Projection;
import com.bbn.openmap.proj.coords.GeoCoordTransformation;
import com.bbn.openmap.proj.coords.LatLonGCT;
import com.bbn.openmap.proj.coords.LatLonPoint;
import com.bbn.openmap.util.DataBounds;
import com.bbn.openmap.util.Debug;
/**
* The ImageTranslator is the object that takes a BufferedImage and creates an
* OMRaster from it based on a Projection object.
*/
public class ImageWarp {
public static Logger logger = Logger.getLogger("com.bbn.openmap.omGraphics.util.ImageWarp");
/**
* Source image pixels.
*/
protected int[] pixels = null;
/** Image width, */
protected int iwidth;
/** Image height, */
protected int iheight;
/**
* Horizontal units/pixel in the source BufferedImage projection. Assumed to
* be constant across the image.
*/
protected double hor_upp;
/**
* Vertical units/pixel in the source BufferedImage projection. Assumed to
* be constant across the image.
*/
protected double ver_upp;
/**
* The vertical origin pixel location in the source image for the coordinate
* system origin.
*/
protected double verOrigin;
/**
* The horizontal origin pixel location in the source image for the
* coordinate system origin.
*/
protected double horOrigin;
/**
* A transformation for the projection of the source image. If not set, the
* image is assumed to be equal arc projection.
*/
protected GeoCoordTransformation geoTrans = new LatLonGCT();
/**
* The coordinate bounds of the image, represented in the coordinate system
* of the image.
*/
protected DataBounds sourceImageBounds;
/**
* The coordinate image bounds of the projected image on the map window.
*/
protected DataBounds projectedImageBounds;
/**
* Create an image warp for an image assumed to be world wide coverage, with
* the top at 90 degrees, the bottom at -90, the left side at -180 and the
* right side at 180. Assumes the origin point is in the middle of the
* image.
*/
public ImageWarp(BufferedImage bi) {
this(bi, LatLonGCT.INSTANCE, new DataBounds(-180.0, -90.0, 180.0, 90.0));
}
/**
* Create an image warp with some additional transform information.
*
* @param bi BufferedImage of the source
* @param transform the GeoCoordTransformation for the projection of the
* image.
* @param imageBounds the bounds of the image in the image's coordinate
* system.
*/
public ImageWarp(BufferedImage bi, GeoCoordTransformation transform, DataBounds imageBounds) {
if (bi != null) {
iwidth = bi.getWidth();
iheight = bi.getHeight();
setGeoTrans(transform);
setImageBounds(imageBounds);
pixels = getPixels(bi, 0, 0, iwidth, iheight);
// See if this saves on memory. Seems to.
bi = null;
}
}
/**
* Create an image warp with some additional transform information.
*
* @param bi BufferedImage of the source
* @param transform the GeoCoordTransformation for the projection of the
* image.
* @param worldFile the WorldFile describing the image's location.
*/
public ImageWarp(BufferedImage bi, GeoCoordTransformation transform, WorldFile worldFile) {
if (bi != null) {
iwidth = bi.getWidth();
iheight = bi.getHeight();
setGeoTrans(transform);
setImageBounds(worldFile);
pixels = getPixels(bi, 0, 0, iwidth, iheight);
// See if this saves on memory. Seems to.
bi = null;
}
}
/**
* Create an image warp for an image assumed to be world wide coverage, with
* the top at 90 degrees, the bottom at -90, the left side at -180 and the
* right side at 180. Assumes the origin point is in the middle of the
* image.
*
* @param pix ARGB array of pixel values for image.
* @param width pixel width of image.
* @param height pixel height of image.
*/
public ImageWarp(int[] pix, int width, int height) {
this(pix, width, height, LatLonGCT.INSTANCE, new DataBounds(-180.0, -90.0, 180.0, 90.0));
}
/**
* Create an image warp with some additional transform information.
*
* @param pix ARGB array of pixel values for image.
* @param width pixel width of image.
* @param height pixel height of image.
* @param transform the GeoCoordTransformation for the projection of the
* image.
* @param imageBounds the bounds of the image in the image's coordinate
* system.
*/
public ImageWarp(int[] pix, int width, int height, GeoCoordTransformation transform,
DataBounds imageBounds) {
if (pix != null) {
iwidth = width;
iheight = height;
setGeoTrans(transform);
setImageBounds(imageBounds);
pixels = pix;
}
}
/**
* Create an image warp with some additional transform information.
*
* @param pix ARGB array of pixel values for image.
* @param width pixel width of image.
* @param height pixel height of image.
* @param transform the GeoCoordTransformation for the projection of the
* image.
* @param worldFile the WorldFile describing the image's location.
*/
public ImageWarp(int[] pix, int width, int height, GeoCoordTransformation transform,
WorldFile worldFile) {
if (pix != null) {
iwidth = width;
iheight = height;
setGeoTrans(transform);
setImageBounds(worldFile);
pixels = pix;
}
}
/**
* The pixels used in the OMRaster.
*/
// int[] tmpPixels = new int[0];
/**
* Return an OMRaster that covers the given projection, with the image
* warped for the projection.
*
* @param p map projection
* @return OMRaster or null if the image isn't within the current
* projection.
*/
public OMRaster getOMRaster(Projection p) {
int[] pixels = getImagePixels(p);
if (pixels != null && projectedImageBounds != null) {
int width = (int) Math.ceil(projectedImageBounds.getWidth());
int height = (int) Math.ceil(projectedImageBounds.getHeight());
int x = (int) Math.floor(projectedImageBounds.getMin().getX());
int y = (int) Math.floor(projectedImageBounds.getMin().getY());
OMRaster raster = new OMRaster(x, y, width, height, pixels);
raster.generate(p);
return raster;
}
return null;
}
/**
* Given a projection, return the pixels for an image that will cover the
* projection area.
*
* @param p map projection
* @return int[] of ARGB pixels for an image covering the given projection.
*/
public int[] getImagePixels(Projection p) {
if (pixels != null && p != null) {
projectedImageBounds = calculateProjectedImageBounds(p);
if (projectedImageBounds == null) {
// image isn't on the map.
return null;
}
int projHeight = (int) Math.ceil(projectedImageBounds.getHeight());
int projWidth = (int) Math.ceil(projectedImageBounds.getWidth());
// See if we can reuse the pixel array we have.
int[] tmpPixels = new int[projWidth * projHeight];
int numTmpPixels = tmpPixels.length;
logger.fine("tmpPixels[" + numTmpPixels + "]");
int clear = 0x00000000;
Point2D ctp = new Point2D.Double();
Point2D ddll = new Point2D.Double();
Point2D imageCoord = new Point2D.Double();
Point2D center = p.getCenter();
if (logger.isLoggable(Level.FINE)) {
logger.fine(projectedImageBounds.toString());
}
int minx = (int) Math.floor(projectedImageBounds.getMin().getX());
int miny = (int) Math.floor(projectedImageBounds.getMin().getY());
int maxx = (int) Math.ceil(projectedImageBounds.getMax().getX());
int maxy = (int) Math.ceil(projectedImageBounds.getMax().getY());
// i and j are map window pixel values.
for (int i = minx; i < maxx; i++) {
for (int j = miny; j < maxy; j++) {
// ix and iy are pixel coordinates of the destination image.
int ix = i - minx;
int iy = j - miny;
// index into the OMRaster pixel array
int tmpIndex = (ix + (iy * projWidth));
if (tmpIndex >= numTmpPixels) {
continue;
}
ddll = p.inverse(i, j, ddll);
// If the llp calculated isn't on the map,
// don't bother drawing it. Could be a space
// point in Orthographic projection, for
// instance.
if (ddll.equals(center)) {
p.forward(ddll, ctp);
if (ctp.getX() != i || ctp.getY() != j) {
tmpPixels[tmpIndex] = clear;
continue;
}
}
if (geoTrans != null) {
geoTrans.forward(ddll.getY(), ddll.getX(), imageCoord);
} else {
imageCoord = ddll;
}
if (!sourceImageBounds.contains(imageCoord)) {
tmpPixels[tmpIndex] = clear;
continue;
}
// Find the corresponding pixel location in
// the source image.
int horIndex = (int) Math.round(horOrigin + (imageCoord.getX() / hor_upp));
int verIndex = (int) Math.round(verOrigin + (imageCoord.getY() / ver_upp));
if (horIndex < 0 || horIndex >= iwidth || verIndex < 0 || verIndex >= iheight) {
// pixel not on the source image. This
// happens if the image doesn't cover the
// entire earth.
continue;
}
int imageIndex = horIndex + (verIndex * iwidth);
if (imageIndex >= 0 && imageIndex < pixels.length) {
tmpPixels[tmpIndex] = pixels[imageIndex];
}
}
}
logger.fine("finished creating image");
return tmpPixels;
}
logger.warning("problem creating image, no pixels: " + (pixels == null ? "true" : "false")
+ ", no projection:" + (p == null ? "true" : "false"));
// If you get here, something's not right.
return null;
}
/**
* Returns the image bounds of the image as it would be warped to the
* provided projection.
*
* @param p Projection the image will be displayed on
* @return DataBounds, in the projected coordinate space.
*/
public DataBounds calculateProjectedImageBounds(Projection p) {
// This doesn't seem to do anything but slow things down.
// if (geoTrans.equals(LatLonGCT.INSTANCE)) {
// // whole earth
// logger.fine("just using whole screen image");
// return new DataBounds(0, 0, p.getWidth(), p.getHeight());
// }
DataBounds db = null;
if (sourceImageBounds != null) {
int pw = p.getWidth();
int ph = p.getHeight();
Point2D min = sourceImageBounds.getMin();
Point2D max = sourceImageBounds.getMax();
double x1 = Math.floor(min.getX());
double y1 = Math.floor(min.getY());
double x2 = Math.ceil(max.getX());
double y2 = Math.ceil(max.getY());
double width = sourceImageBounds.getWidth();
double height = sourceImageBounds.getHeight();
// These are just memory savers, reused for every calculation.
LatLonPoint tmpG = new LatLonPoint.Double();
Point2D tmpP = new Point2D.Double();
db = new DataBounds();
db.setHardLimits(new DataBounds(0, 0, pw, ph));
db.add(p.forward(geoTrans.inverse(x1, y1, tmpG), tmpP));
db.add(p.forward(geoTrans.inverse(x1, y2, tmpG), tmpP));
db.add(p.forward(geoTrans.inverse(x2, y1, tmpG), tmpP));
db.add(p.forward(geoTrans.inverse(x2, y2, tmpG), tmpP));
double numSplits = 4;
double xSpacer = width / numSplits;
double ySpacer = height / numSplits;
for (int i = 1; i < numSplits; i++) {
db.add(p.forward(geoTrans.inverse(Math.ceil(x1 + xSpacer * i), y1, tmpG), tmpP));
db.add(p.forward(geoTrans.inverse(x1, Math.ceil(y1 + ySpacer * i), tmpG), tmpP));
db.add(p.forward(geoTrans.inverse(Math.ceil(x1 + xSpacer * i), y2, tmpG), tmpP));
db.add(p.forward(geoTrans.inverse(x2, Math.ceil(y1 + ySpacer * i), tmpG), tmpP));
}
if (db.getWidth() <= 0 || db.getHeight() <= 0) {
logger.fine("dimensions of data bounds bad, returning null " + db);
return null;
}
}
return db;
}
/**
* Convenience function to get projected image bounds for an image that has
* been warped for a given projection. If you've grabbed the pixel ints,
* this is how you get the projected image bounds for those ints.
*
* @return Rectangle for bounds
*/
public Rectangle getProjectedImageBoundsForLastProjection() {
DataBounds projImageBounds = projectedImageBounds;
Rectangle rect = null;
if (projImageBounds != null) {
int minx = (int) Math.floor(projImageBounds.getMin().getX());
int miny = (int) Math.floor(projImageBounds.getMin().getY());
int maxx = (int) Math.ceil(projImageBounds.getMax().getX());
int maxy = (int) Math.ceil(projImageBounds.getMax().getY());
rect = new Rectangle(minx, miny, maxx - minx, maxy - miny);
}
return rect;
}
/**
* Get the pixels from the BufferedImage. If anything goes wrong, returns a
* int[0].
*/
protected int[] getPixels(Image img, int x, int y, int w, int h) {
int[] pixels = new int[w * h];
PixelGrabber pg = new PixelGrabber(img, x, y, w, h, pixels, 0, w);
try {
pg.grabPixels();
} catch (InterruptedException e) {
Debug.error("ImageTranslator: interrupted waiting for pixels!");
return new int[0];
}
if ((pg.getStatus() & ImageObserver.ABORT) != 0) {
System.err.println("ImageTranslator: image fetch aborted or errored");
return new int[0];
}
return pixels;
}
public int getIwidth() {
return iwidth;
}
public void setIwidth(int iwidth) {
this.iwidth = iwidth;
}
public int getIheight() {
return iheight;
}
public void setIheight(int iheight) {
this.iheight = iheight;
}
public double getHor_dpp() {
return hor_upp;
}
public void setHor_dpp(double hor_dpp) {
this.hor_upp = hor_dpp;
}
public double getVer_dpp() {
return ver_upp;
}
public void setVer_dpp(double ver_dpp) {
this.ver_upp = ver_dpp;
}
public double getVerOrigin() {
return verOrigin;
}
public void setVerOrigin(double verOrigin) {
this.verOrigin = verOrigin;
}
public double getHorOrigin() {
return horOrigin;
}
public void setHorOrigin(double horOrigin) {
this.horOrigin = horOrigin;
}
public GeoCoordTransformation getGeoTrans() {
return geoTrans;
}
public void setGeoTrans(GeoCoordTransformation geoTrans) {
this.geoTrans = geoTrans;
}
public DataBounds getImageBounds() {
return sourceImageBounds;
}
public void setImageBounds(DataBounds imageBounds) {
this.sourceImageBounds = imageBounds;
hor_upp = imageBounds.getWidth() / iwidth;
// need the negative sign because latitudes increase in the opposite
// direction as y pixel values.
boolean yDirUp = imageBounds.isyDirUp();
ver_upp = imageBounds.getHeight() / iheight;
if (yDirUp) {
ver_upp *= -1;
}
// We should be able to just go from the lower left corner of the image
// and find zero from there, the min of both bounds values.
double leftX = imageBounds.getMin().getX();
double upperY = yDirUp ? imageBounds.getMax().getY() : imageBounds.getMin().getY();
verOrigin = -upperY / ver_upp; // number of Y pixels to origin.
horOrigin = -leftX / hor_upp; // number of X pixels to origin.
if (logger.isLoggable(Level.FINE)) {
logger.fine("getting image pixels w:" + iwidth + ", h:" + iheight + ", hor upp:"
+ hor_upp + ", ver upp:" + ver_upp + ", verOrigin:" + verOrigin
+ ", horOrigin:" + horOrigin);
logger.fine(imageBounds.toString());
}
}
public void setImageBounds(WorldFile worldFile) {
hor_upp = worldFile.getXDim();
// world file dimensions have direction, negative for going down
ver_upp = worldFile.getYDim();
double leftX = worldFile.getX();
double upperY = worldFile.getY();
verOrigin = -worldFile.getY() / ver_upp; // number of Y pixels to
// origin.
horOrigin = -leftX / hor_upp; // number of X pixels to origin.
sourceImageBounds = new DataBounds(leftX, worldFile.getY() + ver_upp * iheight, leftX
+ hor_upp * iwidth, upperY);
if (logger.isLoggable(Level.FINE)) {
logger.fine("getting image pixels w:" + iwidth + ", h:" + iheight + ", hor upp:"
+ hor_upp + ", ver upp:" + ver_upp + ", verOrigin:" + verOrigin
+ ", horOrigin:" + horOrigin);
logger.fine(sourceImageBounds.toString());
}
}
public static void main(String[] args) {
new ImageWarp(new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB), LatLonGCT.INSTANCE, new DataBounds(25, -90, 180, 90));
}
}