Reverse Java Graphics2D Scaled and Rotated Coordinates

Reverse Java Graphics2D scaled and rotated coordinates

If you keep a copy of the AffineTransform you use when you paint the image, you can use
AffineTransform.inverseTransform(Point2D ptSrc, Point2D ptDst)
to transform a device space coordinate back to user space

Edit: If you capture the current transform of the Graphics2D while painting, beware of the Graphics2D being re-used for multiple lightweight children of the same window/panel, because then the transform will be relative to the parent component but the mouse coordinates will be relative to the child. You need to capture the changes you make to the transform not its final value. Example:

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;

import javax.imageio.ImageIO;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JComponent;
import javax.swing.JFrame;

public class Main {
public static void main(String[] args) throws MalformedURLException, IOException {
JFrame frame = new JFrame();
Box box = new Box(BoxLayout.Y_AXIS);
BufferedImage image = ImageIO.read(new URL("http://sstatic.net/so/img/logo.png"));
AffineTransform xfrm1 = AffineTransform.getScaleInstance(0.95, 1.25);
xfrm1.rotate(-0.3);
box.add(new ImageView(image, xfrm1));
AffineTransform xfrm2 = AffineTransform.getShearInstance(0.1, 0.2);
xfrm2.scale(1.3, 0.9);
box.add(new ImageView(image, xfrm2));
frame.add(box);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}

@SuppressWarnings("serial")
class ImageView extends JComponent {
@Override
public void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
try {
paintXfrm = g2d.getTransform();
paintXfrm.invert();
g2d.translate(getWidth() / 2, getHeight() / 2);
g2d.transform(xfrm);
g2d.translate(image.getWidth() * -0.5, image.getHeight() * -0.5);
paintXfrm.concatenate(g2d.getTransform());
g2d.drawImage(image, 0, 0, this);
} catch (NoninvertibleTransformException ex) {
ex.printStackTrace();
}
}

@Override
public Dimension getPreferredSize() {
return new Dimension(image.getWidth() * 2, image.getHeight() * 2);
}

ImageView(final BufferedImage image, final AffineTransform xfrm) {
this.canvas = image.createGraphics();
canvas.setColor(Color.BLACK);
canvas.setStroke(new BasicStroke(3.0f));
this.image = image;
this.xfrm = xfrm;
addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
try {
mouseDownCoord = e.getPoint();
paintXfrm.inverseTransform(mouseDownCoord, mouseDownCoord);
} catch (NoninvertibleTransformException ex) {
}
}

@Override
public void mouseExited(MouseEvent e) {
mouseDownCoord = null;
}
});
addMouseMotionListener(new MouseMotionAdapter() {
@Override
public void mouseDragged(MouseEvent e) {
Point p = e.getPoint();
try {
paintXfrm.inverseTransform(p, p);
if (mouseDownCoord != null) {
canvas.drawLine(mouseDownCoord.x, mouseDownCoord.y, p.x, p.y);
for (Component sibling: getParent().getComponents()) {
sibling.repaint();
}
}
mouseDownCoord = p;
} catch (NoninvertibleTransformException ex) {
ex.printStackTrace();
}
}
});
}

private Graphics2D canvas;
private BufferedImage image;
private AffineTransform xfrm;
private AffineTransform paintXfrm;
private Point mouseDownCoord;
}

Coordinate transformation Dilemma, User coordinate to Device coordinate?

I find it easier to create an
java.awt.geom.AffineTransform
object and transform the coordinates from meters to pixels myself.
In your case, use AffineTransform.getScaleInstance(sx, sy).

Then I call Graphics2D functions to draw my lines and polygons
using pixel coordinates.

This makes debugging easier: you can see the coordinate values before
and after the transformation.

The Advanced Topics section in the Java Tutorial describes how
to do transformations with Graphics2D, with examples:
http://download.oracle.com/javase/tutorial/2d/index.html

Getting the point of a rectangle's corners on the screen while rotating

You are applying your transform on the graphics object. Therefore, the rectangle object itself is not modified.

AffineTransform has two methods you can make use of. createTransformedShape which returns a new shape that is transformed accordingly or transform(Point2D ptSrc, Point2D ptDst) which will transform the point ptSrc and save the transformed point in ptDst:

Point rotatedPoint = new Point();
transform.transform(new Point(rect.x, rect.y), rotatedPoint);

Resize Graphics2d into JScrollPane

If I understand correctly, you want the scroll pane's scroll bars to reflect the current zoom state. I see two alternatives:

  • Don't override getPreferredSize() in the component, and adjust the preferred size in the mouse listener to include the zoomed image; it appears slightly truncated on the right.

  • Do override getPreferredSize() in the component, and adjust the returned Dimension (now a constant) to include the zoomed boundary implicit in paintComponent().

I'd prefer the latter. I've also found it helpful to write explicit transformation functions to convert zoomed and un-zoomed coordinates, as shown here. An inverse AffineTransform, shown here, is also possible.

Scaling and zoom

ScaledPanel shows how to scale mouse coordinates using explicit transformation methods: scaleX, scaleY, unScaleX and unScaleY. Alternatively, you can use an inverse transformation, as shown here.

A rotated square panel in Java GUI

The critical thing seems to be painting the components after rotating the graphics context. Here's an example:

Sample Image

Addendum 1:As @Atreys comments, the rotated components are drawn, but interact poorly. If the components must remain usable, event coordinates should also be transformed. Compare this (considerably) more complex example that mirrors components.

Addendum 2: If you also need to transform the mouse coordinates, this example may be helpful.

Addendum 3: Alternatively, consider the drawString() examples examined here.

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

/** @see https://stackoverflow.com/questions/6333464 */
public class RotatePanel extends JPanel {

public RotatePanel() {
this.setPreferredSize(new Dimension(320, 240));
this.add(new JLabel("Hello World!", JLabel.CENTER));
}

@Override
public void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
int w2 = getWidth() / 2;
int h2 = getHeight() / 2;
g2d.rotate(-Math.PI / 2, w2, h2);
super.paintComponent(g);
}

private void display() {
JFrame f = new JFrame("RotatePanel");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(this);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}

public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {

@Override
public void run() {
new RotatePanel().display();
}
});
}
}

Graphics2D placing image with specific corner co-ordinates

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 Topics



Leave a reply



Submit