Reputation: 535
I have a jpanel that I draw on. I would like to be able to zoom in and out using the mousewheel, but I want to zoom to the location of the mouse, such that the point under the mouse stays the same. I have found some questions here on stackoverflow, but they did not work for me.
I got pretty close to doing what I want by implementing what is described here. Here is my code:
public class MyPanel extends JPanel {
...
private double zoom = 1;
private double zoom_old = 1;
private int zoomPointX;
private int zoomPointY;
...
class CustomMouseWheelListener implements MouseWheelListener {
@Override
public void mouseWheelMoved(MouseWheelEvent e) {
zoomPointX = e.getX();
zoomPointY = e.getY();
if (e.getPreciseWheelRotation() < 0) {
zoom -= 0.1;
} else {
zoom += 0.1;
}
if (zoom < 0.01) {
zoom = 0.01;
}
repaint();
}
}
...
protected void paintComponent(Graphics g) {
Graphics2D g2D = (Graphics2D) g;
super.paintComponent(g2D);
if (zoom != zoom_old) {
double scalechange = zoom - zoom_old;
zoom_old = zoom;
double offsetX = -(zoomPointX * scalechange);
double offsetY = -(zoomPointY * scalechange) ;
AffineTransform at = new AffineTransform();
at.scale(zoom, zoom);
at.translate(offsetX, offsetY);
g2D.setTransform(at);
}
a_different_class_where_i_do_some_drawing.draw(g2D);
}
}
This ALMOST does what I want. If I try to zoom, I notice that the position of the mouse is taken into account, so for example If I have my mouse on the left of the panel it will roughly zoom in on the left. However, it does not zoom exactly onto the mouse, so the point under the mouse still changes.
Can anyone help me fix this?
EDIT:
Here is a picture of what is happening with the code posted above: I start out with the mouse on the blue square and then just scroll with the mouse wheel. As you can see, the mouse position if being changed:
Upvotes: 0
Views: 1737
Reputation: 49
Here is another solution if anyone is curious how to do this. My program was written differently so I had to take a different approach.
The premise is somewhat simple, but there are some gotchas. I will try to explain how the code works below. You most likely won't be able to copy and paste, so understanding how this works is most important.
public void mouseWheelMoved(MouseWheelEvent e) {
Point2D before = null;
Point2D after = null;
AffineTransform originalTransform = new AffineTransform(at);
AffineTransform zoomedTransform = new AffineTransform();
try {
before = originalTransform.inverseTransform(e.getPoint(), null);
} catch (NoninvertibleTransformException e1) {
e1.printStackTrace();
}
zoom *= 1-(e.getPreciseWheelRotation()/5);
((Painter) Frame1.painter).setScale(zoom/100);
zoomedTransform.setToIdentity();
zoomedTransform.translate(getWidth()/2, getHeight()/2);
zoomedTransform.scale(scale, scale);
zoomedTransform.translate(-getWidth()/2, -getHeight()/2);
zoomedTransform.translate(translateX, translateY);
try {
after = zoomedTransform.inverseTransform(e.getPoint(), null);
} catch (NoninvertibleTransformException e1) {
e1.printStackTrace();
}
double deltaX = after.getX() - before.getX();
double deltaY = after.getY() - before.getY();
translate(deltaX,deltaY);
Frame1.painter.repaint();
}
These variables will hold the transformed mouse position.
Point2D before = null;
Point2D after = null;
What does that mean?
e.getPoint() gives the PIXEL position of the mouse. We need the "world" coordinates of where the mouse is. If we are hovering over a boxes origin at the position (50,40), we want those values, not the mouse position values.
E.g. if we translated our panel by 10 pixels in the X with 1x zoom/scale, our mouse position would be (40,40) while hovering over the box at (50,40). This becomes more complicated when you are zooming AND translating.
To make it easier to get the 'world' coordinates, we need the inverseTransform() function. To prepare for that, we need to save the current AffineTransform BEFORE we zoom. ('at' is the global AffineTransform in my program)
AffineTransform originalTransform = new AffineTransform(at);
Then we will create another transform but this one is an identity transform, it has not been scaled or translated yet.
AffineTransform zoomedTransform = new AffineTransform();
Once we have these, we can get the 'world' coordinates of the mouse (before zooming). (50,40 as mentioned above)
before = originalTransform.inverseTransform(e.getPoint(), null);
// before.getX() returns 50;
// before.getY() returns 40;
Now we set the scale value based on the mouse scroll event.
zoom *= 1-(e.getPreciseWheelRotation()/5);
/// This line just calls a function to set the variable 'scale'
((Painter) Frame1.painter).setScale(zoom/100);
Now we want to take our NEW identity transform and transform it using the old translation values but with the NEW scale value. (NOTE: This must be performed exactly the same way as in your paint() function.)
zoomedTransform.setToIdentity();
zoomedTransform.translate(getWidth()/2, getHeight()/2);
zoomedTransform.scale(scale, scale);
zoomedTransform.translate(-getWidth()/2, -getHeight()/2);
zoomedTransform.translate(translateX, translateY);
NOTE: Our originalTransform is the transform BEFORE scaling/zooming. The zoomedTransform is after applying our new zoom/scale.
Now that we have zoomed in, lets say our mouse is now over top of a different boxes origin at 20,30. Our mouse position has not changed, all we did was scroll the mouse wheel. What we want to do is take our mouse position and get the 20,30 coordinates. We do that by taking the inverse transform of our NEW transform.
after = zoomedTransform.inverseTransform(e.getPoint(), null);
So, 'before' = (50,40) and 'after' = (20,30) We now want to translate the view by this difference. This will move the box at 50,40 under the mouse position. This will give the effect of zooming into the box at 50,40 instead of just zooming into the center of the screen.
double deltaX = after.getX() - before.getX();
double deltaY = after.getY() - before.getY();
translate(deltaX,deltaY);
Then we repaint.
Frame1.painter.repaint();
For reference, here is the code in my paint function. NOTE how the translation and scaling is applied exactly the same as in the mouseWheel function.
public void paint(Graphics g) {
loadColors();
super.paint(g);
Graphics2D g2D = (Graphics2D) g;
//Set anti-alias!
g2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
// Set anti-alias for text
g2D.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
AffineTransform saveTransform = g2D.getTransform();
g2D.setColor(backgroundColor);
g2D.fillRect(0, 0, getWidth(), getHeight());
at = new AffineTransform(saveTransform);
if(newTruss) {
try {
centerView();
} catch (NoninvertibleTransformException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
newTruss = false;
}
at.translate(getWidth()/2, getHeight()/2);
at.scale(scale, scale);
at.translate(-getWidth()/2, -getHeight()/2);
at.translate(translateX, translateY);
g2D.setTransform(at);
if(truss != null) {
g2D.setColor(Color.WHITE);
//truss.drawBoardsOutline(g2D);
truss.drawBoards(g2D, boardColor, lineColor);
truss.drawBoardsLongitudinalCOG(g2D, pointColor, 3);
truss.drawBoardsCOG(g2D, Color.GREEN, 1);
truss.drawSelectedBoards(g2D, Frame1.boardList, boardColor, Color.RED, lastSelectedBoard);
if (lastSelectedBoard > -1) {
truss.drawBoardPath(g2D, lastSelectedBoard, lastSelectedBoardDir, 50.0f, 7);
//System.out.println(Frame1.placementList);
}
//truss.drawBoardPaths(g2D, 50.0f, 7);
}
}
Upvotes: 0
Reputation: 535
I solved the problem by implementing what is being described here
Here is the updated code:
public class MyPanel extends JPanel {
...
private double zoom = 1;
private int zoomPointX;
private int zoomPointY;
...
class CustomMouseWheelListener implements MouseWheelListener {
@Override
public void mouseWheelMoved(MouseWheelEvent e) {
zoomPointX = e.getX();
zoomPointY = e.getY();
if (e.getPreciseWheelRotation() < 0) {
zoom -= 0.1;
} else {
zoom += 0.1;
}
if (zoom < 0.01) {
zoom = 0.01;
}
repaint();
}
}
...
protected void paintComponent(Graphics g) {
Graphics2D g2D = (Graphics2D) g;
super.paintComponent(g2D);
AffineTransform at = g2D.getTransform();
at.translate(zoomPointX, zoomPointY);
at.scale(zoom, zoom);
at.translate(-zoomPointX, -zoomPointY);
g2D.setTransform(at);
a_different_class_where_i_do_some_drawing.draw(g2D);
}
}
Upvotes: 4