修改图片分辨率的工具Python源码

修改图片分辨率的工具Python源码

一、项目背景

在日常工作尤其是素材处理过程中,我常常遇到需要批量调整图片分辨率的场景。虽然可以使用 PhotoShop 等专业工具,但每次手动调整多张图片、统一输出尺寸时,步骤繁琐且容易出错,尤其是需要保持宽高比时,PS中的操作往往无法做到快速精准的批量匹配。

为了提升效率,我用 Python 开发了一个带图形界面的 “图片分辨率批量修改工具”,支持一键导入多张图片、自定义输出尺寸、保持原始宽高比,并能实时预览图片信息。代码完全开源,无任何依赖限制,适合日常办公与学习使用。

图片[1]-修改图片分辨率的工具Python源码-QQ沐编程

二、主要功能特点

  • 📁 批量导入:支持一次性导入多张图片,自动识别常见格式(JPG、PNG、BMP、GIF、WebP 等)
  • 🖼 尺寸灵活设置:可自由设定目标宽度与高度,并可选“保持原始宽高比”防止图片变形
  • 📊 图片信息预览:列表显示每张图片的文件名、原始尺寸与格式
  • 📂 输出路径自定义:可指定保存目录,自动避免文件覆盖
  • 🚀 一键处理与重置:提供开始、重置、退出等操作按钮,界面清晰易用
  • 🖥 纯 Python 实现:基于 tkinter 构建图形界面,使用 PIL(Pillow) 进行图像处理,跨平台运行

三、代码结构与技术实现

工具采用面向对象的方式设计,主要分为界面构建与图像处理两大模块。

