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
)
self
.new_filename
=
tk.StringVar()
self
.output_format
=
tk.StringVar(value
=
"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
)
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
):
self
.progress[
"value"
]
=
0
self
.progress[
"maximum"
]
=
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
)
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
):
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()