oneocr / _archive /screen_capture.py
OneOCR Dev
OneOCR - reverse engineering complete, ONNX pipeline 53% match rate
ce847d4
"""Screen capture using mss — fast, cross-platform screenshots.
Usage:
from src.services.ocr.screen_capture import ScreenCapture
capture = ScreenCapture()
img = capture.grab_primary() # full primary monitor
img = capture.grab_region(100, 200, 800, 600) # x, y, w, h
"""
from __future__ import annotations
import mss
from PIL import Image
class ScreenCapture:
"""Fast screen capture using mss library.
Why per-call mss.mss(): mss uses thread-local GDI device contexts (srcdc)
on Windows. When created in one thread and used from another (e.g. pywebview
API call thread), it raises '_thread._local' object has no attribute 'srcdc'.
Creating a fresh mss context per grab call is cheap and thread-safe.
"""
def __init__(self) -> None:
# Pre-read monitor info (works cross-thread — just metadata)
with mss.mss() as sct:
self._monitors = list(sct.monitors)
@staticmethod
def _grab(monitor: dict) -> Image.Image:
"""Thread-safe grab: creates mss context per call."""
with mss.mss() as sct:
raw = sct.grab(monitor)
return Image.frombytes("RGB", raw.size, raw.bgra, "raw", "BGRX").convert("RGBA")
def grab_primary(self) -> Image.Image:
"""Capture the entire primary monitor.
Returns:
PIL Image (RGBA).
"""
return self._grab(self._monitors[1]) # [0] = all monitors, [1] = primary
def grab_region(self, x: int, y: int, width: int, height: int) -> Image.Image:
"""Capture a rectangular region of the screen.
Args:
x: Left coordinate.
y: Top coordinate.
width: Width in pixels.
height: Height in pixels.
Returns:
PIL Image (RGBA).
"""
region = {"left": x, "top": y, "width": width, "height": height}
return self._grab(region)
def grab_monitor(self, monitor_index: int = 1) -> Image.Image:
"""Capture a specific monitor.
Args:
monitor_index: 1-based monitor index (1 = primary).
Returns:
PIL Image (RGBA).
"""
if monitor_index < 1 or monitor_index >= len(self._monitors):
raise ValueError(
f"Monitor index must be 1..{len(self._monitors) - 1}, got {monitor_index}"
)
return self._grab(self._monitors[monitor_index])
@property
def monitor_count(self) -> int:
"""Number of physical monitors (excluding virtual 'all' monitor)."""
return len(self._monitors) - 1
def close(self) -> None:
"""No-op — mss contexts are per-call now."""
pass