Skip to content

Commit d44cf6b

Browse files
committed
Add configurable overlay obscuring with inpainting
1 parent 90f4d86 commit d44cf6b

30 files changed

Lines changed: 5760 additions & 4881 deletions

docs/api_doc/README.md

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
- [任务配置 (Task Configuration)](#任务配置-task-configuration)
5050
- [屏幕画图 (Screen drawing)](#屏幕画图-screen-drawing)
5151
- [draw\_boxes](#draw_boxes)
52+
- [get\_overlay\_view](#get_overlay_view)
5253
- [clear\_box](#clear_box)
5354
- [OCR](#ocr)
5455
- [ocr](#ocr)
@@ -685,7 +686,7 @@ def get_global_config_desc(self, option) -> str
685686

686687
- **`drop_down`**: 下拉选择框。
687688
- **参数:** `options` (list[str]): 选项列表。
688-
- **可选参数:** `sub_configs` (dict[str, list[str]]): 根据当前下拉选项控制其他配置项是否显示。key 是下拉选项值,value 是需要显示的配置项名称列表这些配置项会按照列表顺序显示在当前配置项下方,未包含在当前选项列表中的配置项会被隐藏
689+
- **`sub_configs`**: 可用于下拉选择框或布尔开关的可选参数。它根据当前值控制其他配置项是否显示;key 是选项值或 `True`/`False`,value 是需要显示的配置项名称列表这些配置项会按照列表顺序显示在当前配置项下方,未包含在当前值列表中的配置项会被隐藏
689690
- **`multi_selection`**: 多选列表。
690691
- **参数:** `options` (list[str]): 选项列表。
691692
- **`text_edit`**: 强制使用多行文本框。
@@ -768,6 +769,38 @@ def clear_box(self)
768769

769770
清除屏幕上由 `draw_boxes` 绘制的所有框。
770771

772+
<a name="get_overlay_view"></a>
773+
774+
### get\_overlay\_view
775+
776+
```python
777+
def get_overlay_view(self)
778+
```
779+
780+
返回覆盖在捕获窗口上的 Qt overlay widget。`BaseTask``CustomTab` 可直接调用此方法;配置的
781+
`my_app` 实例也会获得同名方法。无界面运行时返回 `None`。自定义内容绘制完成后可调用
782+
`overlay_view.request_show()` 令 overlay 在窗口上显示。
783+
784+
应用配置可提供 `blur_area(width, height)` 回调,返回一个 `Box``list[Box]`,用于遮挡
785+
游戏 UID 等静态区域:
786+
787+
```python
788+
from ok import Box
789+
790+
def blur_area(width, height):
791+
return Box(width - 240, height - 42, 240, 42)
792+
793+
config = {
794+
'blur_area': blur_area,
795+
}
796+
```
797+
798+
配置后,基本设置中会出现 `Enable Blur` 开关,启用后可通过子配置 `Blur Algorithm` 选择
799+
`Inpaint`(默认)或 `Blur``Inpaint` 会使用区域周围的像素重建内容,适用于从较简单背景上移除 UID。
800+
开启时 overlay 以最多每秒一次的频率显示处理后的区域;保存截图时始终应用所选算法,与该开关
801+
无关。若还配置了 `screenshot_processor`,它会在处理完成后执行。调试面板中的 `Enable Boxes`
802+
仅启用框绘制;overlay 只在有框、处理区域或自定义绘制内容需要显示时出现。
803+
771804
### OCR
772805

773806
<a name="ocr"></a>

ok/__init__.py

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from ok.gui.MainWindow import MainWindow
2424
from ok.task.TaskExecutor import TaskExecutor
2525
from ok.util.Analytics import Analytics
26-
from ok.util.GlobalConfig import GlobalConfig, basic_options
26+
from ok.util.GlobalConfig import GlobalConfig, register_basic_options
2727
from ok.util.clazz import init_class_by_name
2828
from ok.util.config import Config, ConfigOption
2929
from ok.util.handler import Handler, ExitEvent
@@ -109,10 +109,6 @@ def __init__(self, config, task_executor,
109109
else:
110110
self.to_translate = None
111111

112-
if self.ok_config.get('use_overlay', False):
113-
logger.debug('init overlay')
114-
from ok.gui.overlay.OverlayWindow import OverlayWindow
115-
self.overlay_window = OverlayWindow(og.device_manager.hwnd_window)
116112
self.po_translation = None
117113
if not config.get('window_size'):
118114
logger.info(f'no config.window_size was set use default')
@@ -129,6 +125,8 @@ def __init__(self, config, task_executor,
129125

130126
if my_app := self.config.get('my_app'):
131127
og.my_app = init_class_by_name(my_app[0], my_app[1], exit_event)
128+
if not hasattr(og.my_app, 'get_overlay_view'):
129+
og.my_app.get_overlay_view = self.get_overlay_view
132130

133131
if self.config.get('analytics'):
134132
self.fire_base_analytics = Analytics(self.config, self.exit_event, og.handler, og.device_manager)
@@ -189,15 +187,24 @@ def show_path_ascii_error(self, path):
189187
self.show_message_window(title, content)
190188

191189
def update_overlay(self, visible, x, y, window_width, window_height, width, height, scaling):
190+
overlay_view = self.get_overlay_view()
191+
if overlay_view:
192+
overlay_view.update_overlay(visible, x, y, window_width, window_height, width, height, scaling)
192193

193-
self.overlay_window.update_overlay(visible, x, y, window_width, window_height, width, height, scaling)
194+
def get_overlay_view(self):
195+
"""Return the overlay widget exposed to tasks, custom tabs, and my_app."""
196+
if self.overlay_window is None:
197+
from ok.gui.overlay.OverlayWindow import OverlayWindow
198+
self.overlay_window = OverlayWindow(og.device_manager.hwnd_window)
199+
communicate.window.connect(self.overlay_window.update_overlay)
200+
self.overlay_window.set_boxes_enabled(self.ok_config.get('use_overlay', False))
201+
return self.overlay_window
194202

195203
def show_main_window(self):
196204
self.do_show_main()
197205

198206
def do_show_main(self):
199-
if self.overlay_window:
200-
communicate.window.connect(self.overlay_window.update_overlay)
207+
self.get_overlay_view()
201208

202209
self.main_window = MainWindow(self, self.config, self.ok_config, self.icon, self.title, self.version,
203210
self.debug,
@@ -263,6 +270,8 @@ def __init__(self, config, exit_event=None):
263270
og.app = self
264271
if my_app := self.config.get('my_app'):
265272
og.my_app = init_class_by_name(my_app[0], my_app[1], exit_event)
273+
if not hasattr(og.my_app, 'get_overlay_view'):
274+
og.my_app.get_overlay_view = self.get_overlay_view
266275
logger.debug('init headless app end')
267276

268277
def tr(self, key):
@@ -289,6 +298,9 @@ def quit(self):
289298
if self.exit_event:
290299
self.exit_event.set()
291300

301+
def get_overlay_view(self):
302+
return None
303+
292304

293305
def get_my_id():
294306
mac = uuid.getnode()
@@ -365,7 +377,7 @@ def __init__(self, config):
365377
else:
366378
available_methods.append(method)
367379

368-
self.global_config.get_config(basic_options)
380+
register_basic_options(self.global_config, enable_blur=callable(config.get('blur_area')))
369381
og.global_config = self.global_config
370382
og.set_use_dml()
371383
try:
@@ -758,6 +770,11 @@ def set_use_dml(self):
758770
logger.info(f'use_dml result is {use_dml}')
759771
self.use_dml = use_dml
760772

773+
def get_overlay_view(self):
774+
if self.app and hasattr(self.app, 'get_overlay_view'):
775+
return self.app.get_overlay_view()
776+
return None
777+
761778
def get_trial_expire_util_str(self):
762779
# Convert the timestamp to a datetime object
763780
expire_date = datetime.fromtimestamp(self.trial_expire)

ok/gui/Communicate.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ class Communicate(QObject):
1616
notification = Signal(str, str, bool, bool, str, object)
1717
executor_paused: Signal = Signal(bool)
1818
screenshot = Signal(object, str, bool, object)
19+
blur_overlay = Signal(object)
20+
clear_blur_overlay = Signal()
1921
adb_devices: Signal = Signal(bool)
2022
config_validation: Signal = Signal(str)
2123
tab = Signal(str)

ok/gui/debug/DebugTab.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -217,12 +217,10 @@ def capture(processor=None):
217217
try:
218218
frame = og.device_manager.capture_method.get_frame()
219219
if frame is not None:
220-
if processor:
221-
frame = processor(frame.copy())
222220
current_capture = str(og.device_manager.capture_method) + '_' + str(time.time() * 1000)
223221
file_path = og.ok.screenshot.generate_screen_shot(frame, og.ok.screenshot.ui_dict,
224222
og.ok.screenshot.screenshot_folder,
225-
current_capture, True, None)
223+
current_capture, True, None, processor=processor)
226224

227225
# Use subprocess.Popen to open the file explorer and select the file
228226
subprocess.Popen(r'explorer /select,"{}"'.format(file_path))

ok/gui/debug/OverlayWidget.py

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import win32api
22
from PySide6.QtCore import Qt, QPoint, QTimer, QRectF, QRect
3-
from PySide6.QtGui import QPainter, QColor, QPen, QFont, QGuiApplication, QBrush
3+
from PySide6.QtGui import QPainter, QColor, QPen, QFont, QGuiApplication, QBrush, QImage
44
from PySide6.QtWidgets import QWidget
55

66
from ok import Logger
@@ -38,6 +38,7 @@ def __init__(self):
3838
screen = QGuiApplication.primaryScreen()
3939
self.scaling = screen.devicePixelRatio()
4040
self.logs = []
41+
self.blur_images = []
4142
communicate.log.connect(self.add_log)
4243

4344
def add_log(self, level_no, message):
@@ -159,13 +160,42 @@ def paintEvent(self, event):
159160
if not self.isVisible():
160161
return
161162
painter = QPainter(self)
162-
self.paint_border(painter)
163-
self.paint_boxes(painter)
164-
self.paint_mouse_position(painter)
165-
self.paint_logs(painter)
166-
self.paint_alt_overlay(painter)
167-
if og.config.get('debug_cover_uid'):
168-
self.paint_uid_cover(painter)
163+
self.paint_blur(painter)
164+
boxes_active = getattr(self, '_boxes_enabled', True) and getattr(self, '_boxes_active', False)
165+
if boxes_active:
166+
self.paint_boxes(painter)
167+
self.paint_border(painter)
168+
self.paint_mouse_position(painter)
169+
self.paint_logs(painter)
170+
self.paint_alt_overlay(painter)
171+
if og.config.get('debug_cover_uid'):
172+
self.paint_uid_cover(painter)
173+
174+
def set_blur_patches(self, patches):
175+
images = []
176+
for x, y, width, height, patch in patches:
177+
if patch is None or patch.size == 0:
178+
continue
179+
if len(patch.shape) == 2:
180+
image_data = patch.copy()
181+
image_format = QImage.Format_Grayscale8
182+
else:
183+
image_data = patch[:, :, :3][:, :, ::-1].copy()
184+
image_format = QImage.Format_RGB888
185+
image = QImage(image_data.data, width, height, image_data.strides[0], image_format).copy()
186+
images.append((x, y, width, height, image))
187+
self.blur_images = images
188+
self.update()
189+
190+
def clear_blur_patches(self):
191+
self.blur_images = []
192+
self.update()
193+
194+
def paint_blur(self, painter):
195+
frame_ratio = self.frame_ratio()
196+
for x, y, width, height, image in self.blur_images:
197+
painter.drawImage(QRectF(x * frame_ratio, y * frame_ratio,
198+
width * frame_ratio, height * frame_ratio), image)
169199

170200
def paint_alt_overlay(self, painter):
171201
if not getattr(self, '_is_alt_down', False):

ok/gui/debug/Screenshot.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from ok import Box, og
1313
from ok import Logger
1414
from ok.gui.Communicate import communicate
15+
from ok.util.blur import apply_blur_areas, get_blur_algorithm
1516
from ok.util.file import find_first_existing_file, clear_folder, sanitize_filename, \
1617
get_relative_path
1718

@@ -126,10 +127,10 @@ def _worker(self):
126127
self.generate_screen_shot(task[0], task[1], task[2], task[3], task[4], task[5])
127128
self.task_queue.task_done()
128129

129-
def generate_screen_shot(self, frame, ui_dict, folder, name, show_box, frame_box):
130+
def generate_screen_shot(self, frame, ui_dict, folder, name, show_box, frame_box, processor=None):
130131
if folder is None:
131132
return
132-
pil_image = self.to_pil_image(frame)
133+
pil_image = self.to_pil_image(frame, processor=processor)
133134
if pil_image is None:
134135
return
135136

@@ -180,10 +181,14 @@ def stop(self):
180181
if self.task_queue is not None:
181182
self.task_queue.put(None)
182183

183-
def to_pil_image(self, frame):
184+
def to_pil_image(self, frame, processor=None):
184185
if frame is None:
185186
return None
186-
if processor := og.config.get('screenshot_processor'):
187+
frame = apply_blur_areas(frame, og.config.get('blur_area'),
188+
get_blur_algorithm(getattr(og, 'global_config', None)))
189+
if processor is None:
190+
processor = og.config.get('screenshot_processor')
191+
if processor:
187192
frame = processor(frame.copy())
188193
if len(frame.shape) == 2:
189194
frame = cv2.cvtColor(frame, cv2.COLOR_GRAY2BGR)

ok/gui/i18n/en_US.qm

534 Bytes
Binary file not shown.

ok/gui/i18n/en_US.ts

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -819,12 +819,12 @@
819819
<translation>Choose Interaction</translation>
820820
</message>
821821
<message>
822-
<source>Show Overlay</source>
823-
<translation>Show Overlay</translation>
822+
<source>Enable Boxes</source>
823+
<translation>Enable Boxes</translation>
824824
</message>
825825
<message>
826-
<source>Hide Overlay</source>
827-
<translation>Hide Overlay</translation>
826+
<source>Disable Boxes</source>
827+
<translation>Disable Boxes</translation>
828828
</message>
829829
<message>
830830
<source>Show Log on Overlay</source>
@@ -1171,6 +1171,30 @@
11711171
<source>Basic Options</source>
11721172
<translation>Basic Options</translation>
11731173
</message>
1174+
<message>
1175+
<source>Enable Blur</source>
1176+
<translation>Enable Blur</translation>
1177+
</message>
1178+
<message>
1179+
<source>Blur Game UID etc to enhance OLED life</source>
1180+
<translation>Blur Game UID etc to enhance OLED life</translation>
1181+
</message>
1182+
<message>
1183+
<source>Blur Algorithm</source>
1184+
<translation>Blur Algorithm</translation>
1185+
</message>
1186+
<message>
1187+
<source>Method used to obscure configured areas</source>
1188+
<translation>Method used to obscure configured areas</translation>
1189+
</message>
1190+
<message>
1191+
<source>Blur</source>
1192+
<translation>Blur</translation>
1193+
</message>
1194+
<message>
1195+
<source>Inpaint</source>
1196+
<translation>Inpaint</translation>
1197+
</message>
11741198
<message>
11751199
<source>Use DirectML</source>
11761200
<translation>Use DirectML</translation>

ok/gui/i18n/es_ES.qm

628 Bytes
Binary file not shown.

ok/gui/i18n/es_ES.ts

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -817,12 +817,12 @@
817817
<translation>Elegir interacción</translation>
818818
</message>
819819
<message>
820-
<source>Show Overlay</source>
821-
<translation>Mostrar superposición de depuración</translation>
820+
<source>Enable Boxes</source>
821+
<translation>Activar cuadros</translation>
822822
</message>
823823
<message>
824-
<source>Hide Overlay</source>
825-
<translation>Ocultar superposición de depuración</translation>
824+
<source>Disable Boxes</source>
825+
<translation>Desactivar cuadros</translation>
826826
</message>
827827
<message>
828828
<source>Show Log on Overlay</source>
@@ -1169,6 +1169,30 @@
11691169
<source>Basic Options</source>
11701170
<translation>Opciones básicas</translation>
11711171
</message>
1172+
<message>
1173+
<source>Enable Blur</source>
1174+
<translation>Activar desenfoque</translation>
1175+
</message>
1176+
<message>
1177+
<source>Blur Game UID etc to enhance OLED life</source>
1178+
<translation>Desenfoca el UID del juego y otros elementos para prolongar la vida del OLED</translation>
1179+
</message>
1180+
<message>
1181+
<source>Blur Algorithm</source>
1182+
<translation>Algoritmo de ocultación</translation>
1183+
</message>
1184+
<message>
1185+
<source>Method used to obscure configured areas</source>
1186+
<translation>Método utilizado para ocultar las áreas configuradas</translation>
1187+
</message>
1188+
<message>
1189+
<source>Blur</source>
1190+
<translation>Desenfoque</translation>
1191+
</message>
1192+
<message>
1193+
<source>Inpaint</source>
1194+
<translation>Relleno inteligente</translation>
1195+
</message>
11721196
<message>
11731197
<source>Use DirectML</source>
11741198
<translation>Usar DirectML</translation>

0 commit comments

Comments
 (0)