Spaces:
Paused
Paused
Update app.py
Browse files
app.py
CHANGED
|
@@ -556,61 +556,146 @@ def _get_mouse_pos(display: str) -> tuple:
|
|
| 556 |
return 0, 0
|
| 557 |
|
| 558 |
|
| 559 |
-
def
|
| 560 |
-
|
| 561 |
-
|
| 562 |
-
|
| 563 |
-
|
| 564 |
-
|
| 565 |
-
|
| 566 |
-
if img is None:
|
| 567 |
-
return {"data": "", "width": 1920, "height": 1080, "mouse_x": 0, "mouse_y": 0}
|
| 568 |
|
| 569 |
-
mx, my = (force_mx, force_my) if force_mx is not None else _get_mouse_pos(display)
|
| 570 |
|
| 571 |
-
|
| 572 |
-
|
| 573 |
-
|
| 574 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 575 |
|
| 576 |
-
step_x = int(
|
| 577 |
-
step_y = int(
|
| 578 |
-
|
| 579 |
-
|
|
|
|
| 580 |
|
| 581 |
-
x_sc, x_r = step_x,
|
|
|
|
| 582 |
while x_sc < sw:
|
| 583 |
-
|
| 584 |
-
draw.
|
| 585 |
-
|
| 586 |
-
|
| 587 |
-
|
| 588 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 589 |
while y_sc < sh:
|
| 590 |
-
|
| 591 |
-
draw.
|
| 592 |
-
|
| 593 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 594 |
|
| 595 |
msx = int(mx * sw / ow)
|
| 596 |
msy = int(my * sh / oh)
|
| 597 |
-
|
| 598 |
-
draw.ellipse([(msx-r, msy-r), (msx+r, msy+r)], outline=(255, 50, 50, 240), width=2)
|
| 599 |
-
draw.line([(msx-18, msy), (msx+18, msy)], fill=(255, 50, 50, 200), width=1)
|
| 600 |
-
draw.line([(msx, msy-18), (msx, msy+18)], fill=(255, 50, 50, 200), width=1)
|
| 601 |
|
| 602 |
-
final =
|
| 603 |
draw2 = ImageDraw.Draw(final)
|
| 604 |
draw2.rectangle([(0, 0), (sw, 20)], fill=(0, 0, 0))
|
| 605 |
-
draw2.text((4, 3), f"SCREEN {ow}x{oh} | MOUSE:({mx},{my}) | GRID=
|
| 606 |
draw2.rectangle([(0, sh-20), (sw, sh)], fill=(0, 0, 0))
|
| 607 |
-
draw2.text((4, sh-17), "
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 608 |
|
| 609 |
-
|
| 610 |
-
|
| 611 |
-
|
|
|
|
|
|
|
|
|
|
| 612 |
|
| 613 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 614 |
|
| 615 |
|
| 616 |
def _frame_hash(data: str) -> str:
|
|
@@ -925,6 +1010,9 @@ async def handle_action(ws: WebSocket, msg: dict, sess: dict):
|
|
| 925 |
await send({
|
| 926 |
"type": "screenshot",
|
| 927 |
"data": result["data"],
|
|
|
|
|
|
|
|
|
|
| 928 |
"ts": int(time.time() * 1000),
|
| 929 |
"auto": True, "label": label,
|
| 930 |
"screen_width": result["width"],
|
|
@@ -944,6 +1032,9 @@ async def handle_action(ws: WebSocket, msg: dict, sess: dict):
|
|
| 944 |
await send({
|
| 945 |
"type": "screenshot",
|
| 946 |
"data": result2["data"],
|
|
|
|
|
|
|
|
|
|
| 947 |
"ts": int(time.time() * 1000),
|
| 948 |
"auto": True, "label": f"{label} (delayed)",
|
| 949 |
"screen_width": result2["width"],
|
|
@@ -963,6 +1054,9 @@ async def handle_action(ws: WebSocket, msg: dict, sess: dict):
|
|
| 963 |
await send({
|
| 964 |
"type": "screenshot",
|
| 965 |
"data": result["data"],
|
|
|
|
|
|
|
|
|
|
| 966 |
"ts": int(time.time() * 1000),
|
| 967 |
"auto": False, "label": label,
|
| 968 |
"screen_width": result["width"],
|
|
@@ -1282,6 +1376,9 @@ async def websocket_endpoint(
|
|
| 1282 |
await ws.send_text(json.dumps({
|
| 1283 |
"type": "screenshot",
|
| 1284 |
"data": result["data"],
|
|
|
|
|
|
|
|
|
|
| 1285 |
"ts": int(time.time() * 1000),
|
| 1286 |
"label": "Initial screen",
|
| 1287 |
"screen_width": result["width"],
|
|
@@ -1326,6 +1423,9 @@ async def rest_screenshot(user_id: str = "anonymous"):
|
|
| 1326 |
result = await asyncio.to_thread(capture_with_grid, display, 0.7, 75)
|
| 1327 |
return JSONResponse({
|
| 1328 |
"image": result["data"],
|
|
|
|
|
|
|
|
|
|
| 1329 |
"ts": int(time.time() * 1000),
|
| 1330 |
"screen_width": result["width"],
|
| 1331 |
"screen_height": result["height"],
|
|
|
|
| 556 |
return 0, 0
|
| 557 |
|
| 558 |
|
| 559 |
+
def _draw_mouse_marker(draw, msx, msy, color=(255, 50, 50, 240)):
|
| 560 |
+
"""يرسم علامة الماوس (دائرة + خطوط تقاطع) في نقطة محددة على صورة."""
|
| 561 |
+
r = 11
|
| 562 |
+
line_color = (color[0], color[1], color[2], 200)
|
| 563 |
+
draw.ellipse([(msx-r, msy-r), (msx+r, msy+r)], outline=color, width=2)
|
| 564 |
+
draw.line([(msx-18, msy), (msx+18, msy)], fill=line_color, width=1)
|
| 565 |
+
draw.line([(msx, msy-18), (msx, msy+18)], fill=line_color, width=1)
|
|
|
|
|
|
|
| 566 |
|
|
|
|
| 567 |
|
| 568 |
+
def _render_grid_variant(base_img, ow, oh, sw, sh, mx, my, display,
|
| 569 |
+
grid_step: int, line_color: tuple, label_every: int,
|
| 570 |
+
major_only_labels: bool = True) -> "Image.Image":
|
| 571 |
+
"""
|
| 572 |
+
يرسم نسخة شبكة إحداثيات واحدة فوق نسخة من الصورة الأساسية.
|
| 573 |
+
grid_step → المسافة بالبكسل الحقيقي بين كل خط شبكة.
|
| 574 |
+
line_color → لون الخطوط والأرقام (RGBA)، مثل أخضر/سماوي أو أحمر.
|
| 575 |
+
label_every → كل كم بكسل حقيقي تُكتب فيه تسمية إحداثي (x,y) كاملة عند التقاطعات.
|
| 576 |
+
major_only_labels → إذا True، الأرقام على الحواف تظهر فقط عند خطوط "رئيسية" (مضاعفات label_every).
|
| 577 |
+
"""
|
| 578 |
+
from PIL import Image, ImageDraw
|
| 579 |
+
img = base_img.copy()
|
| 580 |
+
draw = ImageDraw.Draw(img, "RGBA")
|
| 581 |
|
| 582 |
+
step_x = max(1, int(grid_step * sw / ow))
|
| 583 |
+
step_y = max(1, int(grid_step * sh / oh))
|
| 584 |
+
minor_color = (line_color[0], line_color[1], line_color[2], 35)
|
| 585 |
+
major_color = (line_color[0], line_color[1], line_color[2], 110)
|
| 586 |
+
text_color = line_color
|
| 587 |
|
| 588 |
+
x_sc, x_r = step_x, grid_step
|
| 589 |
+
x_majors = []
|
| 590 |
while x_sc < sw:
|
| 591 |
+
is_major = (x_r % label_every == 0)
|
| 592 |
+
draw.line([(x_sc, 0), (x_sc, sh)], fill=(major_color if is_major else minor_color), width=1)
|
| 593 |
+
if is_major or not major_only_labels:
|
| 594 |
+
draw.rectangle([(x_sc+1, 2), (x_sc+34, 15)], fill=(0, 0, 0, 175))
|
| 595 |
+
draw.text((x_sc+2, 3), str(x_r), fill=text_color)
|
| 596 |
+
if is_major:
|
| 597 |
+
x_majors.append((x_sc, x_r))
|
| 598 |
+
x_sc += step_x; x_r += grid_step
|
| 599 |
+
|
| 600 |
+
y_sc, y_r = step_y, grid_step
|
| 601 |
+
y_majors = []
|
| 602 |
while y_sc < sh:
|
| 603 |
+
is_major = (y_r % label_every == 0)
|
| 604 |
+
draw.line([(0, y_sc), (sw, y_sc)], fill=(major_color if is_major else minor_color), width=1)
|
| 605 |
+
if is_major or not major_only_labels:
|
| 606 |
+
draw.rectangle([(2, y_sc+1), (38, y_sc+14)], fill=(0, 0, 0, 175))
|
| 607 |
+
draw.text((3, y_sc+2), str(y_r), fill=text_color)
|
| 608 |
+
if is_major:
|
| 609 |
+
y_majors.append((y_sc, y_r))
|
| 610 |
+
y_sc += step_y; y_r += grid_step
|
| 611 |
+
|
| 612 |
+
# تسمية إحداثي (x,y) كاملة عند كل تقاطع "رئيسي" — تضمن رقماً حقيقياً قريباً من كل منطقة
|
| 613 |
+
for (xs, xr) in x_majors:
|
| 614 |
+
for (ys, yr) in y_majors:
|
| 615 |
+
label = f"{xr},{yr}"
|
| 616 |
+
tw = 6 * len(label) + 4
|
| 617 |
+
draw.rectangle([(xs+2, ys+2), (xs+2+tw, ys+13)], fill=(0, 0, 0, 150))
|
| 618 |
+
draw.text((xs+4, ys+2), label, fill=text_color)
|
| 619 |
|
| 620 |
msx = int(mx * sw / ow)
|
| 621 |
msy = int(my * sh / oh)
|
| 622 |
+
_draw_mouse_marker(draw, msx, msy, color=(line_color[0], line_color[1], line_color[2], 240))
|
|
|
|
|
|
|
|
|
|
| 623 |
|
| 624 |
+
final = img.convert("RGB")
|
| 625 |
draw2 = ImageDraw.Draw(final)
|
| 626 |
draw2.rectangle([(0, 0), (sw, 20)], fill=(0, 0, 0))
|
| 627 |
+
draw2.text((4, 3), f"SCREEN {ow}x{oh} | MOUSE:({mx},{my}) | GRID={grid_step}px | DSP:{display}", fill=(0, 220, 160))
|
| 628 |
draw2.rectangle([(0, sh-20), (sw, sh)], fill=(0, 0, 0))
|
| 629 |
+
draw2.text((4, sh-17), "CLICKCOORDS = numbers at every intersection (real screen pixels)", fill=(180, 180, 70))
|
| 630 |
+
return final
|
| 631 |
+
|
| 632 |
+
|
| 633 |
+
def capture_with_grid(display: str, scale: float = 0.65, quality: int = 72,
|
| 634 |
+
force_mx: int | None = None,
|
| 635 |
+
force_my: int | None = None,
|
| 636 |
+
grid_step: int = 50) -> dict:
|
| 637 |
+
"""
|
| 638 |
+
يلتقط الشاشة من display محدد ويُنتج 4 نسخ من نفس اللقطة بالضبط (نفس اللحظة):
|
| 639 |
+
- "data" → النسخة العادية (نظيفة تماماً) + علامة الماوس فقط، بدون أي Grid.
|
| 640 |
+
هذه فقط تُعرض للمستخدم في الواجهة كـ "الشاشة الحقيقية".
|
| 641 |
+
- "data_grid" → شبكة إحداثيات عادية (كل 50px)، أخضر/سماوي — للذكاء الاصطناعي.
|
| 642 |
+
- "data_grid2" → شبكة إحداثيات أدق (كل 25px)، نفس الألوان — للذكاء الاصطناعي.
|
| 643 |
+
- "data_grid3" → شبكة إحداثيات فائقة الدقة (كل 10px تقريباً)، باللون الأحمر — للذكاء الاصطناعي.
|
| 644 |
+
الثلاث نسخ الأخيرة كلها من نفس اللقطة الأصلية بالضبط (نفس اللحظة، لا التقاط متكرر)
|
| 645 |
+
لضمان تطابقها تماماً ولتفادي أي تأخير أو اختلاف بينها.
|
| 646 |
+
"""
|
| 647 |
+
from PIL import Image, ImageDraw
|
| 648 |
|
| 649 |
+
img, ow, oh = _capture_raw(display)
|
| 650 |
+
if img is None:
|
| 651 |
+
return {"data": "", "data_grid": "", "data_grid2": "", "data_grid3": "",
|
| 652 |
+
"width": 1920, "height": 1080, "mouse_x": 0, "mouse_y": 0}
|
| 653 |
+
|
| 654 |
+
mx, my = (force_mx, force_my) if force_mx is not None else _get_mouse_pos(display)
|
| 655 |
|
| 656 |
+
sw = int(ow * scale)
|
| 657 |
+
sh = int(oh * scale)
|
| 658 |
+
base_img = img.resize((sw, sh), Image.LANCZOS)
|
| 659 |
+
msx = int(mx * sw / ow)
|
| 660 |
+
msy = int(my * sh / oh)
|
| 661 |
+
|
| 662 |
+
# ── النسخة العادية النظيفة (بدون أي شبكة) — فقط علامة الماوس ─────────
|
| 663 |
+
clean_img = base_img.copy()
|
| 664 |
+
draw_clean = ImageDraw.Draw(clean_img, "RGBA")
|
| 665 |
+
_draw_mouse_marker(draw_clean, msx, msy)
|
| 666 |
+
clean_final = clean_img.convert("RGB")
|
| 667 |
+
buf_clean = io.BytesIO()
|
| 668 |
+
clean_final.save(buf_clean, format="JPEG", quality=quality, optimize=True)
|
| 669 |
+
data_clean = base64.b64encode(buf_clean.getvalue()).decode()
|
| 670 |
+
|
| 671 |
+
# ── نسخة شبكة عادية: كل 50px، أخضر/سماوي ────────────────────────────
|
| 672 |
+
grid1 = _render_grid_variant(base_img, ow, oh, sw, sh, mx, my, display,
|
| 673 |
+
grid_step=grid_step, line_color=(0, 255, 180, 235),
|
| 674 |
+
label_every=100)
|
| 675 |
+
buf1 = io.BytesIO(); grid1.save(buf1, format="JPEG", quality=quality, optimize=True)
|
| 676 |
+
data_grid1 = base64.b64encode(buf1.getvalue()).decode()
|
| 677 |
+
|
| 678 |
+
# ── نسخة شبكة أدق: كل 25px، نفس الألوان ──────────────────────────────
|
| 679 |
+
grid2 = _render_grid_variant(base_img, ow, oh, sw, sh, mx, my, display,
|
| 680 |
+
grid_step=25, line_color=(0, 255, 180, 235),
|
| 681 |
+
label_every=100)
|
| 682 |
+
buf2 = io.BytesIO(); grid2.save(buf2, format="JPEG", quality=quality, optimize=True)
|
| 683 |
+
data_grid2 = base64.b64encode(buf2.getvalue()).decode()
|
| 684 |
+
|
| 685 |
+
# ── نسخة شبكة فائقة الدقة: كل 10px تقريباً، أحمر بالكامل ────────────
|
| 686 |
+
grid3 = _render_grid_variant(base_img, ow, oh, sw, sh, mx, my, display,
|
| 687 |
+
grid_step=10, line_color=(255, 60, 60, 235),
|
| 688 |
+
label_every=50, major_only_labels=True)
|
| 689 |
+
buf3 = io.BytesIO(); grid3.save(buf3, format="JPEG", quality=max(quality, 78), optimize=True)
|
| 690 |
+
data_grid3 = base64.b64encode(buf3.getvalue()).decode()
|
| 691 |
+
|
| 692 |
+
return {
|
| 693 |
+
"data": data_clean, # النسخة العادية النظيفة (تُعرض للمستخدم فقط)
|
| 694 |
+
"data_grid": data_grid1, # شبكة عادية 50px (أخضر/سماوي)
|
| 695 |
+
"data_grid2": data_grid2, # شبكة أدق 25px (أخضر/سماوي)
|
| 696 |
+
"data_grid3": data_grid3, # شبكة فائقة الدقة 10px (أحمر)
|
| 697 |
+
"width": ow, "height": oh, "mouse_x": mx, "mouse_y": my,
|
| 698 |
+
}
|
| 699 |
|
| 700 |
|
| 701 |
def _frame_hash(data: str) -> str:
|
|
|
|
| 1010 |
await send({
|
| 1011 |
"type": "screenshot",
|
| 1012 |
"data": result["data"],
|
| 1013 |
+
"data_grid": result.get("data_grid", ""),
|
| 1014 |
+
"data_grid2": result.get("data_grid2", ""),
|
| 1015 |
+
"data_grid3": result.get("data_grid3", ""),
|
| 1016 |
"ts": int(time.time() * 1000),
|
| 1017 |
"auto": True, "label": label,
|
| 1018 |
"screen_width": result["width"],
|
|
|
|
| 1032 |
await send({
|
| 1033 |
"type": "screenshot",
|
| 1034 |
"data": result2["data"],
|
| 1035 |
+
"data_grid": result2.get("data_grid", ""),
|
| 1036 |
+
"data_grid2": result2.get("data_grid2", ""),
|
| 1037 |
+
"data_grid3": result2.get("data_grid3", ""),
|
| 1038 |
"ts": int(time.time() * 1000),
|
| 1039 |
"auto": True, "label": f"{label} (delayed)",
|
| 1040 |
"screen_width": result2["width"],
|
|
|
|
| 1054 |
await send({
|
| 1055 |
"type": "screenshot",
|
| 1056 |
"data": result["data"],
|
| 1057 |
+
"data_grid": result.get("data_grid", ""),
|
| 1058 |
+
"data_grid2": result.get("data_grid2", ""),
|
| 1059 |
+
"data_grid3": result.get("data_grid3", ""),
|
| 1060 |
"ts": int(time.time() * 1000),
|
| 1061 |
"auto": False, "label": label,
|
| 1062 |
"screen_width": result["width"],
|
|
|
|
| 1376 |
await ws.send_text(json.dumps({
|
| 1377 |
"type": "screenshot",
|
| 1378 |
"data": result["data"],
|
| 1379 |
+
"data_grid": result.get("data_grid", ""),
|
| 1380 |
+
"data_grid2": result.get("data_grid2", ""),
|
| 1381 |
+
"data_grid3": result.get("data_grid3", ""),
|
| 1382 |
"ts": int(time.time() * 1000),
|
| 1383 |
"label": "Initial screen",
|
| 1384 |
"screen_width": result["width"],
|
|
|
|
| 1423 |
result = await asyncio.to_thread(capture_with_grid, display, 0.7, 75)
|
| 1424 |
return JSONResponse({
|
| 1425 |
"image": result["data"],
|
| 1426 |
+
"image_grid": result.get("data_grid", ""),
|
| 1427 |
+
"image_grid2": result.get("data_grid2", ""),
|
| 1428 |
+
"image_grid3": result.get("data_grid3", ""),
|
| 1429 |
"ts": int(time.time() * 1000),
|
| 1430 |
"screen_width": result["width"],
|
| 1431 |
"screen_height": result["height"],
|