├── readme.md ├── rimo_utils ├── __init__.py ├── _杂物.py ├── cache.py ├── cef_tools │ ├── fake_wx.py │ ├── qtcef.py │ ├── vue_ob.py │ └── wxcef.py ├── cv0.py ├── matrix.py └── 计时.py └── setup.py /readme.md: -------------------------------------------------------------------------------- 1 | # 莉沫工具箱 2 | 3 | 经常用到的一些小代码,原本它们是我的各个repo里的重复代码,想了想就整理出来单独做一个repo用来import好了。 4 | 5 | ## 内容 6 | 7 | + 常用功能 8 | - `rimo_utils.good_open(path, mode='r', encoding=None)` 9 | - 包装原本的`open`,可以自动识别文本编码。 10 | 11 | + cv0 12 | - OpenCV的包装,解决一些OpenCV里让人不舒服的地方。 13 | - 包含了`cv2`原本的所有属性 14 | - `cv0.show(img)` 15 | - `cv2.imshow`的包装。 16 | - 不需要设置名字,自带`waitKey`。 17 | - `cv0.read(img_path)` 18 | - 替代`cv2.imread`。 19 | - 解决中文路径读取失败的问题。 20 | - 在读取失败时不返回`None`,而是`raise` 21 | - `cv0.write(img, img_path, param=None)` 22 | - 替代`cv2.imwrite` 23 | - 解决中文路径写入失败的问题。 24 | - 为非`np.uint8`的图片转换格式。 25 | - `cv0.VideoCapGen(source, size: tuple = None)` 26 | - `cv2.VideoCapture`的包装 27 | - 一个生成器 28 | - 在读取失败时不返回`None`,而是`raise` 29 | - 绘图函数,包括: 30 | - `cv0.circle(img, center, radius, color, thickness=1, lineType=16)` 31 | - `cv0.putText(img, text, org, fontFace, fontScale, color, thickness=1, lineType=16, bottomLeftOrigin=False)` 32 | - 替代原本的同名函数。 33 | - 位置参数(如`center`)不再受限于`tuple`类型,内部元素也可以是`float`。 34 | - 默认使用带抗锯齿的`lineType`。 35 | 36 | + matrix 37 | - 齐次座标系的常用变换矩阵 38 | - `matrix.scale(x, y, z)` 39 | - 缩放矩阵 40 | - `matrix.rotate_ax(r, axis: tuple)` 41 | - 绕座标轴旋转矩阵 42 | - `matrix.rotate(angle, v)` 43 | - 旋转矩阵 44 | - `matrix.translate(x, y, z)` 45 | - 平移矩阵 46 | - `matrix.perspective(view_distance)` 47 | - 透视矩阵 48 | 49 | + 计时 50 | - 使用上下文管理器的计时器 51 | - `with 计时.计时(名字=''):` 52 | - 计算with块中的代码执行用时并print 53 | - `with 计时.帧率计(名字=''):` 54 | - 计算with块中的代码帧率 55 | - 在累积时长每到达3秒时会print一次 56 | 57 | + cache 58 | - 缓存装饰器工具 59 | - `@disk_cache(path)` 60 | - 为函数添加本地硬盘缓存 61 | 62 | ## 使用方法 63 | 64 | 莉沫工具箱可以直接通过pip安装,只要—— 65 | 66 | ```bash 67 | pip install rimo-utils 68 | ``` 69 | 70 | 然后`from rimo_utils import 什么`就行了。 71 | 72 | 有一些注意事项: 73 | 74 | + 如果你想升级莉沫工具箱,在`pip -U`的时候遇到了错误,这可能是因为你的pip版本太旧了。 75 | 76 | + pip只会帮你装上常用功能的requirements。因为这个仓库安装所有依赖会非常慢,所以你应该在遇到缺依赖的时候再用pip手动安装。 77 | 78 | 79 | 话说真的有人用吗…… 80 | 81 | -------------------------------------------------------------------------------- /rimo_utils/__init__.py: -------------------------------------------------------------------------------- 1 | from ._杂物 import * 2 | -------------------------------------------------------------------------------- /rimo_utils/_杂物.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | import chardet 4 | 5 | 6 | def good_open(path, mode='r', encoding=None): 7 | if mode == 'r' and encoding is None: 8 | with open(path, 'rb') as f: 9 | a = chardet.detect(f.read()) 10 | if a['encoding'] == 'GB2312': 11 | a['encoding'] = 'GBK' 12 | if a['confidence'] < 0.75: 13 | try: 14 | open(path).read() 15 | except UnicodeDecodeError: 16 | logging.warning(f'没能自动识别「{path}」的编码,尝试用utf8编码打开。') 17 | return open(path, encoding='utf8') 18 | else: 19 | logging.warning(f'没能自动识别「{path}」的编码,尝试用默认编码打开。') 20 | return open(path) 21 | else: 22 | return open(path, encoding=a['encoding']) 23 | return open(path, mode=mode, encoding=encoding) 24 | -------------------------------------------------------------------------------- /rimo_utils/cache.py: -------------------------------------------------------------------------------- 1 | import pickle 2 | import hashlib 3 | from pathlib import Path 4 | 5 | 6 | def disk_cache(path): 7 | path = Path(path) 8 | if path.is_file(): 9 | raise Exception('你不对劲') 10 | path.mkdir(parents=True, exist_ok=True) 11 | def q(func): 12 | name = func.__name__ 13 | def 假func(*li, **d): 14 | s = pickle.dumps([name, li, d]) 15 | md5 = hashlib.md5(s).hexdigest() 16 | 名字 = f'{name}_{md5}' 17 | if (path/名字).is_file(): 18 | with open(path/名字, 'rb') as f: 19 | return pickle.loads(f.read()) 20 | else: 21 | r = func(*li, **d) 22 | with open(path/名字, 'wb') as f: 23 | f.write(pickle.dumps(r)) 24 | return r 25 | return 假func 26 | return q 27 | -------------------------------------------------------------------------------- /rimo_utils/cef_tools/fake_wx.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from PySide2 import QtWidgets 4 | 5 | 6 | class DirDialog: 7 | def __init__(self, window, title): 8 | self._parentWindow = window 9 | self._title = title 10 | 11 | def __enter__(self): 12 | self._dialog = QtWidgets.QFileDialog(parent=self._parentWindow, 13 | caption=self._title) 14 | self._dialog.setAcceptMode(QtWidgets.QFileDialog.AcceptMode.AcceptOpen) 15 | self._dialog.setFileMode(QtWidgets.QFileDialog.FileMode.DirectoryOnly) 16 | self._dialog.setOption(QtWidgets.QFileDialog.ShowDirsOnly, True) 17 | return self 18 | 19 | def __exit__(self, exc_type, exc, tb): 20 | if self._dialog.isVisible(): 21 | self._dialog.close() 22 | 23 | def SetPath(self, path): 24 | self._dialog.setDirectory(path) 25 | 26 | def GetPath(self): 27 | return self._dialog.directory().absolutePath() 28 | 29 | def ShowModal(self): 30 | return self._dialog.exec_() 31 | 32 | 33 | ID_OK = 1 34 | -------------------------------------------------------------------------------- /rimo_utils/cef_tools/qtcef.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import logging 4 | import platform 5 | import urllib 6 | import ctypes 7 | import os 8 | import platform 9 | import sys 10 | 11 | import PySide2 12 | from PySide2 import QtCore 13 | from PySide2.QtGui import * 14 | from PySide2.QtCore import * 15 | from PySide2.QtWidgets import * 16 | 17 | ld_library_path = os.environ.get("LD_LIBRARY_PATH") 18 | from cefpython3 import cefpython as cef 19 | 20 | 21 | def group(url, icon, title, size): 22 | sys.excepthook = ExceptHook 23 | settings = {} 24 | if WINDOWS: 25 | settings["external_message_pump"] = True 26 | elif MAC: 27 | # Issue #442 requires enabling message pump on Mac 28 | # in Qt example. Calling cef.DoMessageLoopWork in a timer 29 | # doesn't work anymore. 30 | settings["external_message_pump"] = True 31 | cef.Initialize(settings=settings, 32 | commandLineSwitches={ 33 | "autoplay-policy": "no-user-gesture-required", 34 | "lang": 'zh-CN' 35 | }) 36 | app = CefApp(url, icon, title, size) 37 | return app, app.frame.browser 38 | 39 | 40 | def ExceptHook(exc_type, exc_value, exc_trace): 41 | ''' 42 | 原本的cef.ExceptHook打印不了中文,我拿來魔改一下。 43 | ''' 44 | import traceback 45 | if exc_type == SystemExit: 46 | hint = '[CEF Python] SystemExit.' 47 | print('\n' + '='*len(hint) + '\n' + hint) 48 | else: 49 | hint = '[CEF Python] ExceptHook: catched exception, will shutdown CEF.' 50 | print('\n' + '='*len(hint) + '\n' + hint) 51 | msg = ''.join(traceback.format_exception(exc_type, exc_value, exc_trace)) 52 | print('\n' + msg) 53 | cef.QuitMessageLoop() 54 | cef.Shutdown() 55 | os._exit(1) 56 | 57 | 58 | # Fix for PyCharm hints warnings when using static methods 59 | WindowUtils = cef.WindowUtils() 60 | 61 | # Platforms 62 | WINDOWS = (platform.system() == "Windows") 63 | LINUX = (platform.system() == "Linux") 64 | MAC = (platform.system() == "Darwin") 65 | 66 | 67 | def main(): 68 | check_versions() 69 | sys.excepthook = cef.ExceptHook # To shutdown all CEF processes on error 70 | settings = {} 71 | if WINDOWS: 72 | settings["external_message_pump"] = True 73 | elif MAC: 74 | # Issue #442 requires enabling message pump on Mac 75 | # in Qt example. Calling cef.DoMessageLoopWork in a timer 76 | # doesn't work anymore. 77 | settings["external_message_pump"] = True 78 | 79 | cef.Initialize(settings) 80 | app = CefApp("http://localhost:8080", None, "localhost", (800, 600)) 81 | app.MainLoop() 82 | del app.main_window # Just to be safe, similarly to "del app" 83 | del app # Must destroy app object before calling Shutdown 84 | cef.Shutdown() 85 | 86 | 87 | def check_versions(): 88 | print("[qt.py] CEF Python {ver}".format(ver=cef.__version__)) 89 | print("[qt.py] Python {ver} {arch}".format( 90 | ver=platform.python_version(), arch=platform.architecture()[0])) 91 | print("[qt.py] PySide2 {v1} (qt {v2})".format(v1=PySide2.__version__, 92 | v2=QtCore.__version__)) 93 | # CEF Python version requirement 94 | assert cef.__version__ >= "55.4", "CEF Python v55.4+ required to run this" 95 | 96 | 97 | class MainWindow(QMainWindow): 98 | def __init__(self, url, icon, title, size): 99 | # noinspection PyArgumentList 100 | super(MainWindow, self).__init__(None) 101 | self.url = url 102 | 103 | logging.debug("[qt.py] MainWindow DPI scaled size: %s" % str(size)) 104 | width, height = tuple(size) 105 | self.resize(width, height) 106 | 107 | self.setWindowTitle(title) 108 | 109 | ic = QIcon(icon) 110 | self.setWindowIcon(ic) 111 | 112 | self.cef_widget = None 113 | self.setFocusPolicy(Qt.StrongFocus) 114 | self.setupLayout() 115 | 116 | def setupLayout(self): 117 | self.cef_widget = CefWidget(self) 118 | layout = QGridLayout() 119 | # noinspection PyArgumentList 120 | layout.addWidget(self.cef_widget, 1, 0) 121 | layout.setContentsMargins(0, 0, 0, 0) 122 | layout.setSpacing(0) 123 | layout.setRowStretch(0, 0) 124 | layout.setRowStretch(1, 1) 125 | # noinspection PyArgumentList 126 | frame = QFrame() 127 | frame.setLayout(layout) 128 | self.setCentralWidget(frame) 129 | 130 | if WINDOWS: 131 | # On Windows with PyQt5 main window must be shown first 132 | # before CEF browser is embedded, otherwise window is 133 | # not resized and application hangs during resize. 134 | self.show() 135 | 136 | # Browser can be embedded only after layout was set up 137 | self.cef_widget.embed_browser(self.url) 138 | 139 | if LINUX: 140 | # On Linux with PyQt5 the QX11EmbedContainer widget is 141 | # no more available. An equivalent in Qt5 is to create 142 | # a hidden window, embed CEF browser in it and then 143 | # create a container for that hidden window and replace 144 | # cef widget in the layout with the container. 145 | # noinspection PyUnresolvedReferences, PyArgumentList 146 | self.container = QWidget.createWindowContainer( 147 | self.cef_widget.hidden_window, parent=self) 148 | # noinspection PyArgumentList 149 | layout.addWidget(self.container, 1, 0) 150 | 151 | def closeEvent(self, event): 152 | # Close browser (force=True) and free CEF reference 153 | if self.cef_widget.browser: 154 | self.cef_widget.browser.CloseBrowser(True) 155 | self.clear_browser_references() 156 | 157 | def clear_browser_references(self): 158 | # Clear browser references that you keep anywhere in your 159 | # code. All references must be cleared for CEF to shutdown cleanly. 160 | self.cef_widget.browser = None 161 | 162 | def set_browser_object(self, name, obj): 163 | bindings = cef.JavascriptBindings() 164 | bindings.SetObject(name, obj) 165 | self.cef_widget.browser.SetJavascriptBindings(bindings) 166 | 167 | def toggleFullScreen(self): 168 | if self.isFullScreen(): 169 | self.showNormal() 170 | else: 171 | self.showFullScreen() 172 | 173 | @property 174 | def browser(self): 175 | return self.cef_widget.browser 176 | 177 | 178 | class CefWidget(QWidget): 179 | def __init__(self, parent=None): 180 | # noinspection PyArgumentList 181 | super(CefWidget, self).__init__(parent) 182 | self.parent = parent 183 | self.browser = None 184 | self.hidden_window = None # Required for PyQt5 on Linux 185 | self.show() 186 | 187 | def focusInEvent(self, event): 188 | # This event seems to never get called on Linux, as CEF is 189 | # stealing all focus due to Issue #284. 190 | if cef.GetAppSetting("debug"): 191 | print("[qt.py] CefWidget.focusInEvent") 192 | if self.browser: 193 | if WINDOWS: 194 | WindowUtils.OnSetFocus(self.getHandle(), 0, 0, 0) 195 | self.browser.SetFocus(True) 196 | 197 | def focusOutEvent(self, event): 198 | # This event seems to never get called on Linux, as CEF is 199 | # stealing all focus due to Issue #284. 200 | if cef.GetAppSetting("debug"): 201 | print("[qt.py] CefWidget.focusOutEvent") 202 | if self.browser: 203 | self.browser.SetFocus(False) 204 | 205 | def embed_browser(self, url): 206 | if LINUX: 207 | # noinspection PyUnresolvedReferences 208 | self.hidden_window = QWindow() 209 | window_info = cef.WindowInfo() 210 | rect = [0, 0, self.width(), self.height()] 211 | window_info.SetAsChild(self.getHandle(), rect) 212 | self.browser = cef.CreateBrowserSync(window_info, url=url, browserSettings={'web_security_disabled': True}) 213 | self.browser.SetClientHandler(FocusHandler(self)) 214 | 215 | def getHandle(self): 216 | if self.hidden_window: 217 | # PyQt5 on Linux 218 | return int(self.hidden_window.winId()) 219 | try: 220 | # PyQt4 and PyQt5 221 | return int(self.winId()) 222 | except: 223 | # PySide: 224 | # | QWidget.winId() returns 225 | # | Converting it to int using ctypes. 226 | ctypes.pythonapi.PyCapsule_GetPointer.restype = (ctypes.c_void_p) 227 | ctypes.pythonapi.PyCapsule_GetPointer.argtypes = ([ 228 | ctypes.py_object 229 | ]) 230 | return ctypes.pythonapi.PyCapsule_GetPointer(self.winId(), None) 231 | 232 | def moveEvent(self, _): 233 | self.x = 0 234 | self.y = 0 235 | if self.browser: 236 | if WINDOWS: 237 | WindowUtils.OnSize(self.getHandle(), 0, 0, 0) 238 | elif LINUX: 239 | self.browser.SetBounds(self.x, self.y, self.width(), 240 | self.height()) 241 | self.browser.NotifyMoveOrResizeStarted() 242 | 243 | def resizeEvent(self, event): 244 | size = event.size() 245 | if self.browser: 246 | if WINDOWS: 247 | WindowUtils.OnSize(self.getHandle(), 0, 0, 0) 248 | elif LINUX: 249 | self.browser.SetBounds(self.x, self.y, size.width(), 250 | size.height()) 251 | self.browser.NotifyMoveOrResizeStarted() 252 | 253 | 254 | class CefApp(QApplication): 255 | def __init__(self, url, icon, title, size): 256 | super(CefApp, self).__init__([]) 257 | if cef.GetAppSetting("external_message_pump") or \ 258 | cef.GetAppSetting("multi_threaded_message_loop"): 259 | self.timer = None 260 | else: 261 | self.timer = self.createTimer() 262 | 263 | self.url, self.icon, self.title, self.size = url, icon, title, size 264 | 265 | self.main_window = MainWindow(url, icon, title, size) 266 | self.main_window.show() 267 | self.main_window.activateWindow() 268 | self.main_window.raise_() 269 | self.frame = self.main_window 270 | 271 | def MainLoop(self): 272 | self.exec_() 273 | if self.timer is not None: 274 | self.stopTimer() 275 | 276 | def createTimer(self): 277 | timer = QTimer() 278 | # noinspection PyUnresolvedReferences 279 | timer.timeout.connect(self.onTimer) 280 | timer.start(10) 281 | return timer 282 | 283 | def onTimer(self): 284 | cef.MessageLoopWork() 285 | 286 | def stopTimer(self): 287 | # Stop the timer after Qt's message loop has ended 288 | self.timer.stop() 289 | 290 | 291 | class FocusHandler(object): 292 | def __init__(self, cef_widget): 293 | self.cef_widget = cef_widget 294 | 295 | def OnTakeFocus(self, **_): 296 | if cef.GetAppSetting("debug"): 297 | print("[qt.py] FocusHandler.OnTakeFocus") 298 | 299 | def OnSetFocus(self, **_): 300 | if cef.GetAppSetting("debug"): 301 | print("[qt.py] FocusHandler.OnSetFocus") 302 | 303 | def OnGotFocus(self, browser, **_): 304 | if cef.GetAppSetting("debug"): 305 | print("[qt.py] FocusHandler.OnGotFocus") 306 | self.cef_widget.setFocus() 307 | # Temporary fix no. 1 for focus issues on Linux (Issue #284) 308 | if LINUX: 309 | browser.SetFocus(True) 310 | 311 | 312 | if __name__ == '__main__': 313 | main() 314 | -------------------------------------------------------------------------------- /rimo_utils/cef_tools/vue_ob.py: -------------------------------------------------------------------------------- 1 | class vue_ob: 2 | class vue_sync: 3 | def __init__(self, master): 4 | self._master = master 5 | self._內容 = {} 6 | 7 | def __getattr__(self, x): 8 | return self._內容[x] 9 | 10 | def __setattr__(self, a, b): 11 | if a[0] == '_': 12 | self.__dict__[a] = b 13 | else: 14 | self._內容[a] = b 15 | self._master.更新vue() 16 | 17 | def __init__(self): 18 | self.vue = self.vue_sync(self) 19 | self.vue鏈接 = None 20 | 21 | def vue更新(self, 內容): 22 | self.vue._內容 = 內容 23 | 24 | def 更新vue(self): 25 | if self.vue鏈接: 26 | self.vue鏈接.Call(self.vue._內容) 27 | 28 | def vue連接初始化(self, f): 29 | self.vue鏈接 = f 30 | self.更新vue() 31 | -------------------------------------------------------------------------------- /rimo_utils/cef_tools/wxcef.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import platform 3 | import sys 4 | import os 5 | 6 | import wx 7 | from cefpython3 import cefpython as cef 8 | 9 | 10 | ld_library_path = os.environ.get("LD_LIBRARY_PATH") 11 | 12 | 13 | def group(url, icon, title, size): 14 | icon = str(icon) # pathlib -> str 15 | sys.excepthook = ExceptHook 16 | cef.Initialize(settings={}, commandLineSwitches={ 17 | 'autoplay-policy': 'no-user-gesture-required', 18 | 'lang': 'zh-CN' 19 | }) 20 | app = CefApp(url, icon, title, size) 21 | return app, app.frame.browser 22 | 23 | 24 | def ExceptHook(exc_type, exc_value, exc_trace): 25 | ''' 26 | 原本的cef.ExceptHook打印不了中文,我拿來魔改一下。 27 | ''' 28 | import traceback 29 | if exc_type == SystemExit: 30 | hint = '[CEF Python] SystemExit.' 31 | print('\n' + '='*len(hint) + '\n' + hint) 32 | else: 33 | hint = '[CEF Python] ExceptHook: catched exception, will shutdown CEF.' 34 | print('\n' + '='*len(hint) + '\n' + hint) 35 | msg = ''.join(traceback.format_exception(exc_type, exc_value, exc_trace)) 36 | print('\n' + msg) 37 | cef.QuitMessageLoop() 38 | cef.Shutdown() 39 | os._exit(1) 40 | 41 | 42 | # Platforms 43 | WINDOWS = (platform.system() == "Windows") 44 | LINUX = (platform.system() == "Linux") 45 | MAC = (platform.system() == "Darwin") 46 | 47 | if MAC: 48 | try: 49 | # noinspection PyUnresolvedReferences 50 | from AppKit import NSApp 51 | except ImportError: 52 | logging.debug("[wxpython.py] Error: PyObjC package is missing, " 53 | "cannot fix Issue #371") 54 | logging.debug("[wxpython.py] To install PyObjC type: " 55 | "pip install -U pyobjc") 56 | sys.exit(1) 57 | 58 | if LINUX: 59 | import gi 60 | gi.require_version('Gtk', '3.0') 61 | from gi.repository import Gtk, Gdk, GdkX11 62 | 63 | # Globals 64 | g_count_windows = 0 65 | 66 | 67 | def main(): 68 | check_versions() 69 | sys.excepthook = cef.ExceptHook # To shutdown all CEF processes on error 70 | settings = {} 71 | if MAC: 72 | # Issue #442 requires enabling message pump on Mac 73 | # and calling message loop work in a timer both at 74 | # the same time. This is an incorrect approach 75 | # and only a temporary fix. 76 | settings["external_message_pump"] = True 77 | if WINDOWS: 78 | # noinspection PyUnresolvedReferences, PyArgumentList 79 | cef.DpiAware.EnableHighDpiSupport() 80 | cef.Initialize(settings=settings) 81 | app = CefApp(False) 82 | app.MainLoop() 83 | del app # Must destroy before calling Shutdown 84 | if not MAC: 85 | # On Mac shutdown is called in OnClose 86 | cef.Shutdown() 87 | 88 | 89 | def check_versions(): 90 | logging.debug("[wxpython.py] CEF Python {ver}".format(ver=cef.__version__)) 91 | logging.debug("[wxpython.py] Python {ver} {arch}".format( 92 | ver=platform.python_version(), arch=platform.architecture()[0])) 93 | logging.debug("[wxpython.py] wxPython {ver}".format(ver=wx.version())) 94 | # CEF Python version requirement 95 | assert cef.__version__ >= "66.0", "CEF Python v66.0+ required to run this" 96 | 97 | 98 | class MainFrame(wx.Frame): 99 | 100 | def __init__(self, url, icon, title, size): 101 | self.browser = None 102 | 103 | # Must ignore X11 errors like 'BadWindow' and others by 104 | # installing X11 error handlers. This must be done after 105 | # wx was intialized. 106 | if LINUX: 107 | cef.WindowUtils.InstallX11ErrorHandlers() 108 | 109 | global g_count_windows 110 | g_count_windows += 1 111 | 112 | if WINDOWS: 113 | # noinspection PyUnresolvedReferences, PyArgumentList 114 | logging.debug("[wxpython.py] System DPI settings: %s" 115 | % str(cef.DpiAware.GetSystemDpi())) 116 | if hasattr(wx, "GetDisplayPPI"): 117 | logging.debug("[wxpython.py] wx.GetDisplayPPI = %s" % wx.GetDisplayPPI()) 118 | logging.debug("[wxpython.py] wx.GetDisplaySize = %s" % wx.GetDisplaySize()) 119 | 120 | logging.debug("[wxpython.py] MainFrame DPI scaled size: %s" % str(size)) 121 | 122 | wx.Frame.__init__(self, parent=None, id=wx.ID_ANY, 123 | title=title) 124 | # wxPython will set a smaller size when it is bigger 125 | # than desktop size. 126 | logging.debug("[wxpython.py] MainFrame actual size: %s" % self.GetSize()) 127 | 128 | ic = wx.Icon(icon, wx.BITMAP_TYPE_ICO) 129 | self.SetIcon(ic) 130 | 131 | self.Bind(wx.EVT_CLOSE, self.OnClose) 132 | 133 | # Set wx.WANTS_CHARS style for the keyboard to work. 134 | # This style also needs to be set for all parent controls. 135 | self.browser_panel = wx.Panel(self, size=tuple(size)) 136 | self.browser_panel.Bind(wx.EVT_SIZE, self.OnSize) 137 | wx.Window.Fit(self) 138 | 139 | if MAC: 140 | # Make the content view for the window have a layer. 141 | # This will make all sub-views have layers. This is 142 | # necessary to ensure correct layer ordering of all 143 | # child views and their layers. This fixes Window 144 | # glitchiness during initial loading on Mac (Issue #371). 145 | NSApp.windows()[0].contentView().setWantsLayer_(True) 146 | 147 | if LINUX: 148 | self.Show() 149 | self.embed_browser(url) 150 | else: 151 | self.embed_browser(url) 152 | self.Show() 153 | 154 | def embed_browser(self, url): 155 | window_info = cef.WindowInfo() 156 | (width, height) = self.browser_panel.GetClientSize().Get() 157 | assert self.browser_panel.GetHandle(), "Window handle not available" 158 | if LINUX: 159 | handle_to_use = self.browser_panel.GetHandle() 160 | display = Gdk.Display.get_default() 161 | window = GdkX11.X11Window.foreign_new_for_display(display, handle_to_use) 162 | self.gtk_window = gtk_window = Gtk.Window() 163 | 164 | def callback(gtk_window, window): 165 | print("inside callback") 166 | gtk_window.set_window(window) 167 | gtk_window.set_visual(gtk_window.get_screen().lookup_visual(0x21)) 168 | gtk_window.connect("realize", callback, window) 169 | gtk_window.set_has_window(True) 170 | gtk_window.show() 171 | sw = Gtk.ScrolledWindow() 172 | sw.show() 173 | gtk_window.add(sw) 174 | sw.set_visual(sw.get_screen().lookup_visual(0x21)) 175 | self.sw = sw 176 | self.Show() 177 | window_info.SetAsChild(sw.get_window().get_xid(), [0, 0, width, height]) 178 | else: 179 | window_info.SetAsChild(self.browser_panel.GetHandle(), [0, 0, width, height]) 180 | self.browser = cef.CreateBrowserSync(window_info, url=url, browserSettings={'web_security_disabled': True, }) 181 | self.browser.SetClientHandler(FocusHandler()) 182 | 183 | def set_browser_object(self, name, obj): 184 | bindings = cef.JavascriptBindings() 185 | bindings.SetObject(name, obj) 186 | self.browser.SetJavascriptBindings(bindings) 187 | 188 | def OnSetFocus(self, _): 189 | if not self.browser: 190 | return 191 | if WINDOWS: 192 | cef.WindowUtils.OnSetFocus(self.browser_panel.GetHandle(), 193 | 0, 0, 0) 194 | self.browser.SetFocus(True) 195 | 196 | def OnSize(self, _): 197 | if not self.browser: 198 | return 199 | if WINDOWS: 200 | cef.WindowUtils.OnSize(self.browser_panel.GetHandle(), 201 | 0, 0, 0) 202 | elif LINUX: 203 | (x, y) = (0, 0) 204 | (width, height) = self.browser_panel.GetSize().Get() 205 | self.browser.SetBounds(x, y, width, height) 206 | self.sw.get_window().move_resize(x, y, width, height) 207 | self.browser.NotifyMoveOrResizeStarted() 208 | 209 | def OnClose(self, event): 210 | logging.debug("[wxpython.py] OnClose called") 211 | if not self.browser: 212 | # May already be closing, may be called multiple times on Mac 213 | return 214 | 215 | if MAC: 216 | # On Mac things work differently, other steps are required 217 | self.browser.CloseBrowser() 218 | self.clear_browser_references() 219 | self.Destroy() 220 | global g_count_windows 221 | g_count_windows -= 1 222 | if g_count_windows == 0: 223 | cef.Shutdown() 224 | wx.GetApp().ExitMainLoop() 225 | # Call _exit otherwise app exits with code 255 (Issue #162). 226 | # noinspection PyProtectedMember 227 | os._exit(0) 228 | else: 229 | # Calling browser.CloseBrowser() and/or self.Destroy() 230 | # in OnClose may cause app crash on some paltforms in 231 | # some use cases, details in Issue #107. 232 | self.browser.ParentWindowWillClose() 233 | event.Skip() 234 | self.clear_browser_references() 235 | 236 | def clear_browser_references(self): 237 | # Clear browser references that you keep anywhere in your 238 | # code. All references must be cleared for CEF to shutdown cleanly. 239 | self.browser = None 240 | 241 | def toggleFullScreen(self): 242 | if self.IsFullScreen(): 243 | self.ShowFullScreen(False) 244 | else: 245 | self.ShowFullScreen(True) 246 | 247 | 248 | class FocusHandler(object): 249 | def OnGotFocus(self, browser, **_): 250 | # Temporary fix for focus issues on Linux (Issue #284). 251 | if LINUX: 252 | logging.debug("[wxpython.py] FocusHandler.OnGotFocus:" 253 | " keyboard focus fix (Issue #284)") 254 | browser.SetFocus(True) 255 | 256 | 257 | class CefApp(wx.App): 258 | def __init__(self, url, icon, title, size): 259 | self.url, self.icon, self.title, self.size = url, icon, title, size 260 | 261 | self.timer = None 262 | self.timer_id = 1 263 | self.is_initialized = False 264 | 265 | super(CefApp, self).__init__(redirect=False) 266 | 267 | def OnPreInit(self): 268 | super(CefApp, self).OnPreInit() 269 | # On Mac with wxPython 4.0 the OnInit() event never gets 270 | # called. Doing wx window creation in OnPreInit() seems to 271 | # resolve the problem (Issue #350). 272 | if MAC and wx.version().startswith("4."): 273 | logging.debug("[wxpython.py] OnPreInit: initialize here" 274 | " (wxPython 4.0 fix)") 275 | self.initialize() 276 | 277 | def OnInit(self): 278 | self.initialize() 279 | return True 280 | 281 | def initialize(self): 282 | if self.is_initialized: 283 | return 284 | self.is_initialized = True 285 | self.create_timer() 286 | self.frame = MainFrame(self.url, self.icon, self.title, self.size) 287 | self.SetTopWindow(self.frame) 288 | self.frame.Show() 289 | 290 | def create_timer(self): 291 | # See also "Making a render loop": 292 | # http://wiki.wxwidgets.org/Making_a_render_loop 293 | # Another way would be to use EVT_IDLE in MainFrame. 294 | self.timer = wx.Timer(self, self.timer_id) 295 | self.Bind(wx.EVT_TIMER, self.on_timer, self.timer) 296 | self.timer.Start(10) # 10ms timer 297 | 298 | def on_timer(self, _): 299 | cef.MessageLoopWork() 300 | 301 | def OnExit(self): 302 | self.timer.Stop() 303 | return 0 304 | 305 | 306 | if __name__ == '__main__': 307 | main() 308 | -------------------------------------------------------------------------------- /rimo_utils/cv0.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from cv2 import * 4 | import numpy as np 5 | 6 | 7 | def show(img): 8 | if img.dtype == np.float: 9 | img = (img * 255).astype(np.uint8) 10 | cv2.imshow('show', img) 11 | cv2.waitKey() 12 | 13 | 14 | def read(img_path): 15 | img = cv2.imdecode(np.fromfile(img_path, dtype=np.uint8), -1) 16 | if img is None: 17 | raise Exception('読み込み失敗') 18 | return img 19 | 20 | 21 | def write(img, img_path, param=None): 22 | with open(img_path, 'wb') as f: 23 | if img.dtype in (np.float64, np.float32, np.float): 24 | img = (img * 255).astype(np.uint8) 25 | if param: 26 | _, data = cv2.imencode(img_path, img, param) 27 | else: 28 | _, data = cv2.imencode(img_path, img) 29 | f.write(data) 30 | 31 | 32 | def VideoCapGen(source, size: tuple = None): 33 | cap = cv2.VideoCapture(source) 34 | if size: 35 | x, y = size 36 | cap.set(cv2.CAP_PROP_FRAME_WIDTH, x) 37 | cap.set(cv2.CAP_PROP_FRAME_HEIGHT, y) 38 | while True: 39 | _, img = cap.read() 40 | if img is None: 41 | raise Exception('没图') 42 | yield img 43 | 44 | 45 | def circle(img, center, radius, color, thickness=1, lineType=16): 46 | center = tuple([round(x) for x in center]) 47 | cv2.circle(img, tuple(center), radius, color, thickness, lineType) 48 | return img 49 | 50 | 51 | def putText(img, text, org, fontFace, fontScale, color, thickness=1, lineType=16, bottomLeftOrigin=False): 52 | org = tuple([round(x) for x in org]) 53 | cv2.putText(img, text, org, fontFace, fontScale, color, thickness, lineType, bottomLeftOrigin) 54 | return img 55 | -------------------------------------------------------------------------------- /rimo_utils/matrix.py: -------------------------------------------------------------------------------- 1 | import math 2 | from math import sin, cos 3 | import functools 4 | 5 | import numpy as np 6 | 7 | 8 | @functools.lru_cache(maxsize=128) 9 | def scale(x, y, z): 10 | a = np.eye(4, dtype=np.float32) 11 | a[0, 0] = x 12 | a[1, 1] = y 13 | a[2, 2] = z 14 | return a 15 | 16 | 17 | @functools.lru_cache(maxsize=128) 18 | def rotate_ax(r, axis: tuple): 19 | a = np.eye(4, dtype=np.float32) 20 | a[axis[0], axis[0]] = cos(r) 21 | a[axis[0], axis[1]] = sin(r) 22 | a[axis[1], axis[0]] = -sin(r) 23 | a[axis[1], axis[1]] = cos(r) 24 | return a 25 | 26 | 27 | @functools.lru_cache(maxsize=128) 28 | def rotate(angle, v): 29 | v = np.array(v, dtype=np.float32) 30 | v /= np.linalg.norm(v) 31 | a = np.array([0, 0, 1]) 32 | b = np.cross(v, a) 33 | if np.linalg.norm(b) < np.linalg.norm(v) * np.linalg.norm(a) / 100: 34 | new_a = np.array([0, 1, 0]) 35 | b = np.cross(a, new_a) 36 | c = np.cross(v, b) 37 | 38 | rm = np.array([b, c, v]) 39 | arm = np.linalg.inv(rm) 40 | 41 | rm4 = np.eye(4, dtype=np.float32) 42 | rm4[:3, :3] = rm 43 | arm4 = np.eye(4, dtype=np.float32) 44 | arm4[:3, :3] = arm 45 | 46 | return arm4 @ rotate_ax(angle, axis=(0, 1)) @ rm4 47 | 48 | 49 | @functools.lru_cache(maxsize=128) 50 | def translate(x, y, z): 51 | a = np.eye(4, dtype=np.float32) 52 | a[3, 0] = x 53 | a[3, 1] = y 54 | a[3, 2] = z 55 | return a 56 | 57 | 58 | @functools.lru_cache(maxsize=128) 59 | def perspective(view_distance): 60 | a = np.eye(4, dtype=np.float32) 61 | a[2, 2] = 1 / view_distance 62 | a[3, 2] = -0.0001 63 | a[2, 3] = 1 64 | a[3, 3] = 0 65 | return a 66 | -------------------------------------------------------------------------------- /rimo_utils/计时.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | import time 3 | 4 | 5 | @contextlib.contextmanager 6 | def 计时(名字=''): 7 | 开始时间 = time.time() 8 | yield 9 | if 名字: 10 | 名字 = f'「{名字}」' 11 | print(f'{名字}用时:', time.time()-开始时间) 12 | 13 | 14 | 帧率计冷却时间 = 3 15 | 帧率计平均用时 = {} 16 | @contextlib.contextmanager 17 | def 帧率计(名字=''): 18 | global 帧率计冷却时间, 帧率计平均用时 19 | 开始时间 = time.time() 20 | yield 21 | 用时 = time.time() - 开始时间 22 | if 名字 not in 帧率计平均用时: 23 | 帧率计平均用时[名字] = 用时 24 | 帧率计平均用时[名字] = 0.9*帧率计平均用时[名字] + 0.1*用时 25 | 帧率计冷却时间 -= 用时 26 | if 帧率计冷却时间 < 0: 27 | 帧率计冷却时间 += 1 28 | for k, v in 帧率计平均用时.items(): 29 | if k: 30 | k = f'「{k}」' 31 | print(f'{k}帧率: %.2f' % (1/v)) 32 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | 4 | setuptools.setup( 5 | name='rimo_utils', 6 | version='1.5.0', 7 | author='RimoChan', 8 | author_email='the@librian.net', 9 | description='RimoChan util.', 10 | long_description='喵喵喵!', 11 | long_description_content_type='text/markdown', 12 | url='https://github.com/RimoChan/rimo_utils', 13 | packages=['rimo_utils', 'rimo_utils.cef_tools'], 14 | classifiers=[ 15 | 'Programming Language :: Python :: 3', 16 | 'Operating System :: OS Independent', 17 | ], 18 | install_requires=[ 19 | 'chardet==3.0.4', 20 | ], 21 | python_requires='>=3.6', 22 | ) 23 | --------------------------------------------------------------------------------