一步一步开发一套自己的批量图片压缩程序,单文件200多行代码就搞定了!

一、想法的产生

        今天的工作中遇到了批量压缩图片的需求,虽然某些"免费"的软件也有这个功能,但是导出的时候竟然要开会员...

        好吧,那我们就开发一套完全属于自己的图片批量压缩软件吧。

        (我是只讲干货的九滨云,如有想开发的软件,请留言给我,我会为大家讲解开发步骤)

二、软件开发要分步骤来实现。

        想要开发这样一个软件,我自己的思路是先实现核心程序,并且测试,如果核心程序能满足我的需求,再给它加上界面交互等,所以我把这次的开发分为了以下步骤:

1、写一个图片压缩程序

2、测试这个程序的结果能否满意

3、为这个程序添加界面

4、优化交互等

下面,咱们就一步一步来实现这些步骤

三、完成一个图片压缩程序

        先写了一个基本的图片压缩程序,思路是这样的:找到一个文件夹下所有的图片文件(目前只处理.png和.jpg),然后将其图片最大宽度设定为1080,最大大小设定为500KB,初始质量设置为95,如果超过此大小了,质量减少5继续压缩,直至符合要求。

代码如下:

import os
import glob
from PIL import Image

def compress_image(file_path, max_width, max_size_kb):
    # 打开图片
    with Image.open(file_path) as img:
        # 检查图片宽度
        if img.width > max_width:
            # 计算新的尺寸
            height = int(max_width * img.height / img.width)
            # 调整图片大小
            img = img.resize((max_width, height), Image.ANTIALIAS)

        # 保存图片,循环减少质量直到大小满足要求
        quality = 95  # 初始质量
        while True:
            # 使用BytesIO临时存储图片
            from io import BytesIO
            temp_buffer = BytesIO()
            img.save(temp_buffer, format='JPEG', quality=quality)
            size_kb = temp_buffer.tell() / 1024
            if size_kb <= max_size_kb or quality <= 10:
                break
            quality -= 5  # 减少质量

        # 使用新的质量设置保存图片
        img.save(file_path, 'JPEG', quality=quality)

def compress_images_in_dir(dir_path, max_width=1080, max_size_kb=500):
    # 遍历目录及其子目录中的所有jpg和png文件
    for file_path in glob.glob(dir_path + '/**/*.[jp][pn]g', recursive=True):
        compress_image(file_path, max_width, max_size_kb)
        print(f"Compressed: {file_path}")

# 使用示例
compress_images_in_dir('your_directory_path')

其中用到了Pillow库,没有的话需要安装:

pip install Pillow

运行的时候出现错误:

Traceback (most recent call last):
  File "C:\Users\admin\Desktop\zl\新建文件夹\p.py", line 40, in <module>
    compress_images_in_dir(current_directory)
  File "C:\Users\admin\Desktop\zl\新建文件夹\p.py", line 33, in compress_images_in_dir
    compress_image(file_path, max_width, max_size_kb)
  File "C:\Users\admin\Desktop\zl\新建文件夹\p.py", line 21, in compress_image
    img.save(temp_buffer, format='JPEG', quality=quality)
  File "C:\Users\admin\AppData\Roaming\Python\Python311\site-packages\PIL\Image.py", line 2432, in save
    save_handler(self, fp, filename)
  File "C:\Users\admin\AppData\Roaming\Python\Python311\site-packages\PIL\JpegImagePlugin.py", line 643, in _save
    raise OSError(msg) from e
OSError: cannot write mode RGBA as JPEG

这个错误信息是尝试将一个RGBA模式(即包含透明度通道的图片)保存为JPEG格式,但JPEG格式不支持透明度。为了解决这个问题,你需要在尝试保存图片之前将其转换为不包含透明度的模式,比如RGB。

修改代码如下:

