【Python笔记】AI帮封装Airtest IOS-WDA touch操作时的factor坐标转换

目前想封装一些wda操作为mcp,让llm直接操作手机,封装点击的时候碰到了坐标系统不一致的问题

factor 的实现逻辑

当前 touch_factor 的核心是:

python 复制代码
screen = self.screen_size()
screenshot = self.screenshot_size()
factor = screen["height"] / screenshot["height"]

它解决的是:截图坐标系WDA 点击坐标系 不一定一致的问题。

例如某些 iPhone / WDA 组合下:

text 复制代码
截图尺寸 screenshot_size: 1170 x 2532
WDA 屏幕尺寸 screen_size: 390 x 844

这两个尺寸刚好是 3 倍关系:

text 复制代码
factor = 844 / 2532 = 0.3333

如果模板匹配在截图上找到一个按钮中心:

text 复制代码
screenshot 坐标: (585, 1266)

这个点在 WDA 坐标系里应该是:

text 复制代码
wda 坐标: (585 * 0.3333, 1266 * 0.3333)
        ≈ (195, 422)

如果不乘 factor,直接把 (585, 1266) 传给 WDA,坐标就会超出 WDA 的逻辑屏幕范围,或者点击到错误位置。

所以 factor 本质是:

text 复制代码
screenshot pixel coordinate -> WDA/device logical coordinate

也就是:

python 复制代码
wda_x = int(screenshot_x * factor)
wda_y = int(screenshot_y * factor)

这里用 height 算 factor,是因为 Airtest iOS 也是按高度算,而且 iOS 截图和屏幕通常是等比例缩放,heightwidth 更稳定地反映竖屏/横屏后的实际比例。后续如果要更严谨,可以同时计算:

python 复制代码
factor_x = screen["width"] / screenshot["width"]
factor_y = screen["height"] / screenshot["height"]

但为了和 Airtest 行为一致,目前使用单一 touch_factor = screen_height / screenshot_height 是合理的。

为什么 x/y < 1 可以按 percent 坐标直接传给 WDA

这是 Airtest iOS 的坐标约定。

Airtest 的 iOS touch 逻辑大致可以理解为:

python 复制代码
if x < 1 and y < 1:
    # 百分比坐标,直接交给 wda.click
    pos = (x, y)
else:
    # 截图像素坐标,需要乘 touch_factor
    pos = (int(x * factor), int(y * factor))

原因是 iOS 的 facebook-wda / Airtest 封装里对小于 1 的坐标有特殊语义:

text 复制代码
0 <= x < 1, 0 <= y < 1 表示屏幕比例坐标

例如:

python 复制代码
touch((0.5, 0.5))

意思不是点击物理像素 (0.5, 0.5),而是点击屏幕中心:

text 复制代码
x = 50% width
y = 50% height

所以这种坐标不需要乘 touch_factor。因为它不是截图像素坐标,而是比例坐标,本身和截图分辨率无关。

举例:

text 复制代码
screen_size:     390 x 844
screenshot_size: 1170 x 2532
factor:          0.3333

如果传:

python 复制代码
touch((0.5, 0.5))

语义是屏幕中心。WDA/Airtest 的 percent 处理会把它理解成:

text 复制代码
WDA 实际点击点 ≈ (390 * 0.5, 844 * 0.5)
              ≈ (195, 422)

如果错误地给 percent 坐标也乘 factor:

python 复制代码
0.5 * 0.3333 = 0.1666

那就变成点击大约 16.6% 的位置,明显错了。

所以判断逻辑是:

text 复制代码
x/y < 1:
  这是比例坐标,直接给 WDA/Airtest 封装处理。

x/y >= 1:
  这是截图像素坐标,需要乘 factor 转成 WDA 坐标。

注意这里当前代码里是:

python 复制代码
if not (raw_x < 1 and raw_y < 1):
    # screenshot pixel
else:
    # percent

也就是说只有当两个坐标都 < 1 时才认为是 percent;只要有一个坐标 >= 1,就整体按截图像素坐标处理。这和 Airtest 的语义保持一致。

方案 A 的具体改法

