开发者

基于Python自制一个图片批量处理工具实现格式统一和大小压缩

目录
  • 工具核心功能
  • 环境准备
    • 1. 基础环境
    • 2. 依赖安装
    • 完整代码(可直接复制运行)
  • 使用教程(超简单!)

    在日常工作和生活中,我们经常需要处理大量图片——比如电商商品图需要统一正方形尺寸、公众号配图要控制在3MB以内、多格式图片需转为JPG统一管理。手动处理几十上百张图效率极低,今天就给大家分享一款自制的python图片批量处理工具,无需复杂操作,图形化界面一键搞定所有需求!

    工具核心功能

    这款工具专为批量图片处理设计,整合了4个高频需求,彻底解放双手:

    • 正方形转换:自动将任意尺寸图片转为正方形,短边两侧填充白色背景,避免拉伸变形
    • 格式统一:支持PNG、JPG、BMP、GIF、WebP等多种格式,统一输出为JPG格式
    • 大小压缩:智能控制图片大小在3MB以内,自动平衡质量与体积,避免失真
    • 安全处理:原图保持不变,处理后的图片自动保存到新文件夹,杜绝误操作

    环境准备

    1. 基础环境

    • Python 3.6 及以上版本(推荐3.8+,兼容性更好)
    • tkinter 库(Python标准库,默认已预装,无需额外安装)

    2. 依赖安装

    核心依赖仅需Pillow库(Python图像处理神器),打开命令行执行以下命令:

    pip install pillow
    

    如果需要批量安装,也可以创建requirements.txt文件,写入以下内容后执行pip install -r requirements.txt

    pillow>=9.0.0
    

    完整代码(可直接复制运行)

    import os
    import sys
    from PIL import Image
    import time
    from io import BytesIO
    import tkinter as tk
    from tkinter import filedialog, ttk, messagebox
    import threading
    import queue
    
    # 打包命令:pyinstaller --noconsole --onefile --icon=icon.ico --name="图片批量处理工具" image_processor.py
    class ImageProcessor:
        def __init__(self):
            self.window = tk.Tk()
            self.window.title("图片批量处理工具")
            # 设置窗口大小和位置(居中显示)
            window_width = 800
            window_height = 600
            screen_width = self.window.winfo_screenwidth()
            screen_height = self.window.winfo_screenheight()
            x = (screen_width - window_width) // 2
            y = (screen_height - window_height) // 2
            self.window.geometry(f"{window_width}x{window_height}+{x}+{y}")
            self.window.resizable(False, False)
            
            # 界面样式配置(现代简洁风格)
            self.style = ttk.Style()
            self.style.theme_use('clam')
            self.style.configure("TButton", padding=10, font=('微软雅黑', 10))
            self.style.configure("TLabel", font=('微软雅黑', 10))
            self.style.configure("Header.TLabel", font=('微软雅黑', 16, 'bold'))
            self.style.configure("Info.TLabel", font=('微软雅黑', 9))
            self.style.configure("Custom.TButton", padding=10, font=('微软雅黑', 10, 'bold'))
            
            # 线程通信队列(避免界面卡顿)
            self.message_queue = queue.Queue()
            
            self.create_widgets()
            self.update_messages()
        
        def create_widgets(self):
            # 主框架(统一内边距,布局更整洁)
            main_frame = ttk.Frame(self.window, padding="20")
            main_frame.pack(fill=tk.BOTH, expand=True)
            
            # 标题区域
            title_frame = ttk.Frame(main_frame)
            title_frame.pack(fill=tk.X, pady=(0, 20))
            title_label = ttk.Label(title_frame, text="图片批量处理工具",)
            title_label.pack(side=tk.LEFT)
            
            # 功能说明区域
            features_frame = ttk.LabelFrame(main_frame, text="功能说明", padding="10")
            features_frame.pack(fill=tk.X, pady=(0, 20))
            features_text = """本工具提供以下功能:
    • 将图片转换为正方形(短边两侧填充白色)
    • 统一转换为JPG格式,保证最佳图片质量
    • 智能控制图片大小在3MB以下
    • 自动创建输出文件夹,原图保持不变"""
            features_label = ttk.Label(features_frame, text=features_text, justify=tk.LEFT)
            features_label.pack(pady=5)
            
            # 文件夹选择区域
            folder_frame = ttk.LabelFrame(main_frame, text="选择文件夹", padding="10")
            folder_frame.pack(fill=tk.X, pady=(0, 20))
            self.folder_path = tk.StringVar()
            folder_entry = tjstk.Entry(folder_frame, textvariable=self.folder_path, width=70)
            folder_entry.pack(side=tk.LEFT, padx=(0, 10), fill=tk.X, expand=True)
            folder_button = ttk.Button(folder_frame, text="浏览...", command=self.select_folder,)
            folder_button.pack(side=tk.LEFT)
            
            # 操作按钮区域
            control_frame = ttk.Frame(main_frame)
            control_frame.pack(fill=tk.X, pady=(0, 10))
            self.process_button = ttk.Button(control_frame, text="开始处理", 
                                           command=self.start_processing, 
                                          )
            self.process_button.pack(pady=10)
            
            # 进度显示区域
            progress_frame = ttk.LabelFrame(main_frame, text="处理进度", padding="10")
            progress_frame.pack(fill=tk.X, pady=(0, 20))
            self.progress_var = tk.DoubleVar()
            self.progress_bar = ttk.Progressbar(progress_frame, 
                                              variable=self.progress_var, 
                                              maximum=100,
                                              length=300)
            self.progress_bar.pack(fill=tk.X, pady=(5, 10))
            self.progress_label = ttk.Label(progress_frame, text="等待开始...",)
            self.progress_label.pack()
            
            # 处理详情日志区域
            log_frame = ttk.LabelFrame(main_frame, text="处理详情", padding="10")
            log_frame.pack(fill=tk.BOTH, expand=True)
            text_frame = ttk.Frame(log_frame)
            text_frame.pack(fill=tk.BOTH, expand=True)
            self.log_text = tk.Text(text_frame, height=10, width=70, 
                                   font=('微软雅黑', 9),
                                   wrap=tk.WORD)
            self.log_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
            scrollbar = ttk.Scrollbar(text_frame, orient=tk.VERTICAL, 
                                    command=self.log_text.yview)
            scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
            self.log_text.configure(yscrollcommand=scrollbar.set)
            
            # 状态栏
            self.status_label = ttk.Label(main_frame, text="就绪",)
            self.status_label.pack(pady=(10, 0))
        
        def select_folder(self):
            # 选择文件夹并更新路径
            folder_path = filedialog.askdirectory(title="选择包含图片的文件夹")
            if folder_path:
                self.folder_path.set(folder_path)
                self.log_message(f"已选择文件夹: {folder_path}")
                self.status_label.config(text="已选择文件夹,可以开始处理")
        
        def log_message(self, message):
            # 日志信息入队(线程安全)
            self.message_queue.put(message)
        
        def update_messages(self):
            # 实时更新日志显示
            try:
                while True:
                    message = self.message_queue.get_nowait()
                    self.log_text.insert(tk.END, message + "\n")
                    self.log_text.see(tk.END)
                    self.log_text.update()
            except queue.Empty:
                pass
            self.window.after(100, self.update_messages)
        
        def start_processing(self):
            # 预处理校验(避免无效操作)
            folder_path = self.folder_path.get()
            if not folder_path:
                messagebox.showerror("错误", "请先选择要处理的文件夹!")
                return
            if not os.path.exists(folder_path):
                messagebox.showerror("错误", "选择的文件夹不存在!")
                return
            
            # 禁用按钮防止重复点击
            self.process_button.state(['disabled'])
            self.status_label.config(text="正在处理中...")
            self.progress_label.config(text="正在准备处理...")
            
            # 开启子线程处理图片(避免界面冻结)
            thread = threading.Thread(target=self.process_images_thread, args=(folder_path,))
            thread.daemon = True
            thread.start()
        
        def process_images_thread(self, input_folder):
            try:
                # 支持的图片格式(覆盖主流格式)
                supported_formats = ['.png', '.jpeg', '.jpg', '.bmp', '.gif', '.webp']
                
                # 创建输出文件夹(原文件夹名+“调整”)
                dir_name = os.path.basename(input_folder)
                output_dir_name = dir_name + "调整"
                parent_dir = os.path.dirname(input_folder)
                output_dir = os.path.join(parent_dir, output_dir_name)
                try:
                    os.makedirs(output_dir, exist_ok=True)
                except Exception as e:
                    self.log_message(f"创建输出文件夹失败:{str(e)}")
                    messagebox.showerror("错误", f"创建输出文件夹失败:{str(e)}")
                    return
                
                # 筛选图片文件
                image_files = [f for f in os.listdir(input_folder) 
                             if any(f.lower().endswith(fmt) for fmt in supported_formats)]
                if not image_files:
                    self.log_message("没有找到支持的图片文件!")
                    messagebox.showinfo("提示", "文件夹中没有找到支持的图片文件!")
                    return
                
                # 初始化统计参数
                total_files = len(image_files)
                processed_files = 0
                success_count = 0
                error_count = 0
                size_reduced_count = 0
                total_size_saved = 0
                
                # 日志初始化
                self.log_message(f"\n开始处理图片...")
                self.log_message(f"输入目录: {input_folder}")
                self.log_message(f"输出目录: {output_dir}\n")
                self.log_message(f"共发现 {total_files} 个图片文件\n")
                
                # 批量处理图片
                for filename in image_files:
                    file_path = os.path.join(input_folder, filename)
                    try:
                        # 更新进度
                        processed_files += 1
                        progress = (processed_files / total_files) * 100
                        self.progress_var.set(progress)
                        self.progress_label.config(编程
                            text=f"正在处理: {filename} ({processed_files}/{total_files})"
                        )
                        
                        # 打开图片并处理
                        with Image.open(file_path) as img:
                            # 保留EXIF信息(避免图片方向异常)
                            exif = img.info.get('exif')
                            
                            # 处理透明背景(PNG转JPG时填充白色)
                            if img.mode in ('RGBA', 'P'):
                                background = Image.new('RGB', img.size, 'white')
                                if img.mode == 'P':
                                    img = img.convert('RGBA')
                                background.paste(img, mask=img.split()[3] if img.mode == 'RGBA' else None)
                                img = background
                            
                            # 转为正方形
                            square_img = self.convert_to_square(img)
                            if square_img is None:
                                continue
                            
                            # 生成输出路径
                            new_filename = os.path.splitext(filename)[0] + '.jpg'
                            new_path = os.path.join(output_dir, new_filename)
                            
                            # 计算大小变化
                            original_size = os.path.getsize(file_path) / (1024 * 1024)
                            
                            # 优化图片大小(控制在3MB内)
                            quality = self.optimize_image_size(square_img)
                            
                            # 保存图片
                            save_args = {'format': 'JPEG', 'quality': quality}
                            if exif:
                                save_args['exif'] = exif
                            square_img.save(new_path, **save_args)
                            
                            # 更新统计信息
                            new_size = os.path.getsize(new_path) / (1024 * 1024)
                            success_count += 1
                            if new_size < original_size:
                                size_reduced_count += 1
                                size_reduction = original_size - new_size
                                total_size_saved += size_reduction
                                self.log_message(f"✓ 已处理: {filename} -> {new_filename}")
                                self.log_message(
                                    f"  大小: {original_size:.1f}MB → {new_size:.1f}MB "
                                    f"(节省 {size_reduction:.1f}MB)"
                                )
                            else:
                                self.log_message(f"✓ 已处理: {filename} -> {new_filename}")
                                self.log_message(f"  大小: {new_size:.1f}MB")
                    
                    except Exception as e:
                        error_count += 1
                        self.log_message(f"✗ 处理 {filename} 时出错: {str(e)}编程客栈")
                
                # 输出处理总结
                summary = f"""\n处理完成!
    ----------------------------------------
    成功处理:{success_count} 个文件
    处理失败:{error_count} 个文件
    文件大小优化:{size_reduced_count} 个文件
    总共节省空间:{total_size_saved:.1f}MB
    输出文件夹:{output_dir}
    ----------------------------------------"""
                self.log_message(summary)
                
                # 弹窗提示结果
                if success_count > 0:
                    messagebox.showinfo("完成", 
                        f"图片处理完成!\n成功:{success_count} 个\n失败:{error_count} 个")
                else:
                    messagebox.showwarning("警告", "没有成功处理任何文件!")
            
            except Exception as e:
                self.log_message(f"处理过程出错:{str(e)}")
                messagebox.showerror("错误", f"处理过程出错:{str(e)}")
            
            finally:
                # 恢复界面状态
                self.process_button.state(['!disabled'])
                self.progress_var.set(0)
                self.progress_label.config(text="处理完成")
                self.status_label.config(text="就绪")
        
        def convert_to_square(self, image):
            """将图片转为正方形,最大尺寸限制800x800(避免过大)"""
            try:
                width, height = image.size
                max_size = max(width, height)
                
                # 缩放过大图片
                if max_size > 800:
                    scale = 800 / max_size
                    new_width = int(width * scale)
                    new_height = int(height * scale)
                    image = image.resize((new_width, new_height), Image.LANCZOS)  # 高质量缩放
                    width, height = image.size
                    max_size = max(width, height)
                
                # 创建白色正方形背景
                square_img = Image.new('RGB', (max_size, max_size), 'white')
                
                # 居中粘贴原图
                paste_x = (max_size - width) // 2 if height > width else 0
                paste_y = (max_size - height) // 2 if width > height else 0
                square_img.paste(image, (paste_x, paste_y))
                return square_img
            except Exception as e:
                self.log_message(f"转换图片时出错:{str(e)}")
                return None
        
        def get_file_size(self, img, quality):
            """计算指定质量下的图片大小(字节)"""
            buffer = BytesIO()
            img.save(buffer, format='JPEG', quality=quality)
            size = buffer.tell()
            buffer.close()
            return size
        
        def optimize_image_size(self, img, max_size_mb=2.9):
            """二分查找最优质量,确保图片<3MB(留0.1MB冗余)"""
            max_size_bytes = int(max_size_mb * 1024 * 1024)
            quality = 95
            current_size = self.get_file_size(img, quality)
            
            # 原图已达标,直接返回高质量
            if current_size <= max_size_bytes:
                return quality
            
            # 二分查找最优质量
            min_quality = 5
            max_quality = 95
            best_quality = max_quality
            while min_quality <= max_quality:
                quality = (min_quality + max_quality) // 2
                current_size = self.get_file_size(img, quality)
                if current_size > max_size_bytes:
                    max_quality = quality - 1
                else:
             编程       best_quality = quality
                    min_quality = quality + 1
            
            # 最终校验(确保不超标)
            final_size = self.get_file_size(img, best_quality)
            while final_size > max_size_bytes and best_quality > 5:
                best_quality -= 1
                final_size = self.get_file_size(img, best_quality)
            
            return best_quality
        
        def run(self):
            # 启动GUI
            self.window.mainloop()
    
    def main():
        app = ImageProcessor()
        app.run()
    
    if __name__ == '__main__':
        main()
    

    使用教程(超简单!)

    运行程序

    将上述代码保存python为`image_processor.py

    到此这篇关于基于Python自制一个图片批量处理工具实现格式统一和大小压缩的文章就介绍到这了,更多相关Python图片批量处理内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!

    0

    上一篇:

    下一篇:

    精彩评论

    暂无评论...
    验证码 换一张
    取 消

    最新开发

    开发排行榜