How to pan an image using your mouse in Java Swing
Asked Answered
F

3

6

I am creating a Java app that will allow users to view images and to pan the image using their mouse. To implement the panning of the image I use a combination of mouseClicked and mouseDragged events using JViewports. The bulk of the code is in the mouseDragged method

public void mouseDragged(MouseEvent e, WindowWrapper w) {
    final JViewport vp = someFieldViewPort;

    //Getting the point that the mouse is dragged to to
    Point cp = e.getPoint();
    final Point vPoint = vp.getViewPosition();

    //I found the image went off the content to show the white border so I included this 
    // Here pp is a field that I sent when the mouse is clicked in a separate method

    if(vPoint.getX()+pp.x-cp.x>=0 & vPoint.getY()+pp.y-cp.y>=0)
        vPoint.translate(pp.x-cp.x, pp.y-cp.y);
    else if(vPoint.getX()+pp.x-cp.x>=0 & vPoint.getY()+pp.y-cp.y<0)
        vPoint.translate(pp.x-cp.x, (int) -vPoint.getY());
    else if(vPoint.getX()+pp.x-cp.x<0 & vPoint.getY()+pp.y-cp.y>=0)
        vPoint.translate((int) -vPoint.getX(), pp.y-cp.y);

    //finally set the position of the viewport
    vp.setViewPosition(vPoint);
    vp.repaint();
}

While this works I feel that there must be an easier way to do all of this. If not all of it, could the code to prevent the viewport going off the image to the surrounding border be replaced?

Furman answered 12/11, 2012 at 10:16 Comment(2)
hmm ... don't quite understand your requirement: you want to achieve something like scrolling-by-mouseDrag?Doxology
strongly suggest you follow the path you started (see the answer by @aterai to follow it to its end :-) - Swing has full-blown scrolling support, re-using it is the way to go.Doxology
D
9

Try using scrollRectToVisible(...) method instead of JViewport#setViewPosition(...):

import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import javax.swing.*;
public class HandScrollDemo {
  static class HandScrollListener extends MouseAdapter {
    private final Point pp = new Point();
    @Override public void mouseDragged(MouseEvent e) {
      JViewport vport = (JViewport)e.getSource();
      JComponent label = (JComponent)vport.getView();
      Point cp = e.getPoint();
      Point vp = vport.getViewPosition();
      vp.translate(pp.x-cp.x, pp.y-cp.y);
      label.scrollRectToVisible(new Rectangle(vp, vport.getSize()));
      //vport.setViewPosition(vp);
      pp.setLocation(cp);
    }
    @Override public void mousePressed(MouseEvent e) {
      pp.setLocation(e.getPoint());
    }
  }
  public JComponent makeUI() {
    JLabel label = new JLabel(new Icon() {
      TexturePaint TEXTURE = makeCheckerTexture();
      @Override public void paintIcon(Component c, Graphics g, int x, int y) {
        Graphics2D g2 = (Graphics2D)g.create();
        g2.setPaint(TEXTURE);
        g2.fillRect(x,y,c.getWidth(),c.getHeight());
        g2.dispose();
      }
      @Override public int getIconWidth()  { return 2000; }
      @Override public int getIconHeight() { return 2000; }
    });
    label.setBorder(BorderFactory.createLineBorder(Color.RED, 20));
    JScrollPane scroll = new JScrollPane(label);
    JViewport vport = scroll.getViewport();
    MouseAdapter ma = new HandScrollListener();
    vport.addMouseMotionListener(ma);
    vport.addMouseListener(ma);
    return scroll;
  }
  private static TexturePaint makeCheckerTexture() {
    int cs = 20;
    int sz = cs*cs;
    BufferedImage img = new BufferedImage(sz,sz,BufferedImage.TYPE_INT_ARGB);
    Graphics2D g2 = img.createGraphics();
    g2.setPaint(Color.GRAY);
    for(int i=0; i*cs<sz; i++) { for(int j=0; j*cs<sz; j++) {
      if((i+j)%2==0) { g2.fillRect(i*cs, j*cs, cs, cs); }
    }}
    g2.dispose();
    return new TexturePaint(img, new Rectangle(0,0,sz,sz));
  }
  public static void main(String[] args) {
    EventQueue.invokeLater(new Runnable() {
      @Override public void run() { createAndShowGUI(); }
    });
  }
  public static void createAndShowGUI() {
    JFrame f = new JFrame();
    f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    f.getContentPane().add(new HandScrollDemo().makeUI());
    f.setSize(320, 320);
    f.setLocationRelativeTo(null);
    f.setVisible(true);
  }
}
Dayna answered 12/11, 2012 at 13:11 Comment(6)
+1 for re-using existing scrolling support :-) Personally, would lean to use setValue on the scrollBars, but that's marginality.Doxology
I've tried to implement this by replacing scrollRectToVisible(...) method instead of JViewport#setViewPosition(...) as in the code above. The result was an image that would scroll but got stuck on the bottom edge and was very sensitive to scrolling.Furman
@medPhys-pl but got stuck on the bottom edge what do mean by that? Isn't that the whole point, not allowing the scroll beyond the max scroll range? Sounds like time for an SSCCE.Doxology
@Doxology Yes it didn't allow it to go off screen but I couldn't scroll back in the opposite direction.Furman
@medPhys-pl maybe something wrong elsewhere in your code, can't reproduce any problem with this example (taking the pic from the other answer). To clarify, please add an SSCCE to your question - and on that explain what/not you want to achieve.Doxology
My mistake. I was using the method scrollRectToVisible() on the viewPort instead of the view. Switched it and it works now. This allows me to get rid of the code doing all the checking if it is outside the image. Excellent.Furman
I
3

