---
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
---
* Summary [#summary]
`JScrollPane`内に配置した`JPanel`をマウスホイールを使った拡大縮小とスクロールバーを使った表示領域の移動が可能になるように設定します。
#download(https://lh3.googleusercontent.com/-Um9j8O0t3Kg/VYdMPIUOfwI/AAAAAAAAN7A/LAJ5KRiDdp0/s800/ZoomAndPanPanel.png)
* Source Code Examples [#sourcecode]
#code(link){{
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(0x55_FF_00_00, 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();
}
}
}
}
}
}}
* Description [#explanation]
* Description [#description]
上記のサンプルでは、`JPanel#getPreferredSize()`を拡大後の画像サイズを返すようにオーバーライドすることで、画像が`JViewport`より大きくなる場合はスクロールバーが表示されるように設定しています。
- マウスホイールによるズーム自体は[[JPanelに表示した画像のズームとスクロール>Swing/ZoomingAndPanning]]で使用しているものとほぼ同じ`MouseWheelListener`を使用
-- 画像を描画している`JPanel`を`JScrollPane`内に設定してスクロールバーでのスクロールを可能にするため`JPanel#paintComponent(...)`内での`AffineTransform`の適用方法などを変更
- [[JPanelに表示した画像のズームとスクロール>Swing/ZoomingAndPanning]]のようにズームを行うための`AffineTransform`(このサンプルでは`zoomTransform`)を直接`Graphics2D`に設定すると、元からある`Graphics2D`コンテキスト内の`AffineTransform`(`JScrollBar`による移動)と競合して描画が乱れてしまう
#code{{
// BAD EXAMPLE
g2.setTransform(zoomTransform);
g2.drawImage(img, 0, 0, this);
}}
- `2`つの`AffineTransform`を`AffineTransform#concatenate(AffineTransform)`で連結してから、`Graphics2D#setTransform(AffineTransform)`で設定することで回避可能
#code{{
AffineTransform at = g2.getTransform();
at.concatenate(zoomTransform);
g2.setTransform(at);
g2.drawImage(img, 0, 0, this);
}}
- または[https://docs.oracle.com/javase/jp/8/docs/api/java/awt/Graphics2D.html#drawImage-java.awt.Image-java.awt.geom.AffineTransform-java.awt.image.ImageObserver- Graphics2D#drawImage(Image, AffineTransform, ImageObserver)]メソッドを使用し、`Graphics2D`コンテキスト内の`AffineTransform`が適用される前にイメージにズーム変換を適用しておくことで回避する方法もある
#code{{
g2.drawImage(img, zoomTransform, this);
}}
* Reference [#reference]
- [[JPanelに表示した画像のズームとスクロール>Swing/ZoomingAndPanning]]
-- マウスホイールによるズーム用のリスナを引用
- [[JScrollPaneのViewportをマウスで掴んでスクロール>Swing/HandScroll]]
-- マウスドラッグによるスクロール用のリスナを引用
- [https://docs.oracle.com/javase/jp/8/docs/api/java/awt/Graphics2D.html#drawImage-java.awt.Image-java.awt.geom.AffineTransform-java.awt.image.ImageObserver- Graphics2D#drawImage(Image, AffineTransform, ImageObserver) (Java Platform SE 8)]
- [http://sozai-free.com/ 2000ピクセル以上のフリー写真素材集]
* Comment [#comment]
#comment
- %%画像上でのマウスドラッグによる移動のあとで、スクロールバーを使用した移動を行うと表示位置がおかしくなるバグがある。%% -- &user(aterai); &new{2015-06-22 (月) 17:57:58};
-- ズームによるビュー移動には`JViewport#setViewPosition(Point)`を使用するように変更し、またマウスドラッグによるスクロールは別のリスナを使用するよう修正。 -- &user(aterai); &new{2015-06-23 (火) 17:57:58};
#comment