Как панорамировать изображение с помощью мыши в Java Swing

Я создаю приложение Java, которое позволит пользователям просматривать изображения и перемещать изображение с помощью мыши. Для реализации панорамирования изображения я использую комбинацию событий mouseClicked и mouseDragged с помощью JViewports. Основная часть кода находится в методе mouseDragged.

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();
}

Хотя это работает, я чувствую, что должен быть более простой способ сделать все это. Если не все это, можно ли заменить код, предотвращающий переход окна просмотра от изображения к окружающей границе?


person Codey McCodeface    schedule 12.11.2012    source источник
comment
хм... не совсем понимаю ваше требование: вы хотите добиться чего-то вроде прокрутки мышью?   -  person kleopatra    schedule 12.11.2012
comment
настоятельно рекомендуем вам следовать по пути, который вы начали (см. ответ @aterai, чтобы пройти его до конца :-) - Swing имеет полноценную поддержку прокрутки, повторное использование это путь.   -  person kleopatra    schedule 12.11.2012


Ответы (3)


Попробуйте использовать метод scrollRectToVisible(...) вместо 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);
  }
}
person aterai    schedule 12.11.2012
comment
+1 за повторное использование существующей поддержки прокрутки :-) Лично я бы предпочел использовать setValue для полос прокрутки, но это маргинальность. - person kleopatra; 12.11.2012
comment
Я попытался реализовать это, заменив метод scrollRectToVisible(...) вместо JViewport#setViewPosition(...), как в приведенном выше коде. В результате получилось изображение, которое прокручивалось, но застревало у нижнего края и было очень чувствительным к прокрутке. - person Codey McCodeface; 12.11.2012
comment
@medPhys-pl но застрял на нижнем краю что это значит? Разве не в этом весь смысл, не позволять прокрутке выходить за пределы максимального диапазона прокрутки? Похоже, пришло время для SSCCE. - person kleopatra; 13.11.2012
comment
@kleopatra Да, это не позволяло ему уйти за пределы экрана, но я не мог прокрутить назад в противоположном направлении. - person Codey McCodeface; 13.11.2012
comment
@medPhys-pl, возможно, что-то не так в вашем коде, не могу воспроизвести проблему в этом примере (взяв картинку из другого ответа). Чтобы уточнить, добавьте SSCCE к своему вопросу - и объясните, чего/не хотите достичь. - person kleopatra; 13.11.2012
comment
Виноват. Я использовал метод scrollRectToVisible() для viewPort вместо представления. Переключил и теперь работает. Это позволяет мне избавиться от кода, выполняющего всю проверку, находится ли он за пределами изображения. Превосходно. - person Codey McCodeface; 13.11.2012

Я бы сделал это по-другому. Я бы, вероятно, определил объект с именем Image или подобным. Он определил бы BufferedImage и два значения int: x и y.

Объект Image также будет иметь метод draw(), который просто знает, как нарисовать изображение для объекта Graphics2D в месте x, y.

В событиях мыши я бы изменил значения x и y внутри объекта Image и под paint компонента, который я бы назвал image.draw(g2).

person Dan D.    schedule 12.11.2012
comment
То есть вы бы не использовали JViewport? - person Codey McCodeface; 12.11.2012

+1 к ответу @Dans.

Вот пример, который я сделал, в основном использует JPanel с добавленными MouseAdapter и переопределяет методы mousePressed() и mouseDragged(). Метод mouseDragged() будет увеличивать x и y координаты изображения соответственно и будет отрисовываться через paintComponent(...) из JPanel и Graphics2D#drawImage(Image img,int x,int y,ImageObserver io).

Перед щелчком и перетаскиванием мыши:

введите здесь описание изображения

После щелчка и перетаскивания мыши:

введите здесь описание изображения

//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);
    }
}
person David Kroukamp    schedule 12.11.2012
comment
@Dan на самом деле ... я не согласен с повторным изобретением колеса ;-) Установите значок на метку, поместите его в область прокрутки и повторно используйте API прокрутки (либо scrollRectTo, как в ответе aterai, либо scrollBar.setValue в моем - не опубликовано ‹g› ) просто отлично. - person kleopatra; 12.11.2012
comment
Вы когда-нибудь встречали пользователя, который не хочет слышать о полосах прокрутки при работе с изображениями? Масштабирование панорамированием и колесиком мыши (или даже масштабирование щипком на мобильных устройствах) — это то, что им нужно, а не старые полосы прокрутки. - person Dan D.; 12.11.2012
comment
@kleopatra Если это новое изобретение колеса, то колесо должно быть очень маленьким;). И +1 к комментарию Дэна, я согласен. - person David Kroukamp; 12.11.2012
comment
@mKorbel, кто может устоять перед голосованием за пост с Porsche в качестве содержания?! :П - person David Kroukamp; 12.11.2012
comment
@Dan Нет причин не использовать их функциональность повторно (просто скрыть их) - person kleopatra; 12.11.2012
comment
@David Kroukamp (кто может устоять перед голосованием за пост с Porsche в качестве его содержания?!) me f.e. и это не Порше, это Порше Каррера - person mKorbel; 12.11.2012
comment
Наконец (после обкатки) решил поставить -1, в основном за то, что не решил проблему. Суть (как я понял ;-) заключалась в том, чтобы ограничить панораму границами родительского контейнера. Это здесь позволяет свободно позиционировать везде, даже перетаскивая родителя. Плюс логика неверная (перетаскивание залипает относительно начального нажатия). Re-invent имеет отрицательный вес (независимо от размера колеса) ... ;-) - person kleopatra; 12.11.2012
comment
Я чьи-то мнения, все, что не совсем так, как они это видят, означает переизобретать, что неправильно, конечно. - person Dan D.; 12.11.2012
comment
@Dan, предполагая, что кто-то будет мной ;-) Суть в том, что здесь панорамирование работает неправильно. Что демонстрирует одну цену, которую нужно заплатить за повторное изобретение, вам придется снова проводить все наземные испытания. Тем не менее, вполне может быть, что я не полностью понимаю требование ОП, ожидая разъяснений. - person kleopatra; 13.11.2012
comment
Не волнуйтесь. Спасибо, что проголосовали против любого, у кого есть идеи, отличные от ваших. - person Dan D.; 13.11.2012