I need to place an image onto a canvas with the corners at specific co-ordinates.
// Blank canvas
BufferedImage img = new BufferedImage(2338, 1654, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = img.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setBackground(Color.WHITE);
g2d.clearRect(0, 0, width, EXTRA_HEADER_HEIGHT);
I have all 4 corner co-ordinates that the image corners must be placed at on the background canvas. The problem is that the original image might need to be rotated. This is basically what I need to achieve:
I don't have much experience with Graphics2D but based on a quick review of the API I can't see a method to achieve this. I am hoping that I am wrong here and that somebody can save me some time but my current thinking is:
Use the co-ordinates to calculate the rotation of the placed image relative to the supplied image.
Place the image with one of its corners in the correct position.
Rotate the image around that corner (without rotating background canvas).
Any help with the above would be appreciated.
As tucuxi commented, if you really have 4 points and want the transform to place the image corners at these exact points, and affine transform won't do -- you'll need a perspective transform.
However, if you select two points of the four, you can do what you want, but you may have to scale the image. So let's say you just want to place a rotated and scaled version of your image such that its top edge goes from A' to B'. What you'll have to do is compute the affine transform, which involves determining the rotation angle, scaling factor, and translation from the segment AB to A'B'.
Here's a commented method that should do just that. I have not thoroughly tested it, but it shows how to implement the algorithm in Java.
package stackoverflow;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
public class ComputeImageTransform
{
public static AffineTransform computeTransform(
Rectangle2D imageBounds, Point2D a2, Point2D b2) {
double dx = b2.getX() - a2.getX();
double dy = b2.getY() - a2.getY();
// compute length of segment
double length = Math.hypot(dx, dy);
// compute scaling factor from image width to segment length
double scaling = length / imageBounds.getWidth();
// compute rotation angle
double rotation = Math.atan2(dy, dx);
// build the corresponding transform
// NOTE: the order of the individual transformations are applied is the
// reverse of the order in which the transform will apply them!
AffineTransform transform = new AffineTransform();
transform.translate(a2.getX(), a2.getY());
transform.rotate(rotation);
transform.scale(scaling, scaling);
transform.translate(-imageBounds.getX(), -imageBounds.getY());
return transform;
}
public static void main(String[] args) {
// transform top edge of image within this axis-aligned rectangle...
double imageX = 20;
double imageY = 30;
double imageWidth = 400;
double imageHeight = 300;
Rectangle2D imageBounds = new Rectangle2D.Double(
imageX, imageY, imageWidth, imageHeight);
// to the line segment a2-b2:
Point2D a2 = new Point2D.Double(100, 30);
Point2D b2 = new Point2D.Double(120, 200);
System.out.println("Transform image bounds " + imageBounds);
System.out.println(" to top edge " + a2 + ", " + b2 + ":");
AffineTransform transform = computeTransform(imageBounds, a2, b2);
// test
Point2D corner = new Point2D.Double();
corner.setLocation(imageX, imageY);
System.out.println("top left: " + transform.transform(corner, null));
corner.setLocation(imageX + imageWidth, imageY);
System.out.println("top right: " + transform.transform(corner, null));
corner.setLocation(imageX, imageY + imageHeight);
System.out.println("bottom left: " + transform.transform(corner, null));
corner.setLocation(imageX + imageWidth, imageY + imageHeight);
System.out.println("bottom right: " + transform.transform(corner, null));
}
}
This is the output:
Transform image bounds java.awt.geom.Rectangle2D$Double[x=20.0,y=30.0,w=400.0,h=300.0]
to top edge Point2D.Double[100.0, 30.0], Point2D.Double[120.0, 200.0]:
top left: Point2D.Double[100.0, 30.0]
top right: Point2D.Double[119.99999999999999, 199.99999999999997]
bottom left: Point2D.Double[-27.49999999999997, 44.999999999999986]
bottom right: Point2D.Double[-7.499999999999986, 214.99999999999997]
As you can see, you'll get some rounding errors due to the nature of floating-point computations.
Related
I need to print a polygon given a set of Longitude and Latitude coordinates.
I am starting with just two coordinate for initial testing. The problem is Java Image API works on pixel points and that Longitude and Latitude are big decimals.
So even if I have this code:
BufferedImage bi = new BufferedImage(500, 500, BufferedImage.OPAQUE);
Graphics2D ig2 = bi.createGraphics();
double[] xArr = toDoubleArray(Arrays.asList(Double.valueOf("121.91359648077058"), Double.valueOf("121.92293884686991")));
double[] yArr = toDoubleArray(Arrays.asList(Double.valueOf("11.995724479140792"), Double.valueOf("11.999118908426375")));
Path2D path = new Path2D.Double();
path.moveTo(xArr[0], yArr[0]);
System.out.println("x[0]=" + xArr[0] + "," + "y[0]=" + yArr[0]);
for(int i = 1; i < xArr.length; ++i) {
path.lineTo(xArr[i], yArr[i]);
System.out.println("x[" + i + "]=" + xArr[i] + "," + "y[" + i + "]" + yArr[i]);
}
path.closePath();
ig2.draw(path);
ImageIO.write(bi, "PNG", new File("polygons.png"));
It will just be a single pixel, which is 121 and 11 inside the 500,500 buffer image.
What can be done to be able to render Geolocation coordinates into an image? I really don't need the lines just plot the points in the image is sufficient, hover the Path2D is the closest one I found since it supports double values.
You could use the GeoTools library which will handle coordinates (in any projection) and render them to a Graphics object which can come from an Image.
There are a number of tutorials to get you started and
these questions will help get you started:
Plot the longitude and latitudes on map using GeoTools
GeoTools - drawing points on image
You can use any of the Java Image API for plotting your lat/lon list on image, the integer input of Image APIs should not be a constraint.
You can first decide the min-max lat/lon that you will be plotting on your image. Then you can map your input data with the image's lat/lon bounds and calculate the plotting pixel.
Below code snippet might help you on how to map and plot on image:
try {
int imagePixelWidth = 500;
int bubble_size = 50;
BufferedImage image = new BufferedImage(imagePixelWidth,
imagePixelWidth, BufferedImage.TYPE_INT_ARGB);
//Position you wish to plot
double lat = 11.995724479140792;
double lon = 121.91359648077058;
// min-max plotting lat/lon of your image
double min_lat = 10.897564874;
double min_lon = 120.8975764;
double max_lat = 13.0975875;
double max_lon = 123.9759874;
Graphics2D graphics = (Graphics2D) image.getGraphics();
double latExtent = max_lat - min_lat;
double lonExtent = max_lon - min_lon;
double ly1 = (imagePixelWidth * (lat - min_lat)) / latExtent;
double lx1 = (imagePixelWidth * (lon - min_lon)) / lonExtent;
int ly = (int) (imagePixelWidth - ly1);/* pixel increases downwards. Latitude increases upwards (north direction). So you need to inverse your mapping.*/
int lx = (int) lx1;
graphics.setColor(new Color(0, 0, 0));
graphics.fillOval(lx - bubble_size / 2, ly - bubble_size / 2,
bubble_size, bubble_size);
ImageIO.write(image, "png", new File("/home/ist/test.png"));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
After you map your coordinates to screen coordinate system, the distinction between big decimals and double should be mostly moot. You can then draw each point as a small circle. To get that done without resorting to integers, you can use e.g.
double final r = 1.0; // radius of dots representing points
for(int i = 1; i < xArr.length; ++i) {
ig2.fill(new Ellipse2D.Double(xArr[i]-r, yArr[i]-r, 2*r, 2*r));
}
I am stuck with a problem in my current project. I have 2 copies of same image say image1.tiff and image2.tiff but of different dimensions(Different pixel and DPIs). Suppose a point in image1.tiff lies at co-ordinates (x,y) ,I need to find the co-ordinates of the same point in image2.tiff. I have tried a lot to think of an algorithm. Requesting your help for this ..
You can use AffineTransformOp for this.
As an example:
BufferedImage img1 = new BufferedImage(200, 100, BufferedImage.TYPE_INT_ARGB);
BufferedImage img2 = new BufferedImage(400, 200, BufferedImage.TYPE_INT_ARGB);
double sx = img2.getWidth() / (double) img1.getWidth();
double sy = img2.getHeight() / (double) img1.getHeight();
AffineTransformOp xform =
new AffineTransformOp(AffineTransform.getScaleInstance(sx, sy), null);
Point srcPt = new Point(7, 49);
Point dstPoint = (Point) xform.getPoint2D(srcPt, new Point());
System.err.println("srcPt: " + srcPt);
System.err.println("dstPoint: " + dstPoint);
Will print:
srcPt: java.awt.Point[x=7,y=49]
dstPoint: java.awt.Point[x=14,y=98]
I would suggest the following approach:
double image1_to_image2 = image2.width()/image1.width();
double image2_to_image1 = image1.width()/image2.width();
If you have x1 and y1 as coordinates for the first image, you can calculate the corresponding location for the second image as follows:
int x2 = x1 * image1_to_image2;
int y2 = y1 * image1_to_image2;
If your images have a different aspect ratio, you will need to calculate scaling factors for the height separately.
The basic idea behind the approach is, that you map the coordinates of the images to the interval i_1 = [0;1] by dividing by the width (assuming the width is the larger dimension, but it does not matter if it is smaller than the height). By multiplying the scaled coordinate with the width of the second image, you map the coordinate back to the interval i_2 = [0; x_1 * width_2] which is at most the width of the second image.
Same question as last time but I will provide more detail.
I am currently rotating images using:
int rotateNum //in main class
double rotationRequired = Math.toRadians(rotateNum);
double locationX = img.getWidth(this) / 2;
double locationY = img.getHeight(this) / 2;
AffineTransform tx = AffineTransform.getRotateInstance(rotationRequired, locationX, locationY);
AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_BILINEAR);
g2d.drawImage(op.filter((BufferedImage)img, null), imgX, imgY, null);
And then I am actually rotating the image using:
double deltaX = (double)(imgY - otherImg.imgY);
double deltaY = (double)(imgX - otherImg.imgX);
rotateNum = (int)(180 * Math.atan2(deltaY, deltaX) / Math.PI);
My images vary in size. The smaller images don't get cut off (meaning cut off with white space) but the larger ones do, on the left or right side. Resizing the images doesn't work, and I clipped out the white rectangle around the image using the
GIMP.
Example Images:
Before(ignore the grey area to the left)
After:
See the cutoff at the side
The problem is your source image is not exactly quadratic. When you implement the AffineTransform rotation with at.rotate(-rad, width/2, height/2);, it is the same as:
at.translate(width/2,height/2);
at.rotate(rads);
at.translate(-width/2,-height/2);
So, when it execute the last line, it translates to the origin. And if the width is greater than y (or vice versa), than the origin of the transform will be translated to a smaller distance than the side of greater length.
For example, if your width is 30 and your height is 60, than the origin point will be set as (-15,-30) from where the transform was original set. So, when you translate it, say, 90 degrees, the image will end up with "width" 60 and "height" 30, but according to the origin point, the image original bottom will be drawn at (-30,0), so it overflows the AffineTransform in -15 in X axis. Then this part of image will cut.
To correct this, you can use the following code instead:
double degreesToRotate = 90;
double locationX =bufferedImage.getWidth() / 2;
double locationY = bufferedImage.getHeight() / 2;
double diff = Math.abs(bufferedImage.getWidth() - bufferedImage.getHeight());
//To correct the set of origin point and the overflow
double rotationRequired = Math.toRadians(degreesToRotate);
double unitX = Math.abs(Math.cos(rotationRequired));
double unitY = Math.abs(Math.sin(rotationRequired));
double correctUx = unitX;
double correctUy = unitY;
//if the height is greater than the width, so you have to 'change' the axis to correct the overflow
if(bufferedImage.getWidth() < bufferedImage.getHeight()){
correctUx = unitY;
correctUy = unitX;
}
int posAffineTransformOpX = posX-(int)(locationX)-(int)(correctUx*diff);
int posAffineTransformOpY = posY-(int)(locationY)-(int)(correctUy*diff);
//translate the image center to same diff that dislocates the origin, to correct its point set
AffineTransform objTrans = new AffineTransform();
objTrans.translate(correctUx*diff, correctUy*diff);
objTrans.rotate(rotationRequired, locationX, locationY);
AffineTransformOp op = new AffineTransformOp(objTrans, AffineTransformOp.TYPE_BILINEAR);
// Drawing the rotated image at the required drawing locations
graphic2dObj.drawImage(op.filter(bufferedImage, null), posAffineTransformOpX, posAffineTransformOpY, null);
Hope it help.
I imagine that it's not the size of the image that matters but rather its eccentricity: images that are more square-like have less of a problem then images that are either more fat or more thin.
I think that your problem is that your center of rotation shouldn't be [width / 2, height / 2] -- it's not that simple. Instead think of the image residing in the left upper portion of a large square the length of the square's side will be the image's width or height, whichever is larger. This is what gets rotated whenever you rotate your image.
For example, please see my reply here: https://stackoverflow.com/a/8720123/522444
This is something that java does unfortunately. One way to solve it is to make the shape a square, so that when rotating no clipping occurs.
This problem is covered in David's "Killer game programming in Java" book, books_google_killer+game+programming+clipping+rotating which is a great book if you want to do any java game programming (Even if it is a bit old).
Edit :: This converting of an image to a square can either be done to the raw image through image editing software, or through java itself. Perhaps roll your own rotating method which can check for such collisions..
Rotating the image may also affect the size of the image. Here is some code I found on the old Sun forums a long time ago (I forget the original poster). It recalculates the size required to display the image at its given angle of rotation:
import java.awt.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.io.*;
import java.net.*;
import javax.imageio.*;
import javax.swing.*;
public class RotateImage {
public static void main(String[] args) throws IOException {
URL url = new URL("https://blogs.oracle.com/jag/resource/JagHeadshot-small.jpg");
BufferedImage original = ImageIO.read(url);
GraphicsConfiguration gc = getDefaultConfiguration();
BufferedImage rotated1 = tilt(original, -Math.PI/2, gc);
BufferedImage rotated2 = tilt(original, +Math.PI/4, gc);
BufferedImage rotated3 = tilt(original, Math.PI, gc);
display(original, rotated1, rotated2, rotated3);
}
public static BufferedImage tilt(BufferedImage image, double angle, GraphicsConfiguration gc) {
double sin = Math.abs(Math.sin(angle)), cos = Math.abs(Math.cos(angle));
int w = image.getWidth(), h = image.getHeight();
int neww = (int)Math.floor(w*cos+h*sin), newh = (int)Math.floor(h*cos+w*sin);
int transparency = image.getColorModel().getTransparency();
System.out.println(transparency);
// BufferedImage result = gc.createCompatibleImage(neww, newh, transparency);
BufferedImage result = gc.createCompatibleImage(neww, newh, Transparency.TRANSLUCENT);
Graphics2D g = result.createGraphics();
g.translate((neww-w)/2, (newh-h)/2);
g.rotate(angle, w/2, h/2);
g.drawRenderedImage(image, null);
return result;
}
public static GraphicsConfiguration getDefaultConfiguration() {
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice gd = ge.getDefaultScreenDevice();
return gd.getDefaultConfiguration();
}
public static void display(BufferedImage im1, BufferedImage im2, BufferedImage im3, BufferedImage im4) {
JPanel cp = new JPanel(new GridLayout(2,2));
addImage(cp, im1, "original");
addImage(cp, im2, "rotate -PI/2");
addImage(cp, im3, "rotate +PI/4");
addImage(cp, im4, "rotate PI");
JFrame f = new JFrame("RotateImage");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setContentPane(cp);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
static void addImage(Container cp, BufferedImage im, String title) {
JLabel lbl = new JLabel(new ImageIcon(im));
lbl.setBorder(BorderFactory.createTitledBorder(title));
cp.add(lbl);
}
}
I'm working with a Java 3D application called "Walrus" that is used to display directed graphs. The code already has a feature to highlight a node and draw label adjacent in graph given its screen coordinates.
Upon rotating the screen, the node is no more highlighted.
What I have is the node coordinates in 3D. I need to draw label to it.
Code for highlight using 3D coordinates
Point3d p = new Point3d();
m_graph.getNodeCoordinates(node, p);
PointArray array = new PointArray(1, PointArray.COORDINATES);
array.setCoordinate(0, p);
m_parameters.putModelTransform(gc);
gc.setAppearance(m_parameters.getPickAppearance());
How can I draw Label with 3D coordinates( Raster graphics throws error Renderer: Error creating immediate mode Canvas3D graphics context )
How can I convert 3D coordinates to 2D screen and use existing code to draw label at 2D screen point
Thanks,
Dakshina
I have an algorithm/method for converting [x,y,z] into [x,y] with the depth parameter:
The x value is : (int) (x - (z / depth * x))
The y value is : (int) (y - (z / depth * y))
Essentially, the depth is the focal point. The vanishing point will be at [0,0,depth].
Here's what i used to convert my 3D coordinates into perspective 2D, x2 and y2 being the 2dimensional coordinates, xyz being the 3D coordinates.
use these formulas:
x2 = cos(30)*x - cos(30)*y
y2 = sin(30)*x + sin(30)*y + z
I picked the angle 30 as it is easy for perspective purposes, also used in Isometric grids for drawing 3D on 2D papers. As the z axe will be the vertical one, x and y are the ones at 60 degrees from it right and left. Isometric Grid Picture.
I'm still working on rotation, but without altering the axes, just coordinate rotation in 3D.
Enjoy.
I found the solution.
This is the function to display Text3D at image 2D coordinates
public void drawLabel(GraphicsContext3D gc, double x, double y, int zOffset, String s) {
boolean frontBufferRenderingState = gc.getFrontBufferRendering();
gc.setBufferOverride(true);
gc.setFrontBufferRendering(true);
Point3d eye = getEye();
double labelZ = zOffset * LABEL_Z_OFFSET_SCALE
+ LABEL_Z_SCALE * eye.z + LABEL_Z_OFFSET;
double xOffset = LABEL_X_OFFSET * m_pixelToMeterScale;
double yOffset = LABEL_Y_OFFSET * m_pixelToMeterScale;
Point3d p = new Point3d(x + xOffset, y + yOffset, 0.0);
{
// Project given (x, y) coordinates to the plane z=labelZ.
// Convert from image-plate to eye coordinates.
p.x -= eye.x;
p.y -= eye.y;
double inversePerspectiveScale = 1.0 - labelZ / eye.z;
p.x *= inversePerspectiveScale;
p.y *= inversePerspectiveScale;
// Convert from eye to image-plate coordinates.
p.x += eye.x;
p.y += eye.y;
}
Transform3D scale = new Transform3D();
scale.set(LABEL_SCALE);
Vector3d t = new Vector3d(p.x, p.y, labelZ);
Transform3D translation = new Transform3D();
translation.set(t);
translation.mul(scale);
Transform3D transform = new Transform3D(m_imageToVworld);
transform.mul(translation);
gc.setModelTransform(transform);
//-----------------
int fontSize=(int)(10*m_magnification);
if(fontSize>20)
fontSize=20;
//---------------
// XXX: Courier may not be available on all systems.
Text2D text = new Text2D(s, new Color3f(1.0f, 1.0f, 1.0f),
"Courier", fontSize, Font.BOLD);
gc.draw(text);
gc.flush(true);
// NOTE: Resetting the model transform here is very important.
// For some reason, not doing this causes the immediate
// following frame to render incorrectly (but subsequent
// frames will render correctly). In some ways, this
// makes sense, because most rendering code assumes that
// GraphicsContext3D has been set to some reasonable
// transform.
gc.setModelTransform(m_objectTransform);
gc.setFrontBufferRendering(frontBufferRenderingState);
}
This is the function to take 3D coordinates and convert them to image 2D coordinates and render using above function
private boolean displayOnScreenLabel(int node, String label) {
boolean success = false;
try {
Transform3D transform = m_parameters.getObjectToEyeTransform();
Point3d nodeC = new Point3d();
m_graph.getNodeCoordinates(node, nodeC);
transform.transform(nodeC);
Point3d eye = m_parameters.getEye();
double perspectiveScale = 1.0 / (1.0 - nodeC.z / eye.z);
double centerX = eye.x + nodeC.x * perspectiveScale;
double centerY = eye.y + nodeC.y * perspectiveScale;
GraphicsContext3D gc = m_canvas.getGraphicsContext3D();
m_parameters.drawLabel(gc, centerX, centerY, m_labelZOffsetCounter++, label);
success = true;
} catch (final java.lang.OutOfMemoryError error) {
JOptionPane.showMessageDialog(m_frame, "The 3D Graphics is unable to find enough memory on your system. Kill the application!", "Out Of Memory!", JOptionPane.ERROR_MESSAGE);
} catch (Exception e) {
success = false;
}
return success;
}
I need to create rectangles that are rotated around their center (so they don't need to be parallel to the axes of the coordinate system). So basicelly each rectangle can be defined by center-X, center-Y, width, height and angle. What I want to do then is to perform calculations on whether certain points are contained in these rectangles or not (so no drawing will be involved). I guess I cant use the Rectangle2D class because these rectangles will always be parallel to the x and y-axis of the coordinate system. Is the only way to get this functionality by writing my own rectangle class or is there anything existing (similar to Rectangle2D) I can use?
Rotate all the points you want to test and use contains(Point) method of the Rectangle2D as Mihai did.
But if you really want to rotate the rectangles you can do it like this (this is the integer version but probably you can do it with Rectangle2D aswell :)).
public class TestRotate {
public static void main(String... args) {
Rectangle r = new Rectangle(50, 50, 100, 100);
Point check = new Point(100, 151); // clearly outside
System.out.println("first: " + r.contains(check));
AffineTransform at = AffineTransform.getRotateInstance(
Math.PI/4, r.getCenterX(), r.getCenterY());
Polygon p = new Polygon();
PathIterator i = r.getPathIterator(at);
while (!i.isDone()) {
double[] xy = new double[2];
i.currentSegment(xy);
p.addPoint((int) xy[0], (int) xy[1]);
System.out.println(Arrays.toString(xy));
i.next();
}
// should now be inside :)
System.out.println("second: " + p.contains(check));
}
}
You can use Rectangle2D to check for containment, if instead of rotating your rectangle by an angle, say, counterclockwise, you rotate each of the points you need to check by the same angle clockwise, relative to the center of the rectangle. Something like
double dx = point.x - rectangleCenter.x;
double dy = point.y - rectangleCenter.y;
double newX = rectangleCenter.x - dx*Math.cos(angle) + dy*Math.sin(angle);
double newY = rectangleCenter.x - dx*Math.sin(angle) - dy*Math.cos(angle);