利用python的pillow组件开发一个图片批处理工具

工作中经常需要对大量图片进行成批处理、以便统一尺寸和格式。手工一张一张处理费时费力,利用python的Pillow组件,可以实现对图片的成批处理,达到事半功部的效果。

一、关于Pillow组件:

pillow组件是python下功能强大的图像处理组件。它是 PIL 的替代版本。提供了强大的图像处理功能,包括改变图像大小,旋转图像,图像格式转换,色场空间转换,图像增强,直方图处理,插值和滤波等等。
Pillow的安装很简单:
1、windows环境下:命令行方式下输入:pip install Pillow或pip3 install pillow即可。

2、linux环境下:终端下运行:python3 -m pip install --upgrade pip及python3 -m pip install --upgrade Pillow。
【因为网络原因,可能需要多试几次才能成功】

二、实现功能

我们的图片批处理工具需要实现以下功能:
1、统一图片格式:将某个文件夹中的所有图片都生成新的统一格式的图片(如JPG、PNG或BMP格式)
2、统一图片大小:在不改变图片宽高比例的条件下,按指定比例、指定宽度或指定高度缩放图片大小。
在这里插入图片描述

三、应用界面

应用界面采用pycharm+pyqt5开发,布局采用常用的盒式布局,整个窗口使用一个垂直布局,垂直布局中包括五个水平布局。界面及控件说明如下:
在这里插入图片描述

图片绽放的方式有三种,一是按指定比例缩放,二是按指定宽度缩放(根据原始图片自动计算高度),三是按指定高度计算(根据原始图片自动计算宽度),在选择某一个缩放方式后,只让对应的控件可用,其它两个不可用。
因图片的处理耗时比较长,为了能在多行文本框中及时反馈处理进度和处理信息,我们在新线程中进行图片处理,根据处理进度,不断向主线程反馈,并在多行文本框中显示处理进程,如下图。
在这里插入图片描述

为防止重复操作,在点击开始处理按钮后,立即把按钮的状态变为不可用,待全部处理完成后,根据新线程反馈的信号,再把按钮的状态恢复正常

四、代码及说明

