kotonebot 0.3.1__py3-none-any.whl → 0.5.0__py3-none-any.whl

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.
Files changed (66) hide show
  1. kotonebot/__init__.py +39 -39
  2. kotonebot/backend/bot.py +312 -302
  3. kotonebot/backend/color.py +525 -525
  4. kotonebot/backend/context/__init__.py +3 -3
  5. kotonebot/backend/context/context.py +49 -56
  6. kotonebot/backend/context/task_action.py +183 -175
  7. kotonebot/backend/core.py +129 -126
  8. kotonebot/backend/debug/entry.py +89 -89
  9. kotonebot/backend/debug/mock.py +78 -78
  10. kotonebot/backend/debug/server.py +222 -222
  11. kotonebot/backend/debug/vars.py +351 -351
  12. kotonebot/backend/dispatch.py +227 -227
  13. kotonebot/backend/flow_controller.py +196 -196
  14. kotonebot/backend/loop.py +12 -88
  15. kotonebot/backend/ocr.py +535 -529
  16. kotonebot/backend/preprocessor.py +103 -103
  17. kotonebot/client/__init__.py +9 -9
  18. kotonebot/client/device.py +528 -502
  19. kotonebot/client/fast_screenshot.py +377 -377
  20. kotonebot/client/host/__init__.py +43 -12
  21. kotonebot/client/host/adb_common.py +107 -94
  22. kotonebot/client/host/custom.py +118 -114
  23. kotonebot/client/host/leidian_host.py +196 -201
  24. kotonebot/client/host/mumu12_host.py +353 -358
  25. kotonebot/client/host/protocol.py +214 -213
  26. kotonebot/client/host/windows_common.py +58 -55
  27. kotonebot/client/implements/__init__.py +71 -7
  28. kotonebot/client/implements/adb.py +89 -85
  29. kotonebot/client/implements/adb_raw.py +162 -158
  30. kotonebot/client/implements/nemu_ipc/__init__.py +11 -7
  31. kotonebot/client/implements/nemu_ipc/external_renderer_ipc.py +284 -284
  32. kotonebot/client/implements/nemu_ipc/nemu_ipc.py +327 -327
  33. kotonebot/client/implements/remote_windows.py +188 -192
  34. kotonebot/client/implements/uiautomator2.py +85 -81
  35. kotonebot/client/implements/windows.py +176 -168
  36. kotonebot/client/protocol.py +69 -69
  37. kotonebot/client/registration.py +24 -24
  38. kotonebot/config/base_config.py +96 -96
  39. kotonebot/config/manager.py +36 -36
  40. kotonebot/errors.py +76 -71
  41. kotonebot/interop/win/__init__.py +10 -0
  42. kotonebot/interop/win/_mouse.py +311 -0
  43. kotonebot/interop/win/message_box.py +313 -313
  44. kotonebot/interop/win/reg.py +37 -37
  45. kotonebot/interop/win/shortcut.py +43 -43
  46. kotonebot/interop/win/task_dialog.py +513 -469
  47. kotonebot/logging/__init__.py +2 -2
  48. kotonebot/logging/log.py +17 -17
  49. kotonebot/primitives/__init__.py +17 -17
  50. kotonebot/primitives/geometry.py +862 -290
  51. kotonebot/primitives/visual.py +63 -63
  52. kotonebot/tools/mirror.py +354 -354
  53. kotonebot/ui/file_host/sensio.py +36 -36
  54. kotonebot/ui/file_host/tmp_send.py +54 -54
  55. kotonebot/ui/pushkit/__init__.py +3 -3
  56. kotonebot/ui/pushkit/image_host.py +88 -87
  57. kotonebot/ui/pushkit/protocol.py +13 -13
  58. kotonebot/ui/pushkit/wxpusher.py +54 -53
  59. kotonebot/ui/user.py +148 -143
  60. kotonebot/util.py +436 -409
  61. {kotonebot-0.3.1.dist-info → kotonebot-0.5.0.dist-info}/METADATA +82 -76
  62. kotonebot-0.5.0.dist-info/RECORD +71 -0
  63. {kotonebot-0.3.1.dist-info → kotonebot-0.5.0.dist-info}/licenses/LICENSE +673 -673
  64. kotonebot-0.3.1.dist-info/RECORD +0 -70
  65. {kotonebot-0.3.1.dist-info → kotonebot-0.5.0.dist-info}/WHEEL +0 -0
  66. {kotonebot-0.3.1.dist-info → kotonebot-0.5.0.dist-info}/top_level.txt +0 -0
