在为网站上传图片时,经常会遇到对图片文件大小和分辨率的限制。为了确保图片符合这些要求而不失质量,图片压缩工具成为不可或缺的好帮手。这类工具不仅能够有效减小文件体积,还能保持图像的清晰度,确保网页加载速度不受影响。 主要功能与优势: 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: self.show_message("提示", "原图大小已经小于或等于目标大小,无需压缩。") return self.compress_image(self.image_path, output_path, target_size_kb) self.set_progress_value(100) # 完成后设置进度条为100% self.show_message("成功", "图片已成功压缩并保存!") except Exception as e: logging.error(f"压缩失败: {str(e)}") self.show_message("错误", f"压缩失败: {str(e)}") finally: self.progress.stop() def compress_image(self, image_path, output_path, target_size_kb): """压缩图片至指定大小""" try: img = Image.open(image_path).convert('RGB') # 确保图片模式兼容 quality = 95 original_size_kb = os.path.getsize(image_path) / 1024 format_name = self.get_output_format() # 获取正确的格式名称 while 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() |
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。
评论(0)