前言
为实现二维码传输文件,首先要在电脑屏幕上动态展示已经之前已拆分好的二维码,然后使用摄像头对着电脑屏幕按帧读取,最后还原出原来的文件。
本章使用Python自带的GUI库tkinter实现在电脑屏幕上动态展示已经之前已拆分好的二维码。
目录
一、制作基于tkinter的静态图片显示窗口
首先是使用tkinter库的Tk组件,创建tkinter窗口对象root,并对窗口对象root的标题进行设置。代码如下:
from tkinter import Tk
# 创建窗口
root = Tk()
# 设置窗口的标题
root.title("利用二维码发送文件工具")
然后使用pillow(PIL)库Image组件,读取需要显示的图片。代码如下:
from PIL import Image
# 创建图片组件
_pil_image = Image.open('cache/split_file_000.png')
最后是把图片显示在窗口中,由于事先不知道图片长宽,同时窗口可显示大小有限,太大的图片只能一部分,所以这里需要对图片进行缩放,以适应窗口大小。缩放图片的函数代码如下,函数会返回一个缩放好的图像对象。
# 缩放图片的函数,w是原来图片的宽,h是原来图片的高,w_box是想缩放的宽,h_box是想缩放的高
# pil_image是原图片对象,函数会返回一个缩放好的图像对象
def resize(w, h, w_box, h_box, pil_image):
f1 = 1.0 * w_box / w
f2 = 1.0 * h_box / h
factor = min([f1, f2])
width = int(w * factor)
height = int(h * factor)
return pil_image.resize((width, height), Image.Resampling.LANCZOS)
有了缩放函数后,就可把任意大小的图片显示在tkinter窗口了。这里使用tkinter的Label组件加载图片,Label组件通过.grid(x,y)网格布局模式加入窗口对象root中。
需要注意的是PIL的Image对象不能直接显示在tkinter窗口上,需要先把Image对象转换成ImageTk对象,完整的代码如下:
from tkinter import Tk
from tkinter import Label
from PIL import Image, ImageTk
def open_window():
# 创建窗口
root = Tk()
# 设置窗口的标题
root.title("利用二维码发送文件工具")
# 创建图片组件
_pil_image = Image.open('cache/split_file_000.png')
# 缩放图片
_w, _h = _pil_image.size
_pil_image_resized = resize(_w, _h, 300, 300, _pil_image)
_tk_image = ImageTk.PhotoImage(_pil_image_resized)
# 创建图片组件,放置在root中
_label_dt = Label(root, image=_tk_image, width=300, height=300)
_label_dt.grid(row=0, column=0)
root.mainloop()
# 缩放图片的函数,w是原来图片的宽,h是原来图片的高,w_box是想缩放的宽,h_box是想缩放的高
# pil_image是原图片对象,函数会返回一个缩放好的图像对象
def resize(w, h, w_box, h_box, pil_image):
f1 = 1.0 * w_box / w
f2 = 1.0 * h_box / h
factor = min([f1, f2])
width = int(w * factor)
height = int(h * factor)
return pil_image.resize((width, height), Image.Resampling.LANCZOS)
运行效果如下:
二、读取已拆分的二维码,使用tkinter窗口循环显示
刚才已完成静态二维码图片的显示,现需要将拆分的二维码动态进行显示,这里读取上一篇文章拆分好的29张图片,实现按文件名顺序,每隔0.2S显示一张不同的二维码图片。
首先,编制更新tkinter窗口中Label组件_label_dt图像的函数,若要更新_label_dt中的图片只需使用_label_dt.configure(image=_tk_image)即可,其中_tk_image通过使用按顺序的文件名读取新的二维码图片,_pil_image = Image.open("cache/split_file_00" + str(i) + ".png"),这里通过str(i)循环文件名,并通过函数传入的_time_interval参数控制循环时的时间间隔,同时_label_dt要换成global全局变量,以便分线程可以操作主线程创建的_label_dt组件,主要代码如下:
# 循环更新tkinter窗口中,二维码图片的函数,_time_interval为更新频率,
def update_qr(_time_interval):
for i in range(0, len(os.listdir("cache"))):
time.sleep(_time_interval)
# 读取二维码图片
_image_filepath = "cache/split_file_00" + str(i) + ".png"
_pil_image = Image.open(_image_filepath)
print(_image_filepath)
# 缩放图片
_w, _h = _pil_image.size
_pil_image_resized = resize(_w, _h, 300, 300, _pil_image)
_tk_image = ImageTk.PhotoImage(_pil_image_resized)
global _label_dt
_label_dt.configure(image=_tk_image)
_label_dt.image = _tk_image
有了二维码图片的更新函数后,还有一个问题需要解决,就是更新操作不能阻塞tkinter的窗口主线程,否则当更新图片的时候窗口会被阻塞不能同时进行其他操作,因此这里需要为更新二维码图片update_qr()函数启动单独的线程。
开启线程需要用到Python的threading包,这里需要特别注意的是当.Thread()中的args=(,)只有一个参数时必须要有,逗号结尾,因为它接受的是列表类型的参数,主要代码如下:
# 用于更新图片的线程
def update_qr_thread(_time_interval):
# 设置更新频率为_time_interval秒
# 设置线程参数,target为线程启动函数,name为线程名,args为传递给线程函数的参数
_t = threading.Thread(target=update_qr, name='update_qr', args=(_time_interval,))
# 启动线程
_t.start()
所有准备工作都完成了,现在只需要在创建窗口时调用线程便可,这里使用root.after(3,update_qr_thread(0.2))函数,在创建窗口后3秒才启动线程以免一些窗口初始化工作未完成就启动线程,造成不必要错误。
# 窗口显示后3秒执行update_qr_thread()函数
root.after(3, update_qr_thread(0.2))
# 显示窗口
root.mainloop()
三、运行效果和完整代码
全部代码码完,运行看到以下效果:
完整代码如下:
import os
import time
from tkinter import Tk
from tkinter import Label
from PIL import Image, ImageTk
import threading
# 图片组件
_label_dt = None
def open_window():
# 创建窗口
root = Tk()
# 设置窗口的标题
root.title("利用二维码发送文件工具")
# 创建图片组件
_pil_image = Image.open('cache/split_file_000.png')
# 缩放图片
_w, _h = _pil_image.size
_pil_image_resized = resize(_w, _h, 300, 300, _pil_image)
_tk_image = ImageTk.PhotoImage(_pil_image_resized)
# 创建图片组件,放置在root中
global _label_dt
_label_dt = Label(root, image=_tk_image, width=300, height=300)
_label_dt.grid(row=0, column=0)
# 窗口显示后3秒执行update_qr_thread()函数
root.after(3, update_qr_thread(0.2))
# 显示窗口
root.mainloop()
# 缩放图片的函数,w是原来图片的宽,h是原来图片的高,w_box是想缩放的宽,h_box是想缩放的高
# pil_image是原图片对象,函数会返回一个缩放好的图像对象
def resize(w, h, w_box, h_box, pil_image):
f1 = 1.0 * w_box / w
f2 = 1.0 * h_box / h
factor = min([f1, f2])
width = int(w * factor)
height = int(h * factor)
return pil_image.resize((width, height), Image.Resampling.LANCZOS)
# 用于更新图片的线程
def update_qr_thread(_time_interval):
# 设置更新频率为_time_interval秒
# 设置线程参数,target为线程启动函数,name为线程名,args为传递给线程函数的参数
_t = threading.Thread(target=update_qr, name='update_qr', args=(_time_interval,))
# 启动线程
_t.start()
# 循环更新tkinter窗口中,二维码图片的函数,_time_interval为更新频率,
def update_qr(_time_interval):
for i in range(0, len(os.listdir("cache"))):
time.sleep(_time_interval)
# 读取二维码图片
_image_filepath = "cache/split_file_00" + str(i) + ".png"
_pil_image = Image.open(_image_filepath)
print(_image_filepath)
# 缩放图片
_w, _h = _pil_image.size
_pil_image_resized = resize(_w, _h, 300, 300, _pil_image)
_tk_image = ImageTk.PhotoImage(_pil_image_resized)
global _label_dt
_label_dt.configure(image=_tk_image)
_label_dt.image = _tk_image
if __name__ == '__main__':
open_window()