1. 界面布局(tkinter

  • 标题与主框架:设定窗口标题、尺寸与背景色
  • 控制面板:包含分辨率设置、导入/导出区域、操作按钮
  • 图片列表:使用 ttk.Treeview 表格展示已导入图片的详细信息

2. 核心功能方法

  • import_images():通过文件对话框选择图片,支持多选与格式过滤
  • refresh_image_list():使用 PIL 读取图片尺寸与格式,刷新列表显示
  • start_processing()
  • 根据是否勾选“保持宽高比”,按比例或强制拉伸调整尺寸
  • 使用 LANCZOS 重采样算法,保证缩放质量
  • 自动为重复文件名添加后缀,避免覆盖
  • reset():清空所有设置与列表,恢复初始状态

3. 依赖库

  • tkinter:Python 标准 GUI 库,无需安装
  • PIL(Pillow):图像处理库,需通过 pip install Pillow 安装

四、使用说明

1. 环境准备

pip install Pillow

2. 运行程序

将下方完整代码保存为 image_resizer.py,双击运行或通过命令行启动:

python image_resizer.py

3. 操作步骤

  1. 点击 “导入图片”,选择一张或多张图片
  2. 设置目标 宽度高度(单位:像素)
  3. 如需保持图片原比例,请勾选 “保持原始宽高比”
  4. 点击 “选择输出路径” 指定保存位置(可选,默认为弹窗选择)
  5. 点击 “开始处理”,程序将批量调整尺寸并保存至目标文件夹
  6. 可使用 “重置” 清空当前任务,或 “退出” 关闭程序

五、完整源代码

import tkinter as tk
from tkinter import ttk, filedialog, messagebox
from PIL import Image
import os

class ImageResizerApp:
    def __init__(self, root):
        self.root = root
        self.root.title("图片分辨率修改工具")
        self.root.geometry("800x650")
        self.root.configure(bg="#f0f0f0")

        self.image_paths = []
        self.target_width = tk.IntVar(value=800)
        self.target_height = tk.IntVar(value=600)
        self.keep_aspect_ratio = tk.BooleanVar(value=True)
        self.output_path = ""

        self.setup_ui()

    def setup_ui(self):
        # 标题
        title_label = tk.Label(
            self.root,
            text="图片分辨率修改工具",
            font=("Arial", 18, "bold"),
            bg="#f0f0f0",
            fg="#333"
        )
        title_label.pack(pady=15)

        # 主内容区域
        main_frame = tk.Frame(self.root, bg="#f0f0f0")
        main_frame.pack(fill=tk.BOTH, expand=True, padx=20, pady=10)

        # 左侧控制面板
        control_frame = tk.LabelFrame(
            main_frame,
            text="控制面板",
            font=("Arial", 12),
            bg="#f0f0f0",
            fg="#333",
            padx=10,
            pady=10
        )
        control_frame.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 10))

        # 分辨率设置区域
        resolution_frame = tk.LabelFrame(
            control_frame,
            text="分辨率设置",
            font=("Arial", 10),
            bg="#f0f0f0",
            fg="#333"
        )
        resolution_frame.pack(fill=tk.X, pady=(0, 15))

        # 宽度设置
        width_frame = tk.Frame(resolution_frame, bg="#f0f0f0")
        width_frame.pack(fill=tk.X, pady=5)
        tk.Label(width_frame, text="宽度:", font=("Arial", 10), bg="#f0f0f0").pack(side=tk.LEFT)
        width_entry = tk.Entry(
            width_frame,
            textvariable=self.target_width,
            font=("Arial", 10),
            width=10
        )
        width_entry.pack(side=tk.RIGHT)

        # 高度设置
        height_frame = tk.Frame(resolution_frame, bg="#f0f0f0")
        height_frame.pack(fill=tk.X, pady=5)
        tk.Label(height_frame, text="高度:", font=("Arial", 10), bg="#f0f0f0").pack(side=tk.LEFT)
        height_entry = tk.Entry(
            height_frame,
            textvariable=self.target_height,
            font=("Arial", 10),
            width=10
        )
        height_entry.pack(side=tk.RIGHT)

        # 宽高比选项
        aspect_check = tk.Checkbutton(
            resolution_frame,
            text="保持原始宽高比",
            variable=self.keep_aspect_ratio,
            font=("Arial", 10),
            bg="#f0f0f0"
        )
        aspect_check.pack(pady=5)

        # 导入导出设置
        io_frame = tk.LabelFrame(
            control_frame,
            text="导入导出设置",
            font=("Arial", 10),
            bg="#f0f0f0",
            fg="#333"
        )
        io_frame.pack(fill=tk.X, pady=10)

        tk.Label(io_frame, text="导入图片:", font=("Arial", 10), bg="#f0f0f0").pack(anchor=tk.W, pady=(0, 5))
        self.import_path_var = tk.StringVar(value="未选择")
        import_path_label = tk.Label(
            io_frame,
            textvariable=self.import_path_var,
            font=("Arial", 9),
            bg="#ffffff",
            relief=tk.SUNKEN,
            anchor=tk.W
        )
        import_path_label.pack(fill=tk.X, pady=(0, 5))

        import_btn = tk.Button(
            io_frame,
            text="导入图片",
            command=self.import_images,
            bg="#4CAF50",
            fg="white",
            font=("Arial", 10, "bold"),
            relief=tk.FLAT,
            padx=10,
            pady=5
        )
        import_btn.pack(fill=tk.X, pady=5)

        tk.Label(io_frame, text="输出路径:", font=("Arial", 10), bg="#f0f0f0").pack(anchor=tk.W, pady=(0, 5))
        self.output_path_var = tk.StringVar(value="未选择")
        output_path_label = tk.Label(
            io_frame,
            textvariable=self.output_path_var,
            font=("Arial", 9),
            bg="#ffffff",
            relief=tk.SUNKEN,
            anchor=tk.W
        )
        output_path_label.pack(fill=tk.X, pady=(0, 5))

        select_output_btn = tk.Button(
            io_frame,
            text="选择输出路径",
            command=self.select_output_path,
            bg="#9C27B0",
            fg="white",
            font=("Arial", 10, "bold"),
            relief=tk.FLAT,
            padx=10,
            pady=3
        )
        select_output_btn.pack(fill=tk.X)

        # 操作按钮区域
        button_frame = tk.Frame(control_frame, bg="#f0f0f0")
        button_frame.pack(fill=tk.X, pady=10)

        start_btn = tk.Button(
            button_frame,
            text="开始处理",
            command=self.start_processing,
            bg="#2196F3",
            fg="white",
            font=("Arial", 10, "bold"),
            relief=tk.FLAT,
            padx=10,
            pady=5
        )
        start_btn.pack(fill=tk.X, pady=5)

        reset_btn = tk.Button(
            button_frame,
            text="重置",
            command=self.reset,
            bg="#FF9800",
            fg="white",
            font=("Arial", 10, "bold"),
            relief=tk.FLAT,
            padx=10,
            pady=5
        )
        reset_btn.pack(fill=tk.X, pady=5)

        exit_btn = tk.Button(
            button_frame,
            text="退出",
            command=self.root.quit,
            bg="#f44336",
            fg="white",
            font=("Arial", 10, "bold"),
            relief=tk.FLAT,
            padx=10,
            pady=5
        )
        exit_btn.pack(fill=tk.X, pady=5)

        # 右侧图片列表
        list_frame = tk.LabelFrame(
            main_frame,
            text="图片列表",
            font=("Arial", 12),
            bg="#f0f0f0",
            fg="#333",
            padx=10,
            pady=10
        )
        list_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)

        columns = ('filename', 'original_size', 'format')
        self.tree = ttk.Treeview(list_frame, columns=columns, show='headings', height=5)
        self.tree.heading('filename', text='文件名')
        self.tree.heading('original_size', text='原始尺寸')
        self.tree.heading('format', text='格式')
        self.tree.column('filename', width=200)
        self.tree.column('original_size', width=100)
        self.tree.column('format', width=80)

        scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.tree.yview)
        self.tree.configure(yscrollcommand=scrollbar.set)

        self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)

    def import_images(self):
        supported_formats = [
            ("All Supported Images", "*.jpg;*.jpeg;*.png;*.bmp;*.gif;*.tiff;*.webp;*.ico"),
            ("JPEG", "*.jpg;*.jpeg"),
            ("PNG", "*.png"),
            ("BMP", "*.bmp"),
            ("GIF", "*.gif"),
            ("TIFF", "*.tiff"),
            ("WEBP", "*.webp"),
            ("All Files", "*.*")
        ]

        filenames = filedialog.askopenfilenames(
            title="选择图片文件",
            filetypes=supported_formats
        )

        if filenames:
            for filename in filenames:
                if filename not in self.image_paths:
                    self.image_paths.append(filename)

            if len(filenames) > 1:
                directories = set(os.path.dirname(path) for path in filenames)
                if len(directories) == 1:
                    common_dir = list(directories)[0]
                    display_text = f"来自同目录: {os.path.basename(common_dir)} ({len(filenames)}张)"
                else:
                    display_text = f"多目录导入 ({len(filenames)}张)"
                self.import_path_var.set(display_text)
            elif len(filenames) == 1:
                self.import_path_var.set(os.path.dirname(filenames[0]))
            else:
                self.import_path_var.set("未选择")

            self.refresh_image_list()

    def refresh_image_list(self):
        for item in self.tree.get_children():
            self.tree.delete(item)

        for path in self.image_paths:
            try:
                img = Image.open(path)
                size_str = f"{img.width}x{img.height}"
                format_str = img.format or "Unknown"
                filename = os.path.basename(path)

                self.tree.insert('', tk.END, values=(filename, size_str, format_str))
            except Exception as e:
                print(f"无法读取图片 {path}: {e}")

    def start_processing(self):
        if not self.image_paths:
            messagebox.showwarning("警告", "请先导入图片!")
            return

        if not self.output_path:
            self.output_path = filedialog.askdirectory(title="选择输出目录")
            if not self.output_path:
                return
            self.output_path_var.set(self.output_path)

        target_w = self.target_width.get()
        target_h = self.target_height.get()

        if target_w <= 0 or target_h <= 0:
            messagebox.showerror("错误", "请输入有效的分辨率值!")
            return

        success_count = 0
        error_count = 0

        for img_path in self.image_paths:
            try:
                with Image.open(img_path) as img:
                    original_size = img.size

                    if self.keep_aspect_ratio.get():
                        original_ratio = original_size[0] / original_size[1]
                        target_ratio = target_w / target_h

                        if original_ratio > target_ratio:
                            new_width = target_w
                            new_height = int(target_w / original_ratio)
                        else:
                            new_height = target_h
                            new_width = int(target_h * original_ratio)

                        resized_img = img.resize((new_width, new_height), Image.Resampling.LANCZOS)
                    else:
                        resized_img = img.resize((target_w, target_h), Image.Resampling.LANCZOS)

                    base_name = os.path.splitext(os.path.basename(img_path))[0]
                    extension = os.path.splitext(img_path)[1]
                    output_path = os.path.join(self.output_path, f"{base_name}{extension}")

                    counter = 1
                    original_output_path = output_path
                    while os.path.exists(output_path):
                        name_part = os.path.splitext(original_output_path)[0]
                        ext_part = os.path.splitext(original_output_path)[1]
                        output_path = f"{name_part}_resized_{counter}{ext_part}"
                        counter += 1

                    resized_img.save(output_path, optimize=True, quality=95)
                    success_count += 1

            except Exception as e:
                print(f"处理图片失败 {img_path}: {e}")
                error_count += 1

        messagebox.showinfo(
            "处理完成",
            f"成功处理 {success_count} 张图片\n{error_count} 张图片处理失败"
        )

    def reset(self):
        self.image_paths.clear()
        self.target_width.set(800)
        self.target_height.set(600)
        self.keep_aspect_ratio.set(True)
        self.output_path = ""
        self.output_path_var.set("未选择")
        self.refresh_image_list()

    def select_output_path(self):
        selected_path = filedialog.askdirectory(title="选择输出目录")
        if selected_path:
            self.output_path = selected_path
            self.output_path_var.set(selected_path)

if __name__ == "__main__":
    root = tk.Tk()
    app = ImageResizerApp(root)
    root.mainloop()

六、总结

这个工具虽然界面简洁,但切实解决了图片批量处理中的高频痛点。它避免了在专业软件中反复操作的繁琐,通过几步简单设置即可完成大量图片的尺寸统一,特别适合新媒体运营、电商美工、素材整理等场景。

所有代码均已开源,你可以根据需求进一步扩展功能,例如:

  • 添加图片格式转换(如 PNG 转 JPG)
  • 支持更多分辨率预设选项
  • 增加图片旋转、裁剪等基础编辑功能
  • 添加水印功能

如果你也经常需要处理图片尺寸,不妨尝试使用或改进这个工具,让重复性工作变得更高效。

提示:本工具为个人开发,完全免费开源。如需用于商业场景或批量处理重要图片,建议先在小批量图片上测试效果,确保符合预期后再正式使用。

© 版权声明
THE END
喜欢就支持一下吧
点赞14赞赏 分享