feat: add gesture sequences and calibrated pixel drags

This commit is contained in:
chenmingxuan committed 2026-11-02 11:53:27 +08:00
1 parent 2a3bbaa3fb
commit 479a185192
31 files changed
+2440 -44

No files matched your search

+42 -7
View File
@@ -517,14 +517,20 @@ def crop_and_annotate_target(
return out_buf.getvalue()
def _resolve_coordinates(coord: Any, width: int, height: int) -> tuple[int, int] | None:
def _resolve_coordinates(
coord: Any, width: int, height: int, coordinate_space: str | None = None
) -> tuple[int, int] | None:
"""Helper to safely parse coordinates into pixel space."""
if not isinstance(coord, (list, tuple)) or len(coord) < 2:
return None
try:
x, y = float(coord[0]), float(coord[1])
# If coordinates are normalized (0.0 <= x <= 1.0)
if 0.0 <= x <= 1.0 and 0.0 <= y <= 1.0:
if coordinate_space == "pixel":
px, py = int(round(x)), int(round(y))
elif coordinate_space == "normalized":
px, py = int(round(x * width / 1000)), int(round(y * height / 1000))
elif 0.0 <= x <= 1.0 and 0.0 <= y <= 1.0:
px = int(round(x * width))
py = int(round(y * height))
elif 0 <= x <= 1000 and 0 <= y <= 1000 and (width > 1000 or height > 1000):
@@ -563,6 +569,17 @@ def draw_action_overlay_on_image(
if not image_bytes:
return image_bytes
if action_name == "action_sequence":
receipts = action_args.get("sequence_results")
annotated = image_bytes
for member in receipts if isinstance(receipts, list) else []:
if (
isinstance(member, dict)
and member.get("execution") == "completed"
and member.get("action") in {"click", "drag"}
):
annotated = draw_action_overlay_on_image(annotated, member["action"], member)
return annotated
try:
img = Image.open(io.BytesIO(image_bytes))
if img.mode in ("RGBA", "P"):
@@ -584,7 +601,7 @@ def draw_action_overlay_on_image(
or action_args.get("coordinates")
or action_args.get("point")
)
pt = _resolve_coordinates(coord, width, height)
pt = _resolve_coordinates(coord, width, height, action_args.get("coordinate_space"))
if pt:
x, y = pt
# Outer white aura
@@ -637,7 +654,9 @@ def draw_action_overlay_on_image(
if isinstance(sequence, list):
pts = []
for coord in sequence:
pt = _resolve_coordinates(coord, width, height)
pt = _resolve_coordinates(
coord, width, height, action_args.get("coordinate_space")
)
if pt:
pts.append(pt)
@@ -703,8 +722,12 @@ def draw_action_overlay_on_image(
start_pt, end_pt = None, None
if kind == "coords" and isinstance(target, list) and len(target) == 4:
start_pt = _resolve_coordinates(target[:2], width, height)
end_pt = _resolve_coordinates(target[2:], width, height)
start_pt = _resolve_coordinates(
target[:2], width, height, action_args.get("coordinate_space")
)
end_pt = _resolve_coordinates(
target[2:], width, height, action_args.get("coordinate_space")
)
elif kind == "direction" and isinstance(target, str):
direction = target.lower()
cx, cy = width // 2, height // 2
@@ -758,7 +781,7 @@ def draw_action_overlay_on_image(
elif action in ("input_text", "type"):
coord = action_args.get("target") or action_args.get("coordinates")
if coord:
pt = _resolve_coordinates(coord, width, height)
pt = _resolve_coordinates(coord, width, height, action_args.get("coordinate_space"))
if pt:
x, y = pt
box_w = max(60, int(base_dim * 0.15))
@@ -805,9 +828,21 @@ def overlay_action_on_screenshot(image_bytes: bytes, action_obj: Any) -> bytes |
"sequence",
"targets",
"normalized_coordinates",
"coordinate_space",
"sequence_results",
):
if key in action and key not in action_args:
action_args[key] = action[key]
if (
act_name == "drag"
and isinstance(action.get("coordinates"), list)
and len(action["coordinates"]) == 4
):
action_args.update(
start=action["coordinates"][:2],
end=action["coordinates"][2:],
coordinate_space=action.get("coordinate_space"),
)
annotated = draw_action_overlay_on_image(
image_bytes=image_bytes, action_name=act_name, action_args=action_args
)