// License: GPL. For details, see LICENSE file.
package cadastre_fr;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.Point;
import java.awt.image.BufferedImage;
import java.awt.image.ImageObserver;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import javax.imageio.ImageIO;
import org.openstreetmap.josm.data.coor.EastNorth;
import org.openstreetmap.josm.gui.NavigatableComponent;
public class GeorefImage implements Serializable, ImageObserver, Cloneable {
private static final long serialVersionUID = 1L;
// bbox of the georeferenced image (the nice horizontal and vertical box)
public EastNorth min;
public EastNorth max;
// bbox of the georeferenced original image (raster only) (inclined if rotated and before cropping)
// P[0] is bottom,left then next are clockwise.
public EastNorth[] orgRaster = new EastNorth[4];
// bbox of the georeferenced original image (raster only) after cropping
public EastNorth[] orgCroppedRaster = new EastNorth[4];
// angle with georeferenced original image after rotation (raster images only)(in radian)
public double angle = 0;
public int imageOriginalHeight = 0;
public int imageOriginalWidth = 0;
public BufferedImage image;
public WMSLayer wmsLayer;
private double pixelPerEast;
private double pixelPerNorth;
public GeorefImage(BufferedImage img, EastNorth min, EastNorth max, WMSLayer wmsLayer) {
image = img;
this.min = min;
this.max = max;
this.orgRaster[0] = min;
this.orgRaster[1] = new EastNorth(min.east(), max.north());
this.orgRaster[2] = max;
this.orgRaster[3] = new EastNorth(max.east(), min.north());
this.orgCroppedRaster[0] = min;
this.orgCroppedRaster[1] = new EastNorth(min.east(), max.north());
this.orgCroppedRaster[2] = max;
this.orgCroppedRaster[3] = new EastNorth(max.east(), min.north());
// img can be null for a hack used in overlapping detection
this.imageOriginalHeight = (img == null ? 1 : img.getHeight());
this.imageOriginalWidth = (img == null ? 1 : img.getWidth());
this.wmsLayer = wmsLayer;
updatePixelPer();
}
public static GraphicsConfiguration getDefaultConfiguration() {
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice gd = ge.getDefaultScreenDevice();
return gd.getDefaultConfiguration();
}
/**
* Recalculate the new bounding box of the image based on the four points provided as parameters.
* The new bbox defined in [min.max] will retain the extreme values of both boxes.
* @param p1 one of the bounding box corner
* @param p2 one of the bounding box corner
* @param p3 one of the bounding box corner
* @param p4 one of the bounding box corner
*/
private EastNorthBound computeNewBounding(EastNorth p1, EastNorth p2, EastNorth p3, EastNorth p4) {
EastNorth[] pt = new EastNorth[4];
pt[0] = p1;
pt[1] = p2;
pt[2] = p3;
pt[3] = p4;
double smallestEast = Double.MAX_VALUE;
double smallestNorth = Double.MAX_VALUE;
double highestEast = Double.MIN_VALUE;
double highestNorth = Double.MIN_VALUE;
for (int i = 0; i <= 3; i++) {
smallestEast = Math.min(pt[i].east(), smallestEast);
smallestNorth = Math.min(pt[i].north(), smallestNorth);
highestEast = Math.max(pt[i].east(), highestEast);
highestNorth = Math.max(pt[i].north(), highestNorth);
}
return new EastNorthBound(new EastNorth(smallestEast, smallestNorth),
new EastNorth(highestEast, highestNorth));
}
public boolean contains(EastNorth en) {
return min.east() <= en.east() && en.east() <= max.east() && min.north() <= en.north()
&& en.north() <= max.north();
}
public void paint(Graphics2D g, NavigatableComponent nc, boolean backgroundTransparent, float transparency,
boolean drawBoundaries) {
if (image == null || min == null || max == null)
return;
// apply offsets defined manually when vector images are translated manually (not saved in cache)
double dx = 0, dy = 0;
if (wmsLayer != null) {
dx = wmsLayer.deltaEast;
dy = wmsLayer.deltaNorth;
}
Point minPt = nc.getPoint(new EastNorth(min.east()+dx, min.north()+dy));
Point maxPt = nc.getPoint(new EastNorth(max.east()+dx, max.north()+dy));
if (!g.hitClip(minPt.x, maxPt.y, maxPt.x - minPt.x, minPt.y - maxPt.y))
return;
if (backgroundTransparent && transparency < 1.0f)
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, transparency));
if (drawBoundaries) {
if (orgCroppedRaster == null) {
// this is the old cache format where only [min,max] bbox is stored
g.setColor(Color.green);
g.drawRect(minPt.x, maxPt.y, maxPt.x - minPt.x, minPt.y - maxPt.y);
} else {
Point[] croppedPoint = new Point[5];
for (int i = 0; i < 4; i++) {
croppedPoint[i] = nc.getPoint(
new EastNorth(orgCroppedRaster[i].east()+dx, orgCroppedRaster[i].north()+dy));
}
croppedPoint[4] = croppedPoint[0];
for (int i = 0; i < 4; i++) {
g.setColor(Color.green);
g.drawLine(croppedPoint[i].x, croppedPoint[i].y, croppedPoint[i+1].x, croppedPoint[i+1].y);
}
/*
//Uncomment this section to display the original image size (before cropping)
Point[] orgPoint = new Point[5];
for (int i=0; i<4; i++)
orgPoint[i] = nc.getPoint(orgRaster[i]);
orgPoint[4] = orgPoint[0];
for (int i=0; i<4; i++) {
g.setColor(Color.red);
g.drawLine(orgPoint[i].x, orgPoint[i].y, orgPoint[i+1].x, orgPoint[i+1].y);
}
*/
}
}
g.drawImage(image, minPt.x, maxPt.y, maxPt.x, minPt.y, // dest
0, 0, image.getWidth(), image.getHeight(), // src
null);
if (backgroundTransparent && transparency < 1.0f)
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f));
}
/**
* Is the given bbox overlapping this image ?
*/
public boolean overlap(GeorefImage georefImage) {
if (this.contains(georefImage.min) || this.contains(georefImage.max))
return true;
if (this.contains(new EastNorth(georefImage.min.east(), georefImage.max.north()))
|| this.contains(new EastNorth(georefImage.max.east(), georefImage.min.north())))
return true;
return false;
}
/**
* Make all pixels masked by the given georefImage transparent in this image
*/
public void withdraw(GeorefImage georefImage) {
double minMaskEast = (georefImage.min.east() > this.min.east()) ? georefImage.min.east() : this.min.east();
double maxMaskEast = (georefImage.max.east() < this.max.east()) ? georefImage.max.east() : this.max.east();
double minMaskNorth = (georefImage.min.north() > this.min.north()) ? georefImage.min.north() : this.min.north();
double maxMaskNorth = (georefImage.max.north() < this.max.north()) ? georefImage.max.north() : this.max.north();
if ((maxMaskNorth - minMaskNorth) > 0 && (maxMaskEast - minMaskEast) > 0) {
double pxPerEast = (max.east() - min.east()) / image.getWidth();
double pxPerNorth = (max.north() - min.north()) / image.getHeight();
int minXMaskPixel = (int) ((minMaskEast - min.east()) / pxPerEast);
int minYMaskPixel = (int) ((max.north() - maxMaskNorth) / pxPerNorth);
int widthXMaskPixel = Math.abs((int) ((maxMaskEast - minMaskEast) / pxPerEast));
int heightYMaskPixel = Math.abs((int) ((maxMaskNorth - minMaskNorth) / pxPerNorth));
Graphics g = image.getGraphics();
for (int x = minXMaskPixel; x < minXMaskPixel + widthXMaskPixel; x++) {
for (int y = minYMaskPixel; y < minYMaskPixel + heightYMaskPixel; y++) {
image.setRGB(x, y, VectorImageModifier.cadastreBackgroundTransp);
}
}
g.dispose();
}
}
/**
* Method required by BufferedImage serialization.
* Save only primitives to keep cache independent of software changes.
*/
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
if (WMSLayer.currentFormat >= 2) {
max = new EastNorth(in.readDouble(), in.readDouble());
min = new EastNorth(in.readDouble(), in.readDouble());
}
orgRaster = null;
orgCroppedRaster = null;
if (WMSLayer.currentFormat >= 3) {
orgRaster = new EastNorth[4];
orgCroppedRaster = new EastNorth[4];
angle = in.readDouble();
orgRaster[0] = new EastNorth(in.readDouble(), in.readDouble());
orgRaster[1] = new EastNorth(in.readDouble(), in.readDouble());
orgRaster[2] = new EastNorth(in.readDouble(), in.readDouble());
orgRaster[3] = new EastNorth(in.readDouble(), in.readDouble());
orgCroppedRaster[0] = new EastNorth(in.readDouble(), in.readDouble());
orgCroppedRaster[1] = new EastNorth(in.readDouble(), in.readDouble());
orgCroppedRaster[2] = new EastNorth(in.readDouble(), in.readDouble());
orgCroppedRaster[3] = new EastNorth(in.readDouble(), in.readDouble());
}
if (WMSLayer.currentFormat >= 4) {
imageOriginalHeight = in.readInt();
imageOriginalWidth = in.readInt();
}
image = ImageIO.read(ImageIO.createImageInputStream(in));
updatePixelPer();
}
/**
* Method required by BufferedImage serialization.
* Use only primitives for stability in time (not influenced by josm-core changes).
*/
private void writeObject(ObjectOutputStream out) throws IOException {
out.writeDouble(max.getX()); out.writeDouble(max.getY());
out.writeDouble(min.getX()); out.writeDouble(min.getY());
if (orgRaster == null) { // just in case we save an old format layer already cached
orgRaster = new EastNorth[4];
orgCroppedRaster = new EastNorth[4];
}
out.writeDouble(angle);
out.writeDouble(orgRaster[0].getX()); out.writeDouble(orgRaster[0].getY());
out.writeDouble(orgRaster[1].getX()); out.writeDouble(orgRaster[1].getY());
out.writeDouble(orgRaster[2].getX()); out.writeDouble(orgRaster[2].getY());
out.writeDouble(orgRaster[3].getX()); out.writeDouble(orgRaster[3].getY());
out.writeDouble(orgCroppedRaster[0].getX()); out.writeDouble(orgCroppedRaster[0].getY());
out.writeDouble(orgCroppedRaster[1].getX()); out.writeDouble(orgCroppedRaster[1].getY());
out.writeDouble(orgCroppedRaster[2].getX()); out.writeDouble(orgCroppedRaster[2].getY());
out.writeDouble(orgCroppedRaster[3].getX()); out.writeDouble(orgCroppedRaster[3].getY());
// Write image as a format 3 if cache was loaded with this format to avoid incompatibilities.
if (WMSLayer.currentFormat >= 4) {
out.writeInt(imageOriginalHeight);
out.writeInt(imageOriginalWidth);
}
ImageIO.write(image, "png", ImageIO.createImageOutputStream(out));
}
private void updatePixelPer() {
pixelPerEast = image.getWidth()/(max.east()-min.east());
pixelPerNorth = image.getHeight()/(max.north()-min.north());
}
public double getPixelPerEast() {
return pixelPerEast;
}
public double getPixelPerNorth() {
return pixelPerNorth;
}
@Override
public String toString() {
return "GeorefImage[min=" + min + ", max=" + max + ", image" + image + "]";
}
/*
* Following methods are used for affine transformation of two points p1 and p2
*/
/**
* Add a translation (dx, dy) to this image min,max coordinates
* @param dx delta added to X image coordinate
* @param dy delta added to Y image coordinate
*/
public void shear(double dx, double dy) {
min = new EastNorth(min.east() + dx, min.north() + dy);
max = new EastNorth(max.east() + dx, max.north() + dy);
for (int i = 0; i < 4; i++) {
orgRaster[i] = new EastNorth(orgRaster[i].east() + dx, orgRaster[i].north() + dy);
orgCroppedRaster[i] = new EastNorth(orgCroppedRaster[i].east() + dx, orgCroppedRaster[i].north() + dy);
}
}
/**
* Change this image scale by moving the min,max coordinates around an anchor
*/
public void scale(EastNorth anchor, double proportion) {
min = anchor.interpolate(min, proportion);
max = anchor.interpolate(max, proportion);
for (int i = 0; i < 4; i++) {
orgRaster[i] = anchor.interpolate(orgRaster[i], proportion);
orgCroppedRaster[i] = anchor.interpolate(orgCroppedRaster[i], proportion);
}
updatePixelPer();
}
/**
* Rotate this image and its min/max coordinates around anchor point
* @param anchor anchor of rotation
* @param old_ang previous angle of image before rotation (0 the first time)(in radian)
* @param delta_ang angle of rotation (in radian)
*/
public void rotate(EastNorth anchor, double delta_ang) {
if (orgRaster == null || orgCroppedRaster == null)
return;
// rotate the bounding boxes coordinates first
for (int i = 0; i < 4; i++) {
orgRaster[i] = orgRaster[i].rotate(anchor, delta_ang);
orgCroppedRaster[i] = orgCroppedRaster[i].rotate(anchor, delta_ang);
}
// rotate the image now
double sin = Math.abs(Math.sin(angle+delta_ang)), cos = Math.abs(Math.cos(angle+delta_ang));
int w = imageOriginalWidth, h = imageOriginalHeight;
int neww = (int) Math.floor(w*cos+h*sin);
int newh = (int) Math.floor(h*cos+w*sin);
GraphicsConfiguration gc = getDefaultConfiguration();
BufferedImage result = gc.createCompatibleImage(neww, newh, image.getTransparency());
Graphics2D g = result.createGraphics();
g.translate((neww-image.getWidth())/2, (newh-image.getHeight())/2);
g.rotate(delta_ang, image.getWidth()/2, image.getHeight()/2);
g.drawRenderedImage(image, null);
g.dispose();
image = result;
EastNorthBound enb = computeNewBounding(orgCroppedRaster[0], orgCroppedRaster[1], orgCroppedRaster[2], orgCroppedRaster[3]);
min = enb.min;
max = enb.max;
angle += delta_ang;
}
/**
* Crop the image based on new bbox coordinates adj1 and adj2 (for raster images only).
* @param adj1 is the new corner bottom, left
* @param adj2 is the new corner top, right
*/
public void crop(EastNorth adj1, EastNorth adj2) {
// s1 and s2 have 0,0 at top, left where all EastNorth coord. have 0,0 at bottom, left
int sx1 = (int) ((adj1.getX() - min.getX())*getPixelPerEast());
int sy1 = (int) ((max.getY() - adj2.getY())*getPixelPerNorth());
int sx2 = (int) ((adj2.getX() - min.getX())*getPixelPerEast());
int sy2 = (int) ((max.getY() - adj1.getY())*getPixelPerNorth());
int newWidth = Math.abs(sx2 - sx1);
int newHeight = Math.abs(sy2 - sy1);
BufferedImage new_img = new BufferedImage(newWidth, newHeight, image.getType());
Graphics g = new_img.getGraphics();
g.drawImage(image, 0, 0, newWidth-1, newHeight-1,
sx1, sy1, sx2, sy2,
this);
image = new_img;
this.min = adj1;
this.max = adj2;
this.orgCroppedRaster[0] = min;
this.orgCroppedRaster[1] = new EastNorth(min.east(), max.north());
this.orgCroppedRaster[2] = max;
this.orgCroppedRaster[3] = new EastNorth(max.east(), min.north());
this.imageOriginalWidth = newWidth;
this.imageOriginalHeight = newHeight;
updatePixelPer();
}
@Override
public boolean imageUpdate(Image img, int infoflags, int x, int y,
int width, int height) {
return false;
}
}