0

I am making an Image viewer application for my own interest. I am using JPanel to display a bufferedImage.

Objective - I want to manipulate the image and give user the options to zoom, pan, flip or rotate images.

I am now able to zoom, pan and rotate. But the problem is that it is not the same as what you see when you open a .pdf file in Microsoft Edge.

I have added the code below:

import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionAdapter;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
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 int zoomLevel = 0;
    private int minZoomLevel = -20;
    private int maxZoomLevel = 10;
    private double zoomMultiplicationFactor = 1.2;

    private Point dragStartScreen;
    private Point dragEndScreen;
    private AffineTransform coordTransform = new AffineTransform();

    public ZoomAndPan1() throws IOException {
        this.addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                dragStartScreen = e.getPoint();
                dragEndScreen = null;
            }
        });
        this.addMouseMotionListener(new MouseMotionAdapter() {
            @Override
            public void mouseDragged(MouseEvent e) {
                pan(e);
            }
        });
        this.addMouseWheelListener(new MouseWheelListener() {
            @Override
            public void mouseWheelMoved(MouseWheelEvent e) {
                if (e.isControlDown()) {
                    zoom(e);
                }
            }
        });
        img = ImageIO.read(new File("file.path"));
    }

    @Override
    protected void paintComponent(Graphics g) {

        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D) g;
        int x = (int) (this.size().getWidth() - (img.getWidth() * .2)) / 2;
        int y = (int) (this.size().getHeight() - (img.getHeight() * .2)) / 2;

        AffineTransform at = new AffineTransform();
        at.translate(x, y);
        at.scale(.2, .2);
        if (init) {
            g2.setTransform(at);
            init = false;
            coordTransform = g2.getTransform();
        } else {
            g2.setTransform(coordTransform);
        }

        g2.drawImage(img, 0, 0, this);

        g2.dispose();
    }

    private void pan(MouseEvent e) {
        try {
            dragEndScreen = e.getPoint();
            Point2D.Float dragStart = transformPoint(dragStartScreen);
            Point2D.Float dragEnd = transformPoint(dragEndScreen);
            double dx = dragEnd.getX() - dragStart.getX();
            double dy = dragEnd.getY() - dragStart.getY();
            coordTransform.translate(dx, dy);
            dragStartScreen = dragEndScreen;
            dragEndScreen = null;
            repaint();
        } catch (NoninvertibleTransformException ex) {
            ex.printStackTrace();
        }
    }

    private void zoom(MouseWheelEvent e) {
        try {
            int wheelRotation = e.getWheelRotation();
            Point p = e.getPoint();
            if (wheelRotation > 0) {
                if (zoomLevel < maxZoomLevel) {
                    zoomLevel++;
                    Point2D p1 = transformPoint(p);
                    coordTransform.scale(1 / zoomMultiplicationFactor, 1 / zoomMultiplicationFactor);
                    Point2D p2 = transformPoint(p);
                    coordTransform.translate(p2.getX() - p1.getX(), p2.getY() - p1.getY());
                    repaint();
                }
            } else {
                if (zoomLevel > minZoomLevel) {
                    zoomLevel--;
                    Point2D p1 = transformPoint(p);
                    coordTransform.scale(zoomMultiplicationFactor, zoomMultiplicationFactor);
                    Point2D p2 = transformPoint(p);
                    coordTransform.translate(p2.getX() - p1.getX(), p2.getY() - p1.getY());
                    repaint();
                }
            }
        } catch (NoninvertibleTransformException ex) {
            ex.printStackTrace();
        }
    }

    private Point2D.Float transformPoint(Point p1) throws NoninvertibleTransformException {
        AffineTransform inverse = coordTransform.createInverse();
        Point2D.Float p2 = new Point2D.Float();
        inverse.transform(p1, p2);
        return p2;
    }

    public Dimension getPreferredSize() {
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
        return new Dimension(screenSize.width, screenSize.height);
    }

    public static void main(String[] args) {
        try {
            JFrame frame = new JFrame("Zoom and Pan ");
            frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
            frame.add(new ZoomAndPan1(), BorderLayout.CENTER);
            frame.pack();
            frame.setVisible(true);
        } catch (IOException ex) {
            Logger.getLogger(ZoomAndPan1.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}

This code can do following things :

  1. Zoom in/out on CTRL + mouse scroll

  2. Pan image on mouse drag.

Limitations :

  1. Return to center when zoomed out.

  2. Pan is not restricted with in the Jpanel or viewport boundaries.

I want to achieve this :

enter image description here

Unfortunately, the code does like this :

enter image description here

How to solve this ?

Thank you in advance..

  • Can you explain more about what is happening now and what you are expecting to happen? – Sam Orozco Sep 20 '19 at 03:30
  • Have you seen the gif I attached? https://i.stack.imgur.com/iHAhr.gif –  Sep 20 '19 at 03:31
  • I want to pan the image but only inside the `jpanel` or window boundaries... Please try to run my code –  Sep 20 '19 at 03:32
  • OK that's what you want to achieve what is it doing now? – Sam Orozco Sep 20 '19 at 03:33
  • This is really i want to achieve: 1. Zoom the image with in the mouse point. (done) 2. Pan image using mouse drag (done).. but when I pan the image up to the outside it will disappear on the screen. –  Sep 20 '19 at 03:35
  • This what my code does. https://i.stack.imgur.com/MoiBa.gif –  Sep 20 '19 at 03:42
  • In order for me to run your code, I need an image file in order to create a `BufferedImage`. Can I use any image file? – Abra Sep 20 '19 at 03:50
  • Yes sure.. you can use any image file (i prefer JPEG file)... –  Sep 20 '19 at 03:54
  • [How to Use Scroll Panes](https://docs.oracle.com/javase/tutorial/uiswing/components/scrollpane.html) has an example that I think solves your image panning requirement. As in that example, I think putting your image on a `JLabel` and making that `JLabel` the scrollable client for a `JScrollPane` is the way to go, rather than putting your image on a `JPanel`. – Abra Sep 20 '19 at 04:48
  • How can I implement it to my code –  Sep 20 '19 at 05:03
  • How does your question relate to [tag:pdfbox]? You tagged it so... – mkl Sep 20 '19 at 05:27
  • I am using the PDFBox library to convert pdf to images. –  Sep 20 '19 at 06:32
  • I have removed it. PDFBox has almost nothing to do with it (except maybe zoom but that is just a simple parameter), it is just the producer of the images. – Tilman Hausherr Sep 20 '19 at 06:55
  • *"you can use any image file"* One way to get image(s) for an example is to **hot link** to images seen in [this Q&A](http://stackoverflow.com/q/19209650/418556). E.G. [This answer](https://stackoverflow.com/a/10862262/418556) hot links to an image embedded in [this question](https://stackoverflow.com/q/10861852/418556). – Andrew Thompson Sep 22 '19 at 09:42
  • Can someone help me .. I tried the @Abra suggestion but I didnt work with my problem.. –  Sep 22 '19 at 23:47

2 Answers2

1

Here is a simple program that demonstrates what I suggested, namely a large image set as the icon of a JLabel and the JLabel is the scrollable client for a JScrollPane. Note that I set the preferred size of the JScrollPane to 800 x 600 pixels because the default behavior is to make the JScrollPane the same size as its scrollable client. The image dimensions are 2312 x 1536 pixels which means it is larger than my computer screen. So if I don't set the preferred size, when you run the program the JFrame overflows the screen. You don't see all of the JFrame. This is on purpose so you can see how the image scrolls. The URL for the image is https://unsplash.com/photos/l68Z6eF2peA. And here is the code, which is a MCVE

By the way, I downloaded and saved the image in a file that I named worldmap.jpg

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;

import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.WindowConstants;

public class ScrolImg implements Runnable {
    private JFrame  frame;
/* Start 'Runnable' interface methods. */
    public void run() {
        showGui();
    }
/* End 'Runnable' interface methods. */
    private JScrollPane createMainPanel() {
        Icon ico = new ImageIcon("worldmap.jpg");
        JLabel label = new JLabel(ico);
        JScrollPane scrollPane = new JScrollPane(label);
        scrollPane.setPreferredSize(new Dimension(800, 600));
        return scrollPane;
    }

    private void showGui() {
        frame = new JFrame("ScrolImg");
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.add(createMainPanel(), BorderLayout.CENTER);
        frame.pack();
        frame.setLocationByPlatform(true);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        ScrolImg instance = new ScrolImg();
        EventQueue.invokeLater(instance);
    }
}

Hopefully this code will help you complete your project.

Abra
  • 19,142
  • 7
  • 29
  • 41
  • Thank you for your help.After a series of trial and error I was able to control my pan function. Thanks for your response. –  Sep 23 '19 at 23:55
0

Finally I was able to control the pan funtion..

I replaced my pan function with this listener. It applies the same concept as putting the Image in a label. And I cant afford to use labels since I would be writing on top of the image itself so this is what I did.

//map - represents the container or component the image was drawn.
//then I added a ScrollPane on map..


map.setAutoscrolls(true);
add(new JScrollPane(map));

MouseAdapter ma = new MouseAdapter() {
  private Point origin;

  @Override
  public void mousePressed(MouseEvent e) {
      origin = new Point(e.getPoint());
      System.out.println("click");
  }

  @Override
  public void mouseDragged(MouseEvent e) {
      if (origin != null) {
        JViewport viewPort = (JViewport) SwingUtilities.getAncestorOfClass(JViewport.class, map);
        if (viewPort != null) {
              int deltaX = origin.x - e.getX();
              int deltaY = origin.y - e.getY();

              Rectangle view = viewPort.getViewRect();
              view.x += deltaX;
              view.y += deltaY;

              map.scrollRectToVisible(view);

         }
      }                
   }
};