@@ -1,290 +1,862 @@
1
- from typing import Generic, TypeVar, TypeGuard, overload
2
-
3
- T = TypeVar('T')
4
-
5
- class Vector2D(Generic[T]):
6
- """2D 坐标类"""
7
- def __init__(self, x: T, y: T, *, name: str | None = None):
8
- self.x = x
9
- self.y = y
10
- self.name: str | None = name
11
- """坐标的名称。"""
12
-
13
- def __getitem__(self, item: int):
14
- if item == 0:
15
- return self.x
16
- elif item == 1:
17
- return self.y
18
- else:
19
- raise IndexError
20
-
21
- def __repr__(self) -> str:
22
- return f'Point<"{self.name}" at ({self.x}, {self.y})>'
23
-
24
- def __str__(self) -> str:
25
- return f'({self.x}, {self.y})'
26
-
27
-
28
- class Vector3D(Generic[T]):
29
- """三元组类。"""
30
- def __init__(self, x: T, y: T, z: T, *, name: str | None = None):
31
- self.x = x
32
- self.y = y
33
- self.z = z
34
- self.name: str | None = name
35
- """坐标的名称。"""
36
-
37
- def __getitem__(self, item: int):
38
- if item == 0:
39
- return self.x
40
- elif item == 1:
41
- return self.y
42
- elif item == 2:
43
- return self.z
44
- else:
45
- raise IndexError
46
-
47
- @property
48
- def xyz(self) -> tuple[T, T, T]:
49
- """
50
- 三元组 (x, y, z)。OpenCV 格式的坐标。
51
- """
52
- return self.x, self.y, self.z
53
-
54
- @property
55
- def xy(self) -> tuple[T, T]:
56
- """
57
- 二元组 (x, y)。OpenCV 格式的坐标。
58
- """
59
- return self.x, self.y
60
-
61
- class Vector4D(Generic[T]):
62
- """四元组类。"""
63
- def __init__(self, x: T, y: T, z: T, w: T, *, name: str | None = None):
64
- self.x = x
65
- self.y = y
66
- self.z = z
67
- self.w = w
68
- self.name: str | None = name
69
- """坐标的名称。"""
70
-
71
- def __getitem__(self, item: int):
72
- if item == 0:
73
- return self.x
74
- elif item == 1:
75
- return self.y
76
- elif item == 2:
77
- return self.z
78
- elif item == 3:
79
- return self.w
80
- else:
81
- raise IndexError
82
-
83
- Size = Vector2D[int]
84
- """尺寸。相当于 Vector2D[int]"""
85
- RectTuple = tuple[int, int, int, int]
86
- """矩形。(x, y, w, h)"""
87
- PointTuple = tuple[int, int]
88
- """点。(x, y)"""
89
-
90
- class Point(Vector2D[int]):
91
- """点。"""
92
-
93
- @property
94
- def xy(self) -> PointTuple:
95
- """
96
- 二元组 (x, y)。OpenCV 格式的坐标。
97
- """
98
- return self.x, self.y
99
-
100
- def offset(self, dx: int, dy: int) -> 'Point':
101
- """
102
- 偏移坐标。
103
-
104
- :param dx: 偏移量。
105
- :param dy: 偏移量。
106
- :return: 偏移后的坐标。
107
- """
108
- return Point(self.x + dx, self.y + dy, name=self.name)
109
-
110
- def __add__(self, other: 'Point | PointTuple') -> 'Point':
111
- """
112
- 相加。
113
-
114
- :param other: 另一个 Point 对象或二元组 (x: int, y: int)
115
- :return: 相加后的点。
116
- """
117
- if isinstance(other, Point):
118
- return Point(self.x + other.x, self.y + other.y, name=self.name)
119
- else:
120
- return Point(self.x + other[0], self.y + other[1], name=self.name)
121
-
122
- def __sub__(self, other: 'Point | PointTuple') -> 'Point':
123
- """
124
- 相减。
125
-
126
- :param other: 另一个 Point 对象或二元组 (x: int, y: int)。
127
- :return: 相减后的点。
128
- """
129
- if isinstance(other, Point):
130
- return Point(self.x - other.x, self.y - other.y, name=self.name)
131
- else:
132
- return Point(self.x - other[0], self.y - other[1], name=self.name)
133
-
134
- class Rect:
135
- """
136
- 矩形类。
137
- """
138
- def __init__(
139
- self,
140
- x: int | None = None,
141
- y: int | None = None,
142
- w: int | None = None,
143
- h: int | None = None,
144
- *,
145
- xywh: RectTuple | None = None,
146
- name: str | None = None,
147
- ):
148
- """
149
- 从给定的坐标信息创建矩形。
150
-
151
- 参数 `x`, `y`, `w`, `h` 和 `xywh` 必须至少指定一组。
152
-
153
- :param x: 矩形左上角的 X 坐标。
154
- :param y: 矩形左上角的 Y 坐标。
155
- :param w: 矩形的宽度。
156
- :param h: 矩形的高度。
157
- :param xywh: 四元组 (x, y, w, h)。
158
- :param name: 矩形的名称。
159
- :raises ValueError: 提供的坐标参数不完整时抛出。
160
- """
161
- if xywh is not None:
162
- x, y, w, h = xywh
163
- elif (
164
- x is not None and
165
- y is not None and
166
- w is not None and
167
- h is not None
168
- ):
169
- pass
170
- else:
171
- raise ValueError('Either xywh or x, y, w, h must be provided.')
172
-
173
- self.x1 = x
174
- """矩形左上角的 X 坐标。"""
175
- self.y1 = y
176
- """矩形左上角的 Y 坐标。"""
177
- self.w = w
178
- """矩形的宽度。"""
179
- self.h = h
180
- """矩形的高度。"""
181
- self.name: str | None = name
182
- """矩形的名称。"""
183
-
184
- @classmethod
185
- def from_xyxy(cls, x1: int, y1: int, x2: int, y2: int) -> 'Rect':
186
- """
187
- (x1, y1, x2, y2) 创建矩形。
188
- :return: 创建结果。
189
- """
190
- return cls(x1, y1, x2 - x1, y2 - y1)
191
-
192
- @property
193
- def x2(self) -> int:
194
- """矩形右下角的 X 坐标。"""
195
- return self.x1 + self.w
196
-
197
- @x2.setter
198
- def x2(self, value: int):
199
- self.w = value - self.x1
200
-
201
- @property
202
- def y2(self) -> int:
203
- """矩形右下角的 Y 坐标。"""
204
- return self.y1 + self.h
205
-
206
- @y2.setter
207
- def y2(self, value: int):
208
- self.h = value - self.y1
209
-
210
- @property
211
- def xywh(self) -> RectTuple:
212
- """
213
- 四元组 (x1, y1, w, h)。OpenCV 格式的坐标。
214
- """
215
- return self.x1, self.y1, self.w, self.h
216
-
217
- @property
218
- def xyxy(self) -> RectTuple:
219
- """
220
- 四元组 (x1, y1, x2, y2)。
221
- """
222
- return self.x1, self.y1, self.x2, self.y2
223
-
224
- @property
225
- def top_left(self) -> Point:
226
- """
227
- 矩形的左上角点。
228
- """
229
- if self.name:
230
- name = "Left-top of rect "+ self.name
231
- else:
232
- name = None
233
- return Point(self.x1, self.y1, name=name)
234
-
235
- @property
236
- def bottom_right(self) -> Point:
237
- """
238
- 矩形的右下角点。
239
- """
240
- if self.name:
241
- name = "Right-bottom of rect "+ self.name
242
- else:
243
- name = None
244
- return Point(self.x2, self.y2, name=name)
245
-
246
- @property
247
- def left_bottom(self) -> Point:
248
- """
249
- 矩形的左下角点。
250
- """
251
- if self.name:
252
- name = "Left-bottom of rect "+ self.name
253
- else:
254
- name = None
255
- return Point(self.x1, self.y2, name=name)
256
-
257
- @property
258
- def right_top(self) -> Point:
259
- """
260
- 矩形的右上角点。
261
- """
262
- if self.name:
263
- name = "Right-top of rect "+ self.name
264
- else:
265
- name = None
266
- return Point(self.x2, self.y1, name=name)
267
-
268
- @property
269
- def center(self) -> Point:
270
- """
271
- 矩形的中心点。
272
- """
273
- if self.name:
274
- name = "Center of rect "+ self.name
275
- else:
276
- name = None
277
- return Point(self.x1 + self.w // 2, self.y1 + self.h // 2, name=name)
278
-
279
- def __repr__(self) -> str:
280
- return f'Rect<"{self.name}" at (x={self.x1}, y={self.y1}, w={self.w}, h={self.h})>'
281
-
282
- def __str__(self) -> str:
283
- return f'(x={self.x1}, y={self.y1}, w={self.w}, h={self.h})'
284
-
285
-
286
- def is_point(obj: object) -> TypeGuard[Point]:
287
- return isinstance(obj, Point)
288
-
289
- def is_rect(obj: object) -> TypeGuard[Rect]:
290
- return isinstance(obj, Rect)
1
+ """
2
+ # 几何基础模块 (geometry)
3
+
4
+ 此模块提供了用于处理几何图形和坐标系统的基础类,包括点、向量、矩形等几何对象的表示和操作。
5
+
6
+ ## 主要功能
7
+
8
+ - **坐标系统**:提供二维、三维、四维坐标的表示
9
+ - **点操作**:支持整数和浮点数坐标点的各种运算
10
+ - **矩形操作**:提供矩形的创建、变换、相交检测等功能
11
+ - **向量运算**:支持向量的加减乘除、距离计算、单位化等操作
12
+ - **类型安全**:使用泛型和类型守卫确保类型安全
13
+
14
+ ## 核心类
15
+
16
+ - `Vector2D` - 二维向量,支持泛型
17
+ - `Vector3D` - 三维向量,适用于颜色和空间坐标
18
+ - `Vector4D` - 四维向量,适用于 RGBA 等场景
19
+ - `Point` - 整数坐标点,适用于像素定位
20
+ - `PointF` - 浮点数坐标点,适用于精确计算
21
+ - `Rect` - 矩形类,提供丰富的几何操作
22
+
23
+ ## 使用示例
24
+
25
+ ```python
26
+ # 坐标点操作
27
+ p1 = Point(100, 200, name="起始点")
28
+ p2 = PointF(150.5, 250.8)
29
+ distance = p1.distance_to(p2)
30
+
31
+ # 矩形操作
32
+ rect = Rect(10, 20, 100, 50, name="按钮区域")
33
+ center = rect.center
34
+ enlarged = rect.inflate(5, 5)
35
+
36
+ # 向量运算
37
+ v1 = Vector2D(10, 20)
38
+ v2 = Vector2D(5, 8)
39
+ result = v1 + v2
40
+ ```
41
+
42
+ ## 注意事项
43
+
44
+ - 所有坐标系统遵循屏幕坐标系,原点在左上角
45
+ - 矩形操作使用半开区间规则:[x1, x2) x [y1, y2)
46
+ - 点和向量的运算会根据输入类型自动选择返回类型
47
+ """
48
+
49
+ import math
50
+ from typing import Generic, TypeVar, TypeGuard, overload, Union
51
+
52
+ T = TypeVar('T')
53
+
54
+ class Vector2D(Generic[T]):
55
+ """
56
+ ## Vector2D
57
+ 表示一个二维向量。此类支持泛型,可用于表示不同类型的坐标(如整数、浮点数等)。
58
+
59
+ ###
60
+ ```python
61
+ # 创建二维坐标
62
+ v = Vector2D(10, 20, name="位置")
63
+ print(v.x, v.y) # 10, 20
64
+
65
+ # 使用索引访问
66
+ print(v[0], v[1]) # 10, 20
67
+
68
+ # 坐标计算
69
+ v2 = Vector2D(5, 8)
70
+ print(f"坐标距离: {math.sqrt((v.x - v2.x)**2 + (v.y - v2.y)**2):.2f}")
71
+
72
+ # 支持命名坐标
73
+ center = Vector2D(640, 360, name="屏幕中心")
74
+ print(center) # Point<"屏幕中心" at (640, 360)>
75
+ ```
76
+ """
77
+ def __init__(self, x: T, y: T, *, name: str | None = None):
78
+ self.x = x
79
+ self.y = y
80
+ self.name: str | None = name
81
+ """坐标的名称。"""
82
+
83
+ def __getitem__(self, item: int):
84
+ if item == 0:
85
+ return self.x
86
+ elif item == 1:
87
+ return self.y
88
+ else:
89
+ raise IndexError
90
+
91
+ def __repr__(self) -> str:
92
+ return f'Point<"{self.name}" at ({self.x}, {self.y})>'
93
+
94
+ def __str__(self) -> str:
95
+ return f'({self.x}, {self.y})'
96
+
97
+
98
+ class Vector3D(Generic[T]):
99
+ """
100
+ ## Vector3D
101
+ 表示一个三维向量。
102
+
103
+ ### 例
104
+ ```python
105
+ # 创建三维坐标
106
+ v3 = Vector3D(100, 200, 50, name="3D点")
107
+ print(v3.x, v3.y, v3.z) # 100, 200, 50
108
+
109
+ # 使用索引访问
110
+ print(v3[0], v3[1], v3[2]) # 100, 200, 50
111
+
112
+ # 解构
113
+ x, y, z = v3.xyz # (100, 200, 50)
114
+ x, y = v3.xy # (100, 200)
115
+
116
+ # 颜色值应用
117
+ rgb = Vector3D(255, 128, 64, name="颜色")
118
+ print(f"RGB: {rgb.rgb if hasattr(v3, 'rgb') else '未定义'}")
119
+ ```
120
+ """
121
+ def __init__(self, x: T, y: T, z: T, *, name: str | None = None):
122
+ self.x = x
123
+ self.y = y
124
+ self.z = z
125
+ self.name: str | None = name
126
+ """坐标的名称。"""
127
+
128
+ def __getitem__(self, item: int):
129
+ if item == 0:
130
+ return self.x
131
+ elif item == 1:
132
+ return self.y
133
+ elif item == 2:
134
+ return self.z
135
+ else:
136
+ raise IndexError
137
+
138
+ @property
139
+ def xyz(self) -> tuple[T, T, T]:
140
+ """
141
+ 三元组 (x, y, z)。OpenCV 格式的坐标。
142
+ """
143
+ return self.x, self.y, self.z
144
+
145
+ @property
146
+ def xy(self) -> tuple[T, T]:
147
+ """
148
+ 二元组 (x, y)。OpenCV 格式的坐标。
149
+ """
150
+ return self.x, self.y
151
+
152
+ class Vector4D(Generic[T]):
153
+ """
154
+ ## Vector4D
155
+ 此类用于表示四维坐标或向量,通常用于颜色空间(如RGBA)等场景。
156
+
157
+ ###
158
+ ```python
159
+ # 创建四维坐标
160
+ v4 = Vector4D(100, 200, 150, 255, name="颜色值")
161
+ print(f"RGBA: {v4.x}, {v4.y}, {v4.z}, {v4.w}") # 100, 200, 150, 255
162
+
163
+ # 使用索引访问
164
+ print(v4[0], v4[1], v4[2], v4[3]) # 100, 200, 150, 255
165
+
166
+ # 在颜色空间中使用
167
+ color = Vector4D(255, 0, 0, 128, name="半透明红色")
168
+ print(f"颜色: {color}")
169
+
170
+ # 四维向量运算
171
+ v4_2 = Vector4D(50, 50, 50, 100)
172
+ # 注意:Vector4D 未定义运算,但可用于数据存储
173
+ ```
174
+ """
175
+ def __init__(self, x: T, y: T, z: T, w: T, *, name: str | None = None):
176
+ self.x = x
177
+ self.y = y
178
+ self.z = z
179
+ self.w = w
180
+ self.name: str | None = name
181
+ """坐标的名称。"""
182
+
183
+ def __getitem__(self, item: int):
184
+ if item == 0:
185
+ return self.x
186
+ elif item == 1:
187
+ return self.y
188
+ elif item == 2:
189
+ return self.z
190
+ elif item == 3:
191
+ return self.w
192
+ else:
193
+ raise IndexError
194
+
195
+ Size = Vector2D[int]
196
+ """尺寸。相当于 Vector2D[int]"""
197
+ RectTuple = tuple[int, int, int, int]
198
+ """矩形。(x, y, w, h)"""
199
+ PointTuple = tuple[int, int]
200
+ """点。(x, y)"""
201
+ PointFTuple = tuple[float, float]
202
+ """浮点数点。(x, y)"""
203
+
204
+
205
+ Number = TypeVar('Number', int, float)
206
+ """数字类型,可以是整数或浮点数。"""
207
+ NumberTuple2D = tuple[Number, Number]
208
+ class _BasePoint(Vector2D[Number]):
209
+ @property
210
+ def xy(self) -> NumberTuple2D:
211
+ """
212
+ 二元组 (x, y)。
213
+
214
+ :return: 包含 x 和 y 坐标的元组。
215
+ """
216
+ return self.x, self.y
217
+
218
+ @property
219
+ def length(self) -> float:
220
+ """
221
+ 将点视为从原点出发的向量,其长度(模)。
222
+
223
+ :return: 向量长度。
224
+ """
225
+ return math.sqrt(self.x**2 + self.y**2)
226
+
227
+ def distance_to(self, other: 'AnyPoint | AnyPointTuple') -> float:
228
+ """
229
+ 计算到另一个点的距离。
230
+
231
+ :param other: 另一个点或元组。
232
+ :return: 距离。
233
+ """
234
+ return math.sqrt((self.x - other[0])**2 + (self.y - other[1])**2)
235
+
236
+ def __eq__(self, value: object) -> bool:
237
+ """
238
+ 比较两个点是否相等。
239
+
240
+ :param value: 另一个点。
241
+ :return: 如果坐标相等则返回 True,否则返回 False。
242
+ """
243
+ if isinstance(value, _BasePoint):
244
+ return self.x == value.x and self.y == value.y
245
+ return False
246
+
247
+ def normalized(self) -> 'PointF':
248
+ """
249
+ 返回一个新的、方向相同但长度为 1 的 `PointF` 对象(单位向量)。
250
+
251
+ :return: 单位向量。
252
+ """
253
+ l = self.length
254
+ if l == 0:
255
+ return PointF(0.0, 0.0, name=self.name)
256
+ return PointF(self.x / l, self.y / l, name=self.name)
257
+
258
+ def offset(self, dx: int | float, dy: int | float) -> 'AnyPoint':
259
+ """
260
+ 偏移坐标。
261
+
262
+ 如果 self, dx, dy 均为整数,返回 Point。否则返回 PointF。
263
+
264
+ :param dx: x方向偏移量。
265
+ :param dy: y方向偏移量。
266
+ :return: 偏移后的新 Point PointF 对象。
267
+ """
268
+ new_x = self.x + dx
269
+ new_y = self.y + dy
270
+ if isinstance(self, PointF) or isinstance(dx, float) or isinstance(dy, float):
271
+ return PointF(new_x, new_y, name=self.name)
272
+ return Point(int(new_x), int(new_y), name=self.name)
273
+
274
+ @overload
275
+ def __add__(self: 'Point', other: 'Point | PointTuple') -> 'Point': ...
276
+ @overload
277
+ def __add__(self, other: 'PointF | PointFTuple') -> 'PointF': ...
278
+ @overload
279
+ def __add__(self: 'PointF', other: 'AnyPoint | AnyPointTuple') -> 'PointF': ...
280
+ def __add__(self, other: 'AnyPoint | AnyPointTuple') -> 'AnyPoint':
281
+ """
282
+ 与另一个点或元组相加。
283
+
284
+ 如果任一操作数为浮点数,则结果提升为 PointF。
285
+
286
+ :param other: 另一个 Point/PointF 对象或元组。
287
+ :return: 相加后的新 Point 或 PointF 对象。
288
+ """
289
+ new_x = self.x + other[0]
290
+ new_y = self.y + other[1]
291
+ # hasattr check for tuple, which does not have .x attribute
292
+ if isinstance(self, PointF) or isinstance(other, PointF) or \
293
+ (not hasattr(other, 'x') and (isinstance(other[0], float) or isinstance(other[1], float))):
294
+ return PointF(new_x, new_y, name=self.name)
295
+ return Point(int(new_x), int(new_y), name=self.name)
296
+
297
+ @overload
298
+ def __sub__(self: 'Point', other: 'Point | PointTuple') -> 'Point': ...
299
+ @overload
300
+ def __sub__(self, other: 'PointF | PointFTuple') -> 'PointF': ...
301
+ @overload
302
+ def __sub__(self: 'PointF', other: 'AnyPoint | AnyPointTuple') -> 'PointF': ...
303
+ def __sub__(self, other: 'AnyPoint | AnyPointTuple') -> 'AnyPoint':
304
+ """
305
+ 与另一个点或元组相减。
306
+
307
+ 如果任一操作数为浮点数,则结果提升为 PointF。
308
+
309
+ :param other: 另一个 Point/PointF 对象或元组。
310
+ :return: 相减后的新 Point 或 PointF 对象。
311
+ """
312
+ new_x = self.x - other[0]
313
+ new_y = self.y - other[1]
314
+ if isinstance(self, PointF) or isinstance(other, PointF) or \
315
+ (not hasattr(other, 'x') and (isinstance(other[0], float) or isinstance(other[1], float))):
316
+ return PointF(new_x, new_y, name=self.name)
317
+ return Point(int(new_x), int(new_y), name=self.name)
318
+
319
+ @overload
320
+ def __mul__(self: 'Point', scalar: int) -> 'Point': ...
321
+ @overload
322
+ def __mul__(self, scalar: float) -> 'PointF': ...
323
+ def __mul__(self, scalar: int | float) -> 'AnyPoint':
324
+ """
325
+ 与标量相乘(缩放)。
326
+
327
+ :param scalar: 用于缩放的标量。
328
+ :return: 缩放后的新 Point 或 PointF 对象。
329
+ """
330
+ new_x = self.x * scalar
331
+ new_y = self.y * scalar
332
+ if isinstance(self, PointF) or isinstance(scalar, float):
333
+ return PointF(new_x, new_y, name=self.name)
334
+ return Point(int(new_x), int(new_y), name=self.name)
335
+
336
+ def __truediv__(self, scalar: int | float) -> 'PointF':
337
+ """
338
+ 与标量相除(缩放)。总是返回一个 PointF 对象。
339
+
340
+ :param scalar: 用于缩放的标量。
341
+ :return: 缩放后的新 PointF 对象。
342
+ """
343
+ if scalar == 0:
344
+ raise ValueError("Cannot divide by zero")
345
+ return PointF(self.x / scalar, self.y / scalar, name=self.name)
346
+
347
+ class PointF(_BasePoint[float]):
348
+ """
349
+ ## PointF
350
+ 表示浮点数坐标点。
351
+
352
+ ### 例
353
+ ```python
354
+ # 创建浮点数坐标点
355
+ p1 = PointF(10.5, 20.7, name="精确位置")
356
+ p2 = PointF(5.2, 8.9)
357
+
358
+ # 距离计算
359
+ distance = p1.distance_to(p2) # 计算到另一点的距离
360
+ print(f"距离: {distance:.2f}")
361
+
362
+ # 向量运算
363
+ result = p1 + p2 # 坐标相加
364
+ scaled = p1 * 2 # 坐标缩放
365
+
366
+ # 单位向量
367
+ unit = p1.normalized() # 转换为单位向量
368
+
369
+ # 坐标偏移
370
+ moved = p1.offset(5.0, 10.0) # 偏移坐标
371
+ ```
372
+ """
373
+
374
+
375
+ class Point(_BasePoint[int]):
376
+ """
377
+ ## Point
378
+ 表示整数坐标点。
379
+
380
+ ### 例
381
+ ```python
382
+ # 创建整数坐标点
383
+ pixel = Point(1920, 1080, name="屏幕分辨率")
384
+ button = Point(100, 200, name="按钮位置")
385
+
386
+ # 像素定位操作
387
+ center = Point(640, 360)
388
+ offset = center.offset(10, -5) # 向右移动10像素,向下移动5像素
389
+
390
+ # 点与点的运算
391
+ distance = center.distance_to(button)
392
+ relative_pos = button - center # 相对位置
393
+
394
+ # 检查点是否相等
395
+ if center == Point(640, 360):
396
+ print("找到了中心点")
397
+
398
+ # 与浮点数运算时会自动转换为PointF
399
+ precise = center + (5.5, 3.2) # 结果为PointF类型
400
+ ```
401
+ """
402
+
403
+ class Rect:
404
+ """
405
+ ## Rect
406
+ 表示一个矩形区域,支持多种坐标格式和几何操作。
407
+
408
+ ### 例
409
+ ```python
410
+ # 创建矩形
411
+ rect = Rect(10, 20, 100, 50, name="按钮区域")
412
+ rect2 = Rect(xywh=(10, 20, 100, 50))
413
+
414
+ # 从两点创建矩形
415
+ rect3 = Rect.from_xyxy(10, 20, 110, 70)
416
+
417
+ # 获取矩形属性
418
+ print(rect.center) # 中心点
419
+ print(rect.size) # (100, 50)
420
+ print(rect.top_left) # 左上角点,其他三个角落同理
421
+
422
+ # 矩形操作
423
+ moved = rect.move(10, 10) # 原地移动
424
+ copied = rect.moved(5, 15) # 移动后的新矩形
425
+ enlarged = rect.inflate(5, 5) # 原地扩大
426
+
427
+ # 几何计算
428
+ if rect.contains_point(Point(50, 40)):
429
+ print("点在矩形内")
430
+
431
+ if rect.intersects_with(other_rect):
432
+ print("两个矩形相交")
433
+
434
+ union = rect.union_of(other_rect) # 并集
435
+ intersection = rect.intersection_of(other_rect) # 交集
436
+ ```
437
+ """
438
+ def __init__(
439
+ self,
440
+ x: int | None = None,
441
+ y: int | None = None,
442
+ w: int | None = None,
443
+ h: int | None = None,
444
+ *,
445
+ xywh: RectTuple | None = None,
446
+ name: str | None = None,
447
+ ):
448
+ """
449
+ 从给定的坐标信息创建矩形。
450
+
451
+ 参数 `x`, `y`, `w`, `h` 和 `xywh` 必须至少指定一组。
452
+
453
+ :param x: 矩形左上角的 X 坐标。
454
+ :param y: 矩形左上角的 Y 坐标。
455
+ :param w: 矩形的宽度。
456
+ :param h: 矩形的高度。
457
+ :param xywh: 四元组 (x, y, w, h)。
458
+ :param name: 矩形的名称。
459
+ :raises ValueError: 提供的坐标参数不完整时抛出。
460
+ """
461
+ if xywh is not None:
462
+ x, y, w, h = xywh
463
+ elif (
464
+ x is not None and
465
+ y is not None and
466
+ w is not None and
467
+ h is not None
468
+ ):
469
+ pass
470
+ else:
471
+ raise ValueError('Either xywh or x, y, w, h must be provided.')
472
+
473
+ self.x1 = x
474
+ """矩形左上角的 X 坐标。"""
475
+ self.y1 = y
476
+ """矩形左上角的 Y 坐标。"""
477
+ self.w = w
478
+ """矩形的宽度。"""
479
+ self.h = h
480
+ """矩形的高度。"""
481
+ self.name: str | None = name
482
+ """矩形的名称。"""
483
+
484
+ @classmethod
485
+ def from_xyxy(cls, x1: int, y1: int, x2: int, y2: int) -> 'Rect':
486
+ """
487
+ 从 (x1, y1, x2, y2) 创建矩形。
488
+ :return: 创建结果。
489
+ """
490
+ return cls(x1, y1, x2 - x1, y2 - y1)
491
+
492
+ @property
493
+ def x2(self) -> int:
494
+ """矩形右下角的 X 坐标。"""
495
+ return self.x1 + self.w
496
+
497
+ @x2.setter
498
+ def x2(self, value: int):
499
+ self.w = value - self.x1
500
+
501
+ @property
502
+ def y2(self) -> int:
503
+ """矩形右下角的 Y 坐标。"""
504
+ return self.y1 + self.h
505
+
506
+ @y2.setter
507
+ def y2(self, value: int):
508
+ self.h = value - self.y1
509
+
510
+ @property
511
+ def xywh(self) -> RectTuple:
512
+ """
513
+ 四元组 (x1, y1, w, h)。OpenCV 格式的坐标。
514
+ """
515
+ return self.x1, self.y1, self.w, self.h
516
+
517
+ @property
518
+ def xyxy(self) -> RectTuple:
519
+ """
520
+ 四元组 (x1, y1, x2, y2)。
521
+ """
522
+ return self.x1, self.y1, self.x2, self.y2
523
+
524
+ @property
525
+ def top_left(self) -> Point:
526
+ """
527
+ 矩形的左上角点。
528
+ """
529
+ if self.name:
530
+ name = "Left-top of rect "+ self.name
531
+ else:
532
+ name = None
533
+ return Point(self.x1, self.y1, name=name)
534
+
535
+ @property
536
+ def bottom_right(self) -> Point:
537
+ """
538
+ 矩形的右下角点。
539
+ """
540
+ if self.name:
541
+ name = "Right-bottom of rect "+ self.name
542
+ else:
543
+ name = None
544
+ return Point(self.x2, self.y2, name=name)
545
+
546
+ @property
547
+ def left_bottom(self) -> Point:
548
+ """
549
+ 矩形的左下角点。
550
+ """
551
+ if self.name:
552
+ name = "Left-bottom of rect "+ self.name
553
+ else:
554
+ name = None
555
+ return Point(self.x1, self.y2, name=name)
556
+
557
+ @property
558
+ def right_top(self) -> Point:
559
+ """
560
+ 矩形的右上角点。
561
+ """
562
+ if self.name:
563
+ name = "Right-top of rect "+ self.name
564
+ else:
565
+ name = None
566
+ return Point(self.x2, self.y1, name=name)
567
+
568
+ @property
569
+ def center(self) -> Point:
570
+ """
571
+ 矩形的中心点。
572
+
573
+ :return: 中心点 Point 对象。
574
+ """
575
+ if self.name:
576
+ name = "Center of rect "+ self.name
577
+ else:
578
+ name = None
579
+ return Point(int(self.x1 + self.w / 2), int(self.y1 + self.h / 2), name=name)
580
+
581
+ @property
582
+ def center_x(self) -> int:
583
+ """
584
+ 中心点的 x 坐标。
585
+
586
+ :return: 中心点的 x 坐标。
587
+ """
588
+ return self.x1 + self.w // 2
589
+
590
+ @property
591
+ def center_y(self) -> int:
592
+ """
593
+ 中心点的 y 坐标。
594
+
595
+ :return: 中心点的 y 坐标。
596
+ """
597
+ return self.y1 + self.h // 2
598
+
599
+ @property
600
+ def middle_top(self) -> Point:
601
+ """
602
+ 矩形顶部边的中点。
603
+
604
+ :return: 顶部边的中点。
605
+ """
606
+ return Point(self.center_x, self.y1)
607
+
608
+ @property
609
+ def middle_bottom(self) -> Point:
610
+ """
611
+ 矩形底部边的中点。
612
+
613
+ :return: 底部边的中点。
614
+ """
615
+ return Point(self.center_x, self.y2)
616
+
617
+ @property
618
+ def middle_left(self) -> Point:
619
+ """
620
+ 矩形左侧边的中点。
621
+
622
+ :return: 左侧边的中点。
623
+ """
624
+ return Point(self.x1, self.center_y)
625
+
626
+ @property
627
+ def middle_right(self) -> Point:
628
+ """
629
+ 矩形右侧边的中点。
630
+
631
+ :return: 右侧边的中点。
632
+ """
633
+ return Point(self.x2, self.center_y)
634
+
635
+ @property
636
+ def size(self) -> tuple[int, int]:
637
+ """
638
+ 一个 `(width, height)` 元组。
639
+
640
+ :return: 包含宽度和高度的元组。
641
+ """
642
+ return self.w, self.h
643
+
644
+ @size.setter
645
+ def size(self, value: tuple[int, int]):
646
+ """
647
+ 设置矩形的尺寸。
648
+
649
+ :param value: 包含新宽度和新高度的元组。
650
+ """
651
+ self.w, self.h = value
652
+
653
+ def __repr__(self) -> str:
654
+ return f'Rect<"{self.name}" at (x={self.x1}, y={self.y1}, w={self.w}, h={self.h})>'
655
+
656
+ def __str__(self) -> str:
657
+ return f'(x={self.x1}, y={self.y1}, w={self.w}, h={self.h})'
658
+
659
+ def copy(self) -> 'Rect':
660
+ """
661
+ 返回一个与当前对象完全相同的**新** `Rect` 对象。
662
+
663
+ :return: 当前 Rect 对象的一个副本。
664
+ """
665
+ return Rect(self.x1, self.y1, self.w, self.h, name=self.name)
666
+
667
+ def move(self, dx: int, dy: int) -> 'Rect':
668
+ """
669
+ **原地**移动矩形。
670
+
671
+ :param dx: x 方向的移动距离。
672
+ :param dy: y 方向的移动距离。
673
+ :return: 移动后的矩形本身。
674
+ """
675
+ self.x1 += dx
676
+ self.y1 += dy
677
+ return self
678
+
679
+ def moved(self, dx: int, dy: int) -> 'Rect':
680
+ """
681
+ 返回一个移动后的**新** `Rect` 对象。
682
+
683
+ :param dx: x 方向的移动距离。
684
+ :param dy: y 方向的移动距离。
685
+ :return: 移动后的新 Rect 对象。
686
+ """
687
+ return Rect(self.x1 + dx, self.y1 + dy, self.w, self.h, name=self.name)
688
+
689
+ def inflate(self, dx: int, dy: int) -> 'Rect':
690
+ """
691
+ **原地**缩放矩形(中心点不变)。
692
+
693
+ 矩形的宽度增加 `2 * dx`,高度增加 `2 * dy`。
694
+ 负值会缩小矩形。
695
+
696
+ :param dx: 宽度方向的膨胀量(每边)。
697
+ :param dy: 高度方向的膨胀量(每边)。
698
+ :return: 缩放后的矩形本身。
699
+ """
700
+ self.x1 -= dx
701
+ self.y1 -= dy
702
+ self.w += 2 * dx
703
+ self.h += 2 * dy
704
+ return self
705
+
706
+ def inflated(self, dx: int, dy: int) -> 'Rect':
707
+ """
708
+ 返回一个缩放后的**新** `Rect` 对象。
709
+
710
+ :param dx: 宽度方向的膨胀量(每边)。
711
+ param dy: 高度方向的膨胀量(每边)。
712
+ :return: 缩放后的新 Rect 对象。
713
+ """
714
+ return Rect(self.x1 - dx, self.y1 - dy, self.w + 2 * dx, self.h + 2 * dy, name=self.name)
715
+
716
+ def normalize(self) -> 'Rect':
717
+ """
718
+ **原地**修正矩形,确保 `width` 和 `height` 为正数。
719
+
720
+ 如果宽度或高度为负,则交换坐标以使其为正。
721
+
722
+ :return: 修正后的矩形本身。
723
+ """
724
+ if self.w < 0:
725
+ self.x1 += self.w
726
+ self.w = -self.w
727
+ if self.h < 0:
728
+ self.y1 += self.h
729
+ self.h = -self.h
730
+ return self
731
+
732
+ def normalized(self) -> 'Rect':
733
+ """
734
+ 返回一个修正后的**新** `Rect` 对象,确保 `width` 和 `height` 为正数。
735
+
736
+ :return: 修正后的新 Rect 对象。
737
+ """
738
+ x, y, w, h = self.x1, self.y1, self.w, self.h
739
+ if w < 0:
740
+ x += w
741
+ w = -w
742
+ if h < 0:
743
+ y += h
744
+ h = -h
745
+ return Rect(x, y, w, h, name=self.name)
746
+
747
+ def contains_point(self, point: 'AnyPoint | PointTuple | PointFTuple') -> bool:
748
+ """
749
+ 检查一个点是否在此矩形内部。
750
+
751
+ .. note::
752
+ 对于边界值,左边界与上边界包含,而右边界与下边界不包含。
753
+
754
+ 例如 `Rect(0, 0, 10, 10)` 包含 `Point(0, 0)`,但不包含 `Point(10, 10)`。
755
+
756
+ :param point: 要检查的点。
757
+ :return: 如果点在矩形内,则返回 `True`。
758
+ """
759
+ return self.x1 <= point[0] < self.x2 and self.y1 <= point[1] < self.y2
760
+
761
+ def contains_rect(self, other_rect: 'Rect') -> bool:
762
+ """检查此矩形是否完全包含另一个矩形。
763
+
764
+ :param other_rect: 要检查的另一个矩形。
765
+ :return: 是否完全包含。
766
+ """
767
+ # 使用半开区间规则:矩形表示为 [x1, x2) x [y1, y2)
768
+ # 因此 other_rect 完全包含于 self 当且仅当
769
+ # other_rect.x1 >= self.x1, other_rect.y1 >= self.y1,
770
+ # other_rect.x2 <= self.x2, other_rect.y2 <= self.y2
771
+ return (self.x1 <= other_rect.x1 and self.y1 <= other_rect.y1 and
772
+ other_rect.x2 <= self.x2 and other_rect.y2 <= self.y2)
773
+
774
+
775
+ def intersects_with(self, other_rect: 'Rect') -> bool:
776
+ """
777
+ 检查此矩形是否与另一个矩形相交。
778
+
779
+ .. note::
780
+ 若两个矩形只有边界重叠,不算做相交。
781
+
782
+ :param other_rect: 要检查的另一个矩形。
783
+ :return: 如果两个矩形相交,则返回 `True`。
784
+ """
785
+ return not (self.x2 <= other_rect.x1 or self.x1 >= other_rect.x2 or
786
+ self.y2 <= other_rect.y1 or self.y1 >= other_rect.y2)
787
+
788
+ def union_of(self, other_rect: 'Rect') -> 'Rect':
789
+ """
790
+ 返回一个能同时包含两个矩形的**新** `Rect` 对象(并集)。
791
+
792
+ :param other_rect: 要合并的另一个矩形。
793
+ :return: 包含两个矩形的最小矩形。
794
+ """
795
+ x1 = min(self.x1, other_rect.x1)
796
+ y1 = min(self.y1, other_rect.y1)
797
+ x2 = max(self.x2, other_rect.x2)
798
+ y2 = max(self.y2, other_rect.y2)
799
+ return Rect.from_xyxy(x1, y1, x2, y2)
800
+
801
+ def intersection_of(self, other_rect: 'Rect') -> 'Rect | None':
802
+ """
803
+ 返回两个矩形相交区域的**新** `Rect` 对象(交集)。
804
+
805
+ 如果不相交,则返回 `None`。
806
+
807
+ :param other_rect: 要计算交集的另一个矩形。
808
+ :return: 相交区域的矩形,或 `None`。
809
+ """
810
+ x1 = max(self.x1, other_rect.x1)
811
+ y1 = max(self.y1, other_rect.y1)
812
+ x2 = min(self.x2, other_rect.x2)
813
+ y2 = min(self.y2, other_rect.y2)
814
+ if x1 >= x2 or y1 >= y2:
815
+ return None
816
+ return Rect.from_xyxy(x1, y1, x2, y2)
817
+
818
+ def is_empty(self) -> bool:
819
+ """
820
+ 如果矩形的 `width` 或 `height` 小于等于零,则返回 `True`。
821
+
822
+ :return: 如果矩形为空,则返回 `True`。
823
+ """
824
+ return self.w <= 0 or self.h <= 0
825
+
826
+ @overload
827
+ def __contains__(self, obj: 'Point | PointTuple | PointF | PointFTuple') -> bool: ...
828
+ @overload
829
+ def __contains__(self, obj: 'Rect') -> bool: ...
830
+
831
+ def __contains__(self, obj: 'AnyPoint | PointTuple | PointFTuple | Rect') -> bool:
832
+ """
833
+ 判断点或矩形是否被此矩形包含。
834
+
835
+ - 如果传入的是点或点元组,等价于 `Rect.contains_point`。
836
+ - 如果传入的是 `Rect`,则判断整个矩形是否被包含(完全包含)。
837
+
838
+ :param obj: 要检查的点或矩形。
839
+ :return: 如果被包含则返回 `True`。
840
+ """
841
+ # 如果是矩形,则检查矩形包含关系
842
+ if is_rect(obj):
843
+ return self.contains_rect(obj)
844
+ # 如果是任意点类型(Point / PointF)或长度为2的元组,则视为点
845
+ if is_any_point(obj) or (isinstance(obj, tuple) and len(obj) == 2):
846
+ return self.contains_point(obj)
847
+ raise TypeError("Argument must be a Point, PointF, 2-tuple of int or float, or Rect.")
848
+
849
+ AnyPoint = Union[Point, PointF]
850
+ AnyPointTuple = Union[PointTuple, PointFTuple]
851
+
852
+ def is_point(obj: object) -> TypeGuard[Point]:
853
+ return isinstance(obj, Point)
854
+
855
+ def is_point_f(obj: object) -> TypeGuard[PointF]:
856
+ return isinstance(obj, PointF)
857
+
858
+ def is_any_point(obj: object) -> TypeGuard[AnyPoint]:
859
+ return isinstance(obj, (Point, PointF))
860
+
861
+ def is_rect(obj: object) -> TypeGuard[Rect]:
862
+ return isinstance(obj, Rect)