动机
平时在电脑上阅读文献或者文章的时候,有一些知识点需要前后参照,比如公式。
这个时候你就需要来回翻,那有没有一种方法能直接把需要的部分截下来做成悬浮的图片块呢?
就是这款ScrShotShow小工具。
此小工具还有其他功能,比如拖动,缩放,默认置顶,比较方便。
至于内存占用,一个图片块需要占用25M左右内存,相对轻量,一般同时运行的图片块也就1-3个,不会占用很多内存。
设计思路
首先设计一个类,从剪切板里获取图片到程序中。这一步需要用到pillow库。
然后设计一个显示程序,使用wx库,使用StaticBitmap组件来显示Bitmap图片,同时绑定各种功能,比如直接拖动,缩放。
最后设计一个激活程序,直接调用系统截图,然后通过一个监听键盘任意键按下的阻塞进程等待截图完毕,然后显示截屏块。这一步要用pynput库,之前写过相关操作
具体的代码和注释放在程序中,以上三个模块变成了三个类
"""图片处理用"""
from PIL import ImageGrab
import os
"""显示图片用"""
import wx
"""程序被激活的入口以及监控调用下一步显示"""
from pynput.keyboard import Key, Controller, Listener
class Util:
"""图片处理类"""
@staticmethod
def get_pic():
img = ImageGrab.grabclipboard()
img.save('picture.png')
@staticmethod
def del_pic():
os.remove('picture.png') # 及时清理文件
class PicFrame(wx.Frame):
"""窗体类"""
def __init__(self):
"""生成窗体"""
# 参数设置
self.shrink_rate = 1.08
self.now_rate = 1
self.shrink_quality = wx.IMAGE_QUALITY_NORMAL
# 从剪切板生成图片
Util.get_pic()
# 读取本地图片为wxImage,之后转换生成bitmap对象
self.img = wx.Image('picture.png', wx.BITMAP_TYPE_PNG)
self.size = self.img.GetSize()
# 清理图片
Util.del_pic()
# 初始化展示图片,None表示没有父节点,style通过参数调节没有边框
# 框架的size通过图片的尺寸来自适应,位置用center函数取屏幕中央
super().__init__(None, style=wx.SIMPLE_BORDER |
wx.STAY_ON_TOP |
wx.TRANSPARENT_WINDOW, size=self.size)
self.Center(dir=wx.BOTH)
# 创建一个Bitmap显示组件
self.bitmap = wx.StaticBitmap(self, bitmap=wx.Bitmap(self.img))
# 关联事件,移动窗口
self.bitmap.Bind(wx.EVT_LEFT_DOWN, self.on_left_down)
self.bitmap.Bind(wx.EVT_MOTION, self.on_drag)
self.bitmap.Bind(wx.EVT_LEFT_UP, self.on_left_up)
self.bitmap.Bind(wx.EVT_MOUSEWHEEL, self.on_scroll)
def on_left_down(self, event):
"""左键按下"""
"""位置解析:
默认左上角是(0,0)
self.GetPosition() self代表的Client相对于Screen的位置
event.GetPosition() event相对于Client的位置
wx.GetMousePosition() 获取鼠标位置
self.ClientToScreen()=前面的self+event位置
以下这一句可以验证
print(self.GetPosition()+event.GetPosition(),self.ClientToScreen(self.delta))
"""
self.delta = event.GetPosition()
def on_drag(self, event):
"""鼠标拖动"""
# 如果不加LeftIsDown,就会导致递归移动,移动事件触发移动,鼠标放下也没用
if event.Dragging() and event.LeftIsDown():
mouse = wx.GetMousePosition() # 用记录的delta抵消位置偏差
self.Move((mouse.x - self.delta[0], mouse.y - self.delta[1]))
def on_left_up(self, event):
"""左键放开"""
if self.HasCapture():
self.ReleaseMouse()
def on_scroll(self, event):
"""
滚轮控制缩放
图像因为连续缩放会导致模糊,所以保留最初图像,每次从最初图像计算,保真
"""
# 更新缩放倍数
if event.GetWheelRotation() > 0:
self.now_rate = self.now_rate * self.shrink_rate
else:
self.now_rate = self.now_rate / self.shrink_rate
# 计算临时尺寸
temp_size = (self.size[0] * self.now_rate, self.size[1] * self.now_rate)
# 更改窗口大小
self.SetSize(temp_size)
# 更改图片大小
temp_img = self.img.Scale(*temp_size, self.shrink_quality)
self.bitmap.SetBitmap(wx.Bitmap(temp_img))
class ScrShotShow:
"""主类"""
def __init__(self):
"""主函数"""
# 激活截图,使用无敌简洁的写法,完美!!!
controller = Controller()
with controller.pressed(Key.cmd):
with controller.pressed(Key.shift):
controller.press('s')
controller.release('s')
# 监控enter键,检查到就激活图片处理
# 设定join类监听,阻塞程序,如果出现enter,就将监听线程停掉继续后面的操作
with Listener(
on_press=self.on_press, on_release=None
) as self.listener:
self.listener.join()
# 激活图片显示
app = wx.App() # 创建app
PicFrame().Show() # 生成我的窗体类
app.MainLoop() # 循环
def on_press(self, key):
"""按键按下响应"""
# 停掉监听线程,进行后面的操作,也可以通过 return False实现
self.listener.stop()
# return False
if __name__ == '__main__':
ScrShotShow()
打包的问题
pyinstaller打包以后竟然有100M多,这令我难以置信。
此后我查阅了很多资料,尝试了conda虚拟环境和pipenv虚拟环境,结果conda那个是小了,但是无法运行,pipenv是主流方法,但是出来的结果和直接打包一样大,最后努力了一上午,还是败下阵来。
那就100多M打包就算了。
最后还是再挣扎一下:我通过pyinstaller命令行的INFO,感觉引入了很多hook,还有matplotlib相关的东西,我怀疑这些和pillow库有关系,因为只用wx和pynput只会有最多40M,所以这一个pillow库就有60M?我不理解。
后记
刚写完,就悲催的发现已经有哥们用三年开发出一款强大的截屏软件了:Snipaste