实际工作中,经常需要对大量的图片合并为一个PDF文件,以便文件的传输和保存。网上有许多图片生成PDF的工具。其实利用Python的Pillow组件,我们也可以用pyqt5制作一个有模有样的PDF生成工具。
一 、关于PIL组件:
Pillow是python下功能强大的图像处理组件(前身为PIL组件),提供了强大的图像处理功能,本应用中主要用到他的图像格式转换功能(把图片改为RGB、8位彩色或灰度模式),同时用它的存储功能,把多个图片保存到一个PDF文件中。
如没有安装Pillow,可以命令行下运行:pip install pillow或pip3 install pillow即可。
二 、实现功能
1、将指定文件夹中的所有图片文件合并生成一个PDF文件。
2、支持jpg、png和bmp图片格式,自动过滤掉其它格式文件
3、应用中实现图片预览,方便用户清除无用图片,或调整图片的放置顺序。
4、通过拖动,实现图片文件顺序的调整
5、输入或选择保存PDF的文件名和路径
6、指定PDF中图片模式(RGB,8位彩色或灰度,满足对不同用户对PDF文件大小的要求。
三 、应用界面
应用界面采用pycharm+5开发,布局为常用的盒式布局结构:整个窗口使用一个垂直布局,下面包括五个水平布局。界面结构如下:
主要操作过程:
1、点击第一行的…按钮后,弹出文件夹选择对话框,在选择文件夹后,从文件夹中读取所有PNG、JPG图像文件,并把文件名加载到第二行左侧的列表中。
2、通过点击选中列表中的文件名,可以在第二行右侧预览出该文件对应的图像。
3、通过拖动列表中的文件名,可以改变生成PDF的文件顺序
4、通过点击选中列表中的文件名,可以点击列表下方的称除按钮从列表中移除该图片(但不删除文件)
5、通过在第三行的文本框中输入路径及文件文件名,或点后面的…按钮选择文件,确定文件的保存位置和文件名。
6、在第三行后面的色彩模式中选择图片在PDF文件中的保存模式。
7、点击立即生成,完成后即可生成指定的PDF文件。
生成的pdf文件在指定的文件下。
打开后可以看到PDF中的文件按我们在列表中指定的顺序生成,模式为我们要求的灰度模式。
四 、代码及说明
代码如下,注释中解释的比较清楚,不再赘述。
'''
一个将多个图片合并为一个PDF文件的python工具
#################################################################
主要功能:
1、用于将指定文件夹中的所有图片文件合并生成一个PDF文件。
2、支持jpg、png和bmp图片格式,自动过滤掉其它格式
3、实现图片预览
4、通过拖动,实现图片文件顺序的调整
5、输入或选择保存PDF的文件名和路径
6、指定PDF中图片模式(RGB,8位彩色或灰度,满足对不同用户对PDF文件大小的要求。
#################################################################
界面介绍:
界面使用box布局,一个垂直QVBox布局,包含个多行水平QHBox布局:
第1行:一个显示原始图片文件所在位置的文本框,一个打开文件夹选择对话框按钮,一个重新加截按钮,
第2行:左侧为一个显示指定文件夹内图片文件名的列表框,下面为一个删除按钮,右侧为一个图片,用于预览当前选中的图片文件
第4行:一个文本框,用于输入PDF文件保存位置,一个按钮用于选择文件保存位置,一个下拉列表,用于选择文件保存模式(RGB、8位彩色或灰度)
第5行:一个文本框,用于显示处理进度和处理信息。
第6行:一个居中的开始处理按钮
##################################################################
'''
import os
import sys
from PyQt5.QtCore import pyqtSignal, QThread
from PyQt5.QtGui import QPixmap
from PyQt5.QtWidgets import (QWidget, QPushButton,
QHBoxLayout, QVBoxLayout, QApplication, QLabel,
QLineEdit, QTextEdit, QComboBox, QFileDialog,
QListWidget, QListWidgetItem, QAbstractItemView
)
from PIL import Image
class Thread_do(QThread): # 定义线程类
# 定义带参数一个信号及所需参数
_signal =pyqtSignal(str)
#定义几个成员,用于传递参数,包括:源文件夹,目标文件名,保存模式和图像列表
_picpath=""
_trgfile=""
_picmode=""
_piclist=[]
#重写类线程类的初步化方法,用于将传入的几个参数保存到类成员中
def __init__(self,_path,_trg,_mode,_piclist):
super().__init__()
self._picpath=_path
self._trgfile=_trg
self._picmode=_mode
self._piclist=_piclist
def run(self):#线程的执行方法
list_images=[]#定义一个列表,用于保存每个图像对象
for i in range(len(self._piclist)):
fullPicname=self._picpath+"/"+self._piclist[i]
if self._picmode=="32位真彩":
list_images.append(Image.open(fullPicname).convert("RGB"))#32位真彩
if self._picmode == "8位彩色":
list_images.append(Image.open(fullPicname).convert("P")) # 8位彩色
if self._picmode == "灰度":
list_images.append(Image.open(fullPicname).convert("L")) # 灰度
self._signal.emit("处理..."+fullPicname+"...完成") ##返回处理
# 因为PIL保存为PDF的格式为:
#第一个图像对象.save(目标文件名,保存参数,其它图片对象列表)
#所以要把第一个图像对象从列表中剔除,并赋值给另一个图像对象(im1)
im1=list_images.pop(0)
im1.save(self._trgfile, save_all=True, append_images=list_images)
self._signal.emit("全部完成")##返回全部完成信号
class Example(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
#定义布局
vbox = QVBoxLayout()
hbox_line1 = QHBoxLayout()
hbox_line2 = QHBoxLayout()
hbox_line3 = QHBoxLayout()
hbox_line4 = QHBoxLayout()
hbox_line5 = QHBoxLayout()
#第一行,一个标签,一个文件框,一个打开对话框按钮,一个重载按钮
######################################################
self.txt_SrcPath = QLineEdit("D:/") # 原始文件所在位置,默认为C:/
self.txt_SrcPath.setReadOnly(True)#设置为不可编辑
btn_openDiagSrcPath = QPushButton("...") # 选择源文件夹按钮
#绑定按钮的打开事件
btn_openDiagSrcPath.clicked.connect(self.showDialog_Src)
hbox_line1.addWidget(QLabel("图片路径:"))
hbox_line1.addWidget(self.txt_SrcPath)
hbox_line1.addWidget(btn_openDiagSrcPath)
#第二行,一个垂直布局(上面为一个图片文件列表,下面为操作按钮)一个用于显示当前选中的图片
vbox_s1=QVBoxLayout()
self.pic_listbox = QListWidget(self)
self.pic_listbox.setMaximumHeight(300)
self.pic_listbox.setMinimumHeight(300)
# 使列表框中的元素可拖动并设置拖动模式
self.pic_listbox.setAcceptDrops(True)
self.pic_listbox.setDragDropMode(QAbstractItemView.InternalMove)
#绑定列表框中列表项点击的事件(更新预览图片的显示内容)
self.pic_listbox.itemClicked.connect(self.listItemClick)
btn_del=QPushButton("移除选中的文件")
#绑定删除按钮的事件
btn_del.clicked.connect(self.deleteListItem)
vbox_s1.addWidget(self.pic_listbox)
vbox_s1.addWidget(btn_del)
hbox_line2.addLayout(vbox_s1)
# 添加一个图片,用于显示选中的图片
self.lbl_Image = QLabel(self)
#固定预览图片的大小,可以进一步细化为根据宽度自动适应图片比例
self.lbl_Image.setMinimumSize(480,360)
self.lbl_Image.setMaximumSize(480,360)
self.lbl_Image.setText("暂未选择文件")
self.lbl_Image.setScaledContents(True)
hbox_line2.addWidget(self.lbl_Image)
#第3行,一个保存PDF存放位置的文本框,一个按钮用于打开文件选择对话框,
# 一个下拉列表:选择PDF保存模式:真彩、8位彩色或灰度。
self.txt_PDFPath = QLineEdit("D:/newFile.pdf") # PDF文件默认保存位置和文件名
btn_openDiagPDFFile = QPushButton("...") # 选择目标文件按钮
# 绑定按钮的打开事件
btn_openDiagPDFFile.clicked.connect(self.showDialog_Tgt)
#下拉列表框:图片模式:
self.list_PicFmt = QComboBox() # 定义下拉框,并添加三个图片格式选项
self.list_PicFmt.addItem("32位真彩")
self.list_PicFmt.addItem("8位彩色")
self.list_PicFmt.addItem("灰度")
self.list_PicFmt.setMaximumWidth(140)
self.list_PicFmt.setMinimumWidth(140)
hbox_line3.addWidget(QLabel("PDF路径和文件名:"))
hbox_line3.addWidget(self.txt_PDFPath)
hbox_line3.addWidget(btn_openDiagPDFFile)
hbox_line3.addWidget(QLabel("PDF文件色彩模式:"))
hbox_line3.addWidget(self.list_PicFmt)
#第四行,处理信息文本框
self.txt_PressInof = QTextEdit() # 处理信息文本框
hbox_line4.addWidget(self.txt_PressInof)
#第五行,生成按钮
self.btn_do = QPushButton("立即生成") # 处理按钮
hbox_line5.addWidget(self.btn_do)
#绑定生成按钮的事件
self.btn_do.clicked.connect(self.process_Pic)
#把几个水平布局加入到垂直布局器中
vbox.addLayout(hbox_line1)
vbox.addLayout(hbox_line2)
vbox.addLayout(hbox_line3)
vbox.addLayout(hbox_line4)
vbox.addLayout(hbox_line5)
self.setLayout(vbox)
#设置窗口大小(1027*720)及初始化位置(100,100)
self.setGeometry(100, 100, 1024, 720)
self.setWindowTitle('PIC2PDF')#设置标题
self.show()
def showDialog_Src(self): # 选择源文件夹对话框,把选中的文件夹路径保存在txt_srcpath中
folder_path = QFileDialog.getExistingDirectory(self, "选择原始图片文件夹")
self.txt_SrcPath.setText(folder_path)
self.loadPic(folder_path)
itemCount=self.pic_listbox.count()
#self.lbl_Image.setText(f'共有{itemCount}张可用图片')
#self.statusBar().showMessage(f'共有{itemCount}张可用图片')
def showDialog_Tgt(self):#选择PDF文件的保存路径及文件名
pdffile_name = QFileDialog.getOpenFileName(self, '选择文件', 'D:/newFile.pdf')
if pdffile_name[0]:
self.txt_PDFPath.setText(pdffile_name[0])
def listItemClick(self):#列表框中列表项的点击事件,在预览图片中显示该文件
sel_item=self.pic_listbox.selectedItems()
sel_file=os.path.join(self.txt_SrcPath.text(), sel_item[0].text())
pixmap = QPixmap(sel_file) # 按指定路径找到图片
self.lbl_Image.setPixmap(pixmap) # 在label上显示图片
self.lbl_Image.setScaledContents(True) # 让图片自适应label大小
def deleteListItem(self):#删除列表中某一项的事件
current_row = self.pic_listbox.currentRow()
if current_row != -1:
item = self.pic_listbox.takeItem(current_row)
del item
self.pic_listbox.setCurrentRow(-1)
self.lbl_Image.clear()
self.lbl_Image.setText("暂未选择文件")
def loadPic(self,_path):#把指定文件夹中的图片加载到列表框中的事件,在howDialog_Src函数中调用
self.pic_listbox.clear()
for f in os.listdir(_path):
#如果扩展名为jpg,bmp或PNG,就添加到列表框中,注意区分大小写
if f.endswith(".jpg") or f.endswith(".png") or f.endswith(".bmp") \
or f.endswith(".JPG") or f.endswith(".PNG") or f.endswith(".BMP"):
self.pic_listbox.addItem(QListWidgetItem(f))
def process_Pic(self): # 处理按钮的点击事件
# 取得参数,需要几个参数:
# 1、图片所在路径
# 2、pdf文件名及保存位置
# 3、保存模式(原色、灰度或黑白)
# 4、需处理图片文件名列表
path=self.txt_SrcPath.text()#图片路径文件夹名
trg=self.txt_PDFPath.text()#需生成的PDF文件名及路径
mode=self.list_PicFmt.currentText()#参数2,保存模式,原色灰度或黑白
list_pic=[]#定义一个列表,用于保存需处理图片的路径
for i in range(self.pic_listbox.count()):
file_name=self.pic_listbox.item(i).text()
list_pic.append(file_name)
curText = "开始生成..."
self.txt_PressInof.setText(curText)
# 生成过程中,改变按钮的状态为不可用,且按钮文本变为正在生成
self.btn_do.setEnabled(False)
self.btn_do.setText("正在生成...")
# 定义线程实例并将参数传入
self.thread_do = Thread_do(path,trg,mode,list_pic)
# 信号连接,如果收到信号,就执行对应的函数
self.thread_do._signal.connect(self.update_TxtInfo)
# 启动线程实例
self.thread_do.start()
def update_TxtInfo(self, _info): # 定义收到线程返回信号的方法
if _info == "全部完成": # 如收到全部完成,恢复按钮的状态
self.btn_do.setEnabled(True)
self.btn_do.setText("开始生成")
else: # 否则更新文本框的内容
self.txt_PressInof.append(_info)
#################################################################
# 主程序
###############################################################
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
五 、生成可执行文件
使用python的PyInstaller组件来把python文件生成exe文件。如没有安装过Pyinstaller组年,可以在命令行下运行pip install pyinstaller来安装。
在命令行模式下运行以下代码(以下两种方式均可,我个人比较喜欢第二种)
1、Pyinstall xxxx.py把xxxx.py打包为可执行文件。打包后生成两个文件夹,一个build,为临时文件,完成后可删除,一个dist,里面为可执行文件及支持库文件。
注意:xxxx.py文件名中不能有小数点。
2、Pyinstaller -F xxxx.py:把xxxx.py打包为单独一个可执行文件,可执行文件中已经包括运行所需库文件。
示例:如本应用的python代码文件为pic2pdf.py,我们把这个文件放到d:/pic2pdf文件夹下,则可在命令行模式下进入该文件夹,并运行:
pyinstaller -F pic2pdf.py
结果如下图:
当出现如上成功结束的字样,就能关闭命令行窗口了。
在我的电脑中找到这个文件夹(d:\pic2pdf),就能看到里面多了几个文件和文件夹。
进入dist文件夹,就能看到生成的pic2pdf.exe文件了,运行一下,效果和在pycharm中测试时的完全一致。
六 、源码下载
完整源码及生成的exe文件可从:下载。
七、补充:
这个工具其实还有许多可完善的地方,包括:设计一个漂亮的图标,充分利用pillow的强大功能,实现对图片的翻转、映像、剪裁等更复杂的功能。这些就有待码友们进一步完善了。