Swing/ZoomAndPanPanel の変更点
- 追加された行はこの色です。
- 削除された行はこの色です。
- Swing/ZoomAndPanPanel へ行く。
- Swing/ZoomAndPanPanel の差分を削除
--- 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] `JScrollPane`内に配置した`JPanel`をマウスホイールを使った拡大縮小とスクロールバーを使った表示領域の移動が可能になるように設定します。 #download(https://lh3.googleusercontent.com/-Um9j8O0t3Kg/VYdMPIUOfwI/AAAAAAAAN7A/LAJ5KRiDdp0/s800/ZoomAndPanPanel.png) * サンプルコード [#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(); } } } } } }} * 解説 [#explanation] 上記のサンプルでは、`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) (Java Platform SE 8)]を使用し、`Graphics2D`コンテキスト内の`AffineTransform`が適用される前にイメージにズーム変換を適用しておくことで回避 - または[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] - [[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 - %%画像上でのマウスドラッグによる移動のあとで、スクロールバーを使用した移動を行うと表示位置がおかしくなるバグがある。%% -- &user(aterai); &new{2015-06-22 (月) 17:57:58}; -- ズームによるビュー移動には`JViewport#setViewPosition(Point)`を使用するように変更し、またマウスドラッグによるスクロールは別のリスナを使用するよう修正。 -- &user(aterai); &new{2015-06-23 (火) 17:57:58}; #comment