我建议后续实现时这样改:

  1. WDAClient 中新增统一坐标转换方法,例如:
python 复制代码
def coordinate_scale_info(self) -> Dict[str, Any]:
    screen = self.screen_size()
    screenshot = self.screenshot_size()
    screenshot_height = float(screenshot.get("height") or 0)
    if screenshot_height <= 0:
        raise WDAError(f"Invalid screenshot size for coordinate scale: {screenshot}")
    return {
        "factor": float(screen["height"]) / screenshot_height,
        "screen_size": screen,
        "screenshot_size": screenshot,
    }
  1. 新增 Airtest 坐标归一化:
python 复制代码
def _normalize_airtest_coordinates(self, x, y):
    raw_x = float(x)
    raw_y = float(y)

    if raw_x < 1 and raw_y < 1:
        return raw_x, raw_y, {
            "coordinate_source": "percent",
            "touch_factor": None,
            "screen_size": None,
            "screenshot_size": None,
        }

    scale = self.coordinate_scale_info()
    factor = scale["factor"]
    return int(raw_x * factor), int(raw_y * factor), {
        "coordinate_source": "screenshot_pixel",
        "touch_factor": factor,
        "screen_size": scale["screen_size"],
        "screenshot_size": scale["screenshot_size"],
    }
  1. airtest_touch() 调用这个方法,避免重复 factor 逻辑。

  2. 修改 _normalize_coordinates()auto 语义:

text 复制代码
auto:
  x/y < 1 -> relative/percent
  否则 -> screenshot_pixel + factor

也可以直接把 auto 作为 Airtest 坐标语义。

  1. 修改 swipe() 显式坐标路径:

    • start 和 end 都走同一套 Airtest 坐标转换。
    • 方向滑动,即只传 direction 的情况,仍然基于 window_size() 生成 WDA 坐标即可,因为它本来不是截图坐标,是程序内部生成的逻辑屏幕坐标。
  2. 修改 ios_swipe() 返回值:

    • 返回实际用于 WDA drag 的坐标
    • 返回 factor / screen_size / screenshot_size
    • 方便后续调试

例如:

json 复制代码
{
  "swiped": true,
  "input": {
    "start": [585, 1500],
    "end": [585, 500],
    "coordinate_mode": "auto"
  },
  "wda_start": [195, 500],
  "wda_end": [195, 166],
  "coordinate_source": "screenshot_pixel",
  "touch_factor": 0.3333,
  "screen_size": {"width": 390, "height": 844},
  "screenshot_size": {"width": 1170, "height": 2532}
}

Template 后续应该怎么接

后续封装 Template 时,不建议让 Template 直接返回"点击坐标"这种模糊概念,而是明确返回:

json 复制代码
{
  "matched": true,
  "confidence": 0.92,
  "screenshot_position": {"x": 585, "y": 1266},
  "wda_position": {"x": 195, "y": 422},
  "touch_factor": 0.3333
}

这样:

  • 模板匹配结果保留截图坐标,方便 debug 和可视化标注;
  • 真正 touch/swipe 时使用统一转换后的 WDA 坐标;
  • 所有视觉能力都和 ios_touch / ios_swipe 保持一致。
相关推荐
风之所往_2 小时前
Python 3.6 新特性全面总结
python
abcy0712132 小时前
flask celery hdfs 异步上传
python·hdfs·flask
2301_781833522 小时前
Python 正则表达式入门教程
开发语言·python·正则表达式
tq10862 小时前
OperationSequence DSL 2.1 语法规范
笔记
copyer_xyf2 小时前
Agent Tool 调用
后端·python·agent
Amo Xiang2 小时前
SpiderDemo 第5题:OB混淆实战 —— 反调试绕过与 signature 签名还原
python·js逆向·爬虫逆向·反调试·spiderdemo·ob混淆
copyer_xyf2 小时前
Agent 结构化输出
后端·python·agent
柚鸥ASO优化3 小时前
一篇讲透安卓ASO!开发者千万别只盯着iOS了
android·ios·aso优化
FBI HackerHarry浩3 小时前
Ollama如何安装到D盘
python·ai