
核心功能:想怎么压,就怎么压!
from tkinter import filedialog, ttk, messagebox
from PIL import Image
import os
import math
class ImageCompressor:
def __init__(self, root):
self.root = root
self.root.title("图片压缩工具 - 体积优化版")
self.root.geometry("650x450")
self.root.resizable(True, True)
# 设置中文字体支持
self.style = ttk.Style()
self.style.configure("TLabel", font=("SimHei", 10))
self.style.configure("TButton", font=("SimHei", 10))
self.style.configure("TScale", font=("SimHei", 10))
# 源图片路径
self.source_path = tk.StringVar()
# 输出质量
self.quality = tk.IntVar(value=60) # 降低默认质量,确保体积减小
# 输出尺寸比例
self.scale = tk.DoubleVar(value=0.8) # 默认适当缩小
# 目标文件大小(KB)
self.target_size = tk.IntVar(value=200)
self.create_widgets()
def create_widgets(self):
# 创建主框架
main_frame = ttk.Frame(self.root, padding="20")
main_frame.pack(fill=tk.BOTH, expand=True)
# 源文件选择
ttk.Label(main_frame, text="源图片:").grid(row=0, column=0, sticky=tk.W, pady=5)
ttk.Entry(main_frame, textvariable=self.source_path, width=50).grid(row=0, column=1, pady=5)
ttk.Button(main_frame, text="选择图片", command=self.select_image).grid(row=0, column=2, padx=5, pady=5)
# 原始文件信息
self.original_info = ttk.Label(main_frame, text="原始图片信息: 未选择图片", foreground="gray")
self.original_info.grid(row=1, column=0, columnspan=3, sticky=tk.W, pady=5)
# 输出质量设置
ttk.Label(main_frame, text="压缩质量:").grid(row=2, column=0, sticky=tk.W, pady=5)
quality_scale = ttk.Scale(main_frame, from_=1, to=100, variable=self.quality, orient=tk.HORIZONTAL, length=300)
quality_scale.grid(row=2, column=1, pady=5)
ttk.Label(main_frame, textvariable=self.quality).grid(row=2, column=2, padx=5, pady=5)
ttk.Label(main_frame, text="(数值越小,文件越小,质量可能下降越多)").grid(row=3, column=1, sticky=tk.W, pady=2)
# 输出尺寸设置
ttk.Label(main_frame, text="输出尺寸比例:").grid(row=4, column=0, sticky=tk.W, pady=5)
scale_scale = ttk.Scale(main_frame, from_=0.1, to=1.0, variable=self.scale, orient=tk.HORIZONTAL, length=300)
scale_scale.grid(row=4, column=1, pady=5)
self.scale_label = ttk.Label(main_frame, text=f"{self.scale.get():.1f}x")
self.scale_label.grid(row=4, column=2, padx=5, pady=5)
scale_scale.bind("", self.update_scale_label)
ttk.Label(main_frame, text="(小于1.0表示缩小图片尺寸)").grid(row=5, column=1, sticky=tk.W, pady=2)
# 目标文件大小设置
ttk.Label(main_frame, text="目标文件大小(KB):").grid(row=6, column=0, sticky=tk.W, pady=5)
ttk.Entry(main_frame, textvariable=self.target_size, width=10).grid(row=6, column=1, sticky=tk.W, pady=5)
ttk.Label(main_frame, text="(设置希望的最大文件大小)").grid(row=7, column=1, sticky=tk.W, pady=2)
# 压缩按钮
compress_btn = ttk.Button(main_frame, text="压缩图片", command=self.compress_image)
compress_btn.grid(row=8, column=0, columnspan=3, pady=20)
# 状态标签
self.status_label = ttk.Label(main_frame, text="请选择一张图片进行压缩", foreground="gray")
self.status_label.grid(row=9, column=0, columnspan=3, pady=10)
# 底部信息
ttk.Label(main_frame, text="压缩后的图片将保存为原文件名+_compressed", foreground="blue").grid(row=10, column=0, columnspan=3, pady=5)
def update_scale_label(self, event):
"""更新尺寸比例显示"""
self.scale_label.config(text=f"{self.scale.get():.1f}x")
def select_image(self):
"""选择图片文件并显示信息"""
file_path = filedialog.askopenfilename(
filetypes=[("图片文件", "*.jpg;*.jpeg;*.png;*.bmp;*.gif")]
)
if file_path:
self.source_path.set(file_path)
try:
# 获取图片信息
with Image.open(file_path) as img:
width, height = img.size
file_size = os.path.getsize(file_path) / 1024 # KB
info_text = f"原始图片信息: {width}x{height} 像素, {file_size:.2f}KB, 格式: {img.format}"
self.original_info.config(text=info_text, foreground="black")
self.status_label.config(text=f"已选择: {os.path.basename(file_path)}", foreground="green")
except Exception as e:
self.status_label.config(text=f"获取图片信息失败: {str(e)}", foreground="red")
def compress_image(self):
"""压缩图片,确保体积减小"""
input_path = self.source_path.get()
if not input_path or not os.path.exists(input_path):
messagebox.showerror("错误", "请先选择一张有效的图片")
return
try:
# 打开图片
with Image.open(input_path) as img:
# 转换为RGB模式(对于PNG等有透明通道的格式,避免压缩问题)
if img.mode in ('RGBA', 'LA') or (img.mode == 'P' and 'transparency' in img.info):
background = Image.new(img.mode[:-1], img.size, (255, 255, 255))
background.paste(img, img.split()[-1])
img = background
# 计算新尺寸
width, height = img.size
new_width = int(width * self.scale.get())
new_height = int(height * self.scale.get())
# 确保新尺寸不会大于原尺寸
new_width = min(new_width, width)
new_height = min(new_height, height)
# 调整尺寸 - 使用更高效的压缩算法
resized_img = img.resize((new_width, new_height), Image.Resampling.LANCZOS)
# 生成输出路径
dir_name, file_name = os.path.split(input_path)
base_name, ext = os.path.splitext(file_name)
# 强制转换为JPG格式,通常比PNG更节省空间
output_ext = '.jpg' if ext.lower() not in ['.jpg', '.jpeg'] else ext
output_path = os.path.join(dir_name, f"{base_name}_compressed{output_ext}")
# 检查是否需要多次压缩以达到目标大小
quality = self.quality.get()
target_size_kb = self.target_size.get()
# 第一次压缩
resized_img.save(output_path, format='JPEG' if output_ext.lower() in ['.jpg', '.jpeg'] else 'PNG',
quality=quality, optimize=True, progressive=True)
# 检查文件大小,如果超过目标大小则降低质量重新压缩
current_size_kb = os.path.getsize(output_path) / 1024
max_attempts = 5
attempts = 0
while current_size_kb > target_size_kb and quality > 10 and attempts