I would do it in a different way. I would probably define an object called Image or similar. It would define a BufferedImage and two int values: x and y.

The Image object would also have a draw() method that would just know how to draw an image to a Graphics2D object at the x, y location.

On mouse events, I would modify the x and y values inside the Image object and under the paint of the component I would call image.draw(g2).

Indescribable answered 12/11, 2012 at 10:22 Comment(1)
So you wouldn't use a JViewport?Furman
S
3

+1 to @Dans answer.

Here is an example I made, basically uses JPanel with added MouseAdapter and overrides mousePressed() and mouseDragged() methods. mouseDragged() method will increment x and y co-ordinates of image accordingly and will be drawn via paintComponent(...) of JPanel and Graphics2D#drawImage(Image img,int x,int y,ImageObserver io).

Before click and drag of mouse:

enter image description here

After click and drag of mouse:

enter image description here

//necessary imports
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.image.BufferedImage;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Test {

    /**
     * Default constructor
     */
    public Test() {
        initComponents();
    }

    /**
     * Initialize GUI and components (including ActionListeners etc)
     */
    private void initComponents() {
        JFrame frame = new JFrame("Test");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setResizable(false);

        PanPanel pp = null;
        try {
            pp = new PanPanel(ImageIO.read(new URL("http://www.sellcar.co.za/wp-content/uploads/2011/01/Porsche_911_Turbo.jpg")));
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        frame.add(pp);
        //pack frame (size JFrame to match preferred sizes of added components and set visible
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) {

        /**
         * Create GUI and components on Event-Dispatch-Thread
         */
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    //set nimbus look and feel
                    for (UIManager.LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
                        if ("Nimbus".equals(info.getName())) {
                            UIManager.setLookAndFeel(info.getClassName());
                            break;
                        }
                    }
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException e) {
                    e.printStackTrace();
                }
                //create GUI instance
                Test test = new Test();
            }
        });
    }
}

class PanPanel extends JPanel {

    private int x, y;
    private int width = 400, height = 400;
    BufferedImage img;
    private final static RenderingHints textRenderHints = new RenderingHints(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
    private final static RenderingHints imageRenderHints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    private final static RenderingHints renderHints = new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
    static int startX, startY;

    public PanPanel(BufferedImage img) {
        x = 0;
        y = 0;
        this.img = img;

        addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent me) {
                super.mousePressed(me);
                startX = me.getX();
                startY = me.getY();
            }
        });

        addMouseMotionListener(new MouseMotionAdapter() {
            @Override
            public void mouseDragged(MouseEvent me) {
                super.mouseDragged(me);

                if (me.getX() < startX) {//moving image to right
                    x -= 2;
                } else if (me.getX() > startX) {//moving image to left
                    x += 2;
                }

                if (me.getY() < startY) {//moving image up
                    y -= 2;
                } else if (me.getY() > startY) {//moving image to down
                    y += 2;
                }
                repaint();
            }
        });
    }

    @Override
    protected void paintComponent(Graphics grphcs) {
        super.paintComponent(grphcs);
        Graphics2D g2d = (Graphics2D) grphcs;

        //turn on some nice effects
        applyRenderHints(g2d);

        g2d.drawImage(img, x, y, null);
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(width, height);
    }

    public static void applyRenderHints(Graphics2D g2d) {
        g2d.setRenderingHints(textRenderHints);
        g2d.setRenderingHints(imageRenderHints);
        g2d.setRenderingHints(renderHints);
    }
}
Scots answered 12/11, 2012 at 13:48 Comment(10)
@Dan actually ... I disagree with re-inventing the wheel ;-) Setting the icon to a label, throw it into a scrollpane and re-use the scrolling api (either scrollRectTo as in aterai's answer or scrollBar.setValue in mine - not posted <g> ) is just fine.Doxology
Have you ever met a user that doesn't want to hear about scroll bars when dealing with images? Pan and mouse wheel zoom (or even pinch zoom on mobile devices) is what they want, not the old scroll bars.Indescribable
@Doxology If this is re-inventing the wheel, the wheel must have been really small ;). And +1 to Dans comment I agree.Scots
@mKorbel who can resist up voting a post with a Porsche as its content?! :PScots
@Dan no reason to not re-use their functionality (simply hide them)Doxology
@David Kroukamp (who can resist up voting a post with a Porsche as its content?!) me f.e. and this isn't Porsche, this is Porsche CarreraBedford
Finally (after running) decided to -1, mainly for not solving the problem. The point (as I perceived it ;-) was to limit the pan to the boundaries of the parent container. This here allows free-style positioning everwhere, even dragging out-off the parent. Plus the logic is incorrect (dragging is sticky relative to the initial pressed). Re-invent has negative weigth (independent of wheel size) ...;-)Doxology
I someone's opinions, anything that is not exactly how they see it means re-inventing which is wrong, of course.Indescribable
@Dan assuming that someone would be me ;-) The main point is that here the panning is not working correctly. Which demonstrates one price to pay for re-inventing, you'll have to do all the ground testing again. That said, could well be that I don't fully understand the OP's requirement, waiting for clarification.Doxology
No worries. Thanks for down-voting anyone that has another ideas than the ones you have.Indescribable

© 2022 - 2024 — McMap. All rights reserved.