You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于JFrame与AffineTransform的缩放及鼠标点击定位问题

问题:JFrame中缩放图像后鼠标点击位置无法映射到原图坐标

在使用JFrame显示BufferedImage时遇到问题:鼠标点击位置绑定到JFrame坐标,无法根据缩放比例转换为图像的相对位置。

绘图相关代码

public void paint(Graphics g) {
    super.paint(g);
    Graphics2D g2 = (Graphics2D) g;
    if (zoomer) {
        AffineTransform at = new AffineTransform();
        double xRel = MouseInfo.getPointerInfo().getLocation().getX() - getLocationOnScreen().getX();
        double yRel = MouseInfo.getPointerInfo().getLocation().getY() - getLocationOnScreen().getY();
        double zoomDiv = zoomFactor / prevZoomFactor;
        xOffset = (zoomDiv) * (xOffset) + (1 - zoomDiv) * xRel;
        yOffset = (zoomDiv) * (yOffset) + (1 - zoomDiv) * yRel;
        at.translate(xOffset, yOffset);
        at.scale(zoomFactor, zoomFactor);
        totalZoom *=zoomFactor;
        prevZoomFactor = zoomFactor;
        g2.transform(at);
        zoomer = false;
    }
    if (dragger) {
        AffineTransform at = new AffineTransform();
        at.translate(xOffset + xDiff, yOffset + yDiff);
        at.scale(zoomFactor, zoomFactor);
        g2.transform(at);
        if (released) {
            xOffset += xDiff;
            yOffset += yDiff;
            dragger = false;
        }
    }
}

我尝试通过数学计算转换鼠标点击位置,但未能成功实现:

鼠标点击事件代码

@Override
public void mouseClicked(MouseEvent me) {
    double x = MouseInfo.getPointerInfo().getLocation().getX() - getLocationOnScreen().getX();
    double y = MouseInfo.getPointerInfo().getLocation().getY() - getLocationOnScreen().getY();

    try {
        CommandFromClient clientSubmission= new CommandFromClient(CommandFromClient.GETBOARD);
        os.writeObject(clientSubmission);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
    System.out.println("x: " + getLocationOnScreen().getX()/zoomFactor + "y: " + getLocationOnScreen().getY()/zoomFactor);

    Point clicked = new Point((int)((me.getPoint().x*10-x)),(int)((me.getPoint().y*10-y)));
    System.out.println(1f/zoomFactor);
    System.out.println("CLICK DETECTED: " + clicked);
    for(int i = yellow.size()-1;i>=0;i--) {
        Rectangle bounds = new Rectangle(yellow.get(i).getX(),yellow.get(i).getY(),yellow.get(i).getImg().getWidth(),yellow.get(i).getImg().getHeight());
        System.out.print("checks");
        if(bounds.contains(clicked)){
            System.out.print("collided");
            System.out.print(yellow.get(i));
            break;
        }
    }
}

解决方案

直接手动计算坐标容易出错,尤其是同时存在平移和缩放变换时,正确的做法是利用AffineTransform的逆变换来完成屏幕坐标到原图坐标的映射。

步骤1:维护全局变换矩阵

修改paint方法,将当前使用的变换矩阵保存为全局变量,方便鼠标事件中直接调用:

// 新增全局变量,保存当前的绘图变换
private AffineTransform currentTransform = new AffineTransform();

public void paint(Graphics g) {
    super.paint(g);
    Graphics2D g2 = (Graphics2D) g;
    // 重置变换矩阵
    currentTransform.setToIdentity();

    if (zoomer) {
        double xRel = MouseInfo.getPointerInfo().getLocation().getX() - getLocationOnScreen().getX();
        double yRel = MouseInfo.getPointerInfo().getLocation().getY() - getLocationOnScreen().getY();
        double zoomDiv = zoomFactor / prevZoomFactor;
        xOffset = (zoomDiv) * xOffset + (1 - zoomDiv) * xRel;
        yOffset = (zoomDiv) * yOffset + (1 - zoomDiv) * yRel;
        totalZoom *= zoomFactor;
        prevZoomFactor = zoomFactor;
        zoomer = false;
    }

    // 应用平移变换
    if (dragger) {
        currentTransform.translate(xOffset + xDiff, yOffset + yDiff);
        if (released) {
            xOffset += xDiff;
            yOffset += yDiff;
            dragger = false;
        }
    } else {
        currentTransform.translate(xOffset, yOffset);
    }
    // 应用缩放变换
    currentTransform.scale(zoomFactor, zoomFactor);

    // 将变换应用到绘图上下文
    g2.transform(currentTransform);
}

步骤2:用逆变换转换鼠标坐标

修改mouseClicked方法,通过逆变换直接将屏幕点击坐标转换为原图坐标:

@Override
public void mouseClicked(MouseEvent me) {
    try {
        CommandFromClient clientSubmission = new CommandFromClient(CommandFromClient.GETBOARD);
        os.writeObject(clientSubmission);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }

    // 获取鼠标在组件内的坐标
    Point screenPoint = me.getPoint();
    try {
        // 创建当前变换的逆变换
        AffineTransform inverseTransform = currentTransform.createInverse();
        // 将屏幕坐标转换为原图坐标
        Point2D originalPoint = inverseTransform.transform(screenPoint, null);
        int originalX = (int) Math.round(originalPoint.getX());
        int originalY = (int) Math.round(originalPoint.getY());

        System.out.println("原图坐标: x=" + originalX + ", y=" + originalY);

        // 使用转换后的坐标进行碰撞检测
        Point clicked = new Point(originalX, originalY);
        for (int i = yellow.size() - 1; i >= 0; i--) {
            Rectangle bounds = new Rectangle(yellow.get(i).getX(), yellow.get(i).getY(),
                    yellow.get(i).getImg().getWidth(), yellow.get(i).getImg().getHeight());
            System.out.print("checks");
            if (bounds.contains(clicked)) {
                System.out.print("collided");
                System.out.print(yellow.get(i));
                break;
            }
        }
    } catch (NoninvertibleTransformException e) {
        // 处理变换矩阵不可逆的情况(如缩放因子为0)
        e.printStackTrace();
    }
}

关键说明

  • 全局的currentTransform会在每次paint时更新,确保始终与当前绘图的变换一致。
  • 逆变换会自动处理平移和缩放的反向计算,避免手动计算的误差。
  • 必须捕获NoninvertibleTransformException,当变换矩阵无法求逆时(比如缩放因子为0)会触发该异常。

内容的提问来源于stack exchange,提问作者user27466996

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 01:38:11