''
一个成批缩放处理图片的工具
用于将指定文件夹中的图片文件按指定格式进行进行成批的缩入、旋转及加水印
界面使用box布局,一个垂直QVBox布局,下含个七个水平QHBox布局:
第1行:一个文本框,显示原始图片文件所在位置,一个按钮,用于打开文件夹选择对话框
第2行:一个文本框,显示生成新文件保存位置,一个按钮,用于打开文件夹选择对话框
第3行:一个下拉列表,用于指定图片格式(JPG/PNG或BMP),三个单选按钮,用于设置按图片宽度,高度,或按比例缩放。
两个文本框,用于输入宽度(按宽度缩放时显示)和高度(按高度缩放时显示),一个数学选择器,用于指定图片比例(按比例缩放时显示)
第4行:一个多行文本框,用于滚动显示处理信息。
第5行:一个居中的开始处理按钮
'''
import os
import sys

from PyQt5.QtCore import pyqtSignal, QThread
from PyQt5.QtGui import QPalette
from PyQt5.QtWidgets import (QWidget, QPushButton,
                             QHBoxLayout, QVBoxLayout, QApplication, QLabel,
                             QLineEdit, QTextEdit, QComboBox, QSpinBox, QRadioButton, QFileDialog, QCheckBox,
                             QFontDialog, QColorDialog,
                             )
from PIL import Image

class Thread_do(QThread):  # 定义线程类
    # 定义带参数一个信号及所需参数
    _signal =pyqtSignal(str)
    #定义几个成员,用于传递参数,包括:
    # 源文件夹,目标文件夹,保存格式,处理方式(约束宽度,约束高度,按比例)及参数(指定宽度,高度或缩放比例)
    _filepath=""
    _trgpath=""
    _fmt=""
    _mode=""
    _pa=""
    #重写类线程类的初步化方法,用于将传入的几个参数保存到类成员中
    def __init__(self,_listpara):
        super().__init__()
        self._filepath=_listpara[0]
        self._trgpath=_listpara[1]
        self._fmt=_listpara[2]
        self._mode=_listpara[3]
        self._pa=_listpara[4]

    def run(self):#线程的执行方法
        for root, dirs, files in os.walk(self._filepath):
            # root 表示当前访问的文件夹路径
            # dirs 表示该文件夹下的子文件夹名,本函数不涉及子文件夹,不用
            # files 表示该文件夹下的文件
            # 遍历文件
            for f in files:
                newtxtinfo = "正在处理:" + os.path.join(root, f) + "..."
                im = Image.open(os.path.join(root, f))
                iW, iH = im.width, im.height
                oW, oH = iW, iH
                if self._mode == 3:  # 如果是按指定比例缩放,根据参数计算目标文件的宽度和高度
                    oW = int(iW * int(self._pa) / 100)
                    oH = int(iH * int(self._pa) / 100)
                if self._mode == 1:  # 如果是按指定宽度,根据参数计算高度
                    oW = int(self._pa)
                    oH = int(int(self._pa) / iW * iH)
                if self._mode == 2:  # 如果是按指定高度,根据参数计算目标文件的宽度
                    oH = int(self._pa)
                    oW = int(int(self._pa) / iH * iW)

                om = im.resize((oW, oH))#生成目标文件
                #根据原始文件名和保存格式参数,得到目标文件名,如c:\aaa\bbb.jpg,保存到d:\ccc\bbb.png
                afname = os.path.join(self._trgpath, f)
                bfname = os.path.splitext(afname)[0]
                om.save(f"{bfname}.{self._fmt}")#保存目标文件
                newtxtinfo += "处理完成。"
                self._signal.emit(newtxtinfo)#返回信号

        #for d in dirs:对子件夹不处理。如需处理方法参考上面。
        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("c:/")  # 原始文件所在位置,默认为C:/
        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)
        # 第二行,一个标签,一个文件框,一个打开对话框按钮
        ######################################################
        self.txt_TrgPath = QLineEdit("c:/")  # 原始文件所在位置,默认为C:/
        btn_openDiagTrgPath = QPushButton("...")  # 选择源文件夹按钮
        # 绑定按钮的打开事件
        btn_openDiagTrgPath.clicked.connect(self.showDialog_Trg)
        hbox_line2.addWidget(QLabel("目标图片保存路径:"))
        hbox_line2.addWidget(self.txt_TrgPath)
        hbox_line2.addWidget(btn_openDiagTrgPath)
        # 第三行,一个标签,一个文件框,一个打开对话框按钮
        ######################################################
        # 定义图片格式下拉列表框
        self.lst_PicFmt = QComboBox()  # 定义下拉框,并添加三个图片格式选项
        self.lst_PicFmt.addItem("JPG")
        self.lst_PicFmt.addItem("PNG")
        self.lst_PicFmt.addItem("BMP")
        self.lst_PicFmt.setMaximumWidth(100)
        self.lst_PicFmt.setMinimumWidth(100)
        #定义三个单选按钮
        self.rbtnWidth = QRadioButton("指定宽定:")
        self.rbtnWidth.setObjectName("RW")
        self.rbtnHeight = QRadioButton("指定高度:")
        self.rbtnHeight.setObjectName("RH")
        self.rbtnScale = QRadioButton("按比例缩放:")
        self.rbtnScale.setObjectName("RS")
        self.rbtnScale.setChecked(True)
        # 定义调缩放比例的微调器,并设置其范围为10%-300%,默认值为100%,步长为5
        self.spn_sclBl = QSpinBox()
        self.spn_sclBl.setRange(10, 300)
        self.spn_sclBl.setValue(100)
        self.spn_sclBl.setSingleStep(5)
        self.spn_sclBl.setMinimumWidth(60)
        #定义两个文本框,有于指定图片的宽度或高度
        self.txt_sclWidth = QLineEdit("200")  # 指定缩放宽度
        self.txt_sclWidth.setMinimumWidth(60)
        self.txt_sclWidth.setMaximumWidth(60)
        self.txt_sclHeight = QLineEdit("160")  # 指定缩放高度
        self.txt_sclHeight.setMinimumWidth(60)
        self.txt_sclHeight.setMaximumWidth(60)
        # 默认为按比例缩放,把指定宽度和指定高度文本框设为不可用
        self.txt_sclWidth.setEnabled(False)
        self.txt_sclHeight.setEnabled(False)
        #设置三个单选框的点击事件
        self.rbtnWidth.toggled.connect(lambda: self.Click_rbtn(self.rbtnWidth))
        self.rbtnHeight.toggled.connect(lambda: self.Click_rbtn(self.rbtnHeight))
        self.rbtnScale.toggled.connect(lambda: self.Click_rbtn(self.rbtnScale))
        hbox_line3.addWidget(QLabel("保存图片格式:"))
        hbox_line3.addWidget(self.lst_PicFmt)
        hbox_line3.addWidget(QLabel("   缩放方式:"))
        hbox_line3.addWidget(self.rbtnScale)
        hbox_line3.addWidget(self.spn_sclBl)
        hbox_line3.addWidget(self.rbtnWidth)
        hbox_line3.addWidget(self.txt_sclWidth)
        hbox_line3.addWidget(self.rbtnHeight)
        hbox_line3.addWidget(self.txt_sclHeight)

        # 第四行: 一个多行文本框,用于显示处理进度及信息
        ######################################################
        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)及初始化位置(300,300)
        self.setGeometry(300, 300, 1024, 720)
        self.setWindowTitle('图片批处理工具')#设置标题
        self.show()

    def showDialog_Src(self):  # 选择源文件夹对话框,把选中的文件夹路径保存在txt_srcpath中
        folder_path = QFileDialog.getExistingDirectory(self, "选择原始图片文件夹")
        self.txt_SrcPath.setText(folder_path)


    def showDialog_Trg(self):  # 选择目标文件夹对话框,把选中的文件夹路径保存在txt_trgpath中
        folder_path = QFileDialog.getExistingDirectory(self, "选择保存图片文件夹")
        self.txt_TrgPath.setText(folder_path)


    def Click_rbtn(self, rbtn):  # 单选按钮点击事件,点击后根据其选中状态,隐藏其它单选按钮对应的控件
        if rbtn.objectName() == "RW" and rbtn.isChecked():  # 如果按宽度计算,让宽度文本框可用,其它两个不可用
            self.txt_sclWidth.setEnabled(True)
            self.txt_sclHeight.setEnabled(False)
            self.spn_sclBl.setEnabled(False)
        if rbtn.objectName() == "RH" and rbtn.isChecked():  # 如果按高度计算,让高度文本框可用,其它两个不可用
            self.txt_sclWidth.setEnabled(False)
            self.txt_sclHeight.setEnabled(True)
            self.spn_sclBl.setEnabled(False)
        if rbtn.objectName() == "RS" and rbtn.isChecked():  # 如按比例计算,让比例微调器可用,其它两个不可用
            self.txt_sclWidth.setEnabled(False)
            self.txt_sclHeight.setEnabled(False)
            self.spn_sclBl.setEnabled(True)


    def process_Pic(self):  # 图片处理按钮的点击事件
        # 取得参数,需要几个参数:1源路径2目标路径3保存格式4缩放模式5缩放参数
        # 参数形成一个列表,并列表作为参数传递给新线程
        # 取得参数
        list_para=[]
        list_para.append(self.txt_SrcPath.text())
        list_para.append(self.txt_TrgPath.text())
        list_para.append(self.lst_PicFmt.currentText())

        scaW = self.txt_sclWidth.text()
        scaH = self.txt_sclHeight.text()
        scaS = self.spn_sclBl.text()
        scamod = 3
        oPara = scaS
        if self.rbtnWidth.isChecked():
            scamod = 1
            oPara = scaW
        if self.rbtnHeight.isChecked():
            scamod = 2
            oPara = scaH
        list_para.append(scamod)
        list_para.append(oPara)
        # 在处理住处文本中显示相关设置信息
        curText = "原始图片路径:" + list_para[0] + "\n"
        curText += "目标图片路径:" + list_para[1] + "\n"
        curText += "保存格式为:" + list_para[2] + "\n"
        if scamod == 1:
            curText += "缩放方式:新图像宽度统一为【" + scaW + "】像素\n"
        if scamod == 2:
            curText += "缩放方式:新图像高度统一为【" + self.txt_sclHeight.text() + "】像素\n"
        if scamod == 3:
            curText += "缩放方式:按比例:新图像为原图的【" + self.spn_sclBl.text() + "%】\n"

        curText += "开始处理...\n"
        self.txt_PressInof.setText(curText)
        self.btn_do.setEnabled(False)
        self.btn_do.setText("正在处理...")
        # 定义线程实例并将参数传入
        self.thread_do = Thread_do(list_para)
        # 信号连接,如果收到信号,就执行对应的函数
        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_())

代码中的注释已经非常清楚,不再详细说明。

五、运行效果:

在这里插入图片描述

六、生成可执行文件:

可以使用PyInstaller组件来把python文件生成exe文件。如没有安装过Pyinstaller组年,可以在命令行下运行pip install pyinstaller来安装。
Pyinstaller安装位置与pip和pip3相同,不需要再指定path路径。
常用方法(都在命令行下使用)
1、Pyinstall xxxx.py把xxxx.py打包为可执行文件。打包后生成两个文件夹,一个build,为临时文件,完成后可删除,一个dist,里面为可执行文件及支持库文件。
注意:xxxx.py文件名中不能有小数点。
2、Pyinstaller -F xxxx.py:把xxxx.py打包为单独一个可执行文件,可执行文件中已经包括运行所需库文件。
在这里插入图片描述

七、源码下载地址:

https://download.csdn.net/download/hnkkfan/87928625?spm=1001.2014.3001.5503
包内包括py源码和生成的exe文件

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值