Java Graphics2D - 缩放鼠标位置

问题描述 投票:0回答:2

我有一个可以用来画画的 jpanel。我希望能够使用鼠标滚轮进行放大和缩小,但我想缩放到鼠标的位置,以便鼠标下方的点保持不变。我在 stackoverflow 上发现了一些问题,但它们对我不起作用。

通过实现这里描述的内容,我已经非常接近做我想做的事情了。这是我的代码:

public class MyPanel extends JPanel {
...
    private double zoom = 1;
    private double zoom_old = 1;
    private int zoomPointX;
    private int zoomPointY;
...

   class CustomMouseWheelListener implements MouseWheelListener {
        @Override
        public void mouseWheelMoved(MouseWheelEvent e) {
            zoomPointX = e.getX();
            zoomPointY = e.getY();
            if (e.getPreciseWheelRotation() < 0) {
                zoom -= 0.1;
            } else {
                zoom += 0.1;
            }
            if (zoom < 0.01) {
                zoom = 0.01;
            }
            repaint();
        }
    }
...
    protected void paintComponent(Graphics g) {
        Graphics2D g2D = (Graphics2D) g;
        super.paintComponent(g2D);
        if (zoom != zoom_old) {
            double scalechange = zoom - zoom_old;
            zoom_old = zoom;
            double offsetX = -(zoomPointX * scalechange);
            double offsetY = -(zoomPointY * scalechange) ;
            AffineTransform at = new AffineTransform();
            at.scale(zoom, zoom);
            at.translate(offsetX, offsetY);
            g2D.setTransform(at);
        }
        a_different_class_where_i_do_some_drawing.draw(g2D);
    }

}

这几乎达到了我想要的效果。如果我尝试缩放,我会注意到鼠标的位置被考虑在内,因此例如如果我将鼠标放在面板的左侧,它将大致在左侧放大。但是,它不会精确地缩放到鼠标上,因此鼠标下方的点仍然会发生变化。

谁能帮我解决这个问题吗?

编辑: 下面是上面发布的代码所发生情况的图片:我首先将鼠标放在蓝色方块上,然后用鼠标滚轮滚动。正如你所看到的,如果改变鼠标位置:

java swing zooming graphics2d
2个回答
4
投票

我通过实现所描述的内容解决了问题这里

这是更新后的代码:

public class MyPanel extends JPanel {
...
    private double zoom = 1;
    private int zoomPointX;
    private int zoomPointY;
...

   class CustomMouseWheelListener implements MouseWheelListener {
        @Override
        public void mouseWheelMoved(MouseWheelEvent e) {
            zoomPointX = e.getX();
            zoomPointY = e.getY();
            if (e.getPreciseWheelRotation() < 0) {
                zoom -= 0.1;
            } else {
                zoom += 0.1;
            }
            if (zoom < 0.01) {
                zoom = 0.01;
            }
            repaint();
        }
    }
...
    protected void paintComponent(Graphics g) {
        Graphics2D g2D = (Graphics2D) g;
        super.paintComponent(g2D);
        AffineTransform at = g2D.getTransform();
        at.translate(zoomPointX, zoomPointY);
        at.scale(zoom, zoom);
        at.translate(-zoomPointX, -zoomPointY);
        g2D.setTransform(at);
        a_different_class_where_i_do_some_drawing.draw(g2D);
    }

}


0
投票

如果有人好奇如何做到这一点,这是另一个解决方案。我的程序编写方式不同,所以我必须采取不同的方法。

前提有点简单,但有一些陷阱。我将尝试解释下面的代码是如何工作的。您很可能无法复制和粘贴,因此了解其工作原理最为重要。

    public void mouseWheelMoved(MouseWheelEvent e) {
            Point2D before = null;
            Point2D after = null;

            AffineTransform originalTransform = new AffineTransform(at);
            AffineTransform zoomedTransform = new AffineTransform();
            
            try {
                before = originalTransform.inverseTransform(e.getPoint(), null);
            } catch (NoninvertibleTransformException e1) {
                e1.printStackTrace();
            }

            zoom *= 1-(e.getPreciseWheelRotation()/5);
            ((Painter) Frame1.painter).setScale(zoom/100);
            
            zoomedTransform.setToIdentity();
            zoomedTransform.translate(getWidth()/2, getHeight()/2);
            zoomedTransform.scale(scale, scale);
            zoomedTransform.translate(-getWidth()/2, -getHeight()/2);
            zoomedTransform.translate(translateX, translateY);

            try {
                after = zoomedTransform.inverseTransform(e.getPoint(), null);
            } catch (NoninvertibleTransformException e1) {
                e1.printStackTrace();
            }
            
            
            double deltaX = after.getX() - before.getX();
            double deltaY = after.getY() - before.getY();
            translate(deltaX,deltaY);
    
            Frame1.painter.repaint();
}

