• category: swing folder: ZoomAndPanPanel title: JScrollPane内に配置したJPanelをマウスで拡大、縮小、移動する tags: [JScrollPane, JPanel, AffineTransform, Image, MouseWheelListener] author: aterai pubdate: 2015-06-22T10:03:20+09:00 description: JScrollPane内に配置したJPanelを、マウスホイールを使った拡大縮小と、スクロールバーを使った表示領域の移動が可能になるように設定します。 image: https://lh3.googleusercontent.com/-Um9j8O0t3Kg/VYdMPIUOfwI/AAAAAAAAN7A/LAJ5KRiDdp0/s800/ZoomAndPanPanel.png hreflang:
       href: https://java-swing-tips.blogspot.com/2015/06/an-image-inside-jscrollpane-zooming-by.html
       lang: en

概要

JScrollPane内に配置したJPanelを、マウスホイールを使った拡大縮小と、スクロールバーを使った表示領域の移動が可能になるように設定します。

サンプルコード

class ZoomAndPanePanel extends JPanel {
  private final AffineTransform zoomTransform = new AffineTransform();
  private final transient Image img;
  private final Rectangle imgrect;
  private transient ZoomHandler handler;
  private transient DragScrollListener listener;

  protected ZoomAndPanePanel(Image img) {
    super();
    this.img = img;
    this.imgrect = new Rectangle(img.getWidth(this), img.getHeight(this));
  }
  @Override protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    Graphics2D g2 = (Graphics2D) g.create();
    g2.setPaint(new Color(0x55FF0000, true));
    Rectangle r = new Rectangle(500, 140, 150, 150);

    // use: AffineTransform#concatenate(...) and Graphics2D#setTransform(...)
    // AffineTransform at = g2.getTransform();
    // at.concatenate(zoomTransform);
    // g2.setTransform(at);
    // g2.drawImage(img, 0, 0, this);
    // g2.fill(r);

    // or use: Graphics2D#drawImage(Image, AffineTransform, ImageObserver)
    g2.drawImage(img, zoomTransform, this);
    // or: g2.drawRenderedImage((RenderedImage) img, zoomTransform);
    g2.fill(zoomTransform.createTransformedShape(r));

    // BAD EXAMPLE
    // g2.setTransform(zoomTransform);
    // g2.drawImage(img, 0, 0, this);

    g2.dispose();
  }
  @Override public Dimension getPreferredSize() {
    Rectangle r = zoomTransform.createTransformedShape(imgrect).getBounds();
    return new Dimension(r.width, r.height);
  }
  @Override public void updateUI() {
    removeMouseListener(listener);
    removeMouseMotionListener(listener);
    removeMouseWheelListener(handler);
    super.updateUI();
    listener = new DragScrollListener();
    addMouseListener(listener);
    addMouseMotionListener(listener);
    handler = new ZoomHandler();
    addMouseWheelListener(handler);
  }

  protected class ZoomHandler extends MouseAdapter {
    private static final double ZOOM_MULTIPLICATION_FACTOR = 1.2;
    private static final int MIN_ZOOM = -10;
    private static final int MAX_ZOOM = 10;
    private static final int EXTENT = 1;
    private final BoundedRangeModel zoomRange = new DefaultBoundedRangeModel(
        0, EXTENT, MIN_ZOOM, MAX_ZOOM + EXTENT);
    @Override public void mouseWheelMoved(MouseWheelEvent e) {
      int dir = e.getWheelRotation();
      int z = zoomRange.getValue();
      zoomRange.setValue(z + EXTENT * (dir > 0 ? -1 : 1));
      if (z != zoomRange.getValue()) {
        Component c = e.getComponent();
        Container p = SwingUtilities.getAncestorOfClass(JViewport.class, c);
        if (p instanceof JViewport) {
          JViewport vport = (JViewport) p;
          Rectangle ovr = vport.getViewRect();
          double s = dir > 0 ? 1d / ZOOM_MULTIPLICATION_FACTOR
                             : ZOOM_MULTIPLICATION_FACTOR;
          zoomTransform.scale(s, s);
          // double s = 1d + zoomRange.getValue() * .1;
          // zoomTransform.setToScale(s, s);
          Rectangle nvr = AffineTransform.getScaleInstance(s, s)
                                         .createTransformedShape(ovr)
                                         .getBounds();
          Point vp = nvr.getLocation();
          vp.translate((nvr.width - ovr.width) / 2, (nvr.height - ovr.height) / 2);
          vport.setViewPosition(vp);
          c.revalidate();
          c.repaint();
        }
      }
    }
  }
}
View in GitHub: Java, Kotlin

解説

上記のサンプルでは、JPanel#getPreferredSize()を拡大後の画像サイズを返すようにオーバーライドすることで、画像がJViewportより大きくなる場合はスクロールバーが表示されるように設定しています。


ズーム自体はJPanelに表示した画像のズームとスクロールで使用しているものとほぼ同じMouseWheelListenerを使用していますが、画像を描画しているJPanelJScrollPane内に設定してスクロールバーでのスクロールを可能にしているため、JPanel#paintComponent(...)内でのAffineTransformの適用方法などを変更しています。

  • JPanelに表示した画像のズームとスクロールのようにズームを行うためのAffineTransform(このサンプルではzoomTransform)を直接Graphics2Dに設定すると、元からあるGraphics2Dコンテキスト内のAffineTransform(JScrollBarによる移動)と競合して描画が乱れてしまう
    // BAD EXAMPLE
    g2.setTransform(zoomTransform);
    g2.drawImage(img, 0, 0, this);
    
  • 2つのAffineTransformAffineTransform#concatenate(AffineTransform)で連結してから、Graphics2D#setTransform(AffineTransform)で設定することで回避
    AffineTransform at = g2.getTransform();
    at.concatenate(zoomTransform);
    g2.setTransform(at);
    g2.drawImage(img, 0, 0, this);
    

参考リンク

コメント