kotonebot 0.5.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/__init__.py +39 -39
- kotonebot/backend/bot.py +312 -312
- kotonebot/backend/color.py +525 -525
- kotonebot/backend/context/__init__.py +3 -3
- kotonebot/backend/context/context.py +1002 -1002
- kotonebot/backend/context/task_action.py +183 -183
- kotonebot/backend/core.py +86 -129
- kotonebot/backend/debug/entry.py +89 -89
- kotonebot/backend/debug/mock.py +78 -78
- kotonebot/backend/debug/server.py +222 -222
- kotonebot/backend/debug/vars.py +351 -351
- kotonebot/backend/dispatch.py +227 -227
- kotonebot/backend/flow_controller.py +196 -196
- kotonebot/backend/image.py +36 -5
- kotonebot/backend/loop.py +222 -208
- kotonebot/backend/ocr.py +535 -535
- kotonebot/backend/preprocessor.py +103 -103
- kotonebot/client/__init__.py +9 -9
- kotonebot/client/device.py +369 -529
- kotonebot/client/fast_screenshot.py +377 -377
- kotonebot/client/host/__init__.py +43 -43
- kotonebot/client/host/adb_common.py +101 -107
- kotonebot/client/host/custom.py +118 -118
- kotonebot/client/host/leidian_host.py +196 -196
- kotonebot/client/host/mumu12_host.py +353 -353
- kotonebot/client/host/protocol.py +214 -214
- kotonebot/client/host/windows_common.py +58 -58
- kotonebot/client/implements/__init__.py +65 -70
- kotonebot/client/implements/adb.py +89 -89
- kotonebot/client/implements/nemu_ipc/__init__.py +11 -11
- kotonebot/client/implements/nemu_ipc/external_renderer_ipc.py +284 -284
- kotonebot/client/implements/nemu_ipc/nemu_ipc.py +327 -327
- kotonebot/client/implements/remote_windows.py +188 -188
- kotonebot/client/implements/uiautomator2.py +85 -85
- kotonebot/client/implements/windows.py +176 -176
- kotonebot/client/protocol.py +69 -69
- kotonebot/client/registration.py +24 -24
- kotonebot/client/scaler.py +467 -0
- kotonebot/config/base_config.py +96 -96
- kotonebot/config/config.py +61 -0
- kotonebot/config/manager.py +36 -36
- 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/{tools → devtools}/mirror.py +354 -354
- 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 +76 -76
- kotonebot/interop/win/__init__.py +11 -9
- kotonebot/interop/win/_mouse.py +310 -310
- kotonebot/interop/win/message_box.py +313 -313
- kotonebot/interop/win/reg.py +37 -37
- kotonebot/interop/win/shake_mouse.py +224 -0
- kotonebot/interop/win/shortcut.py +43 -43
- kotonebot/interop/win/task_dialog.py +513 -513
- kotonebot/logging/__init__.py +2 -2
- kotonebot/logging/log.py +17 -17
- kotonebot/primitives/__init__.py +19 -17
- kotonebot/primitives/geometry.py +1067 -862
- kotonebot/primitives/visual.py +143 -63
- kotonebot/ui/file_host/sensio.py +36 -36
- kotonebot/ui/file_host/tmp_send.py +54 -54
- kotonebot/ui/pushkit/__init__.py +3 -3
- kotonebot/ui/pushkit/image_host.py +88 -88
- kotonebot/ui/pushkit/protocol.py +13 -13
- kotonebot/ui/pushkit/wxpusher.py +54 -54
- kotonebot/ui/user.py +148 -148
- kotonebot/util.py +436 -436
- {kotonebot-0.5.0.dist-info → kotonebot-0.6.0.dist-info}/METADATA +84 -82
- kotonebot-0.6.0.dist-info/RECORD +105 -0
- kotonebot-0.6.0.dist-info/entry_points.txt +2 -0
- {kotonebot-0.5.0.dist-info → kotonebot-0.6.0.dist-info}/licenses/LICENSE +673 -673
- kotonebot/client/implements/adb_raw.py +0 -163
- kotonebot-0.5.0.dist-info/RECORD +0 -71
- /kotonebot/{tools → devtools/project}/__init__.py +0 -0
- {kotonebot-0.5.0.dist-info → kotonebot-0.6.0.dist-info}/WHEEL +0 -0
- {kotonebot-0.5.0.dist-info → kotonebot-0.6.0.dist-info}/top_level.txt +0 -0
kotonebot/primitives/geometry.py
CHANGED
|
@@ -1,862 +1,1067 @@
|
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
self.
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
return self.
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
"""
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
def
|
|
259
|
-
"""
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
"""
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
def
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
"""
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
return
|
|
317
|
-
return
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
""
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
"""
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
"""
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
"""
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
"""
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
:
|
|
664
|
-
|
|
665
|
-
return
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
:
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
self.
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
"""
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
:
|
|
758
|
-
|
|
759
|
-
return
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
"""
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
return
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
def
|
|
776
|
-
"""
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
:param
|
|
783
|
-
:
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
:return:
|
|
809
|
-
"""
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
return
|
|
817
|
-
|
|
818
|
-
def
|
|
819
|
-
"""
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
:return:
|
|
823
|
-
"""
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
def
|
|
862
|
-
|
|
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
|
+
__slots__ = ('x', 'y', 'name')
|
|
78
|
+
|
|
79
|
+
def __init__(self, x: T, y: T, *, name: str | None = None):
|
|
80
|
+
self.x = x
|
|
81
|
+
self.y = y
|
|
82
|
+
self.name: str | None = name
|
|
83
|
+
"""坐标的名称。"""
|
|
84
|
+
|
|
85
|
+
def __getitem__(self, item: int):
|
|
86
|
+
if item == 0:
|
|
87
|
+
return self.x
|
|
88
|
+
elif item == 1:
|
|
89
|
+
return self.y
|
|
90
|
+
else:
|
|
91
|
+
raise IndexError
|
|
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
|
+
|
|
101
|
+
def __repr__(self) -> str:
|
|
102
|
+
return f'Point<"{self.name}" at ({self.x}, {self.y})>'
|
|
103
|
+
|
|
104
|
+
def __str__(self) -> str:
|
|
105
|
+
return f'({self.x}, {self.y})'
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class Vector3D(Generic[T]):
|
|
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
|
+
|
|
133
|
+
def __init__(self, x: T, y: T, z: T, *, name: str | None = None):
|
|
134
|
+
self.x = x
|
|
135
|
+
self.y = y
|
|
136
|
+
self.z = z
|
|
137
|
+
self.name: str | None = name
|
|
138
|
+
"""坐标的名称。"""
|
|
139
|
+
|
|
140
|
+
def __getitem__(self, item: int):
|
|
141
|
+
if item == 0:
|
|
142
|
+
return self.x
|
|
143
|
+
elif item == 1:
|
|
144
|
+
return self.y
|
|
145
|
+
elif item == 2:
|
|
146
|
+
return self.z
|
|
147
|
+
else:
|
|
148
|
+
raise IndexError
|
|
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
|
+
|
|
159
|
+
@property
|
|
160
|
+
def xyz(self) -> tuple[T, T, T]:
|
|
161
|
+
"""
|
|
162
|
+
三元组 (x, y, z)。OpenCV 格式的坐标。
|
|
163
|
+
"""
|
|
164
|
+
return self.x, self.y, self.z
|
|
165
|
+
|
|
166
|
+
@property
|
|
167
|
+
def xy(self) -> tuple[T, T]:
|
|
168
|
+
"""
|
|
169
|
+
二元组 (x, y)。OpenCV 格式的坐标。
|
|
170
|
+
"""
|
|
171
|
+
return self.x, self.y
|
|
172
|
+
|
|
173
|
+
class Vector4D(Generic[T]):
|
|
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
|
+
|
|
198
|
+
def __init__(self, x: T, y: T, z: T, w: T, *, name: str | None = None):
|
|
199
|
+
self.x = x
|
|
200
|
+
self.y = y
|
|
201
|
+
self.z = z
|
|
202
|
+
self.w = w
|
|
203
|
+
self.name: str | None = name
|
|
204
|
+
"""坐标的名称。"""
|
|
205
|
+
|
|
206
|
+
def __getitem__(self, item: int):
|
|
207
|
+
if item == 0:
|
|
208
|
+
return self.x
|
|
209
|
+
elif item == 1:
|
|
210
|
+
return self.y
|
|
211
|
+
elif item == 2:
|
|
212
|
+
return self.z
|
|
213
|
+
elif item == 3:
|
|
214
|
+
return self.w
|
|
215
|
+
else:
|
|
216
|
+
raise IndexError
|
|
217
|
+
|
|
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
|
+
|
|
228
|
+
RectTuple = tuple[int, int, int, int]
|
|
229
|
+
"""矩形。(x, y, w, h)"""
|
|
230
|
+
PointTuple = tuple[int, int]
|
|
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
|
+
|
|
241
|
+
|
|
242
|
+
Number = TypeVar('Number', int, float)
|
|
243
|
+
"""数字类型,可以是整数或浮点数。"""
|
|
244
|
+
NumberTuple2D = tuple[Number, Number]
|
|
245
|
+
class _BasePoint(Vector2D[Number]):
|
|
246
|
+
__slots__ = (*Vector2D.__slots__,)
|
|
247
|
+
|
|
248
|
+
@property
|
|
249
|
+
def xy(self) -> NumberTuple2D:
|
|
250
|
+
"""
|
|
251
|
+
二元组 (x, y)。
|
|
252
|
+
|
|
253
|
+
:return: 包含 x 和 y 坐标的元组。
|
|
254
|
+
"""
|
|
255
|
+
return self.x, self.y
|
|
256
|
+
|
|
257
|
+
@property
|
|
258
|
+
def length(self) -> float:
|
|
259
|
+
"""
|
|
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
|
+
计算到另一个点的距离。
|
|
269
|
+
|
|
270
|
+
:param other: 另一个点或元组。
|
|
271
|
+
:return: 距离。
|
|
272
|
+
"""
|
|
273
|
+
return math.sqrt((self.x - other[0])**2 + (self.y - other[1])**2)
|
|
274
|
+
|
|
275
|
+
def __eq__(self, value: object) -> bool:
|
|
276
|
+
"""
|
|
277
|
+
比较两个点是否相等。
|
|
278
|
+
|
|
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:
|
|
287
|
+
"""
|
|
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]
|
|
305
|
+
else:
|
|
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。
|
|
367
|
+
|
|
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':
|
|
386
|
+
"""
|
|
387
|
+
与另一个点或元组相减。
|
|
388
|
+
|
|
389
|
+
如果任一操作数为浮点数,则结果提升为 PointF。
|
|
390
|
+
|
|
391
|
+
:param other: 另一个 Point/PointF 对象或元组。
|
|
392
|
+
:return: 相减后的新 Point 或 PointF 对象。
|
|
393
|
+
"""
|
|
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__,)
|
|
486
|
+
|
|
487
|
+
class Rect:
|
|
488
|
+
"""
|
|
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
|
+
```
|
|
521
|
+
"""
|
|
522
|
+
__slots__ = ('x1', 'y1', 'w', 'h', 'name')
|
|
523
|
+
|
|
524
|
+
def __init__(
|
|
525
|
+
self,
|
|
526
|
+
x: int | None = None,
|
|
527
|
+
y: int | None = None,
|
|
528
|
+
w: int | None = None,
|
|
529
|
+
h: int | None = None,
|
|
530
|
+
*,
|
|
531
|
+
xywh: RectTuple | None = None,
|
|
532
|
+
name: str | None = None,
|
|
533
|
+
):
|
|
534
|
+
"""
|
|
535
|
+
从给定的坐标信息创建矩形。
|
|
536
|
+
|
|
537
|
+
参数 `x`, `y`, `w`, `h` 和 `xywh` 必须至少指定一组。
|
|
538
|
+
|
|
539
|
+
:param x: 矩形左上角的 X 坐标。
|
|
540
|
+
:param y: 矩形左上角的 Y 坐标。
|
|
541
|
+
:param w: 矩形的宽度。
|
|
542
|
+
:param h: 矩形的高度。
|
|
543
|
+
:param xywh: 四元组 (x, y, w, h)。
|
|
544
|
+
:param name: 矩形的名称。
|
|
545
|
+
:raises ValueError: 提供的坐标参数不完整时抛出。
|
|
546
|
+
"""
|
|
547
|
+
if xywh is not None:
|
|
548
|
+
x, y, w, h = xywh
|
|
549
|
+
elif (
|
|
550
|
+
x is not None and
|
|
551
|
+
y is not None and
|
|
552
|
+
w is not None and
|
|
553
|
+
h is not None
|
|
554
|
+
):
|
|
555
|
+
pass
|
|
556
|
+
else:
|
|
557
|
+
raise ValueError('Either xywh or x, y, w, h must be provided.')
|
|
558
|
+
|
|
559
|
+
self.x1 = int(x)
|
|
560
|
+
"""矩形左上角的 X 坐标。"""
|
|
561
|
+
self.y1 = int(y)
|
|
562
|
+
"""矩形左上角的 Y 坐标。"""
|
|
563
|
+
self.w = int(w)
|
|
564
|
+
"""矩形的宽度。"""
|
|
565
|
+
self.h = int(h)
|
|
566
|
+
"""矩形的高度。"""
|
|
567
|
+
self.name: str | None = name
|
|
568
|
+
"""矩形的名称。"""
|
|
569
|
+
|
|
570
|
+
@classmethod
|
|
571
|
+
def from_xyxy(cls, x1: int, y1: int, x2: int, y2: int) -> 'Rect':
|
|
572
|
+
"""
|
|
573
|
+
从 (x1, y1, x2, y2) 创建矩形。
|
|
574
|
+
:return: 创建结果。
|
|
575
|
+
"""
|
|
576
|
+
return cls(int(x1), int(y1), int(x2 - x1), int(y2 - y1))
|
|
577
|
+
|
|
578
|
+
@property
|
|
579
|
+
def x2(self) -> int:
|
|
580
|
+
"""矩形右下角的 X 坐标。"""
|
|
581
|
+
return self.x1 + self.w
|
|
582
|
+
|
|
583
|
+
@x2.setter
|
|
584
|
+
def x2(self, value: int):
|
|
585
|
+
self.w = value - self.x1
|
|
586
|
+
|
|
587
|
+
@property
|
|
588
|
+
def y2(self) -> int:
|
|
589
|
+
"""矩形右下角的 Y 坐标。"""
|
|
590
|
+
return self.y1 + self.h
|
|
591
|
+
|
|
592
|
+
@y2.setter
|
|
593
|
+
def y2(self, value: int):
|
|
594
|
+
self.h = value - self.y1
|
|
595
|
+
|
|
596
|
+
@property
|
|
597
|
+
def xywh(self) -> RectTuple:
|
|
598
|
+
"""
|
|
599
|
+
四元组 (x1, y1, w, h)。OpenCV 格式的坐标。
|
|
600
|
+
"""
|
|
601
|
+
return self.x1, self.y1, self.w, self.h
|
|
602
|
+
|
|
603
|
+
@property
|
|
604
|
+
def xyxy(self) -> RectTuple:
|
|
605
|
+
"""
|
|
606
|
+
四元组 (x1, y1, x2, y2)。
|
|
607
|
+
"""
|
|
608
|
+
return self.x1, self.y1, self.x2, self.y2
|
|
609
|
+
|
|
610
|
+
@property
|
|
611
|
+
def top_left(self) -> Point:
|
|
612
|
+
"""
|
|
613
|
+
矩形的左上角点。
|
|
614
|
+
"""
|
|
615
|
+
if self.name:
|
|
616
|
+
name = "Left-top of rect "+ self.name
|
|
617
|
+
else:
|
|
618
|
+
name = None
|
|
619
|
+
return Point(self.x1, self.y1, name=name)
|
|
620
|
+
|
|
621
|
+
@property
|
|
622
|
+
def bottom_right(self) -> Point:
|
|
623
|
+
"""
|
|
624
|
+
矩形的右下角点。
|
|
625
|
+
"""
|
|
626
|
+
if self.name:
|
|
627
|
+
name = "Right-bottom of rect "+ self.name
|
|
628
|
+
else:
|
|
629
|
+
name = None
|
|
630
|
+
return Point(self.x2, self.y2, name=name)
|
|
631
|
+
|
|
632
|
+
@property
|
|
633
|
+
def left_bottom(self) -> Point:
|
|
634
|
+
"""
|
|
635
|
+
矩形的左下角点。
|
|
636
|
+
"""
|
|
637
|
+
if self.name:
|
|
638
|
+
name = "Left-bottom of rect "+ self.name
|
|
639
|
+
else:
|
|
640
|
+
name = None
|
|
641
|
+
return Point(self.x1, self.y2, name=name)
|
|
642
|
+
|
|
643
|
+
@property
|
|
644
|
+
def right_top(self) -> Point:
|
|
645
|
+
"""
|
|
646
|
+
矩形的右上角点。
|
|
647
|
+
"""
|
|
648
|
+
if self.name:
|
|
649
|
+
name = "Right-top of rect "+ self.name
|
|
650
|
+
else:
|
|
651
|
+
name = None
|
|
652
|
+
return Point(self.x2, self.y1, name=name)
|
|
653
|
+
|
|
654
|
+
@property
|
|
655
|
+
def center(self) -> Point:
|
|
656
|
+
"""
|
|
657
|
+
矩形的中心点。
|
|
658
|
+
|
|
659
|
+
:return: 中心点 Point 对象。
|
|
660
|
+
"""
|
|
661
|
+
if self.name:
|
|
662
|
+
name = "Center of rect "+ self.name
|
|
663
|
+
else:
|
|
664
|
+
name = None
|
|
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
|
|
738
|
+
|
|
739
|
+
def __repr__(self) -> str:
|
|
740
|
+
return f'Rect<"{self.name}" at (x={self.x1}, y={self.y1}, w={self.w}, h={self.h})>'
|
|
741
|
+
|
|
742
|
+
def __str__(self) -> str:
|
|
743
|
+
return f'(x={self.x1}, y={self.y1}, w={self.w}, h={self.h})'
|
|
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.")
|
|
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]。"""
|
|
945
|
+
def is_point(obj: object) -> TypeGuard[Point]:
|
|
946
|
+
return isinstance(obj, Point)
|
|
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
|
+
|
|
954
|
+
def is_rect(obj: object) -> TypeGuard[Rect]:
|
|
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)')
|