my goal is to implement an image viewer, where one can zoom to the mouse position and the JScrollPane correctly response to this zoom gesture. Also the possibility to roate the image would be nice.
I'm thrilled about Affine Transformation and the the smooth zooming described in this post.
So I implemented this affine transformation together with the feature to rotate the image with F7 and F8. (Zoom with control+mouse wheel)
But unfortunately the JScrollPane does not correctly adjust to the zooming and scrolling with the scrollbars leads to a distortion of the image.
I found that the reason for this distortion is the following line in the paintComponent Method:
g2.setTransform(cordTransform); Since removing this line leads to a normal scrolling, but then the zooming is not possible.
Also I tried to use the setView method to correct the alignment of the JScrollPane, but this did also not work properly.
I appreciate any help provided.
Here's my code:
package org.example;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.event.MouseWheelEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.*;
public class ZoomAndPan1 extends JPanel {
BufferedImage img;
private boolean init = true;
private static final double ZOOM_MULTIPLICATION_FACTOR = 1.2;
private AffineTransform cordTransform = new AffineTransform();
private double scale = 1.0;
public ZoomAndPan1() throws IOException {
this.addMouseWheelListener(e -> {
if (e.isControlDown()) {
zoom(e);
}
});
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
int x = (int) (this.getWidth() - (img.getWidth() * scale)) / 2;
int y = (int) (this.getHeight() - (img.getHeight() * scale)) / 2;
AffineTransform at = new AffineTransform();
at.translate(x, y);
at.scale(scale, scale);
if (init) {
g2.setTransform(at);
init = false;
cordTransform = g2.getTransform();
} else {
g2.setTransform(cordTransform);
}
g2.drawImage(img, 0, 0, this);
g2.dispose();
}
private void rotate(int e) {
double centerX = (double) img.getWidth() / 2;
double centerY = (double) img.getHeight() / 2;
cordTransform.translate(centerX, centerY);
if (e > 0) {
cordTransform.quadrantRotate(3);
} else {
cordTransform.quadrantRotate(1);
}
cordTransform.translate(-centerX, -centerY);
repaint();
}
private void zoom(MouseWheelEvent e) {
try {
int wheelRotation = e.getWheelRotation();
Point p = e.getPoint();
Point2D p1 = transformPoint(p);
if (wheelRotation > 0) {
scale *= 1 / ZOOM_MULTIPLICATION_FACTOR;
cordTransform.scale(1 / ZOOM_MULTIPLICATION_FACTOR, 1 / ZOOM_MULTIPLICATION_FACTOR);
} else {
scale *= ZOOM_MULTIPLICATION_FACTOR;
cordTransform.scale(ZOOM_MULTIPLICATION_FACTOR, ZOOM_MULTIPLICATION_FACTOR);
}
Point2D p2 = transformPoint(p);
cordTransform.translate(p2.getX() - p1.getX(), p2.getY() - p1.getY());
revalidate();
repaint();
} catch (NoninvertibleTransformException ex) {
ex.printStackTrace();
}
}
@Override
public Dimension getPreferredSize() {
int w = (int) (scale * img.getWidth());
int h = (int) (scale * img.getHeight());
return new Dimension(w, h);
}
private Point2D.Float transformPoint(Point p1) throws NoninvertibleTransformException {
AffineTransform inverse = cordTransform.createInverse();
Point2D.Float p2 = new Point2D.Float();
inverse.transform(p1, p2);
return p2;
}
public void setImg(BufferedImage img) {
this.img = img;
}
public static void main(String[] args) {
try {
BufferedImage img = ImageIO.read(new File("src/main/resources/car.jpg"));
MapScale mapScale = new MapScale(img);
JFrame frame = new JFrame("Zoom and Pan ");
Dimension d = new Dimension(1920,1080);
frame.setSize(d);
frame.setPreferredSize(d);
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
ZoomAndPan1 zoomAndPan1 = new ZoomAndPan1();
zoomAndPan1.setImg(img);
JScrollPane scrollPane = new JScrollPane(mapScale);
scrollPane.setViewportView(zoomAndPan1);
frame.add(scrollPane, BorderLayout.CENTER);
frame.pack();
frame.setVisible(true);
frame.addKeyListener(new KeyListener() {
@Override
public void keyTyped(KeyEvent e) {
// NOP
}
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_F7) {
zoomAndPan1.rotate(1);
}
if (e.getKeyCode() == KeyEvent.VK_F8) {
zoomAndPan1.rotate(-1);
}
}
@Override
public void keyReleased(KeyEvent e) {
// NOP
}
});
} catch (IOException ex) {
Logger.getLogger(ZoomAndPan1.class.getName()).log(Level.SEVERE, null, ex);
}
}
}