// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.tools;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
/**
* Image warping algorithm.
*
* Deforms an image geometrically according to a given transformation formula.
* @since 11858
*/
public class ImageWarp {
/**
* Transformation that translates the pixel coordinates.
*/
public interface PointTransform {
Point2D transform(Point2D pt);
}
/**
* Interpolation method.
*/
public enum Interpolation {
/**
* Nearest neighbor.
*
* Simplest possible method. Faster, but not very good quality.
*/
NEAREST_NEIGHBOR(1),
/**
* Bilinear.
*
* Decent quality.
*/
BILINEAR(2);
private final int margin;
private Interpolation(int margin) {
this.margin = margin;
}
/**
* Number of pixels to scan outside the source image.
* Used to get smoother borders.
* @return the margin
*/
public int getMargin() {
return margin;
}
}
/**
* Warp an image.
* @param srcImg the original image
* @param targetDim dimension of the target image
* @param invTransform inverse transformation (translates pixel coordinates
* of the target image to pixel coordinates of the original image)
* @param interpolation the interpolation method
* @return the warped image
*/
public static BufferedImage warp(BufferedImage srcImg, Dimension targetDim, PointTransform invTransform, Interpolation interpolation) {
BufferedImage imgTarget = new BufferedImage(targetDim.width, targetDim.height, BufferedImage.TYPE_INT_ARGB);
Rectangle2D srcRect = new Rectangle2D.Double(0, 0, srcImg.getWidth(), srcImg.getHeight());
for (int j = 0; j < imgTarget.getHeight(); j++) {
for (int i = 0; i < imgTarget.getWidth(); i++) {
Point2D srcCoord = invTransform.transform(new Point2D.Double(i, j));
if (isInside(srcCoord, srcRect, interpolation.getMargin())) {
int rgb;
switch (interpolation) {
case NEAREST_NEIGHBOR:
rgb = getColor((int) Math.round(srcCoord.getX()), (int) Math.round(srcCoord.getY()), srcImg).getRGB();
break;
case BILINEAR:
int x0 = (int) Math.floor(srcCoord.getX());
double dx = srcCoord.getX() - x0;
int y0 = (int) Math.floor(srcCoord.getY());
double dy = srcCoord.getY() - y0;
Color c00 = getColor(x0, y0, srcImg);
Color c01 = getColor(x0, y0 + 1, srcImg);
Color c10 = getColor(x0 + 1, y0, srcImg);
Color c11 = getColor(x0 + 1, y0 + 1, srcImg);
int red = (int) Math.round(
(c00.getRed() * (1-dx) + c10.getRed() * dx) * (1-dy) +
(c01.getRed() * (1-dx) + c11.getRed() * dx) * dy);
int green = (int) Math.round(
(c00.getGreen()* (1-dx) + c10.getGreen() * dx) * (1-dy) +
(c01.getGreen() * (1-dx) + c11.getGreen() * dx) * dy);
int blue = (int) Math.round(
(c00.getBlue()* (1-dx) + c10.getBlue() * dx) * (1-dy) +
(c01.getBlue() * (1-dx) + c11.getBlue() * dx) * dy);
int alpha = (int) Math.round(
(c00.getAlpha()* (1-dx) + c10.getAlpha() * dx) * (1-dy) +
(c01.getAlpha() * (1-dx) + c11.getAlpha() * dx) * dy);
rgb = new Color(red, green, blue, alpha).getRGB();
break;
default:
throw new AssertionError();
}
imgTarget.setRGB(i, j, rgb);
}
}
}
return imgTarget;
}
private static boolean isInside(Point2D p, Rectangle2D rect, double margin) {
return isInside(p.getX(), rect.getMinX(), rect.getMaxX(), margin) &&
isInside(p.getY(), rect.getMinY(), rect.getMaxY(), margin);
}
private static boolean isInside(double x, double xMin, double xMax, double margin) {
return x + margin >= xMin && x - margin <= xMax;
}
private static Color getColor(int x, int y, BufferedImage img) {
// border strategy: continue with the color of the outermost pixel,
// but change alpha component to fully translucent
boolean transparent = false;
if (x < 0) {
x = 0;
transparent = true;
} else if (x >= img.getWidth()) {
x = img.getWidth() - 1;
transparent = true;
}
if (y < 0) {
y = 0;
transparent = true;
} else if (y >= img.getHeight()) {
y = img.getHeight() - 1;
transparent = true;
}
Color clr = new Color(img.getRGB(x, y));
if (!transparent)
return clr;
// keep color components, but set transparency to 0
// (the idea is that border fades out and mixes with next tile)
return new Color(clr.getRed(), clr.getGreen(), clr.getBlue(), 0);
}
}