kotonebot 0.4.0__py3-none-any.whl → 0.6.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.
- kotonebot/backend/context/context.py +1002 -1002
- kotonebot/backend/core.py +6 -49
- kotonebot/backend/image.py +36 -5
- kotonebot/backend/loop.py +222 -208
- kotonebot/backend/ocr.py +7 -1
- kotonebot/client/device.py +108 -243
- kotonebot/client/host/__init__.py +34 -3
- kotonebot/client/host/adb_common.py +7 -9
- kotonebot/client/host/custom.py +6 -2
- kotonebot/client/host/leidian_host.py +2 -7
- kotonebot/client/host/mumu12_host.py +2 -7
- kotonebot/client/host/protocol.py +4 -3
- kotonebot/client/implements/__init__.py +62 -11
- kotonebot/client/implements/adb.py +5 -1
- kotonebot/client/implements/nemu_ipc/__init__.py +4 -0
- kotonebot/client/implements/uiautomator2.py +6 -2
- kotonebot/client/implements/windows.py +7 -3
- kotonebot/client/registration.py +1 -1
- kotonebot/client/scaler.py +467 -0
- kotonebot/config/base_config.py +1 -1
- kotonebot/config/config.py +61 -0
- kotonebot/core/__init__.py +13 -0
- kotonebot/core/entities/base.py +182 -0
- kotonebot/core/entities/compound.py +75 -0
- kotonebot/core/entities/ocr.py +117 -0
- kotonebot/core/entities/template_match.py +198 -0
- kotonebot/devtools/__init__.py +42 -0
- kotonebot/devtools/cli/__init__.py +6 -0
- kotonebot/devtools/cli/main.py +53 -0
- kotonebot/devtools/project/project.py +41 -0
- kotonebot/devtools/project/scanner.py +202 -0
- kotonebot/devtools/project/schema.py +99 -0
- kotonebot/devtools/resgen/__init__.py +42 -0
- kotonebot/devtools/resgen/codegen.py +331 -0
- kotonebot/devtools/resgen/core.py +94 -0
- kotonebot/devtools/resgen/parsers.py +360 -0
- kotonebot/devtools/resgen/utils.py +158 -0
- kotonebot/devtools/resgen/validation.py +115 -0
- kotonebot/devtools/web/dist/assets/bootstrap-icons-BOrJxbIo.woff +0 -0
- kotonebot/devtools/web/dist/assets/bootstrap-icons-BtvjY1KL.woff2 +0 -0
- kotonebot/devtools/web/dist/assets/ext-language_tools-CD021WJ2.js +2577 -0
- kotonebot/devtools/web/dist/assets/index-B_m5f2LF.js +2836 -0
- kotonebot/devtools/web/dist/assets/index-BlEDyGGa.css +9 -0
- kotonebot/devtools/web/dist/assets/language-client-C9muzqaq.js +128 -0
- kotonebot/devtools/web/dist/assets/mode-python-CtHp76XS.js +476 -0
- kotonebot/devtools/web/dist/icons/symbol-class.svg +3 -0
- kotonebot/devtools/web/dist/icons/symbol-file.svg +3 -0
- kotonebot/devtools/web/dist/icons/symbol-method.svg +3 -0
- kotonebot/devtools/web/dist/index.html +25 -0
- kotonebot/devtools/web/server/__init__.py +0 -0
- kotonebot/devtools/web/server/rest_api.py +217 -0
- kotonebot/devtools/web/server/server.py +85 -0
- kotonebot/errors.py +7 -2
- kotonebot/interop/win/__init__.py +10 -1
- kotonebot/interop/win/_mouse.py +311 -0
- kotonebot/interop/win/shake_mouse.py +224 -0
- kotonebot/primitives/__init__.py +3 -1
- kotonebot/primitives/geometry.py +817 -40
- kotonebot/primitives/visual.py +81 -1
- kotonebot/ui/pushkit/image_host.py +2 -1
- kotonebot/ui/pushkit/wxpusher.py +2 -1
- {kotonebot-0.4.0.dist-info → kotonebot-0.6.0.dist-info}/METADATA +4 -1
- kotonebot-0.6.0.dist-info/RECORD +105 -0
- kotonebot-0.6.0.dist-info/entry_points.txt +2 -0
- kotonebot/client/implements/adb_raw.py +0 -159
- kotonebot-0.4.0.dist-info/RECORD +0 -70
- /kotonebot/{tools → devtools}/mirror.py +0 -0
- /kotonebot/{tools → devtools/project}/__init__.py +0 -0
- {kotonebot-0.4.0.dist-info → kotonebot-0.6.0.dist-info}/WHEEL +0 -0
- {kotonebot-0.4.0.dist-info → kotonebot-0.6.0.dist-info}/licenses/LICENSE +0 -0
- {kotonebot-0.4.0.dist-info → kotonebot-0.6.0.dist-info}/top_level.txt +0 -0
kotonebot/primitives/geometry.py
CHANGED
|
@@ -1,9 +1,81 @@
|
|
|
1
|
-
|
|
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
|
|
2
51
|
|
|
3
52
|
T = TypeVar('T')
|
|
4
53
|
|
|
5
54
|
class Vector2D(Generic[T]):
|
|
6
|
-
"""
|
|
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
|
+
__slots__ = ('x', 'y', 'name')
|
|
78
|
+
|
|
7
79
|
def __init__(self, x: T, y: T, *, name: str | None = None):
|
|
8
80
|
self.x = x
|
|
9
81
|
self.y = y
|
|
@@ -18,6 +90,14 @@ class Vector2D(Generic[T]):
|
|
|
18
90
|
else:
|
|
19
91
|
raise IndexError
|
|
20
92
|
|
|
93
|
+
def __iter__(self):
|
|
94
|
+
yield self.x
|
|
95
|
+
yield self.y
|
|
96
|
+
|
|
97
|
+
def as_tuple(self) -> tuple[T, T]:
|
|
98
|
+
"""Return coordinates as a tuple of ints: (x, y)."""
|
|
99
|
+
return self.x, self.y
|
|
100
|
+
|
|
21
101
|
def __repr__(self) -> str:
|
|
22
102
|
return f'Point<"{self.name}" at ({self.x}, {self.y})>'
|
|
23
103
|
|
|
@@ -26,7 +106,30 @@ class Vector2D(Generic[T]):
|
|
|
26
106
|
|
|
27
107
|
|
|
28
108
|
class Vector3D(Generic[T]):
|
|
29
|
-
"""
|
|
109
|
+
"""
|
|
110
|
+
## Vector3D
|
|
111
|
+
表示一个三维向量。
|
|
112
|
+
|
|
113
|
+
### 例
|
|
114
|
+
```python
|
|
115
|
+
# 创建三维坐标
|
|
116
|
+
v3 = Vector3D(100, 200, 50, name="3D点")
|
|
117
|
+
print(v3.x, v3.y, v3.z) # 100, 200, 50
|
|
118
|
+
|
|
119
|
+
# 使用索引访问
|
|
120
|
+
print(v3[0], v3[1], v3[2]) # 100, 200, 50
|
|
121
|
+
|
|
122
|
+
# 解构
|
|
123
|
+
x, y, z = v3.xyz # (100, 200, 50)
|
|
124
|
+
x, y = v3.xy # (100, 200)
|
|
125
|
+
|
|
126
|
+
# 颜色值应用
|
|
127
|
+
rgb = Vector3D(255, 128, 64, name="颜色")
|
|
128
|
+
print(f"RGB: {rgb.rgb if hasattr(v3, 'rgb') else '未定义'}")
|
|
129
|
+
```
|
|
130
|
+
"""
|
|
131
|
+
__slots__ = ('x', 'y', 'z', 'name')
|
|
132
|
+
|
|
30
133
|
def __init__(self, x: T, y: T, z: T, *, name: str | None = None):
|
|
31
134
|
self.x = x
|
|
32
135
|
self.y = y
|
|
@@ -44,6 +147,15 @@ class Vector3D(Generic[T]):
|
|
|
44
147
|
else:
|
|
45
148
|
raise IndexError
|
|
46
149
|
|
|
150
|
+
def __iter__(self):
|
|
151
|
+
yield self.x
|
|
152
|
+
yield self.y
|
|
153
|
+
yield self.z
|
|
154
|
+
|
|
155
|
+
def as_tuple(self) -> tuple[T, T, T]:
|
|
156
|
+
"""Return coordinates as a tuple of ints: (x, y, z)."""
|
|
157
|
+
return self.x, self.y, self.z
|
|
158
|
+
|
|
47
159
|
@property
|
|
48
160
|
def xyz(self) -> tuple[T, T, T]:
|
|
49
161
|
"""
|
|
@@ -59,7 +171,30 @@ class Vector3D(Generic[T]):
|
|
|
59
171
|
return self.x, self.y
|
|
60
172
|
|
|
61
173
|
class Vector4D(Generic[T]):
|
|
62
|
-
"""
|
|
174
|
+
"""
|
|
175
|
+
## Vector4D
|
|
176
|
+
此类用于表示四维坐标或向量,通常用于颜色空间(如RGBA)等场景。
|
|
177
|
+
|
|
178
|
+
### 例
|
|
179
|
+
```python
|
|
180
|
+
# 创建四维坐标
|
|
181
|
+
v4 = Vector4D(100, 200, 150, 255, name="颜色值")
|
|
182
|
+
print(f"RGBA: {v4.x}, {v4.y}, {v4.z}, {v4.w}") # 100, 200, 150, 255
|
|
183
|
+
|
|
184
|
+
# 使用索引访问
|
|
185
|
+
print(v4[0], v4[1], v4[2], v4[3]) # 100, 200, 150, 255
|
|
186
|
+
|
|
187
|
+
# 在颜色空间中使用
|
|
188
|
+
color = Vector4D(255, 0, 0, 128, name="半透明红色")
|
|
189
|
+
print(f"颜色: {color}")
|
|
190
|
+
|
|
191
|
+
# 四维向量运算
|
|
192
|
+
v4_2 = Vector4D(50, 50, 50, 100)
|
|
193
|
+
# 注意:Vector4D 未定义运算,但可用于数据存储
|
|
194
|
+
```
|
|
195
|
+
"""
|
|
196
|
+
__slots__ = ('x', 'y', 'z', 'w', 'name')
|
|
197
|
+
|
|
63
198
|
def __init__(self, x: T, y: T, z: T, w: T, *, name: str | None = None):
|
|
64
199
|
self.x = x
|
|
65
200
|
self.y = y
|
|
@@ -80,61 +215,312 @@ class Vector4D(Generic[T]):
|
|
|
80
215
|
else:
|
|
81
216
|
raise IndexError
|
|
82
217
|
|
|
83
|
-
|
|
84
|
-
|
|
218
|
+
def __iter__(self):
|
|
219
|
+
yield self.x
|
|
220
|
+
yield self.y
|
|
221
|
+
yield self.z
|
|
222
|
+
yield self.w
|
|
223
|
+
|
|
224
|
+
def as_tuple(self) -> tuple[T, T, T, T]:
|
|
225
|
+
"""Return coordinates as a tuple of ints: (x, y, z, w)."""
|
|
226
|
+
return self.x, self.y, self.z, self.w
|
|
227
|
+
|
|
85
228
|
RectTuple = tuple[int, int, int, int]
|
|
86
229
|
"""矩形。(x, y, w, h)"""
|
|
87
230
|
PointTuple = tuple[int, int]
|
|
88
231
|
"""点。(x, y)"""
|
|
232
|
+
PointFTuple = tuple[float, float]
|
|
233
|
+
"""浮点数点。(x, y)"""
|
|
234
|
+
Size = Vector2D[int]
|
|
235
|
+
"""尺寸。相当于 Vector2D[int]"""
|
|
236
|
+
SizeTuple = tuple[int, int]
|
|
237
|
+
"""尺寸。(width, height)"""
|
|
238
|
+
SizeLike = Union[Size, SizeTuple]
|
|
239
|
+
"""尺寸类型,可以是 Size 对象或尺寸元组。"""
|
|
240
|
+
|
|
89
241
|
|
|
90
|
-
|
|
91
|
-
|
|
242
|
+
Number = TypeVar('Number', int, float)
|
|
243
|
+
"""数字类型,可以是整数或浮点数。"""
|
|
244
|
+
NumberTuple2D = tuple[Number, Number]
|
|
245
|
+
class _BasePoint(Vector2D[Number]):
|
|
246
|
+
__slots__ = (*Vector2D.__slots__,)
|
|
92
247
|
|
|
93
248
|
@property
|
|
94
|
-
def xy(self) ->
|
|
249
|
+
def xy(self) -> NumberTuple2D:
|
|
95
250
|
"""
|
|
96
|
-
二元组 (x, y)。
|
|
251
|
+
二元组 (x, y)。
|
|
252
|
+
|
|
253
|
+
:return: 包含 x 和 y 坐标的元组。
|
|
97
254
|
"""
|
|
98
255
|
return self.x, self.y
|
|
99
|
-
|
|
100
|
-
|
|
256
|
+
|
|
257
|
+
@property
|
|
258
|
+
def length(self) -> float:
|
|
101
259
|
"""
|
|
102
|
-
|
|
260
|
+
将点视为从原点出发的向量,其长度(模)。
|
|
261
|
+
|
|
262
|
+
:return: 向量长度。
|
|
263
|
+
"""
|
|
264
|
+
return math.sqrt(self.x**2 + self.y**2)
|
|
265
|
+
|
|
266
|
+
def distance_to(self, other: 'AnyPoint | AnyPointTuple') -> float:
|
|
267
|
+
"""
|
|
268
|
+
计算到另一个点的距离。
|
|
103
269
|
|
|
104
|
-
:param
|
|
105
|
-
:
|
|
106
|
-
:return: 偏移后的坐标。
|
|
270
|
+
:param other: 另一个点或元组。
|
|
271
|
+
:return: 距离。
|
|
107
272
|
"""
|
|
108
|
-
return
|
|
109
|
-
|
|
110
|
-
def
|
|
273
|
+
return math.sqrt((self.x - other[0])**2 + (self.y - other[1])**2)
|
|
274
|
+
|
|
275
|
+
def __eq__(self, value: object) -> bool:
|
|
111
276
|
"""
|
|
112
|
-
|
|
277
|
+
比较两个点是否相等。
|
|
113
278
|
|
|
114
|
-
:param
|
|
115
|
-
:return:
|
|
279
|
+
:param value: 另一个点。
|
|
280
|
+
:return: 如果坐标相等则返回 True,否则返回 False。
|
|
281
|
+
"""
|
|
282
|
+
if isinstance(value, _BasePoint):
|
|
283
|
+
return self.x == value.x and self.y == value.y
|
|
284
|
+
return False
|
|
285
|
+
|
|
286
|
+
def __lt__(self, other: 'AnyPoint | AnyPointTuple') -> bool:
|
|
116
287
|
"""
|
|
117
|
-
|
|
118
|
-
|
|
288
|
+
小于比较,按 (x, y) 的字典序进行比较。
|
|
289
|
+
支持 `Point` / `PointF` 或长度为2的元组/列表。
|
|
290
|
+
"""
|
|
291
|
+
if is_any_point(other):
|
|
292
|
+
ox, oy = other.x, other.y
|
|
293
|
+
elif isinstance(other, tuple) and len(other) == 2:
|
|
294
|
+
ox, oy = other[0], other[1]
|
|
295
|
+
else:
|
|
296
|
+
return NotImplemented
|
|
297
|
+
return (self.x, self.y) < (ox, oy)
|
|
298
|
+
|
|
299
|
+
def __le__(self, other: 'AnyPoint | AnyPointTuple') -> bool:
|
|
300
|
+
"""小于等于比较,按 (x, y) 的字典序比较。"""
|
|
301
|
+
if is_any_point(other):
|
|
302
|
+
ox, oy = other.x, other.y
|
|
303
|
+
elif isinstance(other, tuple) and len(other) == 2:
|
|
304
|
+
ox, oy = other[0], other[1]
|
|
119
305
|
else:
|
|
120
|
-
return
|
|
306
|
+
return NotImplemented
|
|
307
|
+
return (self.x, self.y) <= (ox, oy)
|
|
308
|
+
|
|
309
|
+
def __gt__(self, other: 'AnyPoint | AnyPointTuple') -> bool:
|
|
310
|
+
"""大于比较,按 (x, y) 的字典序比较。"""
|
|
311
|
+
if is_any_point(other):
|
|
312
|
+
ox, oy = other.x, other.y
|
|
313
|
+
elif isinstance(other, tuple) and len(other) == 2:
|
|
314
|
+
ox, oy = other[0], other[1]
|
|
315
|
+
else:
|
|
316
|
+
return NotImplemented
|
|
317
|
+
return (self.x, self.y) > (ox, oy)
|
|
318
|
+
|
|
319
|
+
def __ge__(self, other: 'AnyPoint | AnyPointTuple') -> bool:
|
|
320
|
+
"""大于等于比较,按 (x, y) 的字典序比较。"""
|
|
321
|
+
if is_any_point(other):
|
|
322
|
+
ox, oy = other.x, other.y
|
|
323
|
+
elif isinstance(other, tuple) and len(other) == 2:
|
|
324
|
+
ox, oy = other[0], other[1]
|
|
325
|
+
else:
|
|
326
|
+
return NotImplemented
|
|
327
|
+
return (self.x, self.y) >= (ox, oy)
|
|
328
|
+
|
|
329
|
+
def normalized(self) -> 'PointF':
|
|
330
|
+
"""
|
|
331
|
+
返回一个新的、方向相同但长度为 1 的 `PointF` 对象(单位向量)。
|
|
332
|
+
|
|
333
|
+
:return: 单位向量。
|
|
334
|
+
"""
|
|
335
|
+
l = self.length
|
|
336
|
+
if l == 0:
|
|
337
|
+
return PointF(0.0, 0.0, name=self.name)
|
|
338
|
+
return PointF(self.x / l, self.y / l, name=self.name)
|
|
339
|
+
|
|
340
|
+
def offset(self, dx: int | float, dy: int | float) -> 'AnyPoint':
|
|
341
|
+
"""
|
|
342
|
+
偏移坐标。
|
|
343
|
+
|
|
344
|
+
如果 self, dx, dy 均为整数,返回 Point。否则返回 PointF。
|
|
345
|
+
|
|
346
|
+
:param dx: x方向偏移量。
|
|
347
|
+
:param dy: y方向偏移量。
|
|
348
|
+
:return: 偏移后的新 Point 或 PointF 对象。
|
|
349
|
+
"""
|
|
350
|
+
new_x = self.x + dx
|
|
351
|
+
new_y = self.y + dy
|
|
352
|
+
if isinstance(self, PointF) or isinstance(dx, float) or isinstance(dy, float):
|
|
353
|
+
return PointF(new_x, new_y, name=self.name)
|
|
354
|
+
return Point(int(new_x), int(new_y), name=self.name)
|
|
355
|
+
|
|
356
|
+
@overload
|
|
357
|
+
def __add__(self: 'Point', other: 'Point | PointTuple') -> 'Point': ...
|
|
358
|
+
@overload
|
|
359
|
+
def __add__(self, other: 'PointF | PointFTuple') -> 'PointF': ...
|
|
360
|
+
@overload
|
|
361
|
+
def __add__(self: 'PointF', other: 'AnyPoint | AnyPointTuple') -> 'PointF': ...
|
|
362
|
+
def __add__(self, other: 'AnyPoint | AnyPointTuple') -> 'AnyPoint':
|
|
363
|
+
"""
|
|
364
|
+
与另一个点或元组相加。
|
|
365
|
+
|
|
366
|
+
如果任一操作数为浮点数,则结果提升为 PointF。
|
|
121
367
|
|
|
122
|
-
|
|
368
|
+
:param other: 另一个 Point/PointF 对象或元组。
|
|
369
|
+
:return: 相加后的新 Point 或 PointF 对象。
|
|
370
|
+
"""
|
|
371
|
+
new_x = self.x + other[0]
|
|
372
|
+
new_y = self.y + other[1]
|
|
373
|
+
# hasattr check for tuple, which does not have .x attribute
|
|
374
|
+
if isinstance(self, PointF) or isinstance(other, PointF) or \
|
|
375
|
+
(not hasattr(other, 'x') and (isinstance(other[0], float) or isinstance(other[1], float))):
|
|
376
|
+
return PointF(new_x, new_y, name=self.name)
|
|
377
|
+
return Point(int(new_x), int(new_y), name=self.name)
|
|
378
|
+
|
|
379
|
+
@overload
|
|
380
|
+
def __sub__(self: 'Point', other: 'Point | PointTuple') -> 'Point': ...
|
|
381
|
+
@overload
|
|
382
|
+
def __sub__(self, other: 'PointF | PointFTuple') -> 'PointF': ...
|
|
383
|
+
@overload
|
|
384
|
+
def __sub__(self: 'PointF', other: 'AnyPoint | AnyPointTuple') -> 'PointF': ...
|
|
385
|
+
def __sub__(self, other: 'AnyPoint | AnyPointTuple') -> 'AnyPoint':
|
|
123
386
|
"""
|
|
124
|
-
|
|
387
|
+
与另一个点或元组相减。
|
|
388
|
+
|
|
389
|
+
如果任一操作数为浮点数,则结果提升为 PointF。
|
|
125
390
|
|
|
126
|
-
:param other: 另一个 Point
|
|
127
|
-
:return:
|
|
391
|
+
:param other: 另一个 Point/PointF 对象或元组。
|
|
392
|
+
:return: 相减后的新 Point 或 PointF 对象。
|
|
128
393
|
"""
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
394
|
+
new_x = self.x - other[0]
|
|
395
|
+
new_y = self.y - other[1]
|
|
396
|
+
if isinstance(self, PointF) or isinstance(other, PointF) or \
|
|
397
|
+
(not hasattr(other, 'x') and (isinstance(other[0], float) or isinstance(other[1], float))):
|
|
398
|
+
return PointF(new_x, new_y, name=self.name)
|
|
399
|
+
return Point(int(new_x), int(new_y), name=self.name)
|
|
400
|
+
|
|
401
|
+
@overload
|
|
402
|
+
def __mul__(self: 'Point', scalar: int) -> 'Point': ...
|
|
403
|
+
@overload
|
|
404
|
+
def __mul__(self, scalar: float) -> 'PointF': ...
|
|
405
|
+
def __mul__(self, scalar: int | float) -> 'AnyPoint':
|
|
406
|
+
"""
|
|
407
|
+
与标量相乘(缩放)。
|
|
408
|
+
|
|
409
|
+
:param scalar: 用于缩放的标量。
|
|
410
|
+
:return: 缩放后的新 Point 或 PointF 对象。
|
|
411
|
+
"""
|
|
412
|
+
new_x = self.x * scalar
|
|
413
|
+
new_y = self.y * scalar
|
|
414
|
+
if isinstance(self, PointF) or isinstance(scalar, float):
|
|
415
|
+
return PointF(new_x, new_y, name=self.name)
|
|
416
|
+
return Point(int(new_x), int(new_y), name=self.name)
|
|
417
|
+
|
|
418
|
+
def __truediv__(self, scalar: int | float) -> 'PointF':
|
|
419
|
+
"""
|
|
420
|
+
与标量相除(缩放)。总是返回一个 PointF 对象。
|
|
421
|
+
|
|
422
|
+
:param scalar: 用于缩放的标量。
|
|
423
|
+
:return: 缩放后的新 PointF 对象。
|
|
424
|
+
"""
|
|
425
|
+
if scalar == 0:
|
|
426
|
+
raise ValueError("Cannot divide by zero")
|
|
427
|
+
return PointF(self.x / scalar, self.y / scalar, name=self.name)
|
|
428
|
+
|
|
429
|
+
class PointF(_BasePoint[float]):
|
|
430
|
+
"""
|
|
431
|
+
## PointF
|
|
432
|
+
表示浮点数坐标点。
|
|
433
|
+
|
|
434
|
+
### 例
|
|
435
|
+
```python
|
|
436
|
+
# 创建浮点数坐标点
|
|
437
|
+
p1 = PointF(10.5, 20.7, name="精确位置")
|
|
438
|
+
p2 = PointF(5.2, 8.9)
|
|
439
|
+
|
|
440
|
+
# 距离计算
|
|
441
|
+
distance = p1.distance_to(p2) # 计算到另一点的距离
|
|
442
|
+
print(f"距离: {distance:.2f}")
|
|
443
|
+
|
|
444
|
+
# 向量运算
|
|
445
|
+
result = p1 + p2 # 坐标相加
|
|
446
|
+
scaled = p1 * 2 # 坐标缩放
|
|
447
|
+
|
|
448
|
+
# 单位向量
|
|
449
|
+
unit = p1.normalized() # 转换为单位向量
|
|
450
|
+
|
|
451
|
+
# 坐标偏移
|
|
452
|
+
moved = p1.offset(5.0, 10.0) # 偏移坐标
|
|
453
|
+
```
|
|
454
|
+
"""
|
|
455
|
+
__slots__ = (*_BasePoint.__slots__,)
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
class Point(_BasePoint[int]):
|
|
459
|
+
"""
|
|
460
|
+
## Point
|
|
461
|
+
表示整数坐标点。
|
|
462
|
+
|
|
463
|
+
### 例
|
|
464
|
+
```python
|
|
465
|
+
# 创建整数坐标点
|
|
466
|
+
pixel = Point(1920, 1080, name="屏幕分辨率")
|
|
467
|
+
button = Point(100, 200, name="按钮位置")
|
|
468
|
+
|
|
469
|
+
# 像素定位操作
|
|
470
|
+
center = Point(640, 360)
|
|
471
|
+
offset = center.offset(10, -5) # 向右移动10像素,向下移动5像素
|
|
472
|
+
|
|
473
|
+
# 点与点的运算
|
|
474
|
+
distance = center.distance_to(button)
|
|
475
|
+
relative_pos = button - center # 相对位置
|
|
476
|
+
|
|
477
|
+
# 检查点是否相等
|
|
478
|
+
if center == Point(640, 360):
|
|
479
|
+
print("找到了中心点")
|
|
480
|
+
|
|
481
|
+
# 与浮点数运算时会自动转换为PointF
|
|
482
|
+
precise = center + (5.5, 3.2) # 结果为PointF类型
|
|
483
|
+
```
|
|
484
|
+
"""
|
|
485
|
+
__slots__ = (*_BasePoint.__slots__,)
|
|
133
486
|
|
|
134
487
|
class Rect:
|
|
135
488
|
"""
|
|
136
|
-
|
|
489
|
+
## Rect
|
|
490
|
+
表示一个矩形区域,支持多种坐标格式和几何操作。
|
|
491
|
+
|
|
492
|
+
### 例
|
|
493
|
+
```python
|
|
494
|
+
# 创建矩形
|
|
495
|
+
rect = Rect(10, 20, 100, 50, name="按钮区域")
|
|
496
|
+
rect2 = Rect(xywh=(10, 20, 100, 50))
|
|
497
|
+
|
|
498
|
+
# 从两点创建矩形
|
|
499
|
+
rect3 = Rect.from_xyxy(10, 20, 110, 70)
|
|
500
|
+
|
|
501
|
+
# 获取矩形属性
|
|
502
|
+
print(rect.center) # 中心点
|
|
503
|
+
print(rect.size) # (100, 50)
|
|
504
|
+
print(rect.top_left) # 左上角点,其他三个角落同理
|
|
505
|
+
|
|
506
|
+
# 矩形操作
|
|
507
|
+
moved = rect.move(10, 10) # 原地移动
|
|
508
|
+
copied = rect.moved(5, 15) # 移动后的新矩形
|
|
509
|
+
enlarged = rect.inflate(5, 5) # 原地扩大
|
|
510
|
+
|
|
511
|
+
# 几何计算
|
|
512
|
+
if rect.contains_point(Point(50, 40)):
|
|
513
|
+
print("点在矩形内")
|
|
514
|
+
|
|
515
|
+
if rect.intersects_with(other_rect):
|
|
516
|
+
print("两个矩形相交")
|
|
517
|
+
|
|
518
|
+
union = rect.union_of(other_rect) # 并集
|
|
519
|
+
intersection = rect.intersection_of(other_rect) # 交集
|
|
520
|
+
```
|
|
137
521
|
"""
|
|
522
|
+
__slots__ = ('x1', 'y1', 'w', 'h', 'name')
|
|
523
|
+
|
|
138
524
|
def __init__(
|
|
139
525
|
self,
|
|
140
526
|
x: int | None = None,
|
|
@@ -170,13 +556,13 @@ class Rect:
|
|
|
170
556
|
else:
|
|
171
557
|
raise ValueError('Either xywh or x, y, w, h must be provided.')
|
|
172
558
|
|
|
173
|
-
self.x1 = x
|
|
559
|
+
self.x1 = int(x)
|
|
174
560
|
"""矩形左上角的 X 坐标。"""
|
|
175
|
-
self.y1 = y
|
|
561
|
+
self.y1 = int(y)
|
|
176
562
|
"""矩形左上角的 Y 坐标。"""
|
|
177
|
-
self.w = w
|
|
563
|
+
self.w = int(w)
|
|
178
564
|
"""矩形的宽度。"""
|
|
179
|
-
self.h = h
|
|
565
|
+
self.h = int(h)
|
|
180
566
|
"""矩形的高度。"""
|
|
181
567
|
self.name: str | None = name
|
|
182
568
|
"""矩形的名称。"""
|
|
@@ -187,7 +573,7 @@ class Rect:
|
|
|
187
573
|
从 (x1, y1, x2, y2) 创建矩形。
|
|
188
574
|
:return: 创建结果。
|
|
189
575
|
"""
|
|
190
|
-
return cls(x1, y1, x2 - x1, y2 - y1)
|
|
576
|
+
return cls(int(x1), int(y1), int(x2 - x1), int(y2 - y1))
|
|
191
577
|
|
|
192
578
|
@property
|
|
193
579
|
def x2(self) -> int:
|
|
@@ -269,12 +655,86 @@ class Rect:
|
|
|
269
655
|
def center(self) -> Point:
|
|
270
656
|
"""
|
|
271
657
|
矩形的中心点。
|
|
658
|
+
|
|
659
|
+
:return: 中心点 Point 对象。
|
|
272
660
|
"""
|
|
273
661
|
if self.name:
|
|
274
662
|
name = "Center of rect "+ self.name
|
|
275
663
|
else:
|
|
276
664
|
name = None
|
|
277
|
-
return Point(self.x1 + self.w
|
|
665
|
+
return Point(int(self.x1 + self.w / 2), int(self.y1 + self.h / 2), name=name)
|
|
666
|
+
|
|
667
|
+
@property
|
|
668
|
+
def center_x(self) -> int:
|
|
669
|
+
"""
|
|
670
|
+
中心点的 x 坐标。
|
|
671
|
+
|
|
672
|
+
:return: 中心点的 x 坐标。
|
|
673
|
+
"""
|
|
674
|
+
return self.x1 + self.w // 2
|
|
675
|
+
|
|
676
|
+
@property
|
|
677
|
+
def center_y(self) -> int:
|
|
678
|
+
"""
|
|
679
|
+
中心点的 y 坐标。
|
|
680
|
+
|
|
681
|
+
:return: 中心点的 y 坐标。
|
|
682
|
+
"""
|
|
683
|
+
return self.y1 + self.h // 2
|
|
684
|
+
|
|
685
|
+
@property
|
|
686
|
+
def middle_top(self) -> Point:
|
|
687
|
+
"""
|
|
688
|
+
矩形顶部边的中点。
|
|
689
|
+
|
|
690
|
+
:return: 顶部边的中点。
|
|
691
|
+
"""
|
|
692
|
+
return Point(self.center_x, self.y1)
|
|
693
|
+
|
|
694
|
+
@property
|
|
695
|
+
def middle_bottom(self) -> Point:
|
|
696
|
+
"""
|
|
697
|
+
矩形底部边的中点。
|
|
698
|
+
|
|
699
|
+
:return: 底部边的中点。
|
|
700
|
+
"""
|
|
701
|
+
return Point(self.center_x, self.y2)
|
|
702
|
+
|
|
703
|
+
@property
|
|
704
|
+
def middle_left(self) -> Point:
|
|
705
|
+
"""
|
|
706
|
+
矩形左侧边的中点。
|
|
707
|
+
|
|
708
|
+
:return: 左侧边的中点。
|
|
709
|
+
"""
|
|
710
|
+
return Point(self.x1, self.center_y)
|
|
711
|
+
|
|
712
|
+
@property
|
|
713
|
+
def middle_right(self) -> Point:
|
|
714
|
+
"""
|
|
715
|
+
矩形右侧边的中点。
|
|
716
|
+
|
|
717
|
+
:return: 右侧边的中点。
|
|
718
|
+
"""
|
|
719
|
+
return Point(self.x2, self.center_y)
|
|
720
|
+
|
|
721
|
+
@property
|
|
722
|
+
def size(self) -> tuple[int, int]:
|
|
723
|
+
"""
|
|
724
|
+
一个 `(width, height)` 元组。
|
|
725
|
+
|
|
726
|
+
:return: 包含宽度和高度的元组。
|
|
727
|
+
"""
|
|
728
|
+
return self.w, self.h
|
|
729
|
+
|
|
730
|
+
@size.setter
|
|
731
|
+
def size(self, value: tuple[int, int]):
|
|
732
|
+
"""
|
|
733
|
+
设置矩形的尺寸。
|
|
734
|
+
|
|
735
|
+
:param value: 包含新宽度和新高度的元组。
|
|
736
|
+
"""
|
|
737
|
+
self.w, self.h = value
|
|
278
738
|
|
|
279
739
|
def __repr__(self) -> str:
|
|
280
740
|
return f'Rect<"{self.name}" at (x={self.x1}, y={self.y1}, w={self.w}, h={self.h})>'
|
|
@@ -282,9 +742,326 @@ class Rect:
|
|
|
282
742
|
def __str__(self) -> str:
|
|
283
743
|
return f'(x={self.x1}, y={self.y1}, w={self.w}, h={self.h})'
|
|
284
744
|
|
|
745
|
+
def copy(self) -> 'Rect':
|
|
746
|
+
"""
|
|
747
|
+
返回一个与当前对象完全相同的**新** `Rect` 对象。
|
|
748
|
+
|
|
749
|
+
:return: 当前 Rect 对象的一个副本。
|
|
750
|
+
"""
|
|
751
|
+
return Rect(self.x1, self.y1, self.w, self.h, name=self.name)
|
|
752
|
+
|
|
753
|
+
def move(self, dx: int, dy: int) -> 'Rect':
|
|
754
|
+
"""
|
|
755
|
+
**原地**移动矩形。
|
|
756
|
+
|
|
757
|
+
:param dx: x 方向的移动距离。
|
|
758
|
+
:param dy: y 方向的移动距离。
|
|
759
|
+
:return: 移动后的矩形本身。
|
|
760
|
+
"""
|
|
761
|
+
self.x1 += dx
|
|
762
|
+
self.y1 += dy
|
|
763
|
+
return self
|
|
764
|
+
|
|
765
|
+
def moved(self, dx: int, dy: int) -> 'Rect':
|
|
766
|
+
"""
|
|
767
|
+
返回一个移动后的**新** `Rect` 对象。
|
|
768
|
+
|
|
769
|
+
:param dx: x 方向的移动距离。
|
|
770
|
+
:param dy: y 方向的移动距离。
|
|
771
|
+
:return: 移动后的新 Rect 对象。
|
|
772
|
+
"""
|
|
773
|
+
return Rect(self.x1 + dx, self.y1 + dy, self.w, self.h, name=self.name)
|
|
774
|
+
|
|
775
|
+
def inflate(self, dx: int, dy: int) -> 'Rect':
|
|
776
|
+
"""
|
|
777
|
+
**原地**缩放矩形(中心点不变)。
|
|
778
|
+
|
|
779
|
+
矩形的宽度增加 `2 * dx`,高度增加 `2 * dy`。
|
|
780
|
+
负值会缩小矩形。
|
|
781
|
+
|
|
782
|
+
:param dx: 宽度方向的膨胀量(每边)。
|
|
783
|
+
:param dy: 高度方向的膨胀量(每边)。
|
|
784
|
+
:return: 缩放后的矩形本身。
|
|
785
|
+
"""
|
|
786
|
+
self.x1 -= dx
|
|
787
|
+
self.y1 -= dy
|
|
788
|
+
self.w += 2 * dx
|
|
789
|
+
self.h += 2 * dy
|
|
790
|
+
return self
|
|
791
|
+
|
|
792
|
+
def inflated(self, dx: int, dy: int) -> 'Rect':
|
|
793
|
+
"""
|
|
794
|
+
返回一个缩放后的**新** `Rect` 对象。
|
|
795
|
+
|
|
796
|
+
:param dx: 宽度方向的膨胀量(每边)。
|
|
797
|
+
param dy: 高度方向的膨胀量(每边)。
|
|
798
|
+
:return: 缩放后的新 Rect 对象。
|
|
799
|
+
"""
|
|
800
|
+
return Rect(self.x1 - dx, self.y1 - dy, self.w + 2 * dx, self.h + 2 * dy, name=self.name)
|
|
801
|
+
|
|
802
|
+
def normalize(self) -> 'Rect':
|
|
803
|
+
"""
|
|
804
|
+
**原地**修正矩形,确保 `width` 和 `height` 为正数。
|
|
805
|
+
|
|
806
|
+
如果宽度或高度为负,则交换坐标以使其为正。
|
|
807
|
+
|
|
808
|
+
:return: 修正后的矩形本身。
|
|
809
|
+
"""
|
|
810
|
+
if self.w < 0:
|
|
811
|
+
self.x1 += self.w
|
|
812
|
+
self.w = -self.w
|
|
813
|
+
if self.h < 0:
|
|
814
|
+
self.y1 += self.h
|
|
815
|
+
self.h = -self.h
|
|
816
|
+
return self
|
|
817
|
+
|
|
818
|
+
def normalized(self) -> 'Rect':
|
|
819
|
+
"""
|
|
820
|
+
返回一个修正后的**新** `Rect` 对象,确保 `width` 和 `height` 为正数。
|
|
821
|
+
|
|
822
|
+
:return: 修正后的新 Rect 对象。
|
|
823
|
+
"""
|
|
824
|
+
x, y, w, h = self.x1, self.y1, self.w, self.h
|
|
825
|
+
if w < 0:
|
|
826
|
+
x += w
|
|
827
|
+
w = -w
|
|
828
|
+
if h < 0:
|
|
829
|
+
y += h
|
|
830
|
+
h = -h
|
|
831
|
+
return Rect(x, y, w, h, name=self.name)
|
|
832
|
+
|
|
833
|
+
def contains_point(self, point: 'AnyPoint | PointTuple | PointFTuple') -> bool:
|
|
834
|
+
"""
|
|
835
|
+
检查一个点是否在此矩形内部。
|
|
836
|
+
|
|
837
|
+
.. note::
|
|
838
|
+
对于边界值,左边界与上边界包含,而右边界与下边界不包含。
|
|
839
|
+
|
|
840
|
+
例如 `Rect(0, 0, 10, 10)` 包含 `Point(0, 0)`,但不包含 `Point(10, 10)`。
|
|
841
|
+
|
|
842
|
+
:param point: 要检查的点。
|
|
843
|
+
:return: 如果点在矩形内,则返回 `True`。
|
|
844
|
+
"""
|
|
845
|
+
return self.x1 <= point[0] < self.x2 and self.y1 <= point[1] < self.y2
|
|
846
|
+
|
|
847
|
+
def contains_rect(self, other_rect: 'Rect') -> bool:
|
|
848
|
+
"""检查此矩形是否完全包含另一个矩形。
|
|
849
|
+
|
|
850
|
+
:param other_rect: 要检查的另一个矩形。
|
|
851
|
+
:return: 是否完全包含。
|
|
852
|
+
"""
|
|
853
|
+
# 使用半开区间规则:矩形表示为 [x1, x2) x [y1, y2)
|
|
854
|
+
# 因此 other_rect 完全包含于 self 当且仅当
|
|
855
|
+
# other_rect.x1 >= self.x1, other_rect.y1 >= self.y1,
|
|
856
|
+
# other_rect.x2 <= self.x2, other_rect.y2 <= self.y2
|
|
857
|
+
return (self.x1 <= other_rect.x1 and self.y1 <= other_rect.y1 and
|
|
858
|
+
other_rect.x2 <= self.x2 and other_rect.y2 <= self.y2)
|
|
859
|
+
|
|
860
|
+
|
|
861
|
+
def intersects_with(self, other_rect: 'Rect') -> bool:
|
|
862
|
+
"""
|
|
863
|
+
检查此矩形是否与另一个矩形相交。
|
|
864
|
+
|
|
865
|
+
.. note::
|
|
866
|
+
若两个矩形只有边界重叠,不算做相交。
|
|
867
|
+
|
|
868
|
+
:param other_rect: 要检查的另一个矩形。
|
|
869
|
+
:return: 如果两个矩形相交,则返回 `True`。
|
|
870
|
+
"""
|
|
871
|
+
return not (self.x2 <= other_rect.x1 or self.x1 >= other_rect.x2 or
|
|
872
|
+
self.y2 <= other_rect.y1 or self.y1 >= other_rect.y2)
|
|
873
|
+
|
|
874
|
+
def union_of(self, other_rect: 'Rect') -> 'Rect':
|
|
875
|
+
"""
|
|
876
|
+
返回一个能同时包含两个矩形的**新** `Rect` 对象(并集)。
|
|
877
|
+
|
|
878
|
+
:param other_rect: 要合并的另一个矩形。
|
|
879
|
+
:return: 包含两个矩形的最小矩形。
|
|
880
|
+
"""
|
|
881
|
+
x1 = min(self.x1, other_rect.x1)
|
|
882
|
+
y1 = min(self.y1, other_rect.y1)
|
|
883
|
+
x2 = max(self.x2, other_rect.x2)
|
|
884
|
+
y2 = max(self.y2, other_rect.y2)
|
|
885
|
+
return Rect.from_xyxy(x1, y1, x2, y2)
|
|
886
|
+
|
|
887
|
+
def intersection_of(self, other_rect: 'Rect') -> 'Rect | None':
|
|
888
|
+
"""
|
|
889
|
+
返回两个矩形相交区域的**新** `Rect` 对象(交集)。
|
|
890
|
+
|
|
891
|
+
如果不相交,则返回 `None`。
|
|
892
|
+
|
|
893
|
+
:param other_rect: 要计算交集的另一个矩形。
|
|
894
|
+
:return: 相交区域的矩形,或 `None`。
|
|
895
|
+
"""
|
|
896
|
+
x1 = max(self.x1, other_rect.x1)
|
|
897
|
+
y1 = max(self.y1, other_rect.y1)
|
|
898
|
+
x2 = min(self.x2, other_rect.x2)
|
|
899
|
+
y2 = min(self.y2, other_rect.y2)
|
|
900
|
+
if x1 >= x2 or y1 >= y2:
|
|
901
|
+
return None
|
|
902
|
+
return Rect.from_xyxy(x1, y1, x2, y2)
|
|
903
|
+
|
|
904
|
+
def is_empty(self) -> bool:
|
|
905
|
+
"""
|
|
906
|
+
如果矩形的 `width` 或 `height` 小于等于零,则返回 `True`。
|
|
907
|
+
|
|
908
|
+
:return: 如果矩形为空,则返回 `True`。
|
|
909
|
+
"""
|
|
910
|
+
return self.w <= 0 or self.h <= 0
|
|
911
|
+
|
|
912
|
+
@overload
|
|
913
|
+
def __contains__(self, obj: 'Point | PointTuple | PointF | PointFTuple') -> bool: ...
|
|
914
|
+
@overload
|
|
915
|
+
def __contains__(self, obj: 'Rect') -> bool: ...
|
|
916
|
+
|
|
917
|
+
def __contains__(self, obj: 'AnyPoint | PointTuple | PointFTuple | Rect') -> bool:
|
|
918
|
+
"""
|
|
919
|
+
判断点或矩形是否被此矩形包含。
|
|
920
|
+
|
|
921
|
+
- 如果传入的是点或点元组,等价于 `Rect.contains_point`。
|
|
922
|
+
- 如果传入的是 `Rect`,则判断整个矩形是否被包含(完全包含)。
|
|
923
|
+
|
|
924
|
+
:param obj: 要检查的点或矩形。
|
|
925
|
+
:return: 如果被包含则返回 `True`。
|
|
926
|
+
"""
|
|
927
|
+
# 如果是矩形,则检查矩形包含关系
|
|
928
|
+
if is_rect(obj):
|
|
929
|
+
return self.contains_rect(obj)
|
|
930
|
+
# 如果是任意点类型(Point / PointF)或长度为2的元组,则视为点
|
|
931
|
+
if is_any_point(obj) or (isinstance(obj, tuple) and len(obj) == 2):
|
|
932
|
+
return self.contains_point(obj)
|
|
933
|
+
raise TypeError("Argument must be a Point, PointF, 2-tuple of int or float, or Rect.")
|
|
285
934
|
|
|
935
|
+
AnyPoint = Union[Point, PointF]
|
|
936
|
+
"""任意 Point 对象,包括 Point 与 PointF。"""
|
|
937
|
+
AnyPointTuple = Union[PointTuple, PointFTuple]
|
|
938
|
+
"""任意 Point 元组,包括 tuple[int, int] 与 tuple[float, float]。"""
|
|
939
|
+
PointLike = Union[Point, PointTuple]
|
|
940
|
+
PointFLike = Union[PointF, PointFTuple]
|
|
941
|
+
AnyPointLike = Union[AnyPoint, AnyPointTuple]
|
|
942
|
+
"""任意类似于 Point 的对象,包括 Point、PointF、tuple[int, int]、tuple[float, float]。"""
|
|
943
|
+
RectLike = Union[Rect, RectTuple]
|
|
944
|
+
"""任意类似于 Rect 的对象,包括 Rect、tuple[int, int, int, int]。"""
|
|
286
945
|
def is_point(obj: object) -> TypeGuard[Point]:
|
|
287
946
|
return isinstance(obj, Point)
|
|
288
947
|
|
|
948
|
+
def is_point_f(obj: object) -> TypeGuard[PointF]:
|
|
949
|
+
return isinstance(obj, PointF)
|
|
950
|
+
|
|
951
|
+
def is_any_point(obj: object) -> TypeGuard[AnyPoint]:
|
|
952
|
+
return isinstance(obj, (Point, PointF))
|
|
953
|
+
|
|
289
954
|
def is_rect(obj: object) -> TypeGuard[Rect]:
|
|
290
955
|
return isinstance(obj, Rect)
|
|
956
|
+
|
|
957
|
+
def unify_point(point: PointLike) -> Point:
|
|
958
|
+
"""
|
|
959
|
+
将点或元组统一转换为 `Point` 对象。
|
|
960
|
+
|
|
961
|
+
:param point: 要转换的点或元组。
|
|
962
|
+
:return: 转换后的 `Point` 对象。
|
|
963
|
+
:raises TypeError: 如果输入类型不正确则抛出。
|
|
964
|
+
|
|
965
|
+
.. note::
|
|
966
|
+
若输入数据为 float,会被强制转换为 int。
|
|
967
|
+
"""
|
|
968
|
+
# If already an integer Point, return it directly
|
|
969
|
+
if is_point(point):
|
|
970
|
+
return point
|
|
971
|
+
|
|
972
|
+
# If it's a PointF, convert to Point by casting coordinates to int
|
|
973
|
+
if is_point_f(point):
|
|
974
|
+
return Point(int(point.x), int(point.y), name=point.name)
|
|
975
|
+
|
|
976
|
+
# If it's a tuple-like (x, y) sequence, attempt to extract
|
|
977
|
+
if isinstance(point, (tuple, list)) and len(point) == 2:
|
|
978
|
+
x, y = point[0], point[1]
|
|
979
|
+
try:
|
|
980
|
+
return Point(int(x), int(y))
|
|
981
|
+
except Exception:
|
|
982
|
+
raise TypeError('Point tuple must contain numeric values')
|
|
983
|
+
|
|
984
|
+
raise TypeError('Argument must be a Point, PointF, or 2-tuple of numbers')
|
|
985
|
+
|
|
986
|
+
def unify_pointf(point: PointLike) -> PointF:
|
|
987
|
+
"""
|
|
988
|
+
将点或元组统一转换为 `PointF` 对象。
|
|
989
|
+
|
|
990
|
+
:param point: 要转换的点或元组。
|
|
991
|
+
:return: 转换后的 `PointF` 对象。
|
|
992
|
+
:raises TypeError: 如果输入类型不正确则抛出。
|
|
993
|
+
"""
|
|
994
|
+
# If already a PointF, return it
|
|
995
|
+
if is_point_f(point):
|
|
996
|
+
return point
|
|
997
|
+
|
|
998
|
+
# If it's an integer Point, convert to PointF preserving name
|
|
999
|
+
if is_point(point):
|
|
1000
|
+
return PointF(float(point.x), float(point.y), name=point.name)
|
|
1001
|
+
|
|
1002
|
+
# If it's a tuple-like (x, y), convert elements to float
|
|
1003
|
+
if isinstance(point, (tuple, list)) and len(point) == 2:
|
|
1004
|
+
x, y = point[0], point[1]
|
|
1005
|
+
try:
|
|
1006
|
+
return PointF(float(x), float(y))
|
|
1007
|
+
except Exception:
|
|
1008
|
+
raise TypeError('PointF tuple must contain numeric values')
|
|
1009
|
+
|
|
1010
|
+
raise TypeError('Argument must be a Point, PointF, or 2-tuple of numbers')
|
|
1011
|
+
|
|
1012
|
+
def unify_any_point(point: AnyPointLike) -> Point | PointF:
|
|
1013
|
+
"""
|
|
1014
|
+
将点或元组统一转换为 `Point` 或 `PointF` 对象。
|
|
1015
|
+
|
|
1016
|
+
如果输入已是 `Point` 或 `PointF` 对象,直接返回。
|
|
1017
|
+
如果输入是元组,根据其数值类型决定返回类型:
|
|
1018
|
+
- 若所有坐标为整数,返回 `Point`
|
|
1019
|
+
- 若有任何坐标为浮点数,返回 `PointF`
|
|
1020
|
+
|
|
1021
|
+
:param point: 要转换的点或元组。
|
|
1022
|
+
:return: 转换后的 `Point` 或 `PointF` 对象。
|
|
1023
|
+
:raises TypeError: 如果输入类型不正确则抛出。
|
|
1024
|
+
"""
|
|
1025
|
+
# If already a Point or PointF, return it directly
|
|
1026
|
+
if is_any_point(point):
|
|
1027
|
+
return point
|
|
1028
|
+
|
|
1029
|
+
# If it's a sequence, check types and convert accordingly
|
|
1030
|
+
if isinstance(point, (tuple, list)) and len(point) == 2:
|
|
1031
|
+
x, y = point[0], point[1]
|
|
1032
|
+
try:
|
|
1033
|
+
# If both are integers, return Point
|
|
1034
|
+
if isinstance(x, int) and isinstance(y, int):
|
|
1035
|
+
return Point(x, y)
|
|
1036
|
+
# Otherwise, return PointF
|
|
1037
|
+
else:
|
|
1038
|
+
return PointF(float(x), float(y))
|
|
1039
|
+
except Exception:
|
|
1040
|
+
raise TypeError('Point tuple must contain numeric values')
|
|
1041
|
+
|
|
1042
|
+
raise TypeError('Argument must be a Point, PointF, or 2-tuple of numbers')
|
|
1043
|
+
|
|
1044
|
+
def unify_rect(rect: RectLike) -> Rect:
|
|
1045
|
+
"""
|
|
1046
|
+
将矩形或元组 (x, y, w, h) 统一转换为 `Rect` 对象。
|
|
1047
|
+
|
|
1048
|
+
:param rect: 要转换的矩形或元组。
|
|
1049
|
+
:return: 转换后的 `Rect` 对象。
|
|
1050
|
+
:raises TypeError: 如果输入类型不正确则抛出。
|
|
1051
|
+
|
|
1052
|
+
.. note::
|
|
1053
|
+
若输入数据为 float,会被强制转换为 int。
|
|
1054
|
+
"""
|
|
1055
|
+
# If already a Rect, return it
|
|
1056
|
+
if is_rect(rect):
|
|
1057
|
+
return rect
|
|
1058
|
+
|
|
1059
|
+
# If it's a tuple-like (x, y, w, h), convert to ints and construct Rect
|
|
1060
|
+
if isinstance(rect, (tuple, list)) and len(rect) == 4:
|
|
1061
|
+
x, y, w, h = rect[0], rect[1], rect[2], rect[3]
|
|
1062
|
+
try:
|
|
1063
|
+
return Rect(int(x), int(y), int(w), int(h))
|
|
1064
|
+
except Exception:
|
|
1065
|
+
raise TypeError('Rect tuple must contain numeric values')
|
|
1066
|
+
|
|
1067
|
+
raise TypeError('Argument must be a Rect or 4-tuple of numbers (x, y, w, h)')
|