def compress_image(file_path, max_width, max_size_kb):
    # 打开图片
    with Image.open(file_path) as img:
        # 如果是RGBA模式,则转换为RGB
        if img.mode == 'RGBA':
            img = img.convert('RGB')

        # 检查图片宽度
        if img.width > max_width:
            # 计算新的尺寸
            height = int(max_width * img.height / img.width)
            # 调整图片大小
            img = img.resize((max_width, height), Image.ANTIALIAS)

        # 保存图片,循环减少质量直到大小满足要求
        quality = 95  # 初始质量
        while True:
            # 使用BytesIO临时存储图片
            from io import BytesIO
            temp_buffer = BytesIO()
            img.save(temp_buffer, format='JPEG', quality=quality)
            size_kb = temp_buffer.tell() / 1024
            if size_kb <= max_size_kb or quality <= 10:
                break
            quality -= 5  # 减少质量

        # 使用新的质量设置保存图片
        img.save(file_path, 'JPEG', quality=quality)

运行时又报错:

Traceback (most recent call last):
  File "C:\Users\admin\AppData\Roaming\Python\Python311\site-packages\PIL\JpegImagePlugin.py", line 640, in _save
    rawmode = RAWMODE[im.mode]
              ~~~~~~~^^^^^^^^^
KeyError: 'P'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "C:\Users\admin\Desktop\zl\新建文件夹\p.py", line 45, in <module>
    compress_images_in_dir(current_directory)
  File "C:\Users\admin\Desktop\zl\新建文件夹\p.py", line 38, in compress_images_in_dir
    compress_image(file_path, max_width, max_size_kb)
  File "C:\Users\admin\Desktop\zl\新建文件夹\p.py", line 25, in compress_image
    img.save(temp_buffer, format='JPEG', quality=quality)
  File "C:\Users\admin\AppData\Roaming\Python\Python311\site-packages\PIL\Image.py", line 2432, in save
    save_handler(self, fp, filename)
  File "C:\Users\admin\AppData\Roaming\Python\Python311\site-packages\PIL\JpegImagePlugin.py", line 643, in _save
    raise OSError(msg) from e
OSError: cannot write mode P as JPEG

这个新的错误信息意思是:正在尝试将一张模式为'P'(调色板模式,Palette mode)的图片保存为JPEG格式。JPEG格式不支持调色板模式,因此你需要在保存之前将图片转换为标准的RGB或RGBA格式。

继续修改:

def compress_image(file_path, max_width, max_size_kb):
    # 打开图片
    with Image.open(file_path) as img:
        # 如果是RGBA或P模式,则转换为RGB
        if img.mode == 'RGBA' or img.mode == 'P':
            img = img.convert('RGB')

        # 检查图片宽度
        if img.width > max_width:
            # 计算新的尺寸
            height = int(max_width * img.height / img.width)
            # 调整图片大小
            img = img.resize((max_width, height), Image.ANTIALIAS)

        # 保存图片,循环减少质量直到大小满足要求
        quality = 95  # 初始质量
        while True:
            # 使用BytesIO临时存储图片
            from io import BytesIO
            temp_buffer = BytesIO()
            img.save(temp_buffer, format='JPEG', quality=quality)
            size_kb = temp_buffer.tell() / 1024
            if size_kb <= max_size_kb or quality <= 10:
                break
            quality -= 5  # 减少质量

        # 使用新的质量设置保存图片
        img.save(file_path, 'JPEG', quality=quality)

运行程序:

python p.py

运行完程序,压缩以后的图片能达到我的要求,然后开始进行下一步。

四、为程序添加界面

        要将Python脚本制作成一个桌面应用程序,需要使用PyQt5库。

pip install pyqt5

        做界面也需要从易到难,一步一步的实现所有的功能,首先做第一步:做一个简单的只有一个选择目录按钮的界面,选择完成后即运行图片压缩,代码如下:

import sys
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton, QLabel, QFileDialog
from PIL import Image
import os
import glob

# 图片压缩函数
def compress_image(file_path, max_width, max_size_kb):
    # 之前的压缩逻辑...

