用tkinter代替pyqt5重新生成image_converter

• 15 分钟阅读 • python

py打包的exe之所以大,是因为pyqt5大,本文用tkinter重新生成。

主要代码

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

class ImageConverterApp:
    def __init__(self, root):
        self.root = root
        
        # 初始化所有变量
        self.src_dir = tk.StringVar()
        self.dest_dir = tk.StringVar()
        self.output_format = tk.StringVar(value="webp")
        self.running = False
        
        # 设置窗口
        self.setup_window()
        
        # 创建控件
        self.create_widgets()

    def setup_window(self):
        """初始化窗口设置"""
        self.root.title("图片格式转换工具")
        
        # 设置窗口图标
        try:
            icon_path = os.path.abspath("icon.ico")
            if os.path.exists(icon_path):
                self.root.iconbitmap(default=icon_path)
        except Exception as e:
            print(f"图标加载失败: {e}")
        
        # Windows系统启用DPI感知
        if os.name == 'nt':
            from ctypes import windll
            windll.shcore.SetProcessDpiAwareness(1)
        
        # 设置全局字体
        self.set_system_font()

    def set_system_font(self):
        """设置系统最佳中文字体"""
        system_fonts = {
            'win32': ('Microsoft YaHei', 12),
            'darwin': ('PingFang SC', 14),
            'linux': ('Noto Sans CJK SC', 12)
        }
        default_font = system_fonts.get(sys.platform, ('TkDefaultFont', 12))
        self.root.option_add("*Font", default_font)
        style = ttk.Style()
        style.configure(".", font=default_font)

    def create_widgets(self):
        """创建界面控件"""
        main_frame = ttk.Frame(self.root, padding=15)
        main_frame.pack(fill=tk.BOTH, expand=True)
        
        # 源目录选择
        src_frame = ttk.Frame(main_frame)
        src_frame.pack(fill=tk.X, pady=5)
         
        ttk.Label(src_frame, text="源 目 录 :").pack(side=tk.LEFT)
        ttk.Entry(src_frame, textvariable=self.src_dir, width=40).pack(side=tk.LEFT, padx=5, expand=True, fill=tk.X)
        ttk.Button(src_frame, text="浏览...", command=self.browse_src_dir).pack(side=tk.LEFT)
        
        # 目标目录选择
        dest_frame = ttk.Frame(main_frame)
        dest_frame.pack(fill=tk.X, pady=5)
        
        ttk.Label(dest_frame, text="目标目录:").pack(side=tk.LEFT)
        ttk.Entry(dest_frame, textvariable=self.dest_dir, width=40).pack(side=tk.LEFT, padx=5, expand=True, fill=tk.X)
        ttk.Button(dest_frame, text="浏览...", command=self.browse_dest_dir).pack(side=tk.LEFT)
        
        # 输出格式选择
        format_frame = ttk.Frame(main_frame)
        format_frame.pack(fill=tk.X, pady=5)
        
        ttk.Label(format_frame, text="输出格式:").pack(side=tk.LEFT)
        formats = ["webp", "jpg", "png", "bmp", "gif", "tiff"]
        ttk.Combobox(format_frame, textvariable=self.output_format, values=formats, width=8, state="readonly").pack(side=tk.LEFT, padx=5)
        
        # 进度条
        self.progress = ttk.Progressbar(main_frame, orient=tk.HORIZONTAL, length=450, mode='determinate')
        self.progress.pack(pady=15, fill=tk.X)
        
        # 操作按钮
        btn_frame = ttk.Frame(main_frame)
        btn_frame.pack(pady=10)
        
        self.convert_btn = ttk.Button(btn_frame, text="开始转换", command=self.start_conversion)
        self.convert_btn.pack(side=tk.LEFT, padx=5)
        
        self.cancel_btn = ttk.Button(btn_frame, text="取消", command=self.cancel_conversion, state=tk.DISABLED)
        self.cancel_btn.pack(side=tk.LEFT, padx=5)
        
        # 状态栏
        self.status_var = tk.StringVar(value="准备就绪")
        ttk.Label(main_frame, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W, padding=(5, 5)).pack(fill=tk.X, pady=(10, 0))

    def browse_src_dir(self):
        """选择源目录"""
        dir_path = filedialog.askdirectory()
        if dir_path:
            self.src_dir.set(dir_path)
            if not self.dest_dir.get():
                self.dest_dir.set(os.path.join(dir_path, "converted"))

    def browse_dest_dir(self):
        """选择目标目录"""
        dir_path = filedialog.askdirectory()
        if dir_path:
            self.dest_dir.set(dir_path)

    def start_conversion(self):
        """开始转换图片"""
        src_dir = self.src_dir.get()
        dest_dir = self.dest_dir.get()
        output_format = self.output_format.get().lower()
        
        if not src_dir or not dest_dir:
            messagebox.showwarning("警告", "请选择源目录和目标目录!")
            return
        
        if src_dir == dest_dir:
            messagebox.showwarning("警告", "源目录和目标目录不能相同!")
            return
        
        self.running = True
        self.convert_btn.config(state=tk.DISABLED)
        self.cancel_btn.config(state=tk.NORMAL)
        self.progress["value"] = 0
        self.status_var.set("正在转换...")
        
        threading.Thread(
            target=self.convert_images,
            args=(src_dir, dest_dir, output_format),
            daemon=True
        ).start()

    def convert_images(self, src_dir, dest_dir, output_format):
        """执行图片转换"""
        try:
            if not os.path.exists(src_dir):
                self.show_error(f"源目录不存在: {src_dir}")
                return
            
            os.makedirs(dest_dir, exist_ok=True)
            
            image_files = [
                f for f in os.listdir(src_dir) 
                if f.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.gif', '.tiff', '.webp'))
            ]
            
            total_files = len(image_files)
            if total_files == 0:
                self.show_error("未找到支持的图片文件")
                return
            
            for i, filename in enumerate(image_files):
                if not self.running:
                    break
                
                try:
                    img_path = os.path.join(src_dir, filename)
                    output_path = os.path.join(
                        dest_dir, 
                        f"{os.path.splitext(filename)[0]}.{output_format}"
                    )
                    
                    with Image.open(img_path) as img:
                        if img.mode in ('RGBA', 'LA') and output_format in ('jpg', 'jpeg'):
                            background = Image.new('RGB', img.size, (255, 255, 255))
                            background.paste(img, mask=img.split()[-1])
                            img = background
                        
                        save_params = {}
                        if output_format == 'webp':
                            save_params = {'quality': 100, 'method': 6}
                        elif output_format in ('jpg', 'jpeg'):
                            save_params = {'quality': 95}
                        
                        img.save(output_path, **save_params)
                    
                    progress = (i + 1) / total_files * 100
                    self.update_progress(progress)
                
                except Exception as e:
                    self.show_error(f"处理文件 {filename} 失败: {str(e)}")
                    continue
            
            if self.running:
                self.conversion_complete(f"转换完成!共处理 {total_files} 个文件")
        
        except Exception as e:
            self.show_error(f"转换过程中出错: {str(e)}")

    def update_progress(self, value):
        """更新进度条"""
        self.root.after(0, lambda: self.progress.configure(value=value))

    def conversion_complete(self, message):
        """转换完成处理"""
        self.root.after(0, lambda: [
            self.status_var.set(message),
            self.convert_btn.config(state=tk.NORMAL),
            self.cancel_btn.config(state=tk.DISABLED),
            messagebox.showinfo("完成", message)
        ])

    def show_error(self, error_message):
        """显示错误信息"""
        self.root.after(0, lambda: [
            self.status_var.set(f"错误: {error_message}"),
            self.convert_btn.config(state=tk.NORMAL),
            self.cancel_btn.config(state=tk.DISABLED),
            messagebox.showerror("错误", error_message)
        ])

    def cancel_conversion(self):
        """取消转换"""
        self.running = False
        self.status_var.set("转换已取消")
        self.convert_btn.config(state=tk.NORMAL)
        self.cancel_btn.config(state=tk.DISABLED)

def main():
    root = tk.Tk()
    window_width = 550
    window_height = 320
    screen_width = root.winfo_screenwidth()
    screen_height = root.winfo_screenheight()
    x = (screen_width - window_width) // 2
    y = (screen_height - window_height) // 2
    root.geometry(f"{window_width}x{window_height}+{x}+{y}")
    
    app = ImageConverterApp(root)
    root.mainloop()

if __name__ == "__main__":
    main()

保存为image_converter.py。
有个小问题,运行的py程序图标无法显示为指定的图标,怎么改都不行,不管了。
界面字体用tk默认字体会比较模糊,这里指定使用操作系统自带的字体。

生成exe文件

pyinstaller --onefile --windowed --icon=icon.ico --add-data "icon.ico;." image_converter.py
dist目录下生成的exe文件大小为11.4M,比含pyqt5的32M小多了。

为什么 Tkinter 版更小

文章标签: python

上一篇 : 生成命令行版本image-converter
下一篇 : 使用 Tauri (轻量级替代方案)打包编辑器
留言
阅读进度 0%