gov.nasa.worldwind.util.ImageUtil Maven / Gradle / Ivy
The newest version!
/*
* Copyright (C) 2012 United States Government as represented by the Administrator of the
* National Aeronautics and Space Administration.
* All Rights Reserved.
*/
package gov.nasa.worldwind.util;
import gov.nasa.worldwind.avlist.*;
import gov.nasa.worldwind.data.*;
import gov.nasa.worldwind.exception.WWRuntimeException;
import gov.nasa.worldwind.formats.tiff.GeotiffReader;
import gov.nasa.worldwind.formats.worldfile.WorldFile;
import gov.nasa.worldwind.geom.*;
import gov.nasa.worldwind.geom.coords.*;
import gov.nasa.worldwind.globes.Earth;
import javax.imageio.*;
import javax.imageio.stream.*;
import javax.swing.*;
import java.awt.*;
import java.awt.color.*;
import java.awt.image.*;
import java.io.*;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.List;
/**
* @author tag
* @version $Id: ImageUtil.java 1353 2013-05-20 18:43:06Z tgaskins $
*/
public class ImageUtil
{
public static int NEAREST_NEIGHBOR_INTERPOLATION = 1;
public static int BILINEAR_INTERPOLATION = 2;
public static int IMAGE_TILE_SIZE = 1024; // default size to make subimages
public static Color TRANSPARENT = new Color(0, 0, 0, 0);
/**
* Draws the specified image
onto the canvas
, scaling or stretching the image to fit the
* canvas. This will apply a bilinear filter to the image if any scaling or stretching is necessary.
*
* @param image the BufferedImage to draw, potentially scaling or stretching to fit the canvas
.
* @param canvas the BufferedImage to receive the scaled or stretched image
.
*
* @throws IllegalArgumentException if either image
or canvas
is null.
*/
public static void getScaledCopy(BufferedImage image, BufferedImage canvas)
{
if (image == null)
{
String message = Logging.getMessage("nullValue.ImageIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (canvas == null)
{
String message = Logging.getMessage("nullValue.CanvasIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
java.awt.Graphics2D g2d = canvas.createGraphics();
try
{
g2d.setComposite(java.awt.AlphaComposite.Src);
g2d.setRenderingHint(
java.awt.RenderingHints.KEY_INTERPOLATION, java.awt.RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.drawImage(image, 0, 0, canvas.getWidth(), canvas.getHeight(), null);
}
finally
{
g2d.dispose();
}
}
/**
* Rasterizes the image into the canvas, given a transform that maps canvas coordinates to image coordinates.
*
* @param image the source image.
* @param canvas the image to receive the transformed source image.
* @param canvasToImageTransform Matrix
that maps a canvas coordinates to image coordinates.
*
* @throws IllegalArgumentException if any of image
, canvas
, or canvasToImageTransform
* are null.
*/
public static void warpImageWithTransform(BufferedImage image, BufferedImage canvas, Matrix canvasToImageTransform)
{
if (image == null)
{
String message = Logging.getMessage("nullValue.ImageIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (canvas == null)
{
String message = Logging.getMessage("nullValue.CanvasIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (canvasToImageTransform == null)
{
String message = Logging.getMessage("nullValue.MatrixIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
int sourceWidth = image.getWidth();
int sourceHeight = image.getHeight();
int destWidth = canvas.getWidth();
int destHeight = canvas.getHeight();
for (int dy = 0; dy < destHeight; dy++)
{
for (int dx = 0; dx < destWidth; dx++)
{
Vec4 vec = new Vec4(dx, dy, 1).transformBy3(canvasToImageTransform);
if (vec.x >= 0 && vec.y >= 0 && vec.x <= (sourceWidth - 1) && vec.y <= (sourceHeight - 1))
{
int x0 = (int) Math.floor(vec.x);
int x1 = (int) Math.ceil(vec.x);
double xf = vec.x - x0;
int y0 = (int) Math.floor(vec.y);
int y1 = (int) Math.ceil(vec.y);
double yf = vec.y - y0;
int color = interpolateColor(xf, yf,
image.getRGB(x0, y0),
image.getRGB(x1, y0),
image.getRGB(x0, y1),
image.getRGB(x1, y1));
canvas.setRGB(dx, dy, color);
}
}
}
}
/**
* Convenience method for transforming a georeferenced source image into a geographically aligned destination image.
* The source image is georeferenced by either three four control points. Each control point maps a location in the
* source image to a geographic location. This is equivalent to calling {#transformConstrainedImage3} or
* {#transformConstrainedImage4}, depending on the number of control points.
*
* @param sourceImage the source image to transform.
* @param imagePoints three or four control points in the source image.
* @param geoPoints three or four geographic locations corresponding to each source control point.
* @param destImage the destination image to receive the transformed source imnage.
*
* @return bounding sector for the geographically aligned destination image.
*
* @throws IllegalArgumentException if any of sourceImage
, destImage
,
* imagePoints
or geoPoints
is null, or if either
* imagePoints
or geoPoints
have length less than 3.
*/
public static Sector warpImageWithControlPoints(BufferedImage sourceImage, java.awt.geom.Point2D[] imagePoints,
LatLon[] geoPoints, BufferedImage destImage)
{
if (sourceImage == null)
{
String message = Logging.getMessage("nullValue.SourceImageIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (destImage == null)
{
String message = Logging.getMessage("nullValue.DestinationImageIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
String message = validateControlPoints(3, imagePoints, geoPoints);
if (message != null)
{
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (imagePoints.length >= 4 && geoPoints.length >= 4)
{
return warpImageWithControlPoints4(sourceImage, imagePoints, geoPoints, destImage);
}
else // (imagePoints.length == 3)
{
return warpImageWithControlPoints3(sourceImage, imagePoints, geoPoints, destImage);
}
}
/**
* Transforms a georeferenced source image into a geographically aligned destination image. The source image is
* georeferenced by four control points. Each control point maps a location in the source image to a geographic
* location.
*
* @param sourceImage the source image to transform.
* @param imagePoints four control points in the source image.
* @param geoPoints four geographic locations corresponding to each source control point.
* @param destImage the destination image to receive the transformed source imnage.
*
* @return bounding sector for the geographically aligned destination image.
*
* @throws IllegalArgumentException if any of sourceImage
, destImage
,
* imagePoints
or geoPoints
is null, or if either
* imagePoints
or geoPoints
have length less than 4.
*/
public static Sector warpImageWithControlPoints4(BufferedImage sourceImage, java.awt.geom.Point2D[] imagePoints,
LatLon[] geoPoints, BufferedImage destImage)
{
if (sourceImage == null)
{
String message = Logging.getMessage("nullValue.SourceImageIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (destImage == null)
{
String message = Logging.getMessage("nullValue.DestinationImageIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
String message = validateControlPoints(4, imagePoints, geoPoints);
if (message != null)
{
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
// We can only create an affine transform from three of the given points. To increase accruacy, we will compute
// the error for each combination of three points, and choose the combination with the least error.
java.awt.geom.Point2D[] bestFitImagePoints = new java.awt.geom.Point2D[3];
LatLon[] bestFitGeoPoints = new LatLon[3];
computeBestFittingControlPoints4(imagePoints, geoPoints, bestFitImagePoints, bestFitGeoPoints);
return warpImageWithControlPoints3(sourceImage, bestFitImagePoints, bestFitGeoPoints, destImage);
}
/**
* Transforms a georeferenced source image into a geographically aligned destination image. The source image is
* georeferenced by three control points. Each control point maps a location in the source image to a geographic
* location.
*
* @param sourceImage the source image to transform.
* @param imagePoints three control points in the source image.
* @param geoPoints three geographic locations corresponding to each source control point.
* @param destImage the destination image to receive the transformed source imnage.
*
* @return bounding sector for the geographically aligned destination image.
*
* @throws IllegalArgumentException if any of sourceImage
, destImage
,
* imagePoints
or geoPoints
is null, or if either
* imagePoints
or geoPoints
have length less than 3.
*/
public static Sector warpImageWithControlPoints3(BufferedImage sourceImage, java.awt.geom.Point2D[] imagePoints,
LatLon[] geoPoints, BufferedImage destImage)
{
if (sourceImage == null)
{
String message = Logging.getMessage("nullValue.SourceImageIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (destImage == null)
{
String message = Logging.getMessage("nullValue.DestinationImageIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
String message = validateControlPoints(3, imagePoints, geoPoints);
if (message != null)
{
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
// Compute the destination sector. We want a lat/lon aligned sector that bounds the source image once it is
// transformed into a lat/lon aligned image. We compute a matrix that will map source grid coordinates to
// geographic coordinates. Then we transform the source image's four corners into geographic coordinates.
// The sector we want is the sector that bounds those four geographic coordinates.
Matrix gridToGeographic = Matrix.fromImageToGeographic(imagePoints, geoPoints);
List corners = computeImageCorners(sourceImage.getWidth(), sourceImage.getHeight(), gridToGeographic);
Sector destSector = Sector.boundingSector(corners);
if (Sector.isSector(corners) && destSector.isSameSector(corners))
{
getScaledCopy(sourceImage, destImage);
}
else
{
// Compute a matrix that will map from destination grid coordinates to source grid coordinates. By using
// matrix multiplication in this order, an incoming vector will be transformed by the last matrix multiplied,
// then the previous, and so on. So an incoming destination coordinate would be transformed into geographic
// coordinates, then into source coordinates.
Matrix transform = Matrix.IDENTITY;
transform = transform.multiply(Matrix.fromGeographicToImage(imagePoints, geoPoints));
transform = transform.multiply(Matrix.fromImageToGeographic(destImage.getWidth(), destImage.getHeight(),
destSector));
warpImageWithTransform(sourceImage, destImage, transform);
}
return destSector;
}
/**
* Transforms a georeferenced source image into a geographically aligned destination image. The source image is
* georeferenced by a world file, which defines an affine transform from source coordinates to geographic
* coordinates.
*
* @param sourceImage the source image to transform.
* @param worldFileParams world file parameters which define an affine transform.
* @param destImage the destination image to receive the transformed source imnage.
*
* @return bounding sector for the geographically aligned destination image.
*
* @throws IllegalArgumentException if any of sourceImage
, destImage
or
* worldFileParams
is null.
*/
public static Sector warpImageWithWorldFile(BufferedImage sourceImage, AVList worldFileParams,
BufferedImage destImage)
{
if (sourceImage == null)
{
String message = Logging.getMessage("nullValue.SourceImageIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (worldFileParams == null)
{
String message = Logging.getMessage("nullValue.ParamsIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (destImage == null)
{
String message = Logging.getMessage("nullValue.DestinationImageIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
Matrix imageToGeographic = Matrix.fromImageToGeographic(worldFileParams);
if (imageToGeographic == null)
{
String message = Logging.getMessage("WorldFile.UnrecognizedValues", "");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
List corners = computeImageCorners(sourceImage.getWidth(), sourceImage.getHeight(), imageToGeographic);
Sector destSector = Sector.boundingSector(corners);
if (Sector.isSector(corners) && destSector.isSameSector(corners))
{
getScaledCopy(sourceImage, destImage);
}
else
{
Matrix transform = Matrix.IDENTITY;
transform = transform.multiply(Matrix.fromGeographicToImage(worldFileParams));
transform = transform.multiply(Matrix.fromImageToGeographic(destImage.getWidth(), destImage.getHeight(),
destSector));
warpImageWithTransform(sourceImage, destImage, transform);
}
return destSector;
}
/**
* Computes which three control points out of four provide the best estimate an image's geographic location. The
* result is placed in the output parameters outImagePoints
and outGeoPoints
, both of
* which must be non-null and at least length 3.
*
* @param imagePoints four control points in the image.
* @param geoPoints four geographic locations corresponding to the four imagePoints
.
* @param outImagePoints three control points that best estimate the image's location.
* @param outGeoPoints three geographic locations corresponding to the three outImagePoints
.
*
* @throws IllegalArgumentException if any of imagePoints
, geoPoints
,
* outImagePoints
or outGeoPoints
is null, or if
* imagePoints
or geoPoints
have length less than 4, or
* if outImagePoints
or outGeoPoints
have length less
* than 3.
*/
public static void computeBestFittingControlPoints4(java.awt.geom.Point2D[] imagePoints, LatLon[] geoPoints,
java.awt.geom.Point2D[] outImagePoints, LatLon[] outGeoPoints)
{
String message = validateControlPoints(4, imagePoints, geoPoints);
if (message != null)
{
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
message = validateControlPoints(3, outImagePoints, outGeoPoints);
if (message != null)
{
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
// Compute the error for each combination of three points, and choose the combination with the least error.
java.awt.geom.Point2D[] bestFitImagePoints = null;
LatLon[] bestFitGeoPoints = null;
double minError = Double.MAX_VALUE;
for (int[] indices : new int[][] {
{0, 1, 2},
{0, 1, 3},
{1, 2, 3},
{0, 2, 3}})
{
java.awt.geom.Point2D[] points = new java.awt.geom.Point2D[] {
imagePoints[indices[0]], imagePoints[indices[1]], imagePoints[indices[2]]};
LatLon[] locations = new LatLon[] {geoPoints[indices[0]], geoPoints[indices[1]], geoPoints[indices[2]]};
Matrix m = Matrix.fromImageToGeographic(points, locations);
double error = 0.0;
for (int j = 0; j < 4; j++)
{
Vec4 vec = new Vec4(imagePoints[j].getX(), imagePoints[j].getY(), 1.0).transformBy3(m);
LatLon ll = LatLon.fromDegrees(vec.y, vec.x);
LatLon diff = geoPoints[j].subtract(ll);
double d = diff.getLatitude().degrees * diff.getLatitude().degrees
+ diff.getLongitude().degrees * diff.getLongitude().degrees;
error += d;
}
if (error < minError)
{
bestFitImagePoints = points;
bestFitGeoPoints = locations;
minError = error;
}
}
if (bestFitImagePoints != null)
{
System.arraycopy(bestFitImagePoints, 0, outImagePoints, 0, 3);
System.arraycopy(bestFitGeoPoints, 0, outGeoPoints, 0, 3);
}
}
/**
* Returns the geographic corners of an image with the specified dimensions, and a transform that maps image
* coordinates to geographic coordinates.
*
* @param imageWidth width of the image grid.
* @param imageHeight height of the image grid.
* @param imageToGeographic Matrix that maps image coordinates to geographic coordinates.
*
* @return List of the image's corner locations in geographic coordinates.
*
* @throws IllegalArgumentException if either imageWidth
or imageHeight
are less than 1,
* or if imageToGeographic
is null.
*/
public static List computeImageCorners(int imageWidth, int imageHeight, Matrix imageToGeographic)
{
if (imageWidth < 1 || imageHeight < 1)
{
String message = Logging.getMessage("generic.InvalidImageSize", imageWidth, imageHeight);
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (imageToGeographic == null)
{
String message = Logging.getMessage("nullValue.MatrixIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
ArrayList corners = new ArrayList();
// Lower left corner.
Vec4 vec = new Vec4(0, imageHeight, 1).transformBy3(imageToGeographic);
corners.add(LatLon.fromDegrees(vec.y, vec.x));
// Lower right corner.
vec = new Vec4(imageWidth, imageHeight, 1).transformBy3(imageToGeographic);
corners.add(LatLon.fromDegrees(vec.y, vec.x));
// Upper right corner.
vec = new Vec4(imageWidth, 0, 1).transformBy3(imageToGeographic);
corners.add(LatLon.fromDegrees(vec.y, vec.x));
// Upper left corner.
vec = new Vec4(0, 0, 1).transformBy3(imageToGeographic);
corners.add(LatLon.fromDegrees(vec.y, vec.x));
return corners;
}
private static String validateControlPoints(int numExpected,
java.awt.geom.Point2D[] imagePoints, LatLon[] geoPoints)
{
if (imagePoints == null)
{
return Logging.getMessage("nullValue.ImagePointsIsNull");
}
if (geoPoints == null)
{
return Logging.getMessage("nullValue.GeoPointsIsNull");
}
if (imagePoints.length < numExpected)
{
return Logging.getMessage("generic.ArrayInvalidLength", imagePoints.length);
}
if (geoPoints.length < numExpected)
{
return Logging.getMessage("generic.ArrayInvalidLength", imagePoints.length);
}
return null;
}
/**
* Merge an image into another image. This method is typically used to assemble a composite, seamless image from
* several individual images. The receiving image, called here the canvas because it's analogous to the Photoshop
* notion of a canvas, merges the incoming image according to the specified aspect ratio.
*
* @param canvasSector the sector defining the canvas' location and range.
* @param imageSector the sector defining the image's locaion and range.
* @param aspectRatio the aspect ratio, width/height, of the assembled image. If the aspect ratio is greater than
* or equal to one, the assembled image uses the full width of the canvas; the height used is
* proportional to the inverse of the aspect ratio. If the aspect ratio is less than one, the
* full height of the canvas is used; the width used is proportional to the aspect ratio.
* The aspect ratio is typically used to maintain consistent width and height units while
* assembling multiple images into a canvas of a different aspect ratio than the canvas sector,
* such as drawing a non-square region into a 1024x1024 canvas. An aspect ratio of 1 causes the
* incoming images to be stretched as necessary in one dimension to match the aspect ratio of
* the canvas sector.
* @param image the image to merge into the canvas.
* @param canvas the canvas into which the images are merged. The canvas is not changed if the specified image
* and canvas sectors are disjoint.
*
* @throws IllegalArgumentException if the any of the reference arguments are null or the aspect ratio is less than
* or equal to zero.
*/
public static void mergeImage(Sector canvasSector, Sector imageSector, double aspectRatio, BufferedImage image,
BufferedImage canvas)
{
if (canvasSector == null || imageSector == null)
{
String message = Logging.getMessage("nullValue.SectorIsNull");
Logging.logger().severe(message);
throw new IllegalStateException(message);
}
if (canvas == null || image == null)
{
String message = Logging.getMessage("nullValue.ImageSource");
Logging.logger().severe(message);
throw new IllegalStateException(message);
}
if (aspectRatio <= 0)
{
String message = Logging.getMessage("Util.AspectRatioInvalid", aspectRatio);
Logging.logger().severe(message);
throw new IllegalStateException(message);
}
if (!(canvasSector.intersects(imageSector)))
return;
// Create an image with the desired aspect ratio within an enclosing canvas of possibly different aspect ratio.
int subWidth = aspectRatio >= 1 ? canvas.getWidth() : (int) Math.ceil((canvas.getWidth() * aspectRatio));
int subHeight = aspectRatio >= 1 ? (int) Math.ceil((canvas.getHeight() / aspectRatio)) : canvas.getHeight();
// yShift shifts image down to change origin from upper-left to lower-left
double yShift = aspectRatio >= 1d ? (1d - 1d / aspectRatio) * canvas.getHeight() : 0d;
double sh = ((double) subHeight / (double) image.getHeight())
* (imageSector.getDeltaLat().divide(canvasSector.getDeltaLat()));
double sw = ((double) subWidth / (double) image.getWidth())
* (imageSector.getDeltaLon().divide(canvasSector.getDeltaLon()));
double dh = subHeight *
(-imageSector.getMaxLatitude().subtract(canvasSector.getMaxLatitude()).degrees
/ canvasSector.getDeltaLat().degrees);
double dw = subWidth *
(imageSector.getMinLongitude().subtract(canvasSector.getMinLongitude()).degrees
/ canvasSector.getDeltaLon().degrees);
Graphics2D g = canvas.createGraphics();
g.translate(dw, dh + yShift);
g.scale(sw, sh);
g.drawImage(image, 0, 0, null);
}
public static Sector positionImage(BufferedImage sourceImage, Point[] imagePoints, LatLon[] geoPoints,
BufferedImage destImage)
{
if (imagePoints.length == 3)
return positionImage3(sourceImage, imagePoints, geoPoints, destImage);
else if (imagePoints.length == 4)
return positionImage4(sourceImage, imagePoints, geoPoints, destImage);
else
return null;
}
public static Sector positionImage3(BufferedImage sourceImage, Point[] imagePoints, LatLon[] geoPoints,
BufferedImage destImage)
{
// TODO: check args
BarycentricTriangle sourceLatLon = new BarycentricTriangle(geoPoints[0], geoPoints[1], geoPoints[2]);
BarycentricTriangle sourcePixels = new BarycentricTriangle(imagePoints[0], imagePoints[1], imagePoints[2]);
ArrayList extremes = new ArrayList(4);
// Lower left corner.
double[] bc = sourcePixels.getBarycentricCoords(new Vec4(0, sourceImage.getHeight(), 0));
extremes.add(sourceLatLon.getLocation(bc));
// Lower right corner.
bc = sourcePixels.getBarycentricCoords(new Vec4(sourceImage.getWidth(), sourceImage.getHeight(), 0));
extremes.add(sourceLatLon.getLocation(bc));
// Upper right corner.
bc = sourcePixels.getBarycentricCoords(new Vec4(sourceImage.getWidth(), 0, 0));
extremes.add(sourceLatLon.getLocation(bc));
// Upper left corner.
bc = sourcePixels.getBarycentricCoords(new Vec4(0, 0, 0));
extremes.add(sourceLatLon.getLocation(bc));
Sector sector = Sector.boundingSector(extremes);
GeoQuad destLatLon = new GeoQuad(sector.asList());
double width = destImage.getWidth();
double height = destImage.getHeight();
for (int row = 0; row < destImage.getHeight(); row++)
{
double t = (double) row / height;
for (int col = 0; col < destImage.getWidth(); col++)
{
double s = (double) col / width;
LatLon latLon = destLatLon.interpolate(1 - t, s);
double[] baryCoords = sourceLatLon.getBarycentricCoords(latLon);
Vec4 pixelPostion = sourcePixels.getPoint(baryCoords);
if (pixelPostion.x < 0 || pixelPostion.x >= sourceImage.getWidth()
|| pixelPostion.y < 0 || pixelPostion.y >= sourceImage.getHeight())
continue;
int pixel = sourceImage.getRGB((int) pixelPostion.x, (int) pixelPostion.y);
destImage.setRGB(col, row, pixel);
}
}
return sector;
}
public static Sector positionImage4(BufferedImage sourceImage, Point[] imagePoints, LatLon[] geoPoints,
BufferedImage destImage)
{
// TODO: check args
BarycentricQuadrilateral sourceLatLon = new BarycentricQuadrilateral(geoPoints[0], geoPoints[1], geoPoints[2],
geoPoints[3]);
BarycentricQuadrilateral sourcePixels = new BarycentricQuadrilateral(imagePoints[0], imagePoints[1],
imagePoints[2], imagePoints[3]);
ArrayList extremes = new ArrayList(4);
// Lower left corner.
double[] bc = sourcePixels.getBarycentricCoords(new Vec4(0, sourceImage.getHeight(), 0));
extremes.add(sourceLatLon.getLocation(bc));
// Lower right corner.
bc = sourcePixels.getBarycentricCoords(new Vec4(sourceImage.getWidth(), sourceImage.getHeight(), 0));
extremes.add(sourceLatLon.getLocation(bc));
// Upper right corner.
bc = sourcePixels.getBarycentricCoords(new Vec4(sourceImage.getWidth(), 0, 0));
extremes.add(sourceLatLon.getLocation(bc));
// Upper left corner.
bc = sourcePixels.getBarycentricCoords(new Vec4(0, 0, 0));
extremes.add(sourceLatLon.getLocation(bc));
Sector sector = Sector.boundingSector(extremes);
GeoQuad destLatLon = new GeoQuad(sector.asList());
double width = destImage.getWidth();
double height = destImage.getHeight();
for (int row = 0; row < destImage.getHeight(); row++)
{
double t = (double) row / height;
for (int col = 0; col < destImage.getWidth(); col++)
{
double s = (double) col / width;
LatLon latLon = destLatLon.interpolate(1 - t, s);
double[] baryCoords = sourceLatLon.getBarycentricCoords(latLon);
Vec4 pixelPostion = sourcePixels.getPoint(baryCoords);
if (pixelPostion.x < 0 || pixelPostion.x >= sourceImage.getWidth()
|| pixelPostion.y < 0 || pixelPostion.y >= sourceImage.getHeight())
continue;
int pixel = sourceImage.getRGB((int) pixelPostion.x, (int) pixelPostion.y);
destImage.setRGB(col, row, pixel);
}
}
return sector;
}
/**
* Builds a sequence of mipmaps for the specified image. The number of mipmap levels created will be equal to
* maxLevel + 1
, including level 0. The level 0 image will be a reference to the original image, not a
* copy. Each mipmap level will be created with the specified BufferedImage type mipmapImageType
. Each
* level will have dimensions equal to 1/2 the previous level's dimensions, rounding down, to a minimum width or
* height of 1.
*
* @param image the BufferedImage to build mipmaps for.
* @param mipmapImageType the BufferedImage type to use when creating each mipmap image.
* @param maxLevel the maximum mip level to create. Specifying zero will return an array containing the
* original image.
*
* @return array of mipmap levels, starting at level 0 and stopping at maxLevel. This array will have length
* maxLevel + 1.
*
* @throws IllegalArgumentException if image
is null, or if maxLevel
is less than zero.
* @see #getMaxMipmapLevel(int, int)
*/
public static BufferedImage[] buildMipmaps(BufferedImage image, int mipmapImageType, int maxLevel)
{
if (image == null)
{
String message = Logging.getMessage("nullValue.ImageIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (maxLevel < 0)
{
String message = Logging.getMessage("generic.ArgumentOutOfRange", "maxLevel < 0");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
BufferedImage[] mipMapLevels = new BufferedImage[1 + maxLevel];
// If the image and mipmap type are equivalent, then just pass the original image along. Otherwise, create a
// copy of the original image with the appropriate image type.
if (image.getType() == mipmapImageType)
{
mipMapLevels[0] = image;
}
else
{
mipMapLevels[0] = new BufferedImage(image.getWidth(), image.getHeight(), mipmapImageType);
getScaledCopy(image, mipMapLevels[0]);
}
for (int level = 1; level <= maxLevel; level++)
{
int width = Math.max(image.getWidth() >> level, 1);
int height = Math.max(image.getHeight() >> level, 1);
mipMapLevels[level] = new BufferedImage(width, height, mipmapImageType);
getScaledCopy(mipMapLevels[level - 1], mipMapLevels[level]);
}
return mipMapLevels;
}
/**
* Builds a sequence of mipmaps for the specified image. This is equivalent to invoking
* buildMipmaps(BufferedImage, int, int)
, with mipmapImageType
equal to
* getMipmapType(image.getType())
, and maxLevel
equal to
* getMaxMipmapLevel(image.getWidth(), image.getHeight())
.
*
* @param image the BufferedImage to build mipmaps for.
*
* @return array of mipmap levels.
*
* @throws IllegalArgumentException if image
is null.
*/
public static BufferedImage[] buildMipmaps(BufferedImage image)
{
if (image == null)
{
String message = Logging.getMessage("nullValue.ImageIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
int mipmapImageType = getMipmapType(image.getType());
int maxLevel = getMaxMipmapLevel(image.getWidth(), image.getHeight());
return buildMipmaps(image, mipmapImageType, maxLevel);
}
/**
* Returns an image type appropriate for generating mipmaps.
*
* @param imageType the original BufferedImage type.
*
* @return mipmap image type.
*/
public static int getMipmapType(int imageType)
{
// We cannot create a BufferedImage of type "custom", so we fall back to a default image type.
if (imageType == BufferedImage.TYPE_CUSTOM)
return BufferedImage.TYPE_INT_ARGB;
return imageType;
}
/**
* Returns the maximum desired mip level for an image with dimensions width
and height
.
* The maximum desired level is the number of levels required to reduce the original image dimensions to a 1x1
* image.
*
* @param width the level 0 image width.
* @param height the level 0 image height.
*
* @return maximum mip level for the specified width
and height
.
*
* @throws IllegalArgumentException if either width
or height
are less than 1.
*/
public static int getMaxMipmapLevel(int width, int height)
{
if (width < 1)
{
String message = Logging.getMessage("generic.ArgumentOutOfRange", "width < 1");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (height < 1)
{
String message = Logging.getMessage("generic.ArgumentOutOfRange", "height < 1");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
int widthLevels = (int) WWMath.logBase2(width);
int heightLevels = (int) WWMath.logBase2(height);
return Math.max(widthLevels, heightLevels);
}
/**
* Returns a copy of the specified image such that the new dimensions are powers of two. The new image dimensions
* will be equal to or greater than the original image. The flag scaleToFit
determines whether the
* original image should be drawn into the new image with no special scaling, or whether the original image should
* be scaled to fit exactly in the new image. If the original image dimensions are already powers of two, this
* method will simply return the original image.
*
* @param image the BufferedImage to convert to a power of two image.
* @param scaleToFit true if image
should be scaled to fit the new image dimensions; false otherwise.s
*
* @return copy of image
with power of two dimensions.
*
* @throws IllegalArgumentException if image
is null.
*/
public static BufferedImage convertToPowerOfTwoImage(BufferedImage image, boolean scaleToFit)
{
if (image == null)
{
String message = Logging.getMessage("nullValue.ImageIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
// If the original image is already a power of two in both dimensions, then simply return it.
if (WWMath.isPowerOfTwo(image.getWidth()) && WWMath.isPowerOfTwo(image.getHeight()))
{
return image;
}
int potWidth = WWMath.powerOfTwoCeiling(image.getWidth());
int potHeight = WWMath.powerOfTwoCeiling(image.getHeight());
BufferedImage potImage = new BufferedImage(potWidth, potHeight, image.getColorModel().hasAlpha() ?
BufferedImage.TYPE_4BYTE_ABGR : BufferedImage.TYPE_3BYTE_BGR);
Graphics2D g2d = potImage.createGraphics();
try
{
if (scaleToFit)
{
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.drawImage(image, 0, 0, potImage.getWidth(), potImage.getHeight(), null);
}
else
{
g2d.drawImage(image, 0, 0, null);
}
}
finally
{
g2d.dispose();
}
return potImage;
}
/**
* Returns a copy of the specified image with any fully-transparent borders removed. The resultant image is cropped
* to fit its contents.
*
* @param image the BufferedImage to trim.
*
* @return copy of image
with its transparent borders trimmed
*
* @throws IllegalArgumentException if image
is null.
*/
public static BufferedImage trimImage(BufferedImage image)
{
if (image == null)
{
String message = Logging.getMessage("nullValue.ImageIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
int width = image.getWidth();
int height = image.getHeight();
int[] rowPixels = new int[width];
int x1 = Integer.MAX_VALUE;
int y1 = Integer.MAX_VALUE;
int x2 = 0;
int y2 = 0;
for (int y = 0; y < height; y++)
{
image.getRGB(0, y, width, 1, rowPixels, 0, width);
for (int x = 0; x < width; x++)
{
int a = ((rowPixels[x] >> 24) & 0xff);
if (a <= 0)
continue;
if (x1 > x)
x1 = x;
if (x2 < x)
x2 = x;
if (y1 > y)
y1 = y;
if (y2 < y)
y2 = y;
}
}
return (x1 < x2 && y1 < y2) ? image.getSubimage(x1, y1, x2 - x1 + 1, y2 - y1 + 1)
: new BufferedImage(BufferedImage.TYPE_INT_ARGB, 0, 0);
}
/**
* Returns the size in bytes of the specified image. This takes into account only the image's backing DataBuffers,
* and not the numerous supporting classes a BufferedImage references.
*
* @param image the BufferedImage to compute the size of.
*
* @return size of the BufferedImage in bytes.
*
* @throws IllegalArgumentException if image
is null.
*/
public static long computeSizeInBytes(BufferedImage image)
{
if (image == null)
{
String message = Logging.getMessage("nullValue.ImageIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
long size = 0L;
java.awt.image.Raster raster = image.getRaster();
if (raster != null)
{
java.awt.image.DataBuffer db = raster.getDataBuffer();
if (db != null)
{
size = computeSizeOfDataBuffer(db);
}
}
return size;
}
private static long computeSizeOfDataBuffer(java.awt.image.DataBuffer dataBuffer)
{
return dataBuffer.getSize() * computeSizeOfBufferDataType(dataBuffer.getDataType());
}
private static long computeSizeOfBufferDataType(int bufferDataType)
{
switch (bufferDataType)
{
case java.awt.image.DataBuffer.TYPE_BYTE:
return (Byte.SIZE / 8);
case java.awt.image.DataBuffer.TYPE_DOUBLE:
return (Double.SIZE / 8);
case java.awt.image.DataBuffer.TYPE_FLOAT:
return (Float.SIZE / 8);
case java.awt.image.DataBuffer.TYPE_INT:
return (Integer.SIZE / 8);
case java.awt.image.DataBuffer.TYPE_SHORT:
case java.awt.image.DataBuffer.TYPE_USHORT:
return (Short.SIZE / 8);
case java.awt.image.DataBuffer.TYPE_UNDEFINED:
break;
}
return 0L;
}
/**
* Opens a spatial image. Reprojects the image if it is in UTM projection.
*
* @param imageFile source image
* @param interpolation_mode the interpolation mode if the image is reprojected.
*
* @return AVList
*
* @throws IOException if there is a problem opening the file.
* @throws WWRuntimeException if the image type is unsupported.
*/
public static AVList openSpatialImage(File imageFile, int interpolation_mode) throws IOException
{
AVList values = new AVListImpl();
BufferedImage image;
Sector sector;
//Check for Geotiff
if ((imageFile.getName().toLowerCase().endsWith(".tiff") || (imageFile.getName().toLowerCase().endsWith(
".tif"))))
{
GeotiffReader reader = new GeotiffReader(imageFile);
int imageIndex = 0;
image = reader.read(imageIndex);
if (reader.isGeotiff(imageIndex))
{
return handleGeotiff(image, reader, imageIndex, interpolation_mode);
}
}
//if not geotiff, contine through for other formats
image = ImageIO.read(imageFile);
if (image == null)
{
String message = Logging.getMessage("generic.ImageReadFailed", imageFile);
Logging.logger().severe(message);
throw new WWRuntimeException(message);
}
File[] worldFiles = WorldFile.getWorldFiles(imageFile.getAbsoluteFile());
if (worldFiles == null || worldFiles.length == 0)
{
String message = Logging.getMessage("WorldFile.WorldFileNotFound", imageFile.getAbsolutePath());
Logging.logger().severe(message);
throw new FileNotFoundException(message);
}
values.setValue(AVKey.IMAGE, image);
WorldFile.decodeWorldFiles(worldFiles, values);
sector = (Sector) values.getValue(AVKey.SECTOR);
if (sector == null)
ImageUtil.reprojectUtmToGeographic(values, interpolation_mode);
sector = (Sector) values.getValue(AVKey.SECTOR);
if (sector == null)
{
String message = "Problem generating bounding sector for the image";
throw new WWRuntimeException(message);
}
values.setValue(AVKey.SECTOR, sector);
return values;
}
/**
* Opens a spatial image. Reprojects the image if it is in UTM projection.
*
* @param imageFile source image
*
* @return AVList
*
* @throws IOException if there is a problem opening the file.
*/
public static AVList openSpatialImage(File imageFile) throws IOException
{
return openSpatialImage(imageFile, ImageUtil.NEAREST_NEIGHBOR_INTERPOLATION);
}
/**
* Reads Geo-referenced metadata from Geo-TIFF file
*
* @param reader GeotiffReader
* @param imageIndex image index (could be a band; GeoTiff file could contain overview images, bands, etc)
* @param values AVList
*
* @return values AVList
*
* @throws IOException if there is a problem opening the file.
*/
public static AVList readGeoKeys(GeotiffReader reader, int imageIndex, AVList values) throws IOException
{
if (null == values)
values = new AVListImpl();
if (null == reader)
return values;
return reader.copyMetadataTo(imageIndex, values);
}
/**
* Opens a Geotiff image image. Reprojects the image if it is in UTM projection.
*
* @param image BufferedImage
* @param reader GeotiffReader
* @param imageIndex image index (GeoTiff file could contain overview images, bands, etc)
* @param interpolation_mode the interpolation mode if the image is reprojected.
*
* @return AVList
*
* @throws IOException if there is a problem opening the file.
*/
private static AVList handleGeotiff(BufferedImage image, GeotiffReader reader, int imageIndex,
int interpolation_mode)
throws IOException
{
AVList values = new AVListImpl();
if (null != image)
{
values.setValue(AVKey.IMAGE, image);
values.setValue(AVKey.WIDTH, image.getWidth());
values.setValue(AVKey.HEIGHT, image.getHeight());
}
ImageUtil.readGeoKeys(reader, imageIndex, values);
if (AVKey.COORDINATE_SYSTEM_PROJECTED.equals(values.getValue(AVKey.COORDINATE_SYSTEM)))
ImageUtil.reprojectUtmToGeographic(values, interpolation_mode);
return values;
}
public static Sector calcBoundingBoxForUTM(AVList params) throws IOException
{
if (null == params)
{
String message = Logging.getMessage("nullValue.ParamsIsNull");
Logging.logger().severe(message);
throw new IOException(message);
}
if (!params.hasKey(AVKey.WIDTH))
{
String message = Logging.getMessage("Geom.WidthInvalid");
Logging.logger().severe(message);
throw new IOException(message);
}
if (!params.hasKey(AVKey.HEIGHT))
{
String message = Logging.getMessage("Geom.HeightInvalid");
Logging.logger().severe(message);
throw new IOException(message);
}
if (!params.hasKey(WorldFile.WORLD_FILE_X_PIXEL_SIZE))
{
String message = Logging.getMessage("WorldFile.NoPixelSizeSpecified", "X");
Logging.logger().severe(message);
throw new IOException(message);
}
if (!params.hasKey(WorldFile.WORLD_FILE_Y_PIXEL_SIZE))
{
String message = Logging.getMessage("WorldFile.NoPixelSizeSpecified", "Y");
Logging.logger().severe(message);
throw new IOException(message);
}
if (!params.hasKey(WorldFile.WORLD_FILE_X_LOCATION))
{
String message = Logging.getMessage("WorldFile.NoLocationSpecified", "X");
Logging.logger().severe(message);
throw new IOException(message);
}
if (!params.hasKey(WorldFile.WORLD_FILE_Y_LOCATION))
{
String message = Logging.getMessage("WorldFile.NoLocationSpecified", "Y");
Logging.logger().severe(message);
throw new IOException(message);
}
if (!params.hasKey(AVKey.PROJECTION_ZONE))
{
String message = Logging.getMessage("generic.ZoneIsMissing");
Logging.logger().severe(message);
throw new IOException(message);
}
if (!params.hasKey(AVKey.PROJECTION_HEMISPHERE))
{
String message = Logging.getMessage("generic.HemisphereIsMissing");
Logging.logger().severe(message);
throw new IOException(message);
}
int width = (Integer) params.getValue(AVKey.WIDTH);
int height = (Integer) params.getValue(AVKey.HEIGHT);
double xPixelSize = (Double) params.getValue(WorldFile.WORLD_FILE_X_PIXEL_SIZE);
double yPixelSize = (Double) params.getValue(WorldFile.WORLD_FILE_Y_PIXEL_SIZE);
double xLocation = (Double) params.getValue(WorldFile.WORLD_FILE_X_LOCATION);
double yLocation = (Double) params.getValue(WorldFile.WORLD_FILE_Y_LOCATION);
Integer zone = (Integer) params.getValue(AVKey.PROJECTION_ZONE);
String hemisphere = (String) params.getValue(AVKey.PROJECTION_HEMISPHERE);
UTMCoord upperLeft = UTMCoord.fromUTM(zone, hemisphere, xLocation, yLocation);
UTMCoord utmUpperLeft = UTMCoord.fromUTM(zone, hemisphere, upperLeft.getEasting() - xPixelSize * .5,
upperLeft.getNorthing() - yPixelSize * .5);
UTMCoord utmLowerRight = UTMCoord.fromUTM(zone, hemisphere, utmUpperLeft.getEasting() + (width * xPixelSize),
utmUpperLeft.getNorthing() + (height * yPixelSize));
//Get rect Geo bbox
UTMCoord utmLowerLeft = UTMCoord.fromUTM(zone, upperLeft.getHemisphere(), utmUpperLeft.getEasting(),
utmLowerRight.getNorthing());
UTMCoord utmUpperRight = UTMCoord.fromUTM(zone, upperLeft.getHemisphere(), utmLowerRight.getEasting(),
utmUpperLeft.getNorthing());
Angle rightExtent = Angle.max(utmUpperRight.getLongitude(), utmLowerRight.getLongitude());
Angle leftExtent = Angle.min(utmLowerLeft.getLongitude(), utmUpperLeft.getLongitude());
Angle topExtent = Angle.max(utmUpperRight.getLatitude(), utmUpperLeft.getLatitude());
Angle bottomExtent = Angle.min(utmLowerRight.getLatitude(), utmLowerLeft.getLatitude());
Sector sector = new Sector(bottomExtent, topExtent, leftExtent, rightExtent);
params.setValue(AVKey.SECTOR, sector);
params.setValue(AVKey.ORIGIN, new LatLon(upperLeft.getLatitude(), upperLeft.getLongitude()));
return sector;
}
/**
* Reprojects an imge in UTM projection to Geo/WGS84.
*
* @param values AVList: contains the bufferedimage and the values from the world file. Stores resulting image in
* values
* @param mode the interpolation mode if the image is reprojected.
*/
public static void reprojectUtmToGeographic(AVList values, int mode)
{
//TODO pull these const from TMCoord?
double False_Easting = 500000;
double False_Northing = 0;
double Scale = 0.9996;
Earth earth = new Earth(); //need globe for TM
if (values == null)
{
String message = Logging.getMessage("nullValue.AVListIsNull");
Logging.logger().severe(message);
throw new IllegalStateException(message);
}
BufferedImage image = (BufferedImage) values.getValue(AVKey.IMAGE);
int width = image.getWidth();
int height = image.getHeight();
BufferedImage biOut;
//Note: image type always BufferedImage.TYPE_INT_ARGB to handle transparent no-data areas after reprojection
if ((image.getColorModel() != null) && (image.getColorModel() instanceof IndexColorModel))
{
biOut = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB,
(IndexColorModel) image.getColorModel());
}
else
{
biOut = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
}
double xPixelSize = 0;
double yPixelSize = 0;
Object o = values.getValue(WorldFile.WORLD_FILE_X_PIXEL_SIZE);
if (o != null && o instanceof Double)
xPixelSize = (Double) o;
o = values.getValue(WorldFile.WORLD_FILE_Y_PIXEL_SIZE);
if (o != null && o instanceof Double)
yPixelSize = (Double) o;
// TODO: validate that all these values exist and are valid
double xLocation = (Double) values.getValue(WorldFile.WORLD_FILE_X_LOCATION);
double yLocation = (Double) values.getValue(WorldFile.WORLD_FILE_Y_LOCATION);
Integer zone = (Integer) values.getValue(AVKey.PROJECTION_ZONE);
String hemisphere = (String) values.getValue(AVKey.PROJECTION_HEMISPHERE);
UTMCoord upperLeft = UTMCoord.fromUTM(zone, hemisphere, xLocation, yLocation);
UTMCoord utmUpperLeft = UTMCoord.fromUTM(zone, hemisphere, upperLeft.getEasting() - xPixelSize * .5,
upperLeft.getNorthing() - yPixelSize * .5);
UTMCoord utmLowerRight = UTMCoord.fromUTM(zone, hemisphere, utmUpperLeft.getEasting() + (width * xPixelSize),
utmUpperLeft.getNorthing() + (height * yPixelSize));
//Get rect Geo bbox
UTMCoord utmLowerLeft = UTMCoord.fromUTM(zone, upperLeft.getHemisphere(), utmUpperLeft.getEasting(),
utmLowerRight.getNorthing());
UTMCoord utmUpperRight = UTMCoord.fromUTM(zone, upperLeft.getHemisphere(), utmLowerRight.getEasting(),
utmUpperLeft.getNorthing());
Angle rightExtent = Angle.max(utmUpperRight.getLongitude(), utmLowerRight.getLongitude());
Angle leftExtent = Angle.min(utmLowerLeft.getLongitude(), utmUpperLeft.getLongitude());
Angle topExtent = Angle.max(utmUpperRight.getLatitude(), utmUpperLeft.getLatitude());
Angle bottomExtent = Angle.min(utmLowerRight.getLatitude(), utmLowerLeft.getLatitude());
Sector sector = new Sector(bottomExtent, topExtent, leftExtent, rightExtent);
values.setValue(AVKey.SECTOR, sector);
//moving to center of pixel
double yPixel = (bottomExtent.getDegrees() - topExtent.getDegrees()) / height;
double xPixel = (rightExtent.getDegrees() - leftExtent.getDegrees()) / width;
double topExtent2 = sector.getMaxLatitude().getDegrees() + (yPixel * .5);
double leftExtent2 = sector.getMinLongitude().getDegrees() + (xPixel * .5);
TMCoord tmUpperLeft = TMCoord.fromLatLon(utmUpperLeft.getLatitude(), utmUpperLeft.getLongitude(),
earth, null, null, Angle.fromDegrees(0.0), utmUpperLeft.getCentralMeridian(),
False_Easting, False_Northing, Scale);
double srcTop = tmUpperLeft.getNorthing() + (yPixelSize * .5);
double srcLeft = tmUpperLeft.getEasting() + (xPixelSize * .5);
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
double yTarget = topExtent2 + y * yPixel;
double xTarget = leftExtent2 + x * xPixel;
TMCoord TM = TMCoord.fromLatLon(Angle.fromDegreesLatitude(yTarget), Angle.fromDegreesLongitude(xTarget),
earth, null, null, Angle.fromDegrees(0.0), utmUpperLeft.getCentralMeridian(),
False_Easting, False_Northing, Scale);
double distFromCornerX = TM.getEasting() - srcLeft;
double distFromCornerY = srcTop - TM.getNorthing();
long rx = Math.round(distFromCornerX / Math.abs(xPixelSize));
long ry = Math.round(distFromCornerY / Math.abs(yPixelSize));
if (mode == ImageUtil.BILINEAR_INTERPOLATION)
{
double rxD = distFromCornerX / Math.abs(xPixelSize);
double ryD = distFromCornerY / Math.abs(yPixelSize);
int iX = (int) Math.floor(rxD);
int iY = (int) Math.floor(ryD);
double dx = rxD - iX;
double dy = ryD - iY;
if ((iX > 0) && (iY > 0))
if ((iX < width - 1) && (iY < height - 1))
{
//get four pixels from image
int a = image.getRGB(iX, iY);
int b = image.getRGB(iX + 1, iY);
int c = image.getRGB(iX, iY + 1);
int d = image.getRGB(iX + 1, iY + 1);
int sum = interpolateColor(dx, dy, a, b, c, d);
biOut.setRGB(x, y, Math.round(sum));
}
else
biOut.setRGB(x, y, 0);
}
else //NEAREST_NEIGHBOR is default
{
if ((rx > 0) && (ry > 0))
if ((rx < width) && (ry < height))
biOut.setRGB(x, y, image.getRGB(Long.valueOf(rx).intValue(), Long.valueOf(ry).intValue()));
else
biOut.setRGB(x, y, 0);
}
}
}
values.setValue(AVKey.IMAGE, biOut);
}
/**
* Performs bilinear interpolation of 32-bit colors over a convex quadrilateral.
*
* @param x horizontal coordinate of the interpolation point relative to the lower left corner of the
* quadrilateral. The value should generally be in the range [0, 1].
* @param y vertical coordinate of the interpolation point relative to the lower left corner of the quadrilateral.
* The value should generally be in the range [0, 1].
* @param c0 color at the lower left corner of the quadrilateral.
* @param c1 color at the lower right corner of the quadrilateral.
* @param c2 color at the pixel upper left corner of the quadrilateral.
* @param c3 color at the pixel upper right corner of the quadrilateral.
*
* @return int the interpolated color.
*/
public static int interpolateColor(double x, double y, int c0, int c1, int c2, int c3)
{
//pull out alpha, red, green, blue values for each pixel
int a0 = (c0 >> 24) & 0xff;
int r0 = (c0 >> 16) & 0xff;
int g0 = (c0 >> 8) & 0xff;
int b0 = c0 & 0xff;
int a1 = (c1 >> 24) & 0xff;
int r1 = (c1 >> 16) & 0xff;
int g1 = (c1 >> 8) & 0xff;
int b1 = c1 & 0xff;
int a2 = (c2 >> 24) & 0xff;
int r2 = (c2 >> 16) & 0xff;
int g2 = (c2 >> 8) & 0xff;
int b2 = c2 & 0xff;
int a3 = (c3 >> 24) & 0xff;
int r3 = (c3 >> 16) & 0xff;
int g3 = (c3 >> 8) & 0xff;
int b3 = c3 & 0xff;
double rx = 1.0d - x;
double ry = 1.0d - y;
double x0 = rx * a0 + x * a1;
double x1 = rx * a2 + x * a3;
int a = (int) (ry * x0 + y * x1); //final alpha value
a = a << 24;
x0 = rx * r0 + x * r1;
x1 = rx * r2 + x * r3;
int r = (int) (ry * x0 + y * x1); //final red value
r = r << 16;
x0 = rx * g0 + x * g1;
x1 = rx * g2 + x * g3;
int g = (int) (ry * x0 + y * x1); //final green value
g = g << 8;
x0 = rx * b0 + x * b1;
x1 = rx * b2 + x * b3;
int b = (int) (ry * x0 + y * x1); //final blue value
return (a | r | g | b);
}
public static class AlignedImage
{
public final Sector sector;
public final BufferedImage image;
public AlignedImage(BufferedImage image, Sector sector)
{
this.image = image;
this.sector = sector;
}
}
/**
* Reprojects an image into an aligned image, one with edges of constant latitude and longitude.
*
* @param sourceImage the image to reproject, typically a non-aligned image
* @param latitudes an array identifying the latitude of each pixels if the source image. There must be an entry
* in the array for all pixels. The values are taken to be in row-major order relative to the
* image -- the horizontal component varies fastest.
* @param longitudes an array identifying the longitude of each pixels if the source image. There must be an entry
* in the array for all pixels. The values are taken to be in row-major order relative to the
* image -- the horizontal component varies fastest.
*
* @return a new image containing the original image but reprojected to align to the bounding sector. Pixels in the
* new image that have no correspondence with the source image are transparent.
*
* @throws InterruptedException if any thread has interrupted the current thread while alignImage is running. The
* interrupted status of the current thread is cleared when this exception is
* thrown.
*/
public static AlignedImage alignImage(BufferedImage sourceImage, float[] latitudes, float[] longitudes)
throws InterruptedException
{
return alignImage(sourceImage, latitudes, longitudes, null, null);
}
/**
* Reprojects an image into an aligned image, one with edges of constant latitude and longitude.
*
* @param sourceImage the image to reproject, typically a non-aligned image
* @param latitudes an array identifying the latitude of each pixels if the source image. There must be an entry
* in the array for all pixels. The values are taken to be in row-major order relative to the
* image -- the horizontal component varies fastest.
* @param longitudes an array identifying the longitude of each pixels if the source image. There must be an entry
* in the array for all pixels. The values are taken to be in row-major order relative to the
* image -- the horizontal component varies fastest.
* @param sector the sector to align the image to. If null, this computes the aligned image's sector.
* @param dimension the the aligned image's dimensions. If null, this computes the aligned image's dimension.
*
* @return a new image containing the original image but reprojected to align to the sector. Pixels in the new image
* that have no correspondence with the source image are transparent.
*
* @throws InterruptedException if any thread has interrupted the current thread while alignImage is running. The
* interrupted status of the current thread is cleared when this exception is
* thrown.
*/
public static AlignedImage alignImage(BufferedImage sourceImage, float[] latitudes, float[] longitudes,
Sector sector, Dimension dimension) throws InterruptedException
{
if (sourceImage == null)
{
String message = Logging.getMessage("nullValue.ImageIsNull");
Logging.logger().severe(message);
throw new IllegalStateException(message);
}
if (latitudes == null || longitudes == null || latitudes.length != longitudes.length)
{
String message = Logging.getMessage("ImageUtil.FieldArrayInvalid");
Logging.logger().severe(message);
throw new IllegalStateException(message);
}
int sourceWidth = sourceImage.getWidth();
int sourceHeight = sourceImage.getHeight();
if (sourceWidth < 1 || sourceHeight < 1)
{
String message = Logging.getMessage("ImageUtil.EmptyImage");
Logging.logger().severe(message);
throw new IllegalStateException(message);
}
if (longitudes.length < sourceWidth * sourceHeight || latitudes.length < sourceWidth * sourceHeight)
{
String message = Logging.getMessage("ImageUtil.FieldArrayTooShort");
Logging.logger().severe(message);
throw new IllegalStateException(message);
}
GeographicImageInterpolator grid = new GeographicImageInterpolator(new Dimension(sourceWidth, sourceHeight),
longitudes, latitudes, 10, 1);
// If the caller did not specify a Sector, then use the image's bounding sector as computed by
// GeographicImageInterpolator. We let GeographicImageInterpolator perform the computation because it computes
// the correct sector for images which cross the international dateline.
if (sector == null)
{
sector = grid.getSector();
}
// If the caller did not specify a dimension for the aligned image, then compute its dimensions as follows:
//
// 1. Assign the output width or height to the source image's maximum dimension, depending on the output
// sector's largest delta (latitude or longitude).
// 2. Compute the remaining output dimension so that it has the same geographic resolution as the dimension
// from #1. This computed dimension is always less than or equal to the dimension from #1.
//
// This has the effect of allocating resolution where the aligned image needs it most, and gives the aligned
// image square pixels in geographic coordinates. Without square pixels the aligned image's resolution can be
// extremely anisotriopic, causing severe aliasing in one dimension.
if (dimension == null)
{
double maxDimension = Math.max(sourceWidth, sourceHeight);
double maxSectorDelta = Math.max(sector.getDeltaLonDegrees(), sector.getDeltaLatDegrees());
double pixelsPerDegree = maxDimension / maxSectorDelta;
dimension = new Dimension(
(int) Math.round(pixelsPerDegree * sector.getDeltaLonDegrees()),
(int) Math.round(pixelsPerDegree * sector.getDeltaLatDegrees()));
}
// Get a buffer containing the source image's pixels.
int[] sourceColors = sourceImage.getRGB(0, 0, sourceWidth, sourceHeight, null, 0, sourceWidth);
// Create a buffer for the aligned image pixels, initially filled with transparent values.
int[] destColors = new int[dimension.width * dimension.height];
// Compute the geographic dimensions of the aligned image's pixels. We divide by the dimension instead of
// dimension-1 because we treat the aligned image pixels as having area.
double dLon = sector.getDeltaLonDegrees() / dimension.width;
double dLat = sector.getDeltaLatDegrees() / dimension.height;
// Compute each aligned image pixel's color by mapping its location into the source image. This treats the
// aligned image pixel's as having area, and the location of each pixel's at its center. This loop begins in the
// center of the upper left hand pixel and continues in row major order across the image, stepping by a pixels
// geographic size.
for (int j = 0; j < dimension.height; j++)
{
// Generate an InterruptedException if the current thread is interrupted. Responding to thread interruptions
// before processing each image row ensures that this method terminates in a reasonable amount of time after
// the currently executing thread is interrupted, but without consuming unecessary CPU time. Using either
// Thread.sleep(1), or executing Thread.sleep() for each pixel would unecessarily increase the this methods
// total CPU time.
Thread.sleep(0);
float lat = (float) (sector.getMaxLatitude().degrees - j * dLat - dLon / 2d);
for (int i = 0; i < dimension.width; i++)
{
float lon = (float) (sector.getMinLongitude().degrees + i * dLon + dLat / 2d);
// Search for a cell in the source image which contains this aligned image pixel's location.
ImageInterpolator.ContainingCell cell = grid.findContainingCell(lon, lat);
// If there's a source cell for this location, then write a color to the destination image by linearly
// interpolating between the four pixels at the cell's corners. Otherwise, don't change the destination
// image. This ensures pixels which don't correspond to the source image remain transparent.
if (cell != null)
{
int color = interpolateColor(cell.uv[0], cell.uv[1],
sourceColors[cell.fieldIndices[0]],
sourceColors[cell.fieldIndices[1]],
sourceColors[cell.fieldIndices[3]],
sourceColors[cell.fieldIndices[2]]
);
destColors[j * dimension.width + i] = color;
}
}
}
// Release memory used by source colors and the grid
//noinspection UnusedAssignment
sourceColors = null;
//noinspection UnusedAssignment
grid = null;
BufferedImage destImage = new BufferedImage(dimension.width, dimension.height, BufferedImage.TYPE_4BYTE_ABGR);
destImage.setRGB(0, 0, dimension.width, dimension.height, destColors, 0, dimension.width);
return new AlignedImage(destImage, sector);
}
public static void alignImageDump(BufferedImage sourceImage, float[] latitudes, float[] longitudes)
{
try
{
JFileChooser fileChooser = new JFileChooser();
int status = fileChooser.showSaveDialog(null);
if (status != JFileChooser.APPROVE_OPTION)
return;
File imageFile = fileChooser.getSelectedFile();
if (!imageFile.getName().endsWith(".png"))
imageFile = new File(imageFile.getPath() + ".png");
ImageIO.write(sourceImage, "png", imageFile);
File latsFile = new File(WWIO.replaceSuffix(imageFile.getPath(), ".lats.dat"));
File lonsFile = new File(WWIO.replaceSuffix(imageFile.getPath(), ".lons.dat"));
DataOutputStream latsOut = new DataOutputStream(new FileOutputStream(latsFile));
DataOutputStream lonsOut = new DataOutputStream(new FileOutputStream(lonsFile));
for (int i = 0; i < latitudes.length; i++)
{
latsOut.writeFloat(latitudes[i]);
lonsOut.writeFloat(longitudes[i]);
}
latsOut.flush();
lonsOut.flush();
latsOut.close();
lonsOut.close();
System.out.println("FILES SAVED");
}
catch (IOException e)
{
e.printStackTrace();
}
}
private static final int MAX_IMAGE_SIZE_TO_CONVERT = 4096;
public static BufferedImage toCompatibleImage(BufferedImage image)
{
if (image == null)
{
String message = Logging.getMessage("nullValue.ImageIsNull");
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (java.awt.GraphicsEnvironment.isHeadless())
return image;
// If the image is not already compatible, and is within the restrictions on dimension, then convert it
// to a compatible image type.
if (!isCompatibleImage(image)
&& (image.getWidth() <= MAX_IMAGE_SIZE_TO_CONVERT)
&& (image.getHeight() <= MAX_IMAGE_SIZE_TO_CONVERT))
{
java.awt.image.BufferedImage compatibleImage =
createCompatibleImage(image.getWidth(), image.getHeight(), image.getTransparency());
java.awt.Graphics2D g2d = compatibleImage.createGraphics();
g2d.drawImage(image, 0, 0, null);
g2d.dispose();
return compatibleImage;
}
// Otherwise return the original image.
else
{
return image;
}
}
public static BufferedImage createCompatibleImage(int width, int height, int transparency)
{
if (width < 1)
{
String message = Logging.getMessage("generic.InvalidWidth", width);
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
if (height < 1)
{
String message = Logging.getMessage("generic.InvalidHeight", height);
Logging.logger().severe(message);
throw new IllegalArgumentException(message);
}
// if (java.awt.GraphicsEnvironment.isHeadless())
{
return new BufferedImage(width, height,
(transparency == Transparency.OPAQUE) ? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB
);
}
//
// java.awt.GraphicsConfiguration gc = getDefaultGraphicsConfiguration();
// return gc.createCompatibleImage(width, height, transparency);
}
protected static boolean isCompatibleImage(BufferedImage image)
{
if (java.awt.GraphicsEnvironment.isHeadless())
return false;
java.awt.GraphicsConfiguration gc = getDefaultGraphicsConfiguration();
java.awt.image.ColorModel gcColorModel = gc.getColorModel(image.getTransparency());
return image.getColorModel().equals(gcColorModel);
}
protected static java.awt.GraphicsConfiguration getDefaultGraphicsConfiguration()
{
java.awt.GraphicsEnvironment ge = java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment();
java.awt.GraphicsDevice gd = ge.getDefaultScreenDevice();
return gd.getDefaultConfiguration();
}
public static BufferedImage mapTransparencyColors(ByteBuffer imageBuffer, int originalColors[])
{
try
{
InputStream inputStream = WWIO.getInputStreamFromByteBuffer(imageBuffer);
BufferedImage image = ImageIO.read(inputStream);
return mapTransparencyColors(image, originalColors);
}
catch (IOException e)
{
Logging.logger().finest(e.getMessage());
return null;
}
}
public static BufferedImage mapTransparencyColors(BufferedImage sourceImage, int[] originalColors)
{
if (sourceImage == null)
{
String message = Logging.getMessage("nullValue.ImageIsNull");
Logging.logger().severe(message);
throw new IllegalStateException(message);
}
if (originalColors == null)
{
String message = Logging.getMessage("nullValue.ColorArrayIsNull");
Logging.logger().severe(message);
throw new IllegalStateException(message);
}
int width = sourceImage.getWidth();
int height = sourceImage.getHeight();
if (width < 1 || height < 1)
{
String message = Logging.getMessage("ImageUtil.EmptyImage");
Logging.logger().severe(message);
throw new IllegalStateException(message);
}
int[] sourceColors = sourceImage.getRGB(0, 0, width, height, null, 0, width);
int[] destColors = Arrays.copyOf(sourceColors, sourceColors.length);
for (int j = 0; j < height; j++)
{
for (int i = 0; i < width; i++)
{
int index = j * width + i;
for (int c : originalColors)
{
if (sourceColors[index] == c)
{
destColors[index] = 0;
break;
}
}
}
}
// Release memory used by source colors prior to creating the new image
//noinspection UnusedAssignment
sourceColors = null;
BufferedImage destImage = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);
destImage.setRGB(0, 0, width, height, destColors, 0, width);
return destImage;
}
public static BufferedImage mapColors(ByteBuffer imageBuffer, int originalColor, int newColor)
{
try
{
InputStream inputStream = WWIO.getInputStreamFromByteBuffer(imageBuffer);
BufferedImage image = ImageIO.read(inputStream);
return mapColors(image, new int[] {originalColor}, new int[] {newColor});
}
catch (IOException e)
{
Logging.logger().finest(e.getMessage());
return null;
}
}
public static BufferedImage mapColors(BufferedImage sourceImage, int[] originalColors, int[] newColors)
{
if (sourceImage == null)
{
String message = Logging.getMessage("nullValue.ImageIsNull");
Logging.logger().severe(message);
throw new IllegalStateException(message);
}
if (originalColors == null || newColors == null)
{
String message = Logging.getMessage("nullValue.ColorArrayIsNull");
Logging.logger().severe(message);
throw new IllegalStateException(message);
}
int width = sourceImage.getWidth();
int height = sourceImage.getHeight();
if (width < 1 || height < 1)
{
String message = Logging.getMessage("ImageUtil.EmptyImage");
Logging.logger().severe(message);
throw new IllegalStateException(message);
}
int[] sourceColors = sourceImage.getRGB(0, 0, width, height, null, 0, width);
int[] destColors = Arrays.copyOf(sourceColors, sourceColors.length);
for (int j = 0; j < height; j++)
{
for (int i = 0; i < width; i++)
{
int index = j * width + i;
for (int c : originalColors)
{
if (sourceColors[index] == originalColors[c])
destColors[index] = newColors[c];
}
}
}
// Release memory used by source colors prior to creating the new image
//noinspection UnusedAssignment
sourceColors = null;
BufferedImage destImage = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);
destImage.setRGB(0, 0, width, height, destColors, 0, width);
return destImage;
}
public static ByteBuffer asJPEG(DataRaster raster)
{
ByteBuffer buffer = null;
if (null == raster)
{
String msg = Logging.getMessage("nullValue.RasterIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
BufferedImage image;
if (raster instanceof BufferedImageRaster)
{
image = ((BufferedImageRaster) raster).getBufferedImage();
}
else if (raster instanceof BufferWrapperRaster)
{
image = ImageUtil.visualize((BufferWrapperRaster) raster);
}
else
{
String msg = Logging.getMessage("generic.UnexpectedRasterType", raster.getClass().getName());
Logging.logger().severe(msg);
throw new WWRuntimeException(msg);
}
ImageOutputStream ios = null;
if (null == image)
{
String msg = Logging.getMessage("nullValue.ImageIsNull");
Logging.logger().severe(msg);
throw new WWRuntimeException(msg);
}
try
{
ByteArrayOutputStream imageBytes = new ByteArrayOutputStream();
ios = new MemoryCacheImageOutputStream(imageBytes);
ColorModel cm = image.getColorModel();
if (cm instanceof ComponentColorModel)
{
ImageWriter writer = ImageIO.getImageWritersByFormatName("jpeg").next();
if (null != writer)
{
ImageWriteParam param = writer.getDefaultWriteParam();
param.setSourceBands(new int[] {0, 1, 2});
cm = new DirectColorModel(24, /*Red*/0x00ff0000, /*Green*/0x0000ff00, /*Blue*/ 0x000000ff,
/*Alpha*/0x0);
param.setDestinationType(new ImageTypeSpecifier(cm, cm.createCompatibleSampleModel(1, 1)));
writer.setOutput(ios);
writer.write(null, new IIOImage(image, null, null), param);
writer.dispose();
}
}
else
ImageIO.write(image, "jpeg", ios);
buffer = ByteBuffer.wrap(imageBytes.toByteArray());
}
catch (Throwable t)
{
Logging.logger().log(java.util.logging.Level.SEVERE, t.getMessage(), t);
}
finally
{
close(ios);
}
return buffer;
}
public static ByteBuffer asPNG(DataRaster raster)
{
ByteBuffer buffer = null;
if (null == raster)
{
String msg = Logging.getMessage("nullValue.RasterIsNull");
Logging.logger().severe(msg);
throw new IllegalArgumentException(msg);
}
BufferedImage image;
if (raster instanceof BufferedImageRaster)
{
image = ((BufferedImageRaster) raster).getBufferedImage();
}
else if (raster instanceof BufferWrapperRaster)
{
image = ImageUtil.visualize((BufferWrapperRaster) raster);
}
else
{
String msg = Logging.getMessage("generic.UnexpectedRasterType", raster.getClass().getName());
Logging.logger().severe(msg);
throw new WWRuntimeException(msg);
}
ImageOutputStream ios = null;
if (null == image)
{
String msg = Logging.getMessage("nullValue.ImageIsNull");
Logging.logger().severe(msg);
throw new WWRuntimeException(msg);
}
try
{
ByteArrayOutputStream imageBytes = new ByteArrayOutputStream();
ios = new MemoryCacheImageOutputStream(imageBytes);
ImageIO.write(image, "png", ios);
buffer = ByteBuffer.wrap(imageBytes.toByteArray());
}
catch (Throwable t)
{
Logging.logger().log(java.util.logging.Level.SEVERE, t.getMessage(), t);
}
finally
{
close(ios);
}
return buffer;
}
protected static void close(ImageOutputStream ios)
{
if (null != ios)
{
try
{
ios.close();
}
catch (Throwable t)
{
Logging.logger().log(java.util.logging.Level.SEVERE, t.getMessage(), t);
}
}
}
/**
* Converts a non-imagery data raster (elevations) to visually representable image raster.
* Calculates min and max values, and normalizes pixel value from 0 - 65,535 and creates
* a GRAY color image raster with pixel data type of unsigned short.
*
* @param raster non-imagery data raster (elevations) instance of data raster derived from BufferWrapperRaster
*
* @return BufferedImage visual representation of the non-imagery data raster
*/
public static BufferedImage visualize(BufferWrapperRaster raster)
{
if (null == raster)
{
String message = Logging.getMessage("nullValue.RasterIsNull");
Logging.logger().severe(message);
throw new WWRuntimeException(message);
}
// we are building UINT DataBuffer, cannot use negative values as -32768 or -9999, so we will use 0
double missingDataSignal = AVListImpl.getDoubleValue(raster, AVKey.MISSING_DATA_SIGNAL,
(double) Short.MIN_VALUE);
int missingDataReplacement = 0;
Double minElevation = (Double) raster.getValue(AVKey.ELEVATION_MIN);
Double maxElevation = (Double) raster.getValue(AVKey.ELEVATION_MAX);
double min = (null != minElevation && minElevation >= Earth.ELEVATION_MIN) ? minElevation : 0d;
double max = (null != maxElevation && minElevation <= Earth.ELEVATION_MAX) ? maxElevation : Earth.ELEVATION_MAX;
int width = raster.getWidth();
int height = raster.getHeight();
int size = width * height;
short[][] data = new short[][]
{
new short[size], // intensity band
new short[size] // alpha (transparency band)
};
final int BAND_Y = 0, BAND_ALPHA = 1;
final int ALPHA_OPAQUE = (short) 0xFFFF;
final int ALPHA_TRANSLUCENT = (short) 0;
int i = 0;
boolean hasVoids = false;
double norm = (max != min) ? Math.abs(65534d / (max - min)) : 0d;
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
double v = raster.getDoubleAtPosition(y, x);
// set pixel and alpha as zero (transparent) for pixel which is:
// - equals to missingDataSignal
// - is zero
// - greater than max elevation or smaller than min elevation
if (v == missingDataSignal || v == 0 || v < min || v > max)
{
data[BAND_Y][i] = (short) (0xFFFF & missingDataReplacement);
data[BAND_ALPHA][i] = ALPHA_TRANSLUCENT;
hasVoids = true;
}
else
{
data[BAND_Y][i] = (short) (0xFFFF & (int) ((v - min) * norm));
data[BAND_ALPHA][i] = ALPHA_OPAQUE;
}
i++;
}
}
int[] bandOrder = (hasVoids) ? new int[] {BAND_Y, BAND_ALPHA} : new int[] {BAND_Y};
int[] offsets = (hasVoids) ? new int[] {0, 0} : new int[] {0};
int[] nBits = (hasVoids) ? new int[] {16, 8} : new int[] {16};
DataBuffer imgBuffer = new DataBufferUShort(data, size);
SampleModel sm = new BandedSampleModel(DataBuffer.TYPE_USHORT, width, height, width, bandOrder, offsets);
WritableRaster wr = Raster.createWritableRaster(sm, imgBuffer, null);
ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_GRAY);
ColorModel cm = new ComponentColorModel(cs, nBits, hasVoids, false,
hasVoids ? Transparency.TRANSLUCENT : Transparency.OPAQUE,
DataBuffer.TYPE_USHORT);
return new BufferedImage(cm, wr, false, null);
}
//
// public static void main(String[] args)
// {
// // Test alignImage(...)
// try
// {
// BufferedImage sourceImage = ImageIO.read(
// new File("src/images/BMNG_world.topo.bathy.200405.3.2048x1024.jpg"));
//
// int width = sourceImage.getWidth();
// int height = sourceImage.getHeight();
//
// float[] xs = new float[width * height];
// float[] ys = new float[xs.length];
//
// double dx = 360d / (width - 1);
// double dy = 180d / (height - 1);
//
// for (int j = 0; j < height; j++)
// {
// for (int i = 0; i < width; i++)
// {
// xs[j * width + i] = -180 + i * (float) dx;
// ys[j * width + i] = -90 + j * (float) dy;
// }
// }
// xs[xs.length - 1] = 180;
// ys[ys.length - 1] = 90;
//
// long start = System.currentTimeMillis();
// AlignedImage destImage = alignImage(sourceImage, ys, xs, Sector.fromDegrees(-90, 90, -180, 180));
//
// System.out.println(System.currentTimeMillis() - start);
//// int[] src = sourceImage.getRGB(0, 0, width, height, null, 0, width);
//// int[] dest = destImage.image.getRGB(0, 0, width, height, null, 0, width);
////
//// ColorModel cm = destImage.getColorModel();
//// double count = 0;
//// for (int i = 0; i < src.length; i++)
//// {
//// if (src[i] != dest[i])
//// {
//// ++count;
//// int s = src[i];
//// int d = dest[i];
//// int sr = cm.getRed(s);
//// int sg = cm.getGreen(s);
//// int sb = cm.getBlue(s);
//// int sa = cm.getAlpha(s);
//// int dr = cm.getRed(d);
//// int dg = cm.getGreen(d);
//// int db = cm.getBlue(d);
//// int da = cm.getAlpha(d);
////
//// if (Math.abs(dr - sr) > 1 || Math.abs(dg - sg) > 1 || Math.abs(db - sb) > 1
//// || Math.abs(da - sa) > 1)
//// System.out.printf("%d: (%d, %d, %d, %d) : (%d, %d, %d, %d) delta: (%d, %d, %d, %d) (%f, %f)\n",
//// i,
//// sr, sg, sb, sa, dr, dg, db, da,
//// dr - sr, dg - sg, db - sb, da - sa,
//// xs[i], ys[i]);
//// }
//// }
////
//// System.out.println(count + " of " + src.length + " (" + 100d * count / src.length + "%)");
// }
// catch (IOException e)
// {
// e.printStackTrace();
// }
// }
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy