visppy 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
visppy-0.0.1/PKG-INFO ADDED
@@ -0,0 +1,11 @@
1
+ Metadata-Version: 2.3
2
+ Name: visppy
3
+ Version: 0.0.1
4
+ Summary: TODO
5
+ Requires-Dist: coredeco>=1.1.0
6
+ Requires-Dist: mss>=10.2.0
7
+ Requires-Dist: ntmemoryapi>=2.6.0
8
+ Requires-Dist: pillow>=12.3.0
9
+ Requires-Python: >=3.12
10
+ Description-Content-Type: text/markdown
11
+
visppy-0.0.1/README.md ADDED
File without changes
@@ -0,0 +1,19 @@
1
+ [project]
2
+ name = "visppy"
3
+ version = "0.0.1"
4
+ description = "TODO"
5
+ readme = "README.md"
6
+ requires-python = ">=3.12"
7
+ dependencies = [
8
+ "coredeco>=1.1.0",
9
+ "mss>=10.2.0",
10
+ "ntmemoryapi>=2.6.0",
11
+ "pillow>=12.3.0",
12
+ ]
13
+
14
+ [project.scripts]
15
+ visppy = "visppy:main"
16
+
17
+ [build-system]
18
+ requires = ["uv_build>=0.12.5,<0.13.0"]
19
+ build-backend = "uv_build"
@@ -0,0 +1,19 @@
1
+ [project]
2
+ name = "visppy"
3
+ version = "0.0.1"
4
+ description = "TODO"
5
+ readme = "README.md"
6
+ requires-python = ">=3.12"
7
+ dependencies = [
8
+ "coredeco>=1.1.0",
9
+ "mss>=10.2.0",
10
+ "ntmemoryapi>=2.6.0",
11
+ "pillow>=12.3.0",
12
+ ]
13
+
14
+ [project.scripts]
15
+ visppy = "visppy:main"
16
+
17
+ [build-system]
18
+ requires = ["uv_build>=0.12.5,<0.13.0"]
19
+ build-backend = "uv_build"
@@ -0,0 +1,138 @@
1
+ import io
2
+ import os
3
+ import mss
4
+ import coredeco
5
+ import PIL.Image
6
+ import dataclasses
7
+ import ntmemoryapi
8
+
9
+ # Local imports
10
+ from . import embed
11
+ from . import windows
12
+
13
+ # Custom types
14
+ type Rect[T] = tuple[T, T, T, T]
15
+ type RGBColor[T] = tuple[T, T, T]
16
+
17
+
18
+ # ==----------------------------------------------------------------== #
19
+ # Dataclasses #
20
+ # ==----------------------------------------------------------------== #
21
+ @dataclasses.dataclass(frozen=True, slots=True)
22
+ class BoudingBox:
23
+ """Dataclass that contains information about found image bouding box and confidence."""
24
+
25
+ # Initial confidence score with which image was found
26
+ confidence: float
27
+
28
+ # Position information
29
+ x: int
30
+ y: int
31
+ w: int
32
+ h: int
33
+
34
+ # ==--------------------------------== #
35
+ # Properties #
36
+ # ==--------------------------------== #
37
+ @property
38
+ def center(self) -> tuple[int, int]:
39
+ """Locate position center."""
40
+
41
+ # Return position
42
+ return self.x + self.w // 2, self.y + self.h // 2
43
+
44
+
45
+ # ==----------------------------------------------------------------== #
46
+ # Classes #
47
+ # ==----------------------------------------------------------------== #
48
+ class VisionEngine:
49
+ """Capture dispay screen, search needle images on haystack image or display screen capture."""
50
+
51
+ # ==--------------------------------== #
52
+ # Public methods #
53
+ # ==--------------------------------== #
54
+ def __init__(self, tracking_window: str | None = None) -> None:
55
+ """Create instance of vision engine, capture `tracking_window` region if defined."""
56
+
57
+ # Save initializer arguments as instance attributes
58
+ self.tracking_window = tracking_window
59
+
60
+ # Instance inner attributes
61
+ self.image_cache = {}
62
+ self.screenshoter = mss.MSS()
63
+
64
+ def screenshot(self, region: Rect[int] | None = None) -> PIL.Image.Image:
65
+ """Get display screenshot."""
66
+
67
+ # Grab display screenshot
68
+ screengrab = self.screenshoter.grab(self.screenshoter.monitors[1] if (region := self.__get_region(region)) is None else {
69
+ key: value for key, value in zip(["left", "top", "width", "height"], region)
70
+ })
71
+
72
+ # Convert screenshot to RGB format image and return
73
+ return PIL.Image.frombuffer(
74
+ "RGB", screengrab.size, screengrab.bgra, "raw", "BGRX"
75
+ )
76
+
77
+ def pixel(self, x: int, y: int, *, cache_haystack: bool = False, haystack: io.BytesIO | PIL.Image.Image | str | None = None) -> RGBColor[int]:
78
+ """Get pixel color on an image, make display screenshot and use it as haystack if `haystack` is `None`."""
79
+
80
+ # Get image to get pixel information
81
+ image = self.screenshot() if haystack is None else self.__load_image(haystack, cache_haystack)
82
+
83
+ # Get pixel information
84
+ return image.getpixel((x, y))
85
+
86
+ # ==--------------------------------== #
87
+ # Private methods #
88
+ # ==--------------------------------== #
89
+ def __get_region(self, region: Rect[int] | None = None) -> Rect[int] | None:
90
+ """Get region for capture."""
91
+
92
+ # If tracking window is not defined
93
+ if self.tracking_window is None or region is not None:
94
+ return region
95
+
96
+ # Retrieve window information
97
+ window_information = windows.get_window_position(self.tracking_window)["visible_area"]
98
+
99
+ # If window if out of the screen
100
+ if window_information["w"] <= 0 or window_information["h"] <= 0:
101
+ raise RuntimeError(f"Window {self.tracking_window} is out of the screen")
102
+
103
+ return tuple(window_information.values())
104
+
105
+ def __load_image(self, data: io.BytesIO | PIL.Image.Image | str, cache_image: bool = False) -> PIL.Image.Image:
106
+ """Load `PIL.Image.Image` instance from given data."""
107
+
108
+ # If data is already an image
109
+ if isinstance(data, PIL.Image.Image):
110
+ return data
111
+
112
+ # If data is not an image
113
+ if isinstance(data, (io.BytesIO, str)):
114
+
115
+ # If image caching required
116
+ if cache_image:
117
+
118
+ # If image is not cached
119
+ if data not in self.image_cache:
120
+
121
+ # Open image and load it
122
+ image = PIL.Image.open(data)
123
+ image.load()
124
+
125
+ # Save image to cache
126
+ self.image_cache[data] = image
127
+
128
+ # Return image from cache
129
+ return self.image_cache[data]
130
+
131
+ # Open image and load it
132
+ image = PIL.Image.open(data)
133
+ image.load()
134
+
135
+ return image
136
+
137
+ # Data is not an image
138
+ raise ValueError("Data can't be converted into pillow image")
File without changes
@@ -0,0 +1,177 @@
1
+ import ctypes
2
+
3
+
4
+ # ==----------------------------------------------------------------== #
5
+ # C-structures #
6
+ # ==----------------------------------------------------------------== #
7
+ class Rect(ctypes.Structure):
8
+ """Structure that contains window rect position."""
9
+
10
+ _fields_ = [
11
+ ("left", ctypes.c_long),
12
+ ("top", ctypes.c_long),
13
+ ("right", ctypes.c_long),
14
+ ("bottom", ctypes.c_long),
15
+ ]
16
+
17
+
18
+ class DevMode(ctypes.Structure):
19
+ """Structure that contains display information."""
20
+
21
+ _fields_ = [
22
+ ("dm_device_name", ctypes.c_wchar * 32),
23
+ ("dm_spec_version", ctypes.c_ushort),
24
+ ("dm_driver_version", ctypes.c_ushort),
25
+ ("dm_size", ctypes.c_ushort),
26
+ ("dm_driver_extra", ctypes.c_ushort),
27
+ ("dm_fields", ctypes.c_ulong),
28
+ ("dm_position_x", ctypes.c_long),
29
+ ("dm_Position_y", ctypes.c_long),
30
+ ("dm_display_orientation", ctypes.c_ulong),
31
+ ("dm_display_fixed_output", ctypes.c_ulong),
32
+ ("dm_color", ctypes.c_short),
33
+ ("dm_duplex", ctypes.c_short),
34
+ ("dm_y_resolution", ctypes.c_short),
35
+ ("dm_tt_option", ctypes.c_short),
36
+ ("dm_collate", ctypes.c_short),
37
+ ("dm_form_name", ctypes.c_wchar * 32),
38
+ ("dm_log_pixels", ctypes.c_ushort),
39
+ ("dm_bits_per_pel", ctypes.c_ulong),
40
+ ("dm_pels_width", ctypes.c_ulong),
41
+ ("dm_pels_height", ctypes.c_ulong),
42
+ ]
43
+
44
+
45
+ # ==----------------------------------------------------------------== #
46
+ # Functions #
47
+ # ==----------------------------------------------------------------== #
48
+ def window_exists(window_title: str) -> bool:
49
+ """Check if window with given title exist."""
50
+
51
+ return bool(ctypes.windll.user32.FindWindowW(None, window_title))
52
+
53
+
54
+ def set_window_title(window_title: str, new_window_title: str) -> None:
55
+ """Set new title for window with given title."""
56
+
57
+ # If window not found
58
+ if not (window_descriptor := ctypes.windll.user32.FindWindowW(None, window_title)):
59
+ raise ctypes.WinError(descr="Unable to find window with `%s` title" % window_title)
60
+
61
+ # Set new window title
62
+ if not ctypes.windll.user32.SetWindowTextW(window_descriptor, new_window_title):
63
+ raise ctypes.WinError(descr="Unable to set `%s` title for window with `%s` title" % (new_window_title, window_title))
64
+
65
+
66
+ def set_window_position(window_title: str, position: tuple[int, int]) -> None:
67
+ """Set new position for window with given title."""
68
+
69
+ # If window not found
70
+ if not (window_descriptor := ctypes.windll.user32.FindWindowW(None, window_title)):
71
+ raise ctypes.WinError(descr="Unable to find window with `%s` title" % window_title)
72
+
73
+ # Set new window position
74
+ if not ctypes.windll.user32.SetWindowPos(window_descriptor, None, *position, 0, 0, 0x1):
75
+ raise ctypes.WinError(descr="Unable to set position for window with `%s` title" % window_title)
76
+
77
+
78
+ def set_window_size(window_title: str, size: tuple[int, int]) -> None:
79
+ """Set new size for window with given title."""
80
+
81
+ # If window not found
82
+ if not (window_descriptor := ctypes.windll.user32.FindWindowW(None, window_title)):
83
+ raise ctypes.WinError(descr="Unable to find window with `%s` title" % window_title)
84
+
85
+ # Set new window size
86
+ if not ctypes.windll.user32.SetWindowPos(window_descriptor, None, 0, 0, *size, 0x2):
87
+ raise ctypes.WinError(descr="Unable to set size for window with `%s` title" % window_title)
88
+
89
+
90
+ def set_window_foreground(window_title: str) -> None:
91
+ """Set window with given title foreground."""
92
+
93
+ # If window not found
94
+ if not (window_descriptor := ctypes.windll.user32.FindWindowW(None, window_title)):
95
+ raise ctypes.WinError(descr="Unable to find window with `%s` title" % window_title)
96
+
97
+ # Set window foreground
98
+ if not ctypes.windll.user32.SetForegroundWindow(window_descriptor):
99
+ raise ctypes.WinError(descr="Unable o set window with `%s` title foreground" % window_title)
100
+
101
+
102
+ def set_window_frameless(window_title: str) -> None:
103
+ """Set window with given title frameless."""
104
+
105
+ # If window not found
106
+ if not (window_descriptor := ctypes.windll.user32.FindWindowW(None, window_title)):
107
+ raise ctypes.WinError(descr="Unable to find window with `%s` title" % window_title)
108
+
109
+ # If unable to retrieve windows styles
110
+ if not (window_style := ctypes.windll.user32.GetWindowLongW(window_descriptor, -16)):
111
+ raise ctypes.WinError(descr="Unable to retrieve styles for window with `%s` title" % window_title)
112
+
113
+ # If unable to update windows styles
114
+ if not ctypes.windll.user32.SetWindowLongW(window_descriptor, -16, window_style & ~(0x10000 | 0x20000 | 0x40000 | 0x80000 | 0xC00000)):
115
+ raise ctypes.WinError(descr="Unable to update styles for window with `%s` tltle" % window_title)
116
+
117
+ # If unable to update window to apply styles
118
+ if not ctypes.windll.user32.SetWindowPos(window_descriptor, None, 0, 0, 0, 0, 0x27):
119
+ raise ctypes.WinError(descr="Unable to update window with `%s` title to apply styles" % window_title)
120
+
121
+
122
+ def get_window_position(window_title: str) -> dict[str, dict | int]:
123
+ """Get window position information that contains its absolute screen coordinates, size and visible area."""
124
+
125
+ # If window not found
126
+ if not (window_descriptor := ctypes.windll.user32.FindWindowW(None, window_title)):
127
+ raise ctypes.WinError(descr="Unable to find window with `%s` title" % window_title)
128
+
129
+ # If unable to retrieve physical dispaly resolutions
130
+ if not ctypes.windll.user32.EnumDisplaySettingsW(None, 0xFFFFFFFF, ctypes.byref(devmode := DevMode(dmSize=ctypes.sizeof(DevMode), dmFields=0x00040000 | 0x00080000))):
131
+ raise ctypes.WinError(descr="Unable to retrieve physical display information")
132
+
133
+ # If unable to retrieve window information
134
+ if ctypes.windll.dwmapi.DwmGetWindowAttribute(window_descriptor, 9, ctypes.byref(window_rect := Rect()), ctypes.sizeof(window_rect)):
135
+ raise ctypes.WinError(descr="Unable to retrieve information for window with `%s` title" % window_title)
136
+
137
+ # Result dict
138
+ result = {"visible_area": {"x": 0, "y": 0, "w": 0, "h": 0}}
139
+
140
+ # Visible area of window section
141
+ visible_area = result["visible_area"]
142
+
143
+ # Save window position
144
+ result["x"] = window_rect.left
145
+ result["y"] = window_rect.top
146
+
147
+ # Save window size
148
+ result["w"] = window_rect.right - window_rect.left
149
+ result["h"] = window_rect.bottom - window_rect.top
150
+
151
+ # Get window visible area horizontaly
152
+ if result["x"] < 0:
153
+ visible_area["x"] = 0
154
+ visible_area["w"] = result["w"] + result["x"]
155
+
156
+ elif result["x"] + result["w"] > devmode.dm_pels_width:
157
+ visible_area["x"] = result["x"]
158
+ visible_area["w"] = result["w"] - (result["w"] + result["x"] - devmode.dm_pels_width)
159
+
160
+ else:
161
+ visible_area["x"] = result["x"]
162
+ visible_area["w"] = result["w"]
163
+
164
+ # Get window visible area verticaly
165
+ if result["y"] < 0:
166
+ visible_area["y"] = 0
167
+ visible_area["h"] = result["h"] + result["y"]
168
+
169
+ elif result["y"] + result["h"] > devmode.dm_pels_height:
170
+ visible_area["y"] = result["y"]
171
+ visible_area["h"] = result["h"] - (result["h"] + result["y"] - devmode.dm_pels_height)
172
+
173
+ else:
174
+ visible_area["y"] = result["y"]
175
+ visible_area["h"] = result["h"]
176
+
177
+ return result