mirror of
https://github.com/HKUDS/OpenSpace.git
synced 2026-08-28 05:15:00 +00:00
255 lines
No EOL
8.2 KiB
Python
255 lines
No EOL
8.2 KiB
Python
import platform
|
|
import os
|
|
import logging
|
|
from typing import Optional, Tuple
|
|
from PIL import Image
|
|
import pyautogui
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
platform_name = platform.system()
|
|
|
|
|
|
class ScreenshotHelper:
|
|
def __init__(self):
|
|
self.platform = platform_name
|
|
self.adapter = None
|
|
|
|
try:
|
|
if platform_name == "Darwin":
|
|
from ..platform_adapters.macos_adapter import MacOSAdapter
|
|
self.adapter = MacOSAdapter()
|
|
elif platform_name == "Linux":
|
|
from ..platform_adapters.linux_adapter import LinuxAdapter
|
|
self.adapter = LinuxAdapter()
|
|
elif platform_name == "Windows":
|
|
from ..platform_adapters.windows_adapter import WindowsAdapter
|
|
self.adapter = WindowsAdapter()
|
|
except ImportError as e:
|
|
logger.warning(f"Failed to import platform adapter: {e}")
|
|
|
|
def capture(self, output_path: str, with_cursor: bool = True) -> bool:
|
|
try:
|
|
# Ensure directory exists
|
|
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
|
|
|
if with_cursor and self.adapter:
|
|
# Use platform-specific method to capture screenshot (with cursor)
|
|
return self.adapter.capture_screenshot_with_cursor(output_path)
|
|
else:
|
|
# Use pyautogui to capture screenshot (without cursor)
|
|
screenshot = pyautogui.screenshot()
|
|
screenshot.save(output_path)
|
|
logger.info(f"Screenshot successfully (without cursor): {output_path}")
|
|
return True
|
|
|
|
except Exception as e:
|
|
logger.error(f"Screenshot failed: {e}")
|
|
return False
|
|
|
|
def capture_region(
|
|
self,
|
|
output_path: str,
|
|
x: int,
|
|
y: int,
|
|
width: int,
|
|
height: int
|
|
) -> bool:
|
|
"""
|
|
Capture specified screen region
|
|
|
|
Args:
|
|
output_path: Output path
|
|
x: Starting x coordinate
|
|
y: Starting y coordinate
|
|
width: Width
|
|
height: Height
|
|
|
|
Returns:
|
|
Whether successful
|
|
"""
|
|
try:
|
|
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
|
|
|
screenshot = pyautogui.screenshot(region=(x, y, width, height))
|
|
screenshot.save(output_path)
|
|
logger.info(f"Region screenshot successfully: {output_path}")
|
|
return True
|
|
|
|
except Exception as e:
|
|
logger.error(f"Region screenshot failed: {e}")
|
|
return False
|
|
|
|
def get_screen_size(self) -> Tuple[int, int]:
|
|
"""
|
|
Get screen size
|
|
|
|
Returns:
|
|
(width, height)
|
|
"""
|
|
try:
|
|
size = pyautogui.size()
|
|
return (size.width, size.height)
|
|
except Exception as e:
|
|
logger.error(f"Failed to get screen size: {e}")
|
|
return (1920, 1080) # Default value
|
|
|
|
def get_cursor_position(self) -> Tuple[int, int]:
|
|
"""
|
|
Get cursor position
|
|
|
|
Returns:
|
|
(x, y)
|
|
"""
|
|
try:
|
|
pos = pyautogui.position()
|
|
return (pos.x, pos.y)
|
|
except Exception as e:
|
|
logger.error(f"Failed to get cursor position: {e}")
|
|
return (0, 0)
|
|
|
|
def capture_to_base64(self, with_cursor: bool = True) -> Optional[str]:
|
|
"""
|
|
Capture screenshot and convert to base64
|
|
|
|
Args:
|
|
with_cursor: Whether to include cursor
|
|
|
|
Returns:
|
|
Base64 encoded image string
|
|
"""
|
|
import tempfile
|
|
import base64
|
|
|
|
try:
|
|
# Create temporary file
|
|
with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as tmp:
|
|
tmp_path = tmp.name
|
|
|
|
# Capture screenshot
|
|
if self.capture(tmp_path, with_cursor):
|
|
# Read and encode
|
|
with open(tmp_path, 'rb') as f:
|
|
img_data = f.read()
|
|
img_base64 = base64.b64encode(img_data).decode('utf-8')
|
|
|
|
# Delete temporary file
|
|
os.remove(tmp_path)
|
|
|
|
return img_base64
|
|
else:
|
|
if os.path.exists(tmp_path):
|
|
os.remove(tmp_path)
|
|
return None
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to convert screenshot to base64: {e}")
|
|
return None
|
|
|
|
def compare_screenshots(self, path1: str, path2: str) -> float:
|
|
"""
|
|
Compare similarity between two screenshots
|
|
|
|
Args:
|
|
path1: First image path
|
|
path2: Second image path
|
|
|
|
Returns:
|
|
Similarity (0-1), 1 means identical
|
|
"""
|
|
try:
|
|
from PIL import ImageChops
|
|
import math
|
|
import operator
|
|
from functools import reduce
|
|
|
|
img1 = Image.open(path1)
|
|
img2 = Image.open(path2)
|
|
|
|
# Ensure same size
|
|
if img1.size != img2.size:
|
|
# Resize to same size
|
|
img2 = img2.resize(img1.size)
|
|
|
|
# Calculate difference
|
|
diff = ImageChops.difference(img1, img2)
|
|
|
|
# Calculate statistics
|
|
stat = diff.histogram()
|
|
sum_of_squares = reduce(
|
|
operator.add,
|
|
map(lambda h, i: h * (i ** 2), stat, range(len(stat)))
|
|
)
|
|
|
|
# Calculate RMS
|
|
rms = math.sqrt(sum_of_squares / float(img1.size[0] * img1.size[1]))
|
|
|
|
# Normalize to 0-1, RMS max value is approximately 441 (for RGB)
|
|
similarity = 1 - (rms / 441.0)
|
|
|
|
return max(0, min(1, similarity))
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to compare screenshots: {e}")
|
|
return 0.0
|
|
|
|
def annotate_screenshot(
|
|
self,
|
|
input_path: str,
|
|
output_path: str,
|
|
annotations: list
|
|
) -> bool:
|
|
"""
|
|
Add annotations to screenshot
|
|
|
|
Args:
|
|
input_path: Input image path
|
|
output_path: Output image path
|
|
annotations: List of annotations, each annotation is a dict:
|
|
{'type': 'rectangle'/'text', 'x': int, 'y': int,
|
|
'width': int, 'height': int, 'text': str, 'color': tuple}
|
|
|
|
Returns:
|
|
Whether successful
|
|
"""
|
|
try:
|
|
from PIL import ImageDraw, ImageFont
|
|
|
|
img = Image.open(input_path)
|
|
draw = ImageDraw.Draw(img)
|
|
|
|
for annotation in annotations:
|
|
ann_type = annotation.get('type', 'rectangle')
|
|
color = annotation.get('color', (255, 0, 0))
|
|
|
|
if ann_type == 'rectangle':
|
|
x = annotation.get('x', 0)
|
|
y = annotation.get('y', 0)
|
|
width = annotation.get('width', 100)
|
|
height = annotation.get('height', 100)
|
|
|
|
draw.rectangle(
|
|
[(x, y), (x + width, y + height)],
|
|
outline=color,
|
|
width=2
|
|
)
|
|
|
|
elif ann_type == 'text':
|
|
x = annotation.get('x', 0)
|
|
y = annotation.get('y', 0)
|
|
text = annotation.get('text', '')
|
|
|
|
try:
|
|
font = ImageFont.truetype("Arial.ttf", 20)
|
|
except:
|
|
font = ImageFont.load_default()
|
|
|
|
draw.text((x, y), text, fill=color, font=font)
|
|
|
|
img.save(output_path)
|
|
logger.info(f"Annotated screenshot successfully: {output_path}")
|
|
return True
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to annotate screenshot: {e}")
|
|
return False |