pyxllib 0.0.43__py3-none-any.whl → 0.3.197__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.
- pyxllib/__init__.py +9 -2
- pyxllib/algo/__init__.py +8 -0
- pyxllib/algo/disjoint.py +54 -0
- pyxllib/algo/geo.py +541 -0
- pyxllib/{util/mathlib.py → algo/intervals.py} +172 -36
- pyxllib/algo/matcher.py +389 -0
- pyxllib/algo/newbie.py +166 -0
- pyxllib/algo/pupil.py +629 -0
- pyxllib/algo/shapelylib.py +67 -0
- pyxllib/algo/specialist.py +241 -0
- pyxllib/algo/stat.py +494 -0
- pyxllib/algo/treelib.py +149 -0
- pyxllib/algo/unitlib.py +66 -0
- pyxllib/autogui/__init__.py +5 -0
- pyxllib/autogui/activewin.py +246 -0
- pyxllib/autogui/all.py +9 -0
- pyxllib/autogui/autogui.py +852 -0
- pyxllib/autogui/uiautolib.py +362 -0
- pyxllib/autogui/virtualkey.py +102 -0
- pyxllib/autogui/wechat.py +827 -0
- pyxllib/autogui/wechat_msg.py +421 -0
- pyxllib/autogui/wxautolib.py +84 -0
- pyxllib/cv/__init__.py +1 -11
- pyxllib/cv/expert.py +267 -0
- pyxllib/cv/{imlib.py → imfile.py} +18 -83
- pyxllib/cv/imhash.py +39 -0
- pyxllib/cv/pupil.py +9 -0
- pyxllib/cv/rgbfmt.py +1525 -0
- pyxllib/cv/slidercaptcha.py +137 -0
- pyxllib/cv/trackbartools.py +163 -49
- pyxllib/cv/xlcvlib.py +1040 -0
- pyxllib/cv/xlpillib.py +423 -0
- pyxllib/data/__init__.py +0 -0
- pyxllib/data/echarts.py +240 -0
- pyxllib/data/jsonlib.py +89 -0
- pyxllib/{util/oss2_.py → data/oss.py} +11 -9
- pyxllib/data/pglib.py +1127 -0
- pyxllib/data/sqlite.py +568 -0
- pyxllib/{util → data}/sqllib.py +13 -31
- pyxllib/ext/JLineViewer.py +505 -0
- pyxllib/ext/__init__.py +6 -0
- pyxllib/{util → ext}/demolib.py +119 -35
- pyxllib/ext/drissionlib.py +277 -0
- pyxllib/ext/kq5034lib.py +12 -0
- pyxllib/{util/main.py → ext/old.py} +122 -284
- pyxllib/ext/qt.py +449 -0
- pyxllib/ext/robustprocfile.py +497 -0
- pyxllib/ext/seleniumlib.py +76 -0
- pyxllib/{util/tklib.py → ext/tk.py} +10 -11
- pyxllib/ext/unixlib.py +827 -0
- pyxllib/ext/utools.py +351 -0
- pyxllib/{util/webhooklib.py → ext/webhook.py} +45 -17
- pyxllib/ext/win32lib.py +40 -0
- pyxllib/ext/wjxlib.py +88 -0
- pyxllib/ext/wpsapi.py +124 -0
- pyxllib/ext/xlwork.py +9 -0
- pyxllib/ext/yuquelib.py +1105 -0
- pyxllib/file/__init__.py +17 -0
- pyxllib/file/docxlib.py +761 -0
- pyxllib/{util → file}/gitlib.py +40 -27
- pyxllib/file/libreoffice.py +165 -0
- pyxllib/file/movielib.py +148 -0
- pyxllib/file/newbie.py +10 -0
- pyxllib/file/onenotelib.py +1469 -0
- pyxllib/file/packlib/__init__.py +330 -0
- pyxllib/{util → file/packlib}/zipfile.py +598 -195
- pyxllib/file/pdflib.py +426 -0
- pyxllib/file/pupil.py +185 -0
- pyxllib/file/specialist/__init__.py +685 -0
- pyxllib/{basic/_5_dirlib.py → file/specialist/dirlib.py} +364 -93
- pyxllib/file/specialist/download.py +193 -0
- pyxllib/file/specialist/filelib.py +2829 -0
- pyxllib/file/xlsxlib.py +3131 -0
- pyxllib/file/xlsyncfile.py +341 -0
- pyxllib/prog/__init__.py +5 -0
- pyxllib/prog/cachetools.py +64 -0
- pyxllib/prog/deprecatedlib.py +233 -0
- pyxllib/prog/filelock.py +42 -0
- pyxllib/prog/ipyexec.py +253 -0
- pyxllib/prog/multiprogs.py +940 -0
- pyxllib/prog/newbie.py +451 -0
- pyxllib/prog/pupil.py +1197 -0
- pyxllib/{sitepackages.py → prog/sitepackages.py} +5 -3
- pyxllib/prog/specialist/__init__.py +391 -0
- pyxllib/prog/specialist/bc.py +203 -0
- pyxllib/prog/specialist/browser.py +497 -0
- pyxllib/prog/specialist/common.py +347 -0
- pyxllib/prog/specialist/datetime.py +199 -0
- pyxllib/prog/specialist/tictoc.py +240 -0
- pyxllib/prog/specialist/xllog.py +180 -0
- pyxllib/prog/xlosenv.py +108 -0
- pyxllib/stdlib/__init__.py +17 -0
- pyxllib/{util → stdlib}/tablepyxl/__init__.py +1 -3
- pyxllib/{util → stdlib}/tablepyxl/style.py +1 -1
- pyxllib/{util → stdlib}/tablepyxl/tablepyxl.py +2 -4
- pyxllib/text/__init__.py +8 -0
- pyxllib/text/ahocorasick.py +39 -0
- pyxllib/text/airscript.js +744 -0
- pyxllib/text/charclasslib.py +121 -0
- pyxllib/text/jiebalib.py +267 -0
- pyxllib/text/jinjalib.py +32 -0
- pyxllib/text/jsa_ai_prompt.md +271 -0
- pyxllib/text/jscode.py +922 -0
- pyxllib/text/latex/__init__.py +158 -0
- pyxllib/text/levenshtein.py +303 -0
- pyxllib/text/nestenv.py +1215 -0
- pyxllib/text/newbie.py +300 -0
- pyxllib/text/pupil/__init__.py +8 -0
- pyxllib/text/pupil/common.py +1121 -0
- pyxllib/text/pupil/xlalign.py +326 -0
- pyxllib/text/pycode.py +47 -0
- pyxllib/text/specialist/__init__.py +8 -0
- pyxllib/text/specialist/common.py +112 -0
- pyxllib/text/specialist/ptag.py +186 -0
- pyxllib/text/spellchecker.py +172 -0
- pyxllib/text/templates/echart_base.html +11 -0
- pyxllib/text/templates/highlight_code.html +17 -0
- pyxllib/text/templates/latex_editor.html +103 -0
- pyxllib/text/vbacode.py +17 -0
- pyxllib/text/xmllib.py +747 -0
- pyxllib/xl.py +39 -0
- pyxllib/xlcv.py +17 -0
- pyxllib-0.3.197.dist-info/METADATA +48 -0
- pyxllib-0.3.197.dist-info/RECORD +126 -0
- {pyxllib-0.0.43.dist-info → pyxllib-0.3.197.dist-info}/WHEEL +4 -5
- pyxllib/basic/_1_strlib.py +0 -945
- pyxllib/basic/_2_timelib.py +0 -488
- pyxllib/basic/_3_pathlib.py +0 -916
- pyxllib/basic/_4_loglib.py +0 -419
- pyxllib/basic/__init__.py +0 -54
- pyxllib/basic/arrow_.py +0 -250
- pyxllib/basic/chardet_.py +0 -66
- pyxllib/basic/dirlib.py +0 -529
- pyxllib/basic/dprint.py +0 -202
- pyxllib/basic/extension.py +0 -12
- pyxllib/basic/judge.py +0 -31
- pyxllib/basic/log.py +0 -204
- pyxllib/basic/pathlib_.py +0 -705
- pyxllib/basic/pytictoc.py +0 -102
- pyxllib/basic/qiniu_.py +0 -61
- pyxllib/basic/strlib.py +0 -761
- pyxllib/basic/timer.py +0 -132
- pyxllib/cv/cv.py +0 -834
- pyxllib/cv/cvlib/_1_geo.py +0 -543
- pyxllib/cv/cvlib/_2_cvprcs.py +0 -309
- pyxllib/cv/cvlib/_2_imgproc.py +0 -594
- pyxllib/cv/cvlib/_3_pilprcs.py +0 -80
- pyxllib/cv/cvlib/_4_cvimg.py +0 -211
- pyxllib/cv/cvlib/__init__.py +0 -10
- pyxllib/cv/debugtools.py +0 -82
- pyxllib/cv/fitz_.py +0 -300
- pyxllib/cv/installer.py +0 -42
- pyxllib/debug/_0_installer.py +0 -38
- pyxllib/debug/_1_typelib.py +0 -277
- pyxllib/debug/_2_chrome.py +0 -198
- pyxllib/debug/_3_showdir.py +0 -161
- pyxllib/debug/_4_bcompare.py +0 -140
- pyxllib/debug/__init__.py +0 -49
- pyxllib/debug/bcompare.py +0 -132
- pyxllib/debug/chrome.py +0 -198
- pyxllib/debug/installer.py +0 -38
- pyxllib/debug/showdir.py +0 -158
- pyxllib/debug/typelib.py +0 -278
- pyxllib/image/__init__.py +0 -12
- pyxllib/torch/__init__.py +0 -20
- pyxllib/torch/modellib.py +0 -37
- pyxllib/torch/trainlib.py +0 -344
- pyxllib/util/__init__.py +0 -20
- pyxllib/util/aip_.py +0 -141
- pyxllib/util/casiadb.py +0 -59
- pyxllib/util/excellib.py +0 -495
- pyxllib/util/filelib.py +0 -612
- pyxllib/util/jsondata.py +0 -27
- pyxllib/util/jsondata2.py +0 -92
- pyxllib/util/labelmelib.py +0 -139
- pyxllib/util/onepy/__init__.py +0 -29
- pyxllib/util/onepy/onepy.py +0 -574
- pyxllib/util/onepy/onmanager.py +0 -170
- pyxllib/util/pyautogui_.py +0 -219
- pyxllib/util/textlib.py +0 -1305
- pyxllib/util/unorder.py +0 -22
- pyxllib/util/xmllib.py +0 -639
- pyxllib-0.0.43.dist-info/METADATA +0 -39
- pyxllib-0.0.43.dist-info/RECORD +0 -80
- pyxllib-0.0.43.dist-info/top_level.txt +0 -1
- {pyxllib-0.0.43.dist-info → pyxllib-0.3.197.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,852 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
# @Author : 陈坤泽
|
4
|
+
# @Email : 877362867@qq.com
|
5
|
+
# @Date : 2020/06/06
|
6
|
+
|
7
|
+
import sys
|
8
|
+
from collections import defaultdict
|
9
|
+
import json
|
10
|
+
import os
|
11
|
+
import time
|
12
|
+
import random
|
13
|
+
|
14
|
+
import mss
|
15
|
+
import numpy as np
|
16
|
+
from pandas.api.types import is_list_like
|
17
|
+
|
18
|
+
if sys.platform == 'win32':
|
19
|
+
import pyautogui
|
20
|
+
import win32gui
|
21
|
+
|
22
|
+
try:
|
23
|
+
import pyscreeze # NOQA pyautogui安装的时候会自动安装依赖的pyscreeze
|
24
|
+
except ModuleNotFoundError:
|
25
|
+
pass
|
26
|
+
|
27
|
+
from pyxllib.prog.newbie import first_nonnone, round_int
|
28
|
+
from pyxllib.prog.pupil import xlwait, DictTool, check_install_package
|
29
|
+
from pyxllib.prog.specialist import TicToc
|
30
|
+
from pyxllib.algo.geo import ComputeIou, ltrb2xywh, xywh2ltrb, ltrb2polygon
|
31
|
+
from pyxllib.algo.shapelylib import ShapelyPolygon
|
32
|
+
from pyxllib.file.specialist import File, Dir, XlPath
|
33
|
+
from pyxllib.cv.expert import xlcv, xlpil
|
34
|
+
from pyxlpr.data.labelme import LabelmeDict
|
35
|
+
|
36
|
+
|
37
|
+
def __20210531():
|
38
|
+
""" 以前设想的一个跟labelme结合的自动化操作类 """
|
39
|
+
|
40
|
+
|
41
|
+
class AutoGuiLabelData:
|
42
|
+
""" AutoGuiLabelData """
|
43
|
+
|
44
|
+
def __init__(self, root):
|
45
|
+
"""
|
46
|
+
data:dict
|
47
|
+
key: 相对路径/图片stem (这一节称为loc)
|
48
|
+
label名称:对应属性dict1 (跟loc拼在一起,称为loclabel)
|
49
|
+
label2:dict2
|
50
|
+
"""
|
51
|
+
self.root = XlPath(root)
|
52
|
+
self.data = defaultdict(dict)
|
53
|
+
self.imfiles = {}
|
54
|
+
|
55
|
+
for file in self.root.rglob_files('*.json'):
|
56
|
+
lmdict = file.read_json()
|
57
|
+
imfile = file.with_name(lmdict['imagePath'])
|
58
|
+
img = xlcv.read(imfile)
|
59
|
+
loc = str(file.relpath(self.root)).replace('\\', '/')[:-5]
|
60
|
+
self.imfiles[loc] = imfile
|
61
|
+
|
62
|
+
for shape in lmdict['shapes']:
|
63
|
+
attrs = self.parse_shape(shape, img)
|
64
|
+
self.data[loc][attrs['text']] = attrs
|
65
|
+
|
66
|
+
@classmethod
|
67
|
+
def parse_shape(cls, shape, image=None):
|
68
|
+
""" 解析一个shape的数据为dict字典,会把一些主要的几何特征也加进label字典字段中 """
|
69
|
+
# 1 解析原label为字典
|
70
|
+
# 如果是来自普通的label,会把原文本自动转换为text字段。如果是来自xllabelme,则一般默认就是字典值,带text字段。
|
71
|
+
attrs = DictTool.json_loads(shape['label'], 'text')
|
72
|
+
attrs.update(DictTool.sub(shape, ['label']))
|
73
|
+
|
74
|
+
# 2 中心点 center
|
75
|
+
shape_type = shape['shape_type']
|
76
|
+
pts = shape['points']
|
77
|
+
if shape_type in ('rectangle', 'polygon', 'line'):
|
78
|
+
attrs['center'] = np.array(np.array(pts).mean(axis=0), dtype=int).tolist()
|
79
|
+
elif shape_type == 'circle':
|
80
|
+
attrs['center'] = pts[0]
|
81
|
+
|
82
|
+
# 3 外接矩形 rect
|
83
|
+
if shape_type in ('rectangle', 'polygon'):
|
84
|
+
attrs['ltrb'] = np.array(pts, dtype=int).reshape(-1).tolist()
|
85
|
+
elif shape_type == 'circle':
|
86
|
+
x, y = pts[0]
|
87
|
+
r = ((x - pts[1][0]) ** 2 + (y - pts[1][1]) ** 2) ** 0.5
|
88
|
+
attrs['ltrb'] = [round_int(v) for v in [x - r, y - r, x + r, y + r]]
|
89
|
+
|
90
|
+
# 4 图片数据 img, etag
|
91
|
+
if image is not None and attrs['ltrb']:
|
92
|
+
attrs['img'] = xlcv.get_sub(image, attrs['ltrb'])
|
93
|
+
# attrs['etag'] = get_etag(attrs['img'])
|
94
|
+
# TODO 这里目的其实就是让相同图片对比尽量相似,所以可以用dhash而不是etag
|
95
|
+
|
96
|
+
# 5 中心点像素值 pixel
|
97
|
+
p = attrs['center']
|
98
|
+
if image is not None and p:
|
99
|
+
attrs['pixel'] = tuple(image[p[1], p[0]].tolist()[::-1])
|
100
|
+
|
101
|
+
# if 'rect' in attrs:
|
102
|
+
# del attrs['rect'] # 旧版的格式数据,删除
|
103
|
+
|
104
|
+
return attrs
|
105
|
+
|
106
|
+
def update_loclabel_img(self, loclabel, img, *, if_exists='update'):
|
107
|
+
""" 添加一张图片数据
|
108
|
+
|
109
|
+
:param if_exists:
|
110
|
+
update,更新
|
111
|
+
skip,跳过,不更新
|
112
|
+
"""
|
113
|
+
loc, label = os.path.split(loclabel)
|
114
|
+
h, w = img.shape[:2]
|
115
|
+
|
116
|
+
# 1 如果loc图片不存在,则新建一个jpg图片
|
117
|
+
update = True
|
118
|
+
if loc not in self.data or not self.data[loc]:
|
119
|
+
imfile = xlcv.write(img, File(loc, self.root, suffix='.jpg'))
|
120
|
+
self.imfiles[loc] = imfile
|
121
|
+
shape = LabelmeDict.gen_shape(label, [[0, 0], [w, h]])
|
122
|
+
self.data[loc][label] = self.parse_shape(shape)
|
123
|
+
# 2 不存在的标签,则在最后一行新建一张图
|
124
|
+
elif label not in self.data[loc]:
|
125
|
+
image = xlcv.read(self.imfiles[loc])
|
126
|
+
height, width = image.shape[:2]
|
127
|
+
assert width == w # 先按行拼接,以后有时间可以扩展更灵活的拼接操作
|
128
|
+
# 拼接,并重新存储为图片
|
129
|
+
image = np.concatenate([image, img])
|
130
|
+
xlcv.write(image, self.imfiles[loc])
|
131
|
+
shape = LabelmeDict.gen_shape(label, [[0, height], [width, height + h]])
|
132
|
+
self.data[loc][label] = self.parse_shape(shape)
|
133
|
+
# 3 已有的图,则进行替换
|
134
|
+
elif if_exists == 'update':
|
135
|
+
image = xlcv.read(self.imfiles[loc])
|
136
|
+
[x1, y1, x2, y2] = self.data[loc][label]['ltrb']
|
137
|
+
image[y1:y2, x1:x2] = img
|
138
|
+
xlcv.write(image, self.imfiles[loc])
|
139
|
+
else:
|
140
|
+
update = False
|
141
|
+
|
142
|
+
if update: # 需要实时保存到文件中
|
143
|
+
self.write(loc)
|
144
|
+
|
145
|
+
def write(self, loc):
|
146
|
+
f = File(loc, self.root, suffix='.json')
|
147
|
+
imfile = self.imfiles[loc]
|
148
|
+
lmdict = LabelmeDict.gen_data(imfile)
|
149
|
+
for label, ann in self.data[loc].items():
|
150
|
+
a = ann.copy()
|
151
|
+
DictTool.isub(a, ['img'])
|
152
|
+
shape = LabelmeDict.gen_shape(json.dumps(a, ensure_ascii=False),
|
153
|
+
a['points'], a['shape_type'],
|
154
|
+
group_id=a['group_id'], flags=a['flags'])
|
155
|
+
lmdict['shapes'].append(shape)
|
156
|
+
f.write(lmdict, indent=2)
|
157
|
+
|
158
|
+
def writes(self):
|
159
|
+
for loc in self.data.keys():
|
160
|
+
self.write(loc)
|
161
|
+
|
162
|
+
def __getitem__(self, loclabel):
|
163
|
+
""" 支持 self['主菜单/道友'] 的格式获取 self.data['主菜单']['道友'] 数据
|
164
|
+
"""
|
165
|
+
# 注意功能效果:os.path.split('a/b/c') -> ('a/b', 'c')
|
166
|
+
loc, label = os.path.split(loclabel)
|
167
|
+
try:
|
168
|
+
return self.data[loc][label]
|
169
|
+
except KeyError:
|
170
|
+
return None
|
171
|
+
|
172
|
+
def __setitem__(self, loclabel, value):
|
173
|
+
loc, label = os.path.split(loclabel)
|
174
|
+
self.data[loc][label] = value
|
175
|
+
|
176
|
+
|
177
|
+
class NamedLocate(AutoGuiLabelData):
|
178
|
+
""" 对有命名的标注数据进行定位
|
179
|
+
|
180
|
+
注意labelme在每张图片上写的label值最好不要有重复,否则按字典存储后可能会被覆盖
|
181
|
+
|
182
|
+
特殊取值:
|
183
|
+
'@IMAGE_ID' 该状态图的标志区域,即游戏中在该位置出现此子图,则认为进入了对应的图像状态
|
184
|
+
|
185
|
+
截图:screenshot,update_shot
|
186
|
+
检查固定位像素:point2pixel,pixel_distance,check_pixel
|
187
|
+
检查固定位图片:rect2img,img_distance,check_img
|
188
|
+
检索图片:img2rects, img2rect,img2point,img2img
|
189
|
+
操作:click,move_to
|
190
|
+
高级:wait,check_click,wait_click
|
191
|
+
"""
|
192
|
+
|
193
|
+
def __init__(self, root, *, region=None, grayscale=None, confidence=0.95, tolerance=20):
|
194
|
+
"""
|
195
|
+
|
196
|
+
:param root: 标注数据所在文件夹
|
197
|
+
有原图jpg、png和对应的json标注数据
|
198
|
+
:param region: 可以限定窗口位置 (xywh格式)
|
199
|
+
:param grayscale: 转成灰度图比较
|
200
|
+
:param confidence: 用于图片比较,相似度阈值
|
201
|
+
:param tolerance: 像素比较时能容忍的误差变化范围
|
202
|
+
|
203
|
+
TODO random_click: 增加该可选参数,返回point时,进行上下左右随机扰动
|
204
|
+
"""
|
205
|
+
super().__init__(root)
|
206
|
+
self.grayscale = grayscale
|
207
|
+
self.confidence = confidence
|
208
|
+
self.tolerance = tolerance
|
209
|
+
self.region = region
|
210
|
+
self.last_shot = self.update_shot()
|
211
|
+
|
212
|
+
def point_local2global(self, p):
|
213
|
+
""" 一个self.region中相对坐标点p,转换到全局坐标位置 """
|
214
|
+
if self.region:
|
215
|
+
x, y = p
|
216
|
+
x += self.region[0]
|
217
|
+
y += self.region[1]
|
218
|
+
return [x, y]
|
219
|
+
else:
|
220
|
+
return p
|
221
|
+
|
222
|
+
def point_global2local(self, p):
|
223
|
+
""" 一个self.region中相对坐标点p,转换到全局坐标位置 """
|
224
|
+
if self.region:
|
225
|
+
x, y = p
|
226
|
+
x -= self.region[0]
|
227
|
+
y -= self.region[1]
|
228
|
+
return [x, y]
|
229
|
+
else:
|
230
|
+
return p
|
231
|
+
|
232
|
+
def screenshot(self, region=None):
|
233
|
+
"""
|
234
|
+
:param region: ltrb
|
235
|
+
"""
|
236
|
+
if isinstance(region, str):
|
237
|
+
region = ltrb2xywh(self[region]['ltrb'])
|
238
|
+
im = pyautogui.screenshot(region=region)
|
239
|
+
return xlpil.to_cv2_image(im)
|
240
|
+
|
241
|
+
def update_shot(self, region=None):
|
242
|
+
region = region or self.region
|
243
|
+
self.last_shot = self.screenshot(region)
|
244
|
+
return self.last_shot
|
245
|
+
|
246
|
+
def point2pixel(self, point, haystack=None):
|
247
|
+
"""
|
248
|
+
|
249
|
+
:param point: 支持输入 (x, y) 坐标,或者 loclabel
|
250
|
+
后面很多参数都是类似,除了标准数据类型,同时支持 loclabel 定位模式
|
251
|
+
|
252
|
+
官方的pixel版本,在windows有时候会出bug
|
253
|
+
OSError: windll.user32.ReleaseDC failed : return 0
|
254
|
+
"""
|
255
|
+
if isinstance(point, str):
|
256
|
+
point = self[point]['center']
|
257
|
+
if haystack is None:
|
258
|
+
haystack = self.update_shot()
|
259
|
+
return tuple(haystack[point[1], point[0]].tolist()[::-1])
|
260
|
+
|
261
|
+
def rect2img(self, ltrb, haystack=None):
|
262
|
+
"""
|
263
|
+
:param ltrb: 可以输入坐标定位,也可以输入 loclabel 定位
|
264
|
+
"""
|
265
|
+
if isinstance(ltrb, str):
|
266
|
+
ltrb = self[ltrb]['ltrb']
|
267
|
+
l, t, r, b = ltrb
|
268
|
+
if haystack is None:
|
269
|
+
haystack = self.update_shot()
|
270
|
+
return haystack[t:b, l:r]
|
271
|
+
|
272
|
+
@classmethod
|
273
|
+
def pixel_distance(cls, pixel1, pixel2):
|
274
|
+
""" 返回最大的像素值差,如果相同返回0 """
|
275
|
+
return max([abs(x - y) for x, y in zip(pixel1, pixel2)])
|
276
|
+
|
277
|
+
@classmethod
|
278
|
+
def img_distance(cls, img1, img2, *, tolerance=10):
|
279
|
+
""" 返回距离:不相同像素占的百分比,相同返回0
|
280
|
+
|
281
|
+
:param tolerance: 每个像素允许10的差距
|
282
|
+
"""
|
283
|
+
cmp = np.array(abs(np.array(img1, dtype=int) - img2) > tolerance)
|
284
|
+
return cmp.sum() / cmp.size
|
285
|
+
|
286
|
+
def check_pixel(self, loclabel, haystack=None, *, tolerance=None):
|
287
|
+
""" 判断对应位置上的像素值是否相同
|
288
|
+
"""
|
289
|
+
tolerance = first_nonnone([tolerance, self.tolerance, 20])
|
290
|
+
p1 = self[loclabel]['pixel']
|
291
|
+
p2 = self.point2pixel(loclabel, haystack)
|
292
|
+
return self.pixel_distance(p1, p2) < tolerance
|
293
|
+
|
294
|
+
def check_img(self, loclabel, needle=None, *, grayscale=None, confidence=None):
|
295
|
+
grayscale = first_nonnone([grayscale, self.grayscale, False])
|
296
|
+
confidence = first_nonnone([confidence, self.confidence, 0.95])
|
297
|
+
if needle is None:
|
298
|
+
needle = self[loclabel]['img']
|
299
|
+
elif isinstance(needle, str):
|
300
|
+
needle = self[needle]['img']
|
301
|
+
haystack = self.screenshot(loclabel)
|
302
|
+
|
303
|
+
if confidence >= 1:
|
304
|
+
return np.array_equal(needle, haystack)
|
305
|
+
else:
|
306
|
+
boxes = pyautogui.locateAll(needle, self.screenshot(loclabel),
|
307
|
+
grayscale=grayscale, confidence=confidence)
|
308
|
+
boxes = [xywh2ltrb(box) for box in list(boxes)]
|
309
|
+
return len(boxes)
|
310
|
+
|
311
|
+
def img2rects(self, img, haystack=None, *, grayscale=None, confidence=None):
|
312
|
+
""" 根据预存的img数据,匹配出多个内容对应的所在的rect位置 """
|
313
|
+
# 1 配置参数
|
314
|
+
if isinstance(img, str):
|
315
|
+
img = self[img]['img']
|
316
|
+
grayscale = first_nonnone([grayscale, self.grayscale, False])
|
317
|
+
confidence = first_nonnone([confidence, self.confidence, 0.95])
|
318
|
+
# 2 查找子图
|
319
|
+
if haystack is None:
|
320
|
+
self.update_shot()
|
321
|
+
haystack = self.last_shot
|
322
|
+
boxes = pyautogui.locateAll(img, haystack,
|
323
|
+
grayscale=grayscale,
|
324
|
+
confidence=confidence)
|
325
|
+
# 3 过滤掉重叠超过一半面积的框
|
326
|
+
rects = ComputeIou.nms_ltrb([xywh2ltrb(box) for box in list(boxes)])
|
327
|
+
return rects
|
328
|
+
|
329
|
+
def img2rect(self, img, haystack=None, *, grayscale=None, confidence=None):
|
330
|
+
""" img2rects的简化,只返回一个匹配框
|
331
|
+
"""
|
332
|
+
rects = self.img2rects(img, haystack, grayscale=grayscale, confidence=confidence)
|
333
|
+
return rects[0] if rects else None
|
334
|
+
|
335
|
+
def img2point(self, img, haystack=None, *, grayscale=None, confidence=None):
|
336
|
+
""" 将 img2rect 的结果统一转成点 """
|
337
|
+
res = self.img2rect(img, haystack, grayscale=grayscale, confidence=confidence)
|
338
|
+
if res:
|
339
|
+
return np.array(np.array(res).reshape(2, 2).mean(axis=0), dtype=int).tolist()
|
340
|
+
|
341
|
+
def img2img(self, img, haystack=None, *, grayscale=None, confidence=None):
|
342
|
+
""" 找到rect后,返回匹配的目标img图片内容 """
|
343
|
+
ltrb = self.img2rect(img, haystack, grayscale=grayscale, confidence=confidence)
|
344
|
+
if ltrb:
|
345
|
+
l, t, r, b = ltrb
|
346
|
+
if haystack is not None:
|
347
|
+
haystack = self.last_shot
|
348
|
+
return haystack[t:b, l:r]
|
349
|
+
|
350
|
+
def click(self, point, *, random_bias=0, back=False, wait_change=False, wait_seconds=0):
|
351
|
+
"""
|
352
|
+
:param back: 点击后鼠标移回原坐标位置
|
353
|
+
:param wait_change: 点击后,等待画面产生变化,才结束函数
|
354
|
+
point为 loclabel 才能用这个功能
|
355
|
+
注意这个只是比较loclabel的局部区域图片变化,不是全图的变化
|
356
|
+
:param int random_bias: 允许在一定范围内随机点击点
|
357
|
+
"""
|
358
|
+
# 1 判断类型
|
359
|
+
if isinstance(point, str):
|
360
|
+
loclabel = point
|
361
|
+
point = self[loclabel]['center']
|
362
|
+
else:
|
363
|
+
loclabel = ''
|
364
|
+
|
365
|
+
# 随机偏移点
|
366
|
+
if random_bias:
|
367
|
+
x, y = point
|
368
|
+
x += random.randint(-random_bias, +random_bias)
|
369
|
+
y += random.randint(-random_bias, +random_bias)
|
370
|
+
point = (x, y)
|
371
|
+
|
372
|
+
# 2 鼠标移动
|
373
|
+
pos0 = pyautogui.position()
|
374
|
+
pyautogui.click(*self.point_local2global(point))
|
375
|
+
if back:
|
376
|
+
pyautogui.moveTo(*pos0) # 恢复鼠标原位置
|
377
|
+
|
378
|
+
# 3 等待画面变化
|
379
|
+
if loclabel and wait_change:
|
380
|
+
func = lambda: self.img_distance(self[loclabel]['img'],
|
381
|
+
self.rect2img(loclabel)) > self.confidence
|
382
|
+
xlwait(func)
|
383
|
+
if wait_seconds:
|
384
|
+
time.sleep(wait_seconds)
|
385
|
+
|
386
|
+
return point
|
387
|
+
|
388
|
+
def move_to(self, point):
|
389
|
+
if isinstance(point, str):
|
390
|
+
point = self[point]['center']
|
391
|
+
pyautogui.moveTo(*self.point_local2global(point))
|
392
|
+
|
393
|
+
def drag_to(self, loclabel, direction=0, duration=1, *, to_tail=False, **kwargs):
|
394
|
+
""" 在loclabel区域内进行拖拽操作
|
395
|
+
|
396
|
+
:param direction: 方向,0123分别表示向"上/右/下/左"拖拽。
|
397
|
+
:param to_tail:
|
398
|
+
False,默认只拖拽一次
|
399
|
+
True,一直拖拽到末尾,即内容不再变化为止
|
400
|
+
"""
|
401
|
+
|
402
|
+
def core():
|
403
|
+
x = self[loclabel]
|
404
|
+
l, t, r, b = x['ltrb']
|
405
|
+
x2, y2 = x['center']
|
406
|
+
|
407
|
+
self.move_to(loclabel)
|
408
|
+
if direction == 0:
|
409
|
+
y2 = (y2 + t) // 2
|
410
|
+
elif direction == 1:
|
411
|
+
x2 = (x2 + r) // 2
|
412
|
+
elif direction == 2:
|
413
|
+
y2 = (y2 + b) // 2
|
414
|
+
elif direction == 3:
|
415
|
+
x2 = (x2 + l) // 2
|
416
|
+
else:
|
417
|
+
raise ValueError
|
418
|
+
|
419
|
+
x2, y2 = self.point_local2global([x2, y2])
|
420
|
+
pyautogui.dragTo(x2, y2, duration=duration, **kwargs)
|
421
|
+
|
422
|
+
if to_tail:
|
423
|
+
last_im = self.rect2img(loclabel)
|
424
|
+
while True:
|
425
|
+
core()
|
426
|
+
time.sleep(1)
|
427
|
+
new_im = self.rect2img(loclabel)
|
428
|
+
if self.img_distance(last_im, new_im) > 0.95: # 95%的像素相似
|
429
|
+
break
|
430
|
+
last_im = new_im
|
431
|
+
else:
|
432
|
+
core()
|
433
|
+
|
434
|
+
def wait(self, loclabel, *, fixpos=False, limit=None, interval=1):
|
435
|
+
""" 封装的比较高层的功能 """
|
436
|
+
# dprint(loclabel, fixpos)
|
437
|
+
if fixpos:
|
438
|
+
xlwait(lambda: self.check_img(loclabel))
|
439
|
+
point = self[loclabel]['center']
|
440
|
+
else:
|
441
|
+
point = xlwait(lambda: self.img2point(loclabel), limit=limit, interval=interval)
|
442
|
+
return point
|
443
|
+
|
444
|
+
def wait_leave(self, loclabel, *, fixpos=False, limit=None, interval=1):
|
445
|
+
""" 封装的比较高层的功能 """
|
446
|
+
if fixpos:
|
447
|
+
xlwait(lambda: self.check_img(loclabel))
|
448
|
+
point = self[loclabel]['center']
|
449
|
+
else:
|
450
|
+
point = xlwait(lambda: not self.img2point(loclabel), limit=limit, interval=interval)
|
451
|
+
return point
|
452
|
+
|
453
|
+
def check_click(self, loclabel, *, back=False, wait_change=False):
|
454
|
+
point = self.img2point(loclabel)
|
455
|
+
if point:
|
456
|
+
self.click(point, back=back, wait_change=wait_change)
|
457
|
+
|
458
|
+
def wait_click(self, loclabel, *, fixpos=False, back=False, wait_change=False):
|
459
|
+
""" 封装的比较高层的功能 """
|
460
|
+
point = self.wait(loclabel, fixpos=fixpos)
|
461
|
+
self.click(point, back=back, wait_change=wait_change)
|
462
|
+
|
463
|
+
def point2loclabels(self, point, loclabels):
|
464
|
+
""" 判断loc这张图里有哪些标签覆盖到了point这个点
|
465
|
+
|
466
|
+
:param loclabels: 可以输入一张图的定位
|
467
|
+
也可以输入多个loclabel清单,会返回所有满足的loclabel名称
|
468
|
+
"""
|
469
|
+
from shapely.geometry import Point
|
470
|
+
|
471
|
+
# 1 loclabels
|
472
|
+
if not is_list_like(loclabels):
|
473
|
+
loclabels = [loclabels]
|
474
|
+
point = Point(*point)
|
475
|
+
|
476
|
+
# 2 result
|
477
|
+
res = []
|
478
|
+
for loclabel in loclabels:
|
479
|
+
if loclabel in self.data:
|
480
|
+
for k, v in self.data.items():
|
481
|
+
if 'ltrb' in v:
|
482
|
+
if point.within(ShapelyPolygon.gen(v['ltrb'])):
|
483
|
+
res.append(f'{loclabel}/{k}')
|
484
|
+
else:
|
485
|
+
v = self[loclabel]
|
486
|
+
if 'ltrb' in v:
|
487
|
+
if point.within(ShapelyPolygon.gen(v['ltrb'])):
|
488
|
+
res.append(f'{loclabel}')
|
489
|
+
|
490
|
+
return res
|
491
|
+
|
492
|
+
def point2loclabel(self, point, loclabels):
|
493
|
+
""" 只返回第一个匹配结果
|
494
|
+
"""
|
495
|
+
from shapely.geometry import Point
|
496
|
+
|
497
|
+
# 1 loclabels
|
498
|
+
if not is_list_like(loclabels):
|
499
|
+
loclabels = [loclabels]
|
500
|
+
point = Point(*point)
|
501
|
+
|
502
|
+
# 2 result
|
503
|
+
for loclabel in loclabels:
|
504
|
+
if loclabel in self.data:
|
505
|
+
for k, v in self.data.items():
|
506
|
+
if 'ltrb' in v:
|
507
|
+
if point.within(ShapelyPolygon.gen(v['ltrb'])):
|
508
|
+
return f'{loclabel}/{k}'
|
509
|
+
else:
|
510
|
+
v = self[loclabel]
|
511
|
+
if 'ltrb' in v:
|
512
|
+
if point.within(ShapelyPolygon.gen(v['ltrb'])):
|
513
|
+
return f'{loclabel}'
|
514
|
+
|
515
|
+
|
516
|
+
class PosTran:
|
517
|
+
""" 坐标位置变换
|
518
|
+
|
519
|
+
应用场景: 原来在A窗口下的点p和区域r,
|
520
|
+
在窗口位置、大小改成B后,p和r的坐标
|
521
|
+
"""
|
522
|
+
|
523
|
+
def __init__(self, w1, w2):
|
524
|
+
"""
|
525
|
+
:param w1: 原窗口位置 (x, y, w, h)
|
526
|
+
:param w2: 新窗口位置
|
527
|
+
"""
|
528
|
+
self.w1 = w1
|
529
|
+
self.w2 = w2
|
530
|
+
|
531
|
+
@classmethod
|
532
|
+
def point2point(cls, w1, p1, w2):
|
533
|
+
""" 窗口w1切到w2,原点p1变换到坐标p2
|
534
|
+
"""
|
535
|
+
x1, y1, w1, h1 = w1
|
536
|
+
x2, y2, w2, h2 = w2
|
537
|
+
x = x2 + (p1[0] - x1) * w2 / w1
|
538
|
+
y = y2 + (p1[1] - y1) * h2 / h1
|
539
|
+
return round(x), round(y)
|
540
|
+
|
541
|
+
def w1point(self, w2point):
|
542
|
+
return self.point2point(self.w2, w2point, self.w1)
|
543
|
+
|
544
|
+
def w2point(self, w1point):
|
545
|
+
"""旧窗口中的点坐标转回新窗口点坐标"""
|
546
|
+
return self.point2point(self.w1, w1point, self.w2)
|
547
|
+
|
548
|
+
@classmethod
|
549
|
+
def dx2dx(cls, w1, dx1, w2):
|
550
|
+
""" 宽度偏移量的变化
|
551
|
+
"""
|
552
|
+
return round(dx1 * w2[2] / w1[2])
|
553
|
+
|
554
|
+
def w1dx(self, dx2):
|
555
|
+
return self.dx2dx(self.w2, dx2, self.w1)
|
556
|
+
|
557
|
+
def w2dx(self, dx1):
|
558
|
+
return self.dx2dx(self.w1, dx1, self.w2)
|
559
|
+
|
560
|
+
@classmethod
|
561
|
+
def dy2dy(cls, w1, dy1, w2):
|
562
|
+
return round(dy1 * w2[3] / w1[3])
|
563
|
+
|
564
|
+
def w1dy(self, dy2):
|
565
|
+
return self.dy2dy(self.w2, dy2, self.w1)
|
566
|
+
|
567
|
+
def w2dy(self, dy1):
|
568
|
+
return self.dy2dy(self.w1, dy1, self.w2)
|
569
|
+
|
570
|
+
@classmethod
|
571
|
+
def region2region(cls, w1, r1, w2):
|
572
|
+
""" 窗口w1切到w2,原区域r1变换到坐标r2
|
573
|
+
"""
|
574
|
+
x, y = cls.point2point(w1, r1[:2], w2)
|
575
|
+
w = round(r1[2] * w2[2] / w1[2])
|
576
|
+
h = round(r1[3] * w2[3] / w1[3])
|
577
|
+
return x, y, w, h
|
578
|
+
|
579
|
+
def w1region(self, w2region):
|
580
|
+
return self.region2region(self.w2, w2region, self.w1)
|
581
|
+
|
582
|
+
def w2region(self, w1region):
|
583
|
+
return self.region2region(self.w1, w1region, self.w2)
|
584
|
+
|
585
|
+
|
586
|
+
def lookup_mouse_position():
|
587
|
+
""" 查看鼠标位置的工具
|
588
|
+
"""
|
589
|
+
from keyboard import read_key
|
590
|
+
|
591
|
+
left_top_point = None
|
592
|
+
while True:
|
593
|
+
k = read_key()
|
594
|
+
if k == 'ctrl':
|
595
|
+
# 定位当前鼠标坐标位置
|
596
|
+
# 也可以用来定位区域时,先确定区域的左上角点
|
597
|
+
left_top_point = pyautogui.position()
|
598
|
+
print('坐标:', *left_top_point)
|
599
|
+
elif k == 'alt':
|
600
|
+
# 定位区域的右下角点,并输出区域
|
601
|
+
p = pyautogui.position()
|
602
|
+
print('区域(x y w h):', left_top_point.x, left_top_point.y, p.x - left_top_point.x, p.y - left_top_point.y)
|
603
|
+
elif k == 'esc':
|
604
|
+
break
|
605
|
+
# keyboard的监控太快了,需要暂停一下
|
606
|
+
time.sleep(0.4)
|
607
|
+
|
608
|
+
|
609
|
+
def lookup_mouse_position2(w1, w2, reverse=False):
|
610
|
+
""" 涉及到窗口位置、大小调整时的坐标计算
|
611
|
+
当前在窗口w1的坐标,切换到w2时的坐标
|
612
|
+
:param reverse: 当前窗口实际是变换后的w2,输出的时候需要按照 w1 -> w2 的格式展示
|
613
|
+
代码实现的时候,其实只要把输出的内容对调即可
|
614
|
+
|
615
|
+
该函数是lookup_mouse_postion的进阶版,两个函数还是先不做合并,便于理解维护
|
616
|
+
"""
|
617
|
+
import keyboard
|
618
|
+
postran = PosTran(w1, w2)
|
619
|
+
|
620
|
+
left_top_point = None
|
621
|
+
while True:
|
622
|
+
k = keyboard.read_key()
|
623
|
+
if k == 'ctrl':
|
624
|
+
# 定位当前鼠标坐标位置
|
625
|
+
# 也可以用来定位区域时,先确定区域的左上角点
|
626
|
+
left_top_point = pyautogui.position()
|
627
|
+
p1, p2 = left_top_point, postran.w2point(left_top_point)
|
628
|
+
if reverse: p1, p2 = p2, p1
|
629
|
+
print('坐标:', *p1, '-->', *p2)
|
630
|
+
elif k == 'alt':
|
631
|
+
# 定位区域的右下角点,并输出区域
|
632
|
+
p = pyautogui.position()
|
633
|
+
r1 = [left_top_point.x, left_top_point.y, p.x - left_top_point.x, p.y - left_top_point.y]
|
634
|
+
r2 = postran.w2region(r1)
|
635
|
+
if reverse: r1, r2 = r2, r1
|
636
|
+
print('区域(x y w h):', *r1, '-->', *r2)
|
637
|
+
elif k == 'esc':
|
638
|
+
break
|
639
|
+
time.sleep(0.4)
|
640
|
+
|
641
|
+
|
642
|
+
def type_text(text):
|
643
|
+
""" 打印出文本内容
|
644
|
+
|
645
|
+
相比pyautogui.write,这里支持中文等unicode格式
|
646
|
+
|
647
|
+
这种需求一般也可以用剪切板实现,但是剪切板不够独立轻量,可能会有意想不到的副作用
|
648
|
+
"""
|
649
|
+
check_install_package('pynput')
|
650
|
+
from pynput.keyboard import Controller
|
651
|
+
|
652
|
+
keyboard = Controller()
|
653
|
+
keyboard.type(text)
|
654
|
+
|
655
|
+
|
656
|
+
def clipboard_decorator(rtype='text', *, copy=True, paste=False, typing=False):
|
657
|
+
""" 装饰器,能方便地将函数的返回值转移到剪切板文本、富文本,甚至typing打印出来。
|
658
|
+
|
659
|
+
Args:
|
660
|
+
rtype: 函数返回值类型
|
661
|
+
paste: 是否执行剪切板的粘贴功能
|
662
|
+
typing: 是否键入文本内容
|
663
|
+
|
664
|
+
Returns:
|
665
|
+
|
666
|
+
"""
|
667
|
+
from bs4 import BeautifulSoup
|
668
|
+
import pyautogui
|
669
|
+
import klembord
|
670
|
+
|
671
|
+
def decorator(func):
|
672
|
+
def wrapper(*args, **kwargs):
|
673
|
+
# 1 运行函数获得结果
|
674
|
+
s = func(*args, **kwargs)
|
675
|
+
|
676
|
+
# 2 复制到剪切板
|
677
|
+
if copy:
|
678
|
+
if rtype == 'text' and isinstance(s, str):
|
679
|
+
klembord.set_text(s)
|
680
|
+
elif rtype == 'html' and isinstance(s, str):
|
681
|
+
s0 = BeautifulSoup(s, 'lxml').text
|
682
|
+
klembord.set_with_rich_text(s0, s)
|
683
|
+
elif rtype == 'dict' and isinstance(s, dict):
|
684
|
+
klembord.set(s)
|
685
|
+
|
686
|
+
# 3 输出
|
687
|
+
if paste:
|
688
|
+
pyautogui.hotkey('ctrl', 'v') # 目前来看就这个方法最靠谱
|
689
|
+
if typing:
|
690
|
+
type_text(s)
|
691
|
+
|
692
|
+
return s
|
693
|
+
|
694
|
+
return wrapper
|
695
|
+
|
696
|
+
return decorator
|
697
|
+
|
698
|
+
|
699
|
+
def get_clipboard_content(rich=False, *, head=False):
|
700
|
+
""" klembord的get_with_rich_text获取富文本有点小问题,所以自己重写了一个版本
|
701
|
+
好在有底层接口,所以也不难改
|
702
|
+
|
703
|
+
:param rich: 是否返回html格式的富文本,默认True
|
704
|
+
:param head: 富文本时,是否要返回<html><body>的标记
|
705
|
+
"""
|
706
|
+
import re
|
707
|
+
import html
|
708
|
+
|
709
|
+
import klembord
|
710
|
+
from klembord import W_HTML, W_UNICODE
|
711
|
+
|
712
|
+
if not rich:
|
713
|
+
return klembord.get_text()
|
714
|
+
|
715
|
+
content = klembord.get((W_HTML, W_UNICODE))
|
716
|
+
html_content = content[W_HTML]
|
717
|
+
if html_content:
|
718
|
+
html_content = html_content.decode('utf8')
|
719
|
+
html_content = re.search(r'<body>\s*(?:<!--StartFragment-->)?(.+?)(?:<!--EndFragment-->)?\s*</body>',
|
720
|
+
html_content).group(1)
|
721
|
+
else:
|
722
|
+
# 没有html的时候,可以封装成html格式返回
|
723
|
+
text = content[W_UNICODE].decode('utf16')
|
724
|
+
html_content = html.escape(text)
|
725
|
+
|
726
|
+
if head:
|
727
|
+
html_content = '<html><body>' + html_content + '</body></html>'
|
728
|
+
|
729
|
+
return html_content
|
730
|
+
|
731
|
+
|
732
|
+
def set_clipboard_content(content, *, rich=False):
|
733
|
+
""" 对klembord剪切板的功能做略微的简化 """
|
734
|
+
from bs4 import BeautifulSoup
|
735
|
+
import klembord
|
736
|
+
|
737
|
+
if rich:
|
738
|
+
klembord.set_with_rich_text(BeautifulSoup(content, 'lxml').text, content)
|
739
|
+
else:
|
740
|
+
klembord.set_text(content)
|
741
|
+
|
742
|
+
|
743
|
+
def grab_pixel_color(radius=0):
|
744
|
+
"""
|
745
|
+
:param radius: 以中心像素为例,计算矩阵范围内的颜色分布情况(未实装)
|
746
|
+
"""
|
747
|
+
from pyxllib.cv.rgbfmt import RgbFormatter
|
748
|
+
last_pos = None
|
749
|
+
print('精确全体/精确中文')
|
750
|
+
|
751
|
+
while True:
|
752
|
+
pos = pyautogui.position()
|
753
|
+
if pos == last_pos:
|
754
|
+
continue
|
755
|
+
else:
|
756
|
+
last_pos = pos
|
757
|
+
|
758
|
+
color = RgbFormatter(*pyautogui.pixel(*pos))
|
759
|
+
desc = color.relative_color_desc(color_range=2, precise_mode=True)
|
760
|
+
print('\r' + f'{color.to_hex()} {desc}', end='', flush=True)
|
761
|
+
time.sleep(0.1)
|
762
|
+
|
763
|
+
|
764
|
+
def grab_monitor(order=0, to_cv2=True):
|
765
|
+
""" 截屏
|
766
|
+
|
767
|
+
:param int order:
|
768
|
+
默认0,截取全部屏幕
|
769
|
+
1,截取第1个屏幕
|
770
|
+
2,截取第2个屏幕
|
771
|
+
...
|
772
|
+
:param to_cv2:
|
773
|
+
True, 转成cv2格式的图片
|
774
|
+
False, 使用pil格式的图片
|
775
|
+
"""
|
776
|
+
from PIL import Image
|
777
|
+
from pyxllib.xlcv import PilImg
|
778
|
+
|
779
|
+
with mss.mss() as sct:
|
780
|
+
assert order < len(sct.monitors), '屏幕下标越界'
|
781
|
+
monitor = sct.monitors[order]
|
782
|
+
sct_img = sct.grab(monitor)
|
783
|
+
img = Image.frombytes("RGB", sct_img.size, sct_img.bgra, "raw", "BGRX")
|
784
|
+
if to_cv2:
|
785
|
+
img = PilImg.read(img).to_cv2_image() # 返回是np矩阵
|
786
|
+
return img
|
787
|
+
|
788
|
+
|
789
|
+
def list_windows(mode=1):
|
790
|
+
""" 列出所有窗口
|
791
|
+
|
792
|
+
:param mode:
|
793
|
+
0, 列出所有窗口
|
794
|
+
1, 只列出有名称,有宽、高的窗口
|
795
|
+
str, 指定名称的窗口
|
796
|
+
注意,当结果有多个的时候,一般后面那个才是紧贴边缘的窗口位置
|
797
|
+
|
798
|
+
:return: [[窗口名, xywh], [窗口2, xywh]...]
|
799
|
+
"""
|
800
|
+
import win32gui
|
801
|
+
|
802
|
+
ls = []
|
803
|
+
|
804
|
+
def callback(hwnd, extra):
|
805
|
+
l, t, r, b = win32gui.GetWindowRect(hwnd)
|
806
|
+
w, h = r - l, b - t
|
807
|
+
name = win32gui.GetWindowText(hwnd)
|
808
|
+
if mode == 0:
|
809
|
+
ls.append([name, [l, t, w, h]])
|
810
|
+
elif mode == 1 and name and w and h:
|
811
|
+
ls.append([name, [l, t, w, h]])
|
812
|
+
elif isinstance(mode, str) and name == mode:
|
813
|
+
ls.append([name, [l, t, w, h]])
|
814
|
+
|
815
|
+
win32gui.EnumWindows(callback, None)
|
816
|
+
return ls
|
817
|
+
|
818
|
+
|
819
|
+
class UiTracePath:
|
820
|
+
""" 在window窗口上用鼠标勾画路径 """
|
821
|
+
|
822
|
+
@classmethod
|
823
|
+
def from_polygon(cls, points, duration_per_circle=3, num_circles=1):
|
824
|
+
"""
|
825
|
+
让鼠标顺时针围绕任意多边形移动。
|
826
|
+
|
827
|
+
:param points: 多边形顶点的坐标列表,按顺时针或逆时针顺序排列
|
828
|
+
:param duration_per_circle: 每圈耗时
|
829
|
+
:param num_circles: 总圈数
|
830
|
+
"""
|
831
|
+
num_points = len(points)
|
832
|
+
segment_duration = duration_per_circle / num_points # 每条边的耗时
|
833
|
+
|
834
|
+
for _ in range(num_circles):
|
835
|
+
# 顺时针移动鼠标
|
836
|
+
for i in range(num_points):
|
837
|
+
start = points[i]
|
838
|
+
pyautogui.moveTo(start[0], start[1])
|
839
|
+
end = points[(i + 1) % num_points]
|
840
|
+
# 使用 moveTo 的 duration 参数控制每条边的移动时间
|
841
|
+
pyautogui.moveTo(end[0], end[1], duration=segment_duration)
|
842
|
+
|
843
|
+
@classmethod
|
844
|
+
def from_ltrb(cls, ltrb, **kwargs):
|
845
|
+
points = ltrb2polygon(ltrb)
|
846
|
+
return cls.from_polygon(points, **kwargs)
|
847
|
+
|
848
|
+
|
849
|
+
if __name__ == '__main__':
|
850
|
+
with TicToc(__name__):
|
851
|
+
agld = AutoGuiLabelData(r'D:\slns\py4101\py4101\touhou\label')
|
852
|
+
agld.writes()
|