# 图片压缩类
class ImageCompressor(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        self.setWindowTitle('Image Compressor')
        self.setGeometry(300, 300, 300, 200)

        layout = QVBoxLayout()
        self.label = QLabel('选择一个目录来压缩图片')
        layout.addWidget(self.label)

        btn = QPushButton('选择目录', self)
        btn.clicked.connect(self.openDirectory)
        layout.addWidget(btn)

        self.setLayout(layout)

    def openDirectory(self):
        dir_path = QFileDialog.getExistingDirectory(self, "选择目录")
        if dir_path:
            self.compressImagesInDir(dir_path)
            self.label.setText(f'图片压缩完成: {dir_path}')

    def compressImagesInDir(self, dir_path, max_width=1080, max_size_kb=500):
        # 遍历目录及其子目录中的所有jpg和png文件
        for file_path in glob.glob(dir_path + '/**/*.[jp][pn]g', recursive=True):
            compress_image(file_path, max_width, max_size_kb)

# 主函数
def main():
    app = QApplication(sys.argv)
    ex = ImageCompressor()
    ex.show()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()

运行以后得到上面的界面,并且测试成功。

五、修正开发中遇到的“未响应”问题

        虽然上面的程序已经测试成功,但是在测试的过程中发现了一个问题,就是在选择的图片较多的情况下,程序会出现未响应的问题,好像是卡死状态,但其实只是处理图片程序占用了进程,导致主UI线程被阻塞。

        怎样解决这个问题呢?我们需要将图片处理过程放在一个单独的线程中执行,这样,主UI线程就不会被阻塞,用户界面仍然可以响应操作。同时,还可以添加一些界面元素来显示进度和状态信息。

        我们引入QThreadpyqtSignal来处理线程和信号,下面是修改后的代码:

import sys
import os
import glob
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton, QLabel, QFileDialog
from PyQt5.QtCore import QThread, pyqtSignal
from PIL import Image

# 图片压缩函数
def compress_image(file_path, max_width, max_size_kb):
    # 你之前的压缩逻辑...

# 图片压缩线程
class CompressThread(QThread):
    progress = pyqtSignal(str)

    def __init__(self, dir_path, max_width=1080, max_size_kb=500):
        QThread.__init__(self)
        self.dir_path = dir_path
        self.max_width = max_width
        self.max_size_kb = max_size_kb

    def run(self):
        for file_path in glob.glob(self.dir_path + '/**/*.[jp][pn]g', recursive=True):
            compress_image(file_path, self.max_width, self.max_size_kb)
            self.progress.emit(f"Compressed: {file_path}")
        self.progress.emit("Completed")

# 图片压缩类
class ImageCompressor(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        self.setWindowTitle('Image Compressor')
        self.setGeometry(300, 300, 300, 200)

        layout = QVBoxLayout()
        self.label = QLabel('选择一个目录来压缩图片')
        layout.addWidget(self.label)

        btn = QPushButton('选择目录', self)
        btn.clicked.connect(self.openDirectory)
        layout.addWidget(btn)

        self.setLayout(layout)

    def openDirectory(self):
        dir_path = QFileDialog.getExistingDirectory(self, "选择目录")
        if dir_path:
            self.thread = CompressThread(dir_path)
            self.thread.progress.connect(self.updateLabel)
            self.thread.start()

    def updateLabel(self, message):
        self.label.setText(message)

# 主函数
def main():
    app = QApplication(sys.argv)
    ex = ImageCompressor()
    ex.show()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()

        在这个示例中,CompressThread类继承自QThread。它在一个单独的线程中执行图片压缩任务,并通过progress信号发送状态消息。ImageCompressor类创建了一个界面,包括一个标签来显示消息和一个按钮来选择目录。当用户选择一个目录时,它会启动一个CompressThread来处理压缩任务,并更新界面上的标签以显示进度信息。

        这种方法将保证主UI保持响应,同时用户能够得到关于图片处理进度的实时反馈。

六、增加一些交互选项

        到目前为止,我们的程序已经可以使用了,但是有些参数是写死在程序中的,比如最大压缩文件大小是500KB,最大图片宽度是1080,这些参数在日常使用的过程中,常常是需要修改的,所以我们要增加软件的灵活度,把这些可修改的参数放到界面上来,能灵活的修改:

import sys
import os
import glob
from PyQt5.QtWidgets import (QApplication, QWidget, QVBoxLayout, QPushButton, QLabel,
                             QFileDialog, QComboBox, QLineEdit, QHBoxLayout, QCheckBox)
from PyQt5.QtCore import QThread, pyqtSignal
from PIL import Image

# 图片压缩函数
def compress_image(file_path, options):
    max_width, max_size_kb, output_format, keep_dimensions = options

    with Image.open(file_path) as img:
        if not keep_dimensions:
            if img.width > max_width:
                # 计算新的尺寸
                height = int(max_width * img.height / img.width)
                # 调整图片大小
                img = img.resize((max_width, height), Image.ANTIALIAS)

        # 输出格式
        img_format = output_format if output_format != '原格式' else img.format

        # 保存图片,循环减少质量直到大小满足要求
        if max_size_kb:
            quality = 95  # 初始质量
            while True:
                from io import BytesIO
                temp_buffer = BytesIO()
                img.save(temp_buffer, format=img_format, quality=quality)
                size_kb = temp_buffer.tell() / 1024
                if size_kb <= max_size_kb or quality <= 10:
                    break
                quality -= 5  # 减少质量
            img.save(file_path, img_format, quality=quality)
        else:
            img.save(file_path, img_format)

# 图片压缩线程
class CompressThread(QThread):
    progress = pyqtSignal(str)

    def __init__(self, dir_path, options):
        QThread.__init__(self)
        self.dir_path = dir_path
        self.options = options

    def run(self):
        for file_path in glob.glob(self.dir_path + '/**/*.[jp][pn]g', recursive=True):
            compress_image(file_path, self.options)
            self.progress.emit(f"Compressed: {file_path}")
        self.progress.emit("Completed")

# 图片压缩类
class ImageCompressor(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        self.setWindowTitle('Image Compressor')
        self.setGeometry(300, 300, 350, 250)

        layout = QVBoxLayout()

        # 格式选择
        format_layout = QHBoxLayout()
        self.formatCombo = QComboBox()
        self.formatCombo.addItems(['原格式', 'JPEG', 'PNG'])
        format_layout.addWidget(QLabel('输出格式:'))
        format_layout.addWidget(self.formatCombo)
        layout.addLayout(format_layout)

        # 尺寸输入
        size_layout = QHBoxLayout()
        self.widthInput = QLineEdit('1080')
        self.keepDimensions = QCheckBox('保持原尺寸')
        size_layout.addWidget(QLabel('最大宽度:'))
        size_layout.addWidget(self.widthInput)
        size_layout.addWidget(self.keepDimensions)
        layout.addLayout(size_layout)

        # 文件大小输入
        size_kb_layout = QHBoxLayout()
        self.sizeInput = QLineEdit('500')
        self.noSizeLimit = QCheckBox('不限制大小')
        size_kb_layout.addWidget(QLabel('最大大小(KB):'))
        size_kb_layout.addWidget(self.sizeInput)
        size_kb_layout.addWidget(self.noSizeLimit)
        layout.addLayout(size_kb_layout)

        # 目录选择
        self.label = QLabel('选择一个目录来压缩图片')
        layout.addWidget(self.label)

        btn = QPushButton('选择目录', self)
        btn.clicked.connect(self.openDirectory)
        layout.addWidget(btn)

        self.setLayout(layout)

    def openDirectory(self):
        dir_path = QFileDialog.getExistingDirectory(self, "选择目录")
        if dir_path:
            options = (
                int(self.widthInput.text()),
                None if self.noSizeLimit.isChecked() else int(self.sizeInput.text()),
                self.formatCombo.currentText(),
                self.keepDimensions.isChecked()
            )
            self.thread = CompressThread(dir_path, options)
            self.thread

界面如下:

七、完善交互

        测试以上的代码功能,发现几个问题:

1、点击保持原尺寸或者不限制大小的时候,前面输入框不变灰(不可用),交互上有些不明显 。

2、选择目录以后就开始执行操作了,不太符合大众习惯。

3、压缩好的图片直接覆盖了源图片,而且没有提示。

4、如果图片较多,处理速度慢。

        为了解决以上的问题,决定这样来修改:

1、增加一个选择输出目录的按钮,把压缩后的图片保存在输出目录中。

2、增加一个【开始压缩】按钮,点击才开始压缩。

3、使用多线程处理图片,如果图片符合宽度不大于最大值及大小不大于最大值,则直接复制不做处理。

4、交互中的一些问题,比如在未选好源目录和目标目录的时候,【开始压缩】按钮不能点击,【开始压缩】按钮点击后,其他控件变成灰色(不可用状态),处理完成后在恢复等等

根据以上的思路和想法,完善了代码,整体代码如下:

import sys
import os
import glob
from PyQt5.QtWidgets import (QApplication, QWidget, QVBoxLayout, QPushButton, QLabel,
                             QFileDialog, QComboBox, QLineEdit, QHBoxLayout, QCheckBox)
from PyQt5.QtCore import QRunnable, QThreadPool, pyqtSignal, QObject
from PIL import Image
import shutil

# 图片压缩函数
def compress_image(input_file_path, output_file_path, options):
    max_width, max_size_kb, output_format, keep_dimensions, _ = options

    with Image.open(input_file_path) as img:
        # 如果图片已符合要求,则将其复制到输出目录
        if (keep_dimensions or img.width <= max_width) and os.path.getsize(input_file_path) / 1024 <= max_size_kb:
            shutil.copyfile(input_file_path, output_file_path)
            return

        # 计算新尺寸并调整图片大小,如果不保持原尺寸
        if not keep_dimensions and img.width > max_width:
            height = int(max_width * img.height / img.width)
            img = img.resize((max_width, height), Image.Resampling.LANCZOS)

        # 确定输出格式
        if output_format == '原格式':
            img_format = img.format
            if not img_format:
                img_format = 'JPEG'  # 默认格式,如果原格式无法确定
            save_path = output_file_path
        else:
            img_format = 'JPEG' if output_format == 'JPEG' else 'PNG'
            save_ext = '.jpg' if output_format == 'JPEG' else '.png'
            save_path = os.path.splitext(file_path)[0] + save_ext


        # 转换RGBA为RGB(如果需要)
        if img.mode == 'RGBA' and img_format == 'JPEG':
            img = img.convert('RGB')

        # 保存图片,循环减少质量直到大小满足要求
        if max_size_kb:
            quality = 95  # 初始质量
            while True:
                from io import BytesIO
                temp_buffer = BytesIO()
                img.save(temp_buffer, format=img_format, quality=quality)
                size_kb = temp_buffer.tell() / 1024
                if size_kb <= max_size_kb or quality <= 10:
                    break
                quality -= 5  # 减少质量
            img.save(output_file_path, img_format, quality=quality)
        else:
            img.save(output_file_path, img_format)

class CompressTask(QRunnable):
    def __init__(self, input_path, output_path, options, callback):
        super().__init__()
        self.input_path = input_path
        self.output_path = output_path
        self.options = options
        self.callback = callback

    def run(self):
        compress_image(self.input_path, self.output_path, self.options)
        self.callback.emit(self.input_path)  # 发送信号更新UI

class WorkerSignals(QObject):
    # 定义一个用于更新UI的信号
    finished = pyqtSignal(str)


# 图片压缩类
class ImageCompressor(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()
        self.threadpool = QThreadPool()        
        self.tasksCompleted = 0  # 用于跟踪完成的任务数量
        self.totalTasks = 0  # 总任务数量
        self.source_dir_path = ''  # 初始化源目录路径为空
        self.output_dir_path = ''  # 初始化输出目录路径为空

    def initUI(self):
        self.setWindowTitle('Image Compressor')
        self.setGeometry(300, 300, 350, 250)

        layout = QVBoxLayout()

        # 格式选择
        format_layout = QHBoxLayout()
        self.formatCombo = QComboBox()
        self.formatCombo.addItems(['原格式', 'JPEG', 'PNG'])
        format_layout.addWidget(QLabel('输出格式:'))
        format_layout.addWidget(self.formatCombo)
        layout.addLayout(format_layout)

        # 尺寸输入
        size_layout = QHBoxLayout()
        self.widthInput = QLineEdit('1080')
        self.keepDimensions = QCheckBox('保持原尺寸')
        self.keepDimensions.stateChanged.connect(self.toggleWidthInput)
        size_layout.addWidget(QLabel('最大宽度:'))
        size_layout.addWidget(self.widthInput)
        size_layout.addWidget(self.keepDimensions)
        layout.addLayout(size_layout)

        # 文件大小输入
        size_kb_layout = QHBoxLayout()
        self.sizeInput = QLineEdit('500')
        self.noSizeLimit = QCheckBox('不限制大小')
        self.noSizeLimit.stateChanged.connect(self.toggleSizeInput)
        size_kb_layout.addWidget(QLabel('最大大小(KB):'))
        size_kb_layout.addWidget(self.sizeInput)
        size_kb_layout.addWidget(self.noSizeLimit)
        layout.addLayout(size_kb_layout)

        # 信息显示
        self.label = QLabel('选择要压缩图片的源目录和输出目录')
        self.label.setStyleSheet("color: red")
        layout.addWidget(self.label)

        # 源目录选择
        self.sourceDirLabel = QLabel('选择一个源目录来压缩图片')
        layout.addWidget(self.sourceDirLabel)

        self.btnSelectSourceDirectory = QPushButton('选择源目录', self)
        self.btnSelectSourceDirectory.clicked.connect(self.openSourceDirectory)
        layout.addWidget(self.btnSelectSourceDirectory)

        # 输出目录选择和显示
        self.outputDirLabel = QLabel('选择一个输出目录来保存图片')
        layout.addWidget(self.outputDirLabel)

        self.btnSelectOutputDirectory = QPushButton('选择输出目录', self)
        self.btnSelectOutputDirectory.clicked.connect(self.openOutputDirectory)
        layout.addWidget(self.btnSelectOutputDirectory)

        # 开始压缩按钮
        self.btnStartCompress = QPushButton('开始压缩', self)
        self.btnStartCompress.clicked.connect(self.startCompression)
        self.btnStartCompress.setEnabled(False)  # 初始时禁用
        layout.addWidget(self.btnStartCompress)


        self.setLayout(layout)

    def toggleWidthInput(self, state):
        self.widthInput.setEnabled(state == 0)

    def toggleSizeInput(self, state):
        self.sizeInput.setEnabled(state == 0)

    def openDirectory(self):
        self.dir_path = QFileDialog.getExistingDirectory(self, "选择目录")
        if self.dir_path:
            self.label.setText(f'选中目录: {self.dir_path}')
            self.btnStartCompress.setEnabled(True)  # 启用开始压缩按钮
            
    def openSourceDirectory(self):
        self.source_dir_path = QFileDialog.getExistingDirectory(self, "选择源目录")
        if self.source_dir_path:
            self.sourceDirLabel.setText(f'选中源目录: {self.source_dir_path}')
            self.validateDirectories()

    def openOutputDirectory(self):
        self.output_dir_path = QFileDialog.getExistingDirectory(self, "选择输出目录")
        if self.output_dir_path:
            self.outputDirLabel.setText(f'选中输出目录: {self.output_dir_path}')
            self.validateDirectories()

    def validateDirectories(self):
        if self.source_dir_path and self.output_dir_path and self.source_dir_path != self.output_dir_path:
            self.btnStartCompress.setEnabled(True)
        else:
            self.btnStartCompress.setEnabled(False)


    def startCompression(self):
        if not self.source_dir_path or not self.output_dir_path:
            self.label.setText('未选择目录,必须选择源目录及输出目录!')
            return

        # 禁用所有控件
        self.disableWidgets(True)

        # 读取压缩选项
        options = (
            int(self.widthInput.text()) if not self.keepDimensions.isChecked() else None,
            None if self.noSizeLimit.isChecked() else int(self.sizeInput.text()),
            self.formatCombo.currentText(),
            self.keepDimensions.isChecked(),
            self.output_dir_path  # 请确保这是正确的参数
        )

        # 创建WorkerSignals实例
        self.signals = WorkerSignals()
        self.signals.finished.connect(self.updateLabel)  # 连接信号

        # 获取所有图像文件路径
        file_paths = glob.glob(self.source_dir_path + '/**/*.[jp][pn]g', recursive=True)

        
        self.tasksCompleted = 0  # 重置计数器
        self.totalTasks = len(file_paths)  # 设置总任务数量

        for file_path in file_paths:
            rel_path = os.path.relpath(file_path, self.source_dir_path)
            output_file_path = os.path.join(self.output_dir_path, rel_path)
            os.makedirs(os.path.dirname(output_file_path), exist_ok=True)

            # 创建并启动压缩任务
            task = CompressTask(file_path, output_file_path, options, self.signals.finished)
            self.threadpool.start(task)

    def updateLabel(self, message):
        # 更新UI,显示当前处理的文件
        self.label.setText(f"Compressed: {message}")
        self.tasksCompleted += 1  # 更新完成的任务数量
        if self.tasksCompleted == self.totalTasks:
            self.label.setText('所有图片压缩完成')
            self.disableWidgets(False)  # 重新启用控件

    def processImage(self, input_path, output_path, options):
        compress_image(input_path, output_path, options)
        self.updateUI("Compressed: " + input_path)

    def updateUI(self, message):
        # 确保UI更新在主线程中执行
        if threading.current_thread() is threading.main_thread():
            self.label.setText(message)
        else:
            self.label.setText('压缩完成')
            self.disableWidgets(False)

    def onCompressionFinished(self):
        self.disableWidgets(False)  # 重新启用控件
        self.dir_path = ''  # 清空选择的目录
        self.btnStartCompress.setEnabled(False)  # 禁用开始压缩按钮    

    def disableWidgets(self, disable):
        # 禁用或启用所有控件
        self.formatCombo.setDisabled(disable)
        self.widthInput.setDisabled(disable)
        self.keepDimensions.setDisabled(disable)
        self.sizeInput.setDisabled(disable)
        self.noSizeLimit.setDisabled(disable)
        self.btnStartCompress.setDisabled(disable)
        self.btnSelectSourceDirectory.setDisabled(disable)
        self.btnSelectOutputDirectory.setDisabled(disable)


# 主函数
def main():
    app = QApplication(sys.argv)
    ex = ImageCompressor()
    ex.show()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()

界面如下:

经测试,各方面都能达到满意的效果。

八、美工及其他功能的添加

        其实到上一步,从功能上已经满足了我的需求,但是如果想作为通用软件进行发布,还需要美化一下,毕竟人是视觉动物,这需要美工来设计,我这里就不继续进行了。

        如果想继续添加其他的功能,也可以在这个基础上继续开发。

九、打包

        我们可以利用pyinstaller将开发好的代码打包成Windows可执行文件(.exe),步骤如下:

        1、安装pyinstaller

pip install pyinstaller

        2、在您的Python脚本所在的目录中打开命令行,然后运行以下命令:

pyinstaller --onefile --windowed your_script_name.py

这里your_script_name.py是您的Python脚本文件名。使用--onefile选项会创建一个单独的可执行文件。

出现Building EXE from EXE-00.toc completed successfully.后,即打包成功:

打包生成的文件会在Python脚本所在的目录下的dist目录下找到,可以脱离python环境运行的纯绿色软件。

十、后记

        以上就是从有一个想法开始,到实际开发完成并打包一套软件的方法,我习惯用这种循序渐进的方法来开发,开发到每一步都可以进行测试,如果能达到自己满意再往下走。对于大型的软件,也是这样的开发流程,只是需要先把软件分为模块,按模块进行开发,高内聚低耦合。

        希望以上的内容能给大家带来帮助,谢谢!如果有需要开发的程序,可以评论或私信我。

  • 23
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

九滨云

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值