第一用pyQt5做了图片合成gif动图的小功能,将知识点做个记录。界面使用pyQt5的designer.exe 应该能好看些,但我全部敲代码,所以,布局很乱,界面很糙。虽然糙,但也是第一次用pyQt5的成果,先看下效果功能
添加图片后,点击合成gif,将这些图片合成成gif动图。
代码分两部分整理,界面部分和事件部分
界面部分
模块整理
使用了pyQt5三个模块:
- QtWidgets:包含创造经典桌面风格的用户界面提供了一套UI元素的类。
- QtGui:包含类窗口系统集成、事件集成、二维图形、基本成像、字体和文本。
- QtCore:包含了核心的非GUI功能。此模块用于处理时间、文件和目录、各种数据类型、流、URL、MIME类型、线程或进程。
以下为该界面用到的模块方法
QtWidgets:
QApplication
用于管理图形用户界面应用程序的控制流和主要设置。
获取屏幕大小
# 屏幕大小
self.desktop = QApplication.desktop()
self.height = self.desktop.height()
self.width = self.desktop.width()
QDesktopWidget
提供屏幕的有关信息。
窗口居中
qr = self.frameGeometry()
cp = QDesktopWidget().availableGeometry().center() # desktop的availableGeometry()获取可用位置和尺寸(除去任务栏等)
qr.moveCenter(cp)
self.move(qr.topLeft())
QFileDialog
打开和保存文件的标准对话框
方法 | 描述 |
---|---|
getOpenFileNames() | 多选文件 |
getOpenFileName() | 单选文件 |
getExistingDirectory() | 返回用户选择的文件路径 |
getOpenFileName() | 返回用户选择的文件名 |
QGridLayout
布局管理器,以网格的形式对组件进行定位
QHBoxLayout
水平布局
方法 | 描述 |
---|---|
setSpacing() | 布局中控件间距 |
addWidget() | 添加控件 |
addStretch() | 添加伸缩空间,在第一个控件之前添加,所有控件达到居右的效果、最后一个控件后添加,所有控件居左、第一个控件之前,最后一个控件之后分别添加则居中 |
QLable
文本标签
方法 | 描述 |
---|---|
setPixmap() | 设置图片,使用QPixmap设置,如:setPixmap(QPixmap(':/pic/add.png')) |
QLineEdit
文本输入框
方法 | 描述 |
---|---|
setVisible() | 设置可见 |
setText() | 设置显示内容 |
text() | 返回显示内容 |
setValidator() | 设置文本框校验器 |
QMainWindow
因为要用到状态栏,所以我选择继承QMainWindow。
方法 | 描述 |
---|---|
setWindowTitle() | 设置窗口标题 |
setWindowIcon() | 设置窗口图标 |
resize() | 设置窗口大小 |
setStatusBar() | 设置状态栏 |
QProgressBar
进度条
方法 | 描述 |
---|---|
setRange() | 设置进度条范围 |
setValue() | 进度条值 |
QPushButton
方法 | 描述 |
---|---|
setMaximumSize() | 设置大小,使用QSize()设置,如:setMaximumSize(QSize(100, 50)) |
setIcon() | 添加图片,使用QIcon设置,如:setIcon(QIcon(':/pic/add.png')) |
setStyleSheet() | 设置按钮样式 |
setCursor() | 设置鼠标手型 |
setEnabled() | 设置可用 |
QRadioButton
方法 | 描述 |
---|---|
setChecked() | 设置选中 |
QScrollArea
滚动区域。
方法 | 描述 |
---|---|
setAutoFillBackground() | 填充背景色 |
setWidgetResizable() | 自动调整窗口小部件的大小,以避免在可以避免滚动条的地方使用滚动条。在该例子中,我使用了改变窗口的大小,所以该方法没效果。 |
setGeometry() | 设置所在位置和大小,(x, y, w, h) 即 (X坐标、Y坐标、宽、高),其中x,y坐标是参照它所在的继承的窗口,非屏幕坐标,单位px |
QStatusBar
窗口状态栏
方法 | 描述 |
---|---|
setStyleSheet() | 设置样式 |
showMessage() | 状态栏显示信息 |
QWidget
基础窗口控件。
QtGui:
QDoubleValidator
校验器。
方法 | 描述 |
---|---|
setRange() | 值范围 |
setDecimals() | 精度位数 |
QIcon
设置图标。
self.setWindowIcon(QIcon(':/pic/aehvi-0m7h6-001.ico'))
QPalette
对话框或控件调色板。
scroll_area_palette = self.scroll_area.palette() # 获取已有的调色板
scroll_area_palette.setColor(QPalette.Window, Qt.white) # 修改背景色为白色
self.scroll_area.setPalette(scroll_area_palette) # 为组件设置调色板
self.scroll_area.setAutoFillBackground(True) # 组件生效调色板
QtCore:
QSize
设置尺寸。
self.add_file_btn.setMaximumSize(QSize(113, 38))
QRect
设置左、上、宽、高。
代码整理
分两个类文件,界面类和事件类。
class Ui_Form(QMainWindow):
def __init__(self):
# 初始化继承父类(QMainWindow)
super(Ui_Form, self).__init__()
# 获取屏幕大小
self.desktop = QApplication.desktop()
self.height = self.desktop.height()
self.width = self.desktop.width()
# 设置窗口大小,并显示在屏幕中间
self.resize(int(self.width * 0.5), int(self.height * 0.65))
self.setWindowTitle("合成gif")
self.setWindowIcon(QIcon(':/pic/aehvi-0m7h6-001.ico'))
self.set_center()
self.initUI()
窗口居中
def set_center(self):
qr = self.frameGeometry()
cp = QDesktopWidget().availableGeometry().center() # desktop的availableGeometry()获取可用位置和尺寸(除去任务栏等)
qr.moveCenter(cp)
self.move(qr.topLeft())
添加控件,主要是QScrollArea控件的滚动条,当区域内的内容超过滚动区域大小时显示滚动条。QScrollArea内部有个QGridLayout(QWidget子组件),因为QScrollArea只能添加widget,先实例一个QWidget,再把QGridLayout添加到QWidget中,就可以添加到QScrollArea里。计算好QGridLayout列数的大小不要超过QScrollArea的宽度,就不会显示横向滚动条,当图片加入QGridLayout的个数总长度超过QScrollArea的高度时,自动出现纵向滚动条。个人觉得这个方法最简单
滚动区域的大小,我按屏幕的大小比例计算,如果写死大小,如果程序打包出来,发给屏幕尺寸不一样的电脑,界面上控件位置就会惨不忍睹。我这个办法个人觉得很low,但是一时没找到更好的方法,先这么将就用了。
# 滚动区域,存放图片
self.scroll_area = QScrollArea(self)
self.scroll_area_contentwidget = QWidget(self)
self.gridLayout = QGridLayout(self.scroll_area_contentwidget)
self.scroll_area.setWidget(self.scroll_area_contentwidget)
# 设置滚动区域的位置和大小
self.scroll_area.setGeometry(10, 10, int(self.width*0.5-20), int(self.height*0.6/3*2))
设置滚动区域背景色,setAutoFillBackground 方法一定要设置为Ture,否则背景色不生效
scroll_area_palette = self.scroll_area.palette() # 获取已有的调色板
scroll_area_palette.setColor(QPalette.Window, Qt.white) # 修改背景色为白色
self.scroll_area.setPalette(scroll_area_palette) # 为组件设置调色板
self.scroll_area.setAutoFillBackground(True) # 组件生效调色板
添加按钮,加入水平布局
# 添加两个按钮
self.btnwidget = QWidget(self)
self.btnwidget.setGeometry(QRect(10, int(self.height*0.6/3*2)+20, 211, 50)) # 按钮控件布局及大小
self.hlayout_btn = QHBoxLayout(self.btnwidget) # 加水平布局
self.hlayout_btn.setSpacing(10) # 水平布局中控件的间隔
# 添加文件按钮
self.add_file_btn = QPushButton('添加文件', self.btnwidget)
self.add_file_btn.setMaximumSize(QSize(113, 38)) # 设置按钮大小
self.add_file_btn.setIcon(QIcon(':/pic/add.png')) # 按钮添加图片
self.add_file_btn.setStyleSheet("border-radius:10px;border:1px groove gray;") # 设置按钮样式
self.add_file_btn.setCursor(Qt.PointingHandCursor) # 鼠标移入显示手型
self.empty_btn = QPushButton('清空列表', self.btnwidget)
self.empty_btn.setMaximumSize(QSize(113, 38))
self.empty_btn.setIcon(QIcon(':/pic/cal.png'))
self.empty_btn.setStyleSheet("border-radius:10px;border:1px groove gray;")
self.empty_btn.setCursor(Qt.PointingHandCursor)
self.empty_btn.setEnabled(False) # 设置按钮不可用
# 水平布局加入按钮
self.hlayout_btn.addWidget(self.add_file_btn)
self.hlayout_btn.addWidget(self.empty_btn)
输出信息块的代码,与按钮添加类似,主要是水平布局在最后添加的控件添加拉伸,使各控件局左,否则各个控件间就会隔的很开
self.hlayout_out.addWidget(self.label_out)
self.hlayout_out.addWidget(self.radio_btn1)
self.hlayout_out.addWidget(self.radio_btn2)
self.hlayout_out.addWidget(self.custom_edit)
self.hlayout_out.addWidget(self.custom_btn)
self.hlayout_out.addStretch() # 设置拉伸
设置时间间隔的文本输入框,使用校验器
self.hlayout_duration = QHBoxLayout(self.switchDuration)
pDoubleValidator = QDoubleValidator() # 数值校验器,只能输入数字
pDoubleValidator.setRange(0, 100) # 输入数值范围
pDoubleValidator.setNotation(QDoubleValidator.StandardNotation)
pDoubleValidator.setDecimals(1) # 输入精度位
self.duration_edit.setValidator(pDoubleValidator) # 文本输入框加入校验器
合成按钮,设置按钮样式
self.com_btn = QPushButton('合成gif', self)
self.com_btn.setStyleSheet("background-color: rgb(170, 170, 255);color: white;"
"border-radius: 10px; border: 2px groove gray;")
到此界面部分就完成!
事件部分
事件处理,新定义了一个类文件,继承界面类
class Ui_Event(Ui_Form):
def __init__(self):
super(Ui_Event, self).__init__()
self.initUI()
self.radio_btn1.toggled.connect(lambda: self.radio_btn_sign(self.radio_btn1)) # 输入信息单选框事件
self.radio_btn2.toggled.connect(lambda: self.radio_btn_sign(self.radio_btn1))
self.add_file_btn.clicked.connect(self.open) # 添加文件按钮事件
self.empty_btn.clicked.connect(self.empty) # 清空图片列表事件
self.custom_btn.clicked.connect(self.custom) # 自定义输出按钮事件
self.com_btn.clicked.connect(self.complex) # 合成按钮事件
self.col = -1
self.row = 0
self.gif_file = './result.gif'
self.img_list = []
主要是添加用户选中的图片到QGridLayout里,这边用到的是先将图片设置到100px*100px的大小(这样方便计算出QGridLayout的一行的大小,QGridLayout不超过QScrollArea的宽度,就不会出现横向的滚动条),存到QLable中,然后把QLable添加到QGridLayout的一个个网格里。
def open(self):
imgfiles, _ = QFileDialog.getOpenFileNames(self, 'Open file', '.', 'Image files (*.jpg *.gif *.png *.jpeg)') # 打开文件目录,设置可选择的文件类型
photo_num = len(imgfiles)
if photo_num != 0:
for i in range(photo_num):
image_id = imgfiles[i]
pixmap = QPixmap(image_id)
self.img_list.append(image_id)
pixmap_change = pixmap.scaled(100, 100, Qt.KeepAspectRatio) # 改变展示图片的大小,保持等比例缩放
label2 = QLabel()
label2.setPixmap(QPixmap(pixmap_change)) # 图片加到lable上
self.addImage(label2)
self.empty_btn.setEnabled(True)
else:
QMessageBox.information(self, '提示', '未选择图片')
添加图片到QGridLayout,这里还是得注意计算QGridLayout各列总宽度不要超过QScrollArea的跨度。总列数*100px(我们前面设置的一张图片的大小)
def get_nr_of_image_columns(self):
# 展示图片的区域,计算每排显示图片数
scroll_area_images_width = int(self.width*0.5-20)
return scroll_area_images_width // 100 - 2 # 除100后可能有余数,可能会超过QScrollArea的宽度,-2 尽量减少列数,保证总列数不超过QScrollArea的宽度
def addImage(self, label2):
nr_of_columns = self.get_nr_of_image_columns()
self.max_columns = nr_of_columns
if self.col < self.max_columns:
self.col += 1
else:
self.col = 0
self.row += 1
self.gridLayout.addWidget(label2, self.row, self.col) # 添加图片的lable到gridLayout具体行列上
清空列表,清空列表要把图片img_list也一起清空了,img_list定义的是全局变量,否则再下次合成后,会把上次加入的图片也合成进去。
def empty(self):
print(self.gridLayout.count())
for i in reversed(range(self.gridLayout.count())):
move_widget = self.gridLayout.itemAt(i).widget()
move_widget.setParent(None)
move_widget.deleteLater()
self.empty_btn.setEnabled(False)
self.col = -1
self.row = 0
self.img_list.clear()
self.label_bar1.setText('准备合成:')
self.label_bar2.setText('准备合成:')
self.pbar.setValue(0)
合成方法就很简单了,imageio模块imread,mimsave方法搞定,注意状态栏进度条的处理。
def create_gif(self, image_list, gif_name, duration=0.35):
frames = []
image_num = len(image_list)
step_size = 500//image_num + 1
for i, image_name in enumerate(image_list):
frames.append(imageio.imread(image_name)) # 加入合成图片
step_sum = step_size*(i+1) # 进度条值计算
max = step_sum if step_sum<500 else 500
self.pbar.setValue(max) # 设置进度条值
imageio.mimsave(gif_name, frames, 'GIF', duration=duration) # 合成gif
return
def complex(self):
com_duration = self.duration_edit.text()
if self.gridLayout.count() == 0:
QMessageBox.information(self, '提示', '未添加合成的图片')
elif not com_duration:
QMessageBox.information(self, '提示', '切换间隔未填写')
else:
self.label_bar1.setText('开始合成')
self.label_bar2.setText('正在合成:')
if self.radio_btn2.isChecked() == True:
self.gif_file = self.custom_edit.text() + '/result.gif'
if not os.path.exists(os.path.dirname(self.gif_file)):
QMessageBox.information(self, '提示', '输出目录不存在')
else:
self.create_gif(self.img_list, self.gif_file, com_duration)
self.label_bar1.setText('完成合成')
self.label_bar2.setText('完成合成:')
整体功能完全,界面优化的地方还有很多,比如窗口最大化,控件不会自适应、界面布局太丑、输入的文件名用户自己定义……
打算后续用designer.exe重新再做过。代码可打包成exe,如何打包exe后续再整理。