在为网站上传图片时,经常会遇到对图片文件大小和分辨率的限制。为了确保图片符合这些要求而不失质量,图片压缩工具成为不可或缺的好帮手。这类工具不仅能够有效减小文件体积,还能保持图像的清晰度,确保网页加载速度不受影响。
主要功能与优势:
1.智能压缩:利用先进的算法,在几乎不影响视觉效果的情况下减少图片文件的大小,确保图片符合网站的上传要求
2.格式转换:部分工具还提供格式转换功能,可以将图片转换为最适合网络使用的格式(如 JPEG、PNG 等多种图片格式),进一步优化文件大小。
3.自定义设置:允许用户调整压缩级别,根据具体需求平衡图片质量和文件大小。
4.即时预览:一些工具提供即时预览功能,让用户可以在压缩前后对比图片质量,确保最终效果满意。 代码源码在页面最下方,有需要的可以自行修改研究。 - import os
- import re
- import tkinter as tk
- from tkinter import filedialog, messagebox, ttk
- from PIL import Image, ImageTk, UnidentifiedImageError
- import threading
- import logging
- # 设置日志配置
- logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
- class ImageCompressorApp:
- def __init__(self, root):
- self.root = root
- self.root.title("图片压缩工具 by ke")
- self.root.geometry("1024x650")
- self.root.configure(bg="#f0f0f0")
- # 初始化变量
- self.image_path = None # 图片路径
- self.output_location = None # 输出位置
- self.target_size_kb = tk.IntVar(value=400) # 默认目标大小为400KB
- self.new_filename = tk.StringVar() # 新文件名
- self.output_format = tk.StringVar(value="JPG") # 修改默认输出格式为JPG
- # 创建界面元素
- self.create_widgets()
- def create_widgets(self):
- main_frame = tk.Frame(self.root, bg="#f0f0f0")
- main_frame.pack(expand=True, fill='both', padx=20, pady=20)
- # 文件选择和预览
- img_frame = tk.Frame(main_frame, bg="#f0f0f0")
- img_frame.pack(fill='x')
- self.img_label = tk.Label(img_frame, text="请加载一张图片", bg="#f0f0f0")
- self.img_label.pack(side=tk.LEFT, padx=10, pady=10)
- browse_button = tk.Button(img_frame, text="选择图片...", command=self.browse_files)
- browse_button.pack(side=tk.RIGHT, padx=10, pady=10)
- # 目标大小输入框
- size_frame = tk.Frame(main_frame, bg="#f0f0f0")
- size_frame.pack(fill='x', pady=5)
- tk.Label(size_frame, text="目标大小 (KB):", bg="#f0f0f0").pack(side=tk.LEFT, padx=5)
- self.size_entry = tk.Entry(size_frame, textvariable=self.target_size_kb, width=10)
- self.size_entry.pack(side=tk.LEFT, padx=5)
- # 设置新文件名
- filename_frame = tk.Frame(main_frame, bg="#f0f0f0")
- filename_frame.pack(fill='x', pady=5)
- tk.Label(filename_frame, text="新文件名:", bg="#f0f0f0").pack(side=tk.LEFT, padx=5)
- tk.Entry(filename_frame, textvariable=self.new_filename, width=30).pack(side=tk.LEFT, padx=5)
- # 选择输出格式
- format_frame = tk.Frame(main_frame, bg="#f0f0f0")
- format_frame.pack(fill='x', pady=5)
- tk.Label(format_frame, text="输出格式:", bg="#f0f0f0").pack(side=tk.LEFT, padx=5)
- format_combobox = ttk.Combobox(format_frame, textvariable=self.output_format,
- values=["JPG", "JPEG", "PNG", "BMP", "GIF"], width=10)
- format_combobox.current(0) # 设置默认选中项为第一个选项,即"JPG"
- format_combobox.pack(side=tk.LEFT, padx=5)
- # 输出位置选择
- output_frame = tk.Frame(main_frame, bg="#f0f0f0")
- output_frame.pack(fill='x', pady=5)
- tk.Label(output_frame, text="输出位置:", bg="#f0f0f0").pack(side=tk.LEFT, padx=5)
- self.output_location_var = tk.StringVar()
- tk.Entry(output_frame, textvariable=self.output_location_var, width=50).pack(side=tk.LEFT, padx=5)
- tk.Button(output_frame, text="选择...", command=self.select_output_location).pack(side=tk.LEFT, padx=5)
- # 创建一个容器用于放置压缩并保存按钮和进度条
- bottom_frame = tk.Frame(self.root, bg="#f0f0f0")
- bottom_frame.pack(side=tk.BOTTOM, fill='x', pady=20)
- # 压缩并保存按钮
- compress_button = tk.Button(bottom_frame, text="压缩并保存", command=self.start_compress_and_save)
- compress_button.pack(side=tk.LEFT, padx=10)
- # 进度条放在最下方
- self.progress = ttk.Progressbar(bottom_frame, orient="horizontal", length=700, mode="determinate")
- self.progress.pack(side=tk.RIGHT, padx=10)
- def load_image(self, path):
- """加载并显示图片预览"""
- try:
- self.image_path = path
- img = Image.open(path)
- img.thumbnail((300, 300))
- self.photo = ImageTk.PhotoImage(img)
- self.img_label.config(image=self.photo)
- except Exception as e:
- logging.error(f"加载图片时出错: {str(e)}")
- self.show_message("错误", f"加载图片时出错: {str(e)}")
- def browse_files(self):
- """打开文件对话框选择图片"""
- filename = filedialog.askopenfilename(
- title="选择图片",
- filetypes=[("Image files", "*.png *.jpg *.jpeg *.bmp *.gif")]
- )
- if filename:
- self.load_image(filename)
- def select_output_location(self):
- """选择图片压缩后的输出位置"""
- directory = filedialog.askdirectory(title="选择输出位置")
- if directory:
- self.output_location_var.set(directory)
- def start_compress_and_save(self):
- """启动压缩线程并在UI上显示进度条"""
- self.progress["value"] = 0 # 初始化进度条为0%
- self.progress["maximum"] = 100 # 设置进度条的最大值为100%
- compress_thread = threading.Thread(target=self.compress_and_save)
- compress_thread.start()
- def compress_and_save(self):
- """执行图片压缩并保存到指定位置"""
- try:
- if not self.image_path or not self.output_location_var.get():
- self.show_message("警告", "请选择图片和输出位置!")
- return
- new_filename = sanitize_filename(self.new_filename.get().strip())
- if not new_filename:
- self.show_message("警告", "请输入有效的文件名!")
- return
- output_filename = f"{new_filename}.{self.get_output_extension()}"
- output_path = os.path.join(self.output_location_var.get(), output_filename)
- # 检查输出路径有效性
- if not os.path.isdir(self.output_location_var.get()):
- logging.error(f"输出位置不存在: {self.output_location_var.get()}")
- self.show_message("错误", "选择的输出位置无效,请重新选择。")
- return
- # 检查是否有写权限
- if not os.access(self.output_location_var.get(), os.W_OK):
- logging.error(f"没有写入权限: {self.output_location_var.get()}")
- self.show_message("错误", "没有足够的权限在选择的位置写入文件。")
- return
- target_size_kb = self.target_size_kb.get()
- original_size_kb = os.path.getsize(self.image_path) / 1024
- if original_size_kb target_size_kb and quality >= 10:
- try:
- img.save(output_path, format=format_name, optimize=True, quality=quality)
- original_size_kb = os.path.getsize(output_path) / 1024
- quality -= 5
- self.update_progress(quality)
- except IOError as e:
- logging.error(f"IO 错误: {str(e)}")
- raise ValueError(f"IO 错误: {str(e)}")
- except Exception as e:
- logging.error(f"保存图片时出错: {str(e)}")
- raise ValueError(f"保存图片时出错: {str(e)}")
- if original_size_kb > target_size_kb:
- raise ValueError("无法将图片压缩到指定大小!请尝试增加目标大小或减少图片复杂度。")
- except UnidentifiedImageError:
- logging.error("无法识别的图片格式")
- self.show_message("错误", "无法识别的图片格式,请选择其他图片。")
- except Exception as e:
- logging.error(f"压缩图片时出错: {str(e)}")
- self.show_message("错误", f"压缩图片时出错: {str(e)}")
- def get_output_extension(self):
- """根据输出格式返回正确的文件扩展名"""
- format_name = self.output_format.get().upper()
- if format_name in ["JPG", "JPEG"]:
- return "jpg"
- elif format_name == "PNG":
- return "png"
- elif format_name == "BMP":
- return "bmp"
- elif format_name == "GIF":
- return "gif"
- else:
- return "jpg"
- def get_output_format(self):
- """根据输出格式返回正确的Pillow格式名称"""
- format_name = self.output_format.get().upper()
- if format_name in ["JPG", "JPEG"]:
- return "JPEG"
- elif format_name == "PNG":
- return "PNG"
- elif format_name == "BMP":
- return "BMP"
- elif format_name == "GIF":
- return "GIF"
- else:
- return "JPEG"
- def update_progress(self, quality):
- """更新进度条的值"""
- max_quality = 95
- min_quality = 10
- progress_value = ((max_quality - quality) / (max_quality - min_quality)) * 100
- self.root.after(0, lambda: self.set_progress_value(min(progress_value, 100)))
- def set_progress_value(self, value):
- """设置进度条的值"""
- self.progress["value"] = value
- def show_message(self, title, message):
- """显示消息框并停止进度条"""
- self.root.after(0, lambda: messagebox.showinfo(title, message))
- def sanitize_filename(filename):
- """清理文件名中的非法字符"""
- return re.sub(r'[\/*?:"|]', "", filename)
- if __name__ == "__main__":
- root = tk.Tk()
- app = ImageCompressorApp(root)
- root.mainloop()
复制代码
|