这些变量将保存变换后的鼠标位置。

Point2D before = null;
Point2D after = null;

这是什么意思?

e.getPoint() 给出鼠标的 PIXEL 位置。我们需要鼠标所在位置的“世界”坐标。如果我们将鼠标悬停在位置 (50,40) 处的框原点上,我们需要这些值,而不是鼠标位置值。

例如如果我们以 1 倍缩放/比例将面板沿 X 方向平移 10 像素,则当鼠标悬停在 (50,40) 的框上时,鼠标位置将为 (40,40)。当您缩放和平移时,这会变得更加复杂。

为了更容易获得“世界”坐标,我们需要 inverseTransform() 函数。为此,我们需要在缩放之前保存当前的 AffineTransform。 ('at'是我程序中的全局AffineTransform)

AffineTransform originalTransform = new AffineTransform(at);

然后我们将创建另一个变换,但这是一个恒等变换,它还没有被缩放或翻译。

AffineTransform zoomedTransform = new AffineTransform();

一旦我们有了这些,我们就可以获得鼠标的“世界”坐标(在缩放之前)。 (如上所述50,40)

before = originalTransform.inverseTransform(e.getPoint(), null);
// before.getX() returns 50;
// before.getY() returns 40;

现在我们根据鼠标滚动事件设置比例值。

zoom *= 1-(e.getPreciseWheelRotation()/5);
/// This line just calls a function to set the variable 'scale'
((Painter) Frame1.painter).setScale(zoom/100);

现在我们想要进行NEW恒等变换,并使用旧的平移值但使用新的比例值对其进行变换。 (注意:这必须以与 Paint() 函数中完全相同的方式执行。)

zoomedTransform.setToIdentity();
zoomedTransform.translate(getWidth()/2, getHeight()/2);
zoomedTransform.scale(scale, scale);
zoomedTransform.translate(-getWidth()/2, -getHeight()/2);
zoomedTransform.translate(translateX, translateY);

注意:我们原来的Transform是缩放/缩放之前的变换。 ZoomedTransform 是在应用我们新的缩放/缩放之后。

现在我们已经放大了,假设我们的鼠标现在位于 20,30 处不同框原点的上方。我们的鼠标位置没有改变,我们所做的只是滚动鼠标滚轮。我们想要做的是获取鼠标位置并获取 20,30 坐标。我们通过对新变换进行逆变换来做到这一点。

after = zoomedTransform.inverseTransform(e.getPoint(), null);

因此,“之前”= (50,40) 和“之后”= (20,30) 我们现在想通过这种差异来翻译视图。这会将框移动到鼠标位置下方 50,40 处。这将产生放大到 50,40 处的框的效果,而不仅仅是放大到屏幕中心。

double deltaX = after.getX() - before.getX();
double deltaY = after.getY() - before.getY();
translate(deltaX,deltaY);

然后我们重新粉刷。

Frame1.painter.repaint();

作为参考,这里是我的绘制函数中的代码。请注意平移和缩放的应用方式与 mouseWheel 函数中的应用方式完全相同。

    public void paint(Graphics g) {
        loadColors();
        super.paint(g);
        Graphics2D g2D = (Graphics2D) g;
        
        //Set  anti-alias!
        g2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON); 

       // Set anti-alias for text
        g2D.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
                RenderingHints.VALUE_TEXT_ANTIALIAS_ON); 
        
        AffineTransform saveTransform = g2D.getTransform();
        g2D.setColor(backgroundColor);
        g2D.fillRect(0, 0, getWidth(), getHeight());
        at = new AffineTransform(saveTransform);
        if(newTruss) {
            try {
                centerView();
            } catch (NoninvertibleTransformException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            newTruss = false;
        }
        at.translate(getWidth()/2, getHeight()/2);
        at.scale(scale, scale);
        at.translate(-getWidth()/2, -getHeight()/2);
        at.translate(translateX, translateY);
        g2D.setTransform(at);

        if(truss != null) {
            g2D.setColor(Color.WHITE);
            //truss.drawBoardsOutline(g2D);
            truss.drawBoards(g2D, boardColor, lineColor);
            truss.drawBoardsLongitudinalCOG(g2D, pointColor, 3);
            truss.drawBoardsCOG(g2D, Color.GREEN, 1);
            truss.drawSelectedBoards(g2D, Frame1.boardList, boardColor, Color.RED, lastSelectedBoard);
            if (lastSelectedBoard > -1) {
                truss.drawBoardPath(g2D, lastSelectedBoard, lastSelectedBoardDir, 50.0f, 7);
                //System.out.println(Frame1.placementList);
            }
            //truss.drawBoardPaths(g2D, 50.0f, 7);
        }
    }
© www.soinside.com 2019 - 2024. All rights reserved.