• 追加された行はこの色です。
  • 削除された行はこの色です。
---
category: swing
folder: ZoomingAndPanning
title: JPanelに表示した画像のズームとスクロール
tags: [AffineTransform, Graphics, JPanel, MouseListener, MouseMotionListener, MouseWheelListener]
author: aterai
pubdate: 2014-04-21T00:28:49+09:00
description: JPanelに表示した画像にAffineTransformによる変換を適用して、マウスを使った拡大・縮小・移動を実行します。
image: https://lh3.googleusercontent.com/-c5Y9hCoRQbU/U1PhhnitgFI/AAAAAAAACD0/ZXIcyPywcr0/s800/ZoomingAndPanning.png
---
* 概要 [#n09fa895]
`JPanel`に表示した画像に`AffineTransform`による変換を適用して、マウスを使った拡大・縮小・移動を実行します。[https://community.oracle.com/thread/1263955 How to implement Zoom & Pan in Java using Graphics2D]に投稿されているコードを参考・引用しています。
* 概要 [#summary]
`JPanel`に表示した画像に`AffineTransform`による変換を適用して、マウスを使った拡大・縮小・移動を実行します。

#download(https://lh3.googleusercontent.com/-c5Y9hCoRQbU/U1PhhnitgFI/AAAAAAAACD0/ZXIcyPywcr0/s800/ZoomingAndPanning.png)

* サンプルコード [#t414a62d]
* サンプルコード [#sourcecode]
#code(link){{
class ZoomAndPanHandler extends MouseAdapter {
  private static final double ZOOM_MULTIPLICATION_FACTOR = 1.2;
  private static final double ZOOM_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);
  private final AffineTransform coordTransform = new AffineTransform();
  private final Point dragStartPoint = new Point();
      0, EXTENT, MIN_ZOOM, MAX_ZOOM + EXTENT);
  private final AffineTransform coordAndZoomAtf = new AffineTransform();
  private final Point2D dragStartPoint = new Point();

  @Override public void mousePressed(MouseEvent e) {
    dragStartPoint.setLocation(e.getPoint());
  }

  @Override public void mouseDragged(MouseEvent e) {
    Point dragEndPoint = e.getPoint();
    Point dragStart = transformPoint(dragStartPoint);
    Point dragEnd   = transformPoint(dragEndPoint);
    coordTransform.translate(dragEnd.x - dragStart.x, dragEnd.y - dragStart.y);
    Point2D dragEndPoint = e.getPoint();
    Point2D dragStart = transformPoint(dragStartPoint);
    Point2D dragEnd = transformPoint(dragEndPoint);
    coordAndZoomAtf.translate(
        dragEnd.getX() - dragStart.getX(), dragEnd.getY() - dragStart.getY());
    dragStartPoint.setLocation(dragEndPoint);
    e.getComponent().repaint();
  }

  @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()) {
      return;
    if (z != zoomRange.getValue()) {
      double scale = dir > 0 ? 1d / ZOOM_FACTOR : ZOOM_FACTOR;
      Point2D pt;
      if (e.isControlDown()) {
        Rectangle r = e.getComponent().getBounds();
        pt = new Point2D.Double(r.getCenterX(), r.getCenterY());
      } else {
        pt = transformPoint(e.getPoint());
      }
      coordAndZoomAtf.translate(pt.getX(), pt.getY());
      coordAndZoomAtf.scale(scale, scale);
      coordAndZoomAtf.translate(-pt.getX(), -pt.getY());
      e.getComponent().repaint();
    }
    Component c = e.getComponent();
    Rectangle r = c.getBounds();
    //Point p = e.getPoint();
    Point p = new Point(r.x + r.width / 2, r.y + r.height / 2);
    Point p1 = transformPoint(p);
    double scale = dir > 0 ? 1 / ZOOM_MULTIPLICATION_FACTOR
                           : ZOOM_MULTIPLICATION_FACTOR;
    coordTransform.scale(scale, scale);
    Point p2 = transformPoint(p);
    coordTransform.translate(p2.getX() - p1.getX(), p2.getY() - p1.getY());
    c.repaint();
  }
  //https://community.oracle.com/thread/1263955
  //How to implement Zoom & Pan in Java using Graphics2D
  private Point transformPoint(Point p1) {
    Point p2 = new Point();
    try {
      AffineTransform inverse = coordTransform.createInverse();
      inverse.transform(p1, p2);
    } catch (NoninvertibleTransformException ex) {
      ex.printStackTrace();

  // https://community.oracle.com/thread/1263955
  // How to implement Zoom & Pan in Java using Graphics2D
  private Point2D transformPoint(Point2D p1) {
    AffineTransform inverse = coordAndZoomAtf;
    boolean hasInverse = coordAndZoomAtf.getDeterminant() != 0d;
    if (hasInverse) {
      try {
        inverse = coordAndZoomAtf.createInverse();
      } catch (NoninvertibleTransformException ex) {
        // should never happen
        assert false;
      }
    }
    Point2D p2 = new Point();
    inverse.transform(p1, p2);
    return p2;
  }
  public AffineTransform getCoordTransform() {
    return coordTransform;

  public AffineTransform getCoordAndZoomTransform() {
    return coordAndZoomAtf;
  }
}
}}

* 解説 [#v5dc7b5e]
* 解説 [#explanation]
`AffineTransform#createInverse()`で取得した`AffineTransform`オブジェクトでマウス位置の逆変換を行い、現在表示されている倍率でのズームの中心や移動距離などを計算しています。

- ズーム
-- ホイール上回転で%%マウスの位置を中心にして%%拡大
-- ホイール下回転で%%マウスの位置を中心にして%%縮小
-- ホイール上回転で拡大
-- ホイール下回転で縮小
- スクロール
-- マウスドラッグで移動

* 参考リンク [#x2408272]
* 参考リンク [#reference]
- [https://community.oracle.com/thread/1263955 How to implement Zoom & Pan in Java using Graphics2D]
- [http://codezine.jp/article/detail/174 ズームとパンの機能を備えたドローソフトを作成する:CodeZine]
- [http://sozai-free.com/ 2000ピクセル以上のフリー写真素材集]
- [http://tech.nitoyon.com/ja/blog/2013/12/13/touch-viewer/ タッチ操作に対応した画像ビューワーをJavaScriptで作るならD3.jsが便利 - てっく煮ブログ]
- [http://d.hatena.ne.jp/torutk/20140415/p1 CanvasでAffine変換で大いにはまる(数学的センスが足りなかった・・・) - torutkの日記]
- [https://torutk.hatenablog.jp/entry/20140415/p1 CanvasでAffine変換で大いにはまる(数学的センスが足りなかった・・・) - torutkのブログ]
- [[JScrollPane内に配置したJPanelをマウスで拡大、縮小、移動する>Swing/ZoomAndPanPanel]]
- [https://medium.com/@benjamin.botto/zooming-at-the-mouse-coordinates-with-affine-transformations-86e7312fd50b Zooming at the Mouse Coordinates with Affine Transformations | by Ben Botto | Medium]

* コメント [#y2de5eb2]
* コメント [#comment]
#comment
- 表示画面の中心を 基準に拡大縮小するように変更。 -- &user(aterai); &new{2014-04-21 (月) 14:55:12};
- 表示画面の中心を基準に拡大縮小するように変更。 -- &user(aterai); &new{2014-04-21 (月) 14:55:12};
- KBD{Ctrl}キーを押しながらホイールスクロールで表示画面の中心、それ以外はマウスカーソルを基準に拡大縮小するように変更。 -- &user(aterai); &new{2022-12-31 (土) 21:05:12};

#comment