图片格式转换器
功能
格式转换
可指定转换后文件大小(指定长宽或指定空间占用)
可选择覆盖或另存
需要的包
需要安装pillow包
代码
import os
import tkinter as tk
from tkinter import filedialog, ttk, messagebox
from PIL import Image, ImageOps
import sys
class ImageConverterApp:
def __init__(self, root):
self.root = root
self.root.title("图片格式转换工具")
self.root.geometry("750x650")
self.root.resizable(True, True)
# 设置中文字体支持
self.setup_fonts()
# 变量初始化
self.input_folder = tk.StringVar()
self.output_folder = tk.StringVar()
self.target_format = tk.StringVar(value="jpg")
self.width = tk.StringVar(value="800")
self.height = tk.StringVar(value="600")
self.max_filesize = tk.StringVar(value="400") # 默认400KB
self.overwrite = tk.BooleanVar(value=False)
self.preserve_ratio = tk.BooleanVar(value=True)
# 处理模式: 0-调整大小, 1-裁切, 2-限制文件大小(保持原图尺寸)
self.processing_mode = tk.IntVar(value=0)
# 支持的图片格式(包含avif)
self.supported_formats = ["jpg", "jpeg", "png", "gif", "bmp", "tiff", "webp", "ico", "avif"]
# 创建UI
self.create_widgets()
def setup_fonts(self):
"""设置支持中文的字体"""
default_font = ('SimHei', 10)
self.root.option_add("*Font", default_font)
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.input_folder, width=50).grid(row=0, column=1, pady=5)
ttk.Button(main_frame, text="浏览...", command=self.browse_input_folder).grid(row=0, column=2, padx=5, pady=5)
# 输出选项
output_frame = ttk.LabelFrame(main_frame, text="输出选项", padding="10")
output_frame.grid(row=1, column=0, columnspan=3, sticky=tk.W + tk.E, pady=10)
# 输出文件夹选择(仅在不覆盖时启用)
ttk.Label(output_frame, text="输出文件夹:").grid(row=0, column=0, sticky=tk.W, pady=5)
self.output_entry = ttk.Entry(output_frame, textvariable=self.output_folder, width=40)
self.output_entry.grid(row=0, column=1, pady=5)
self.output_btn = ttk.Button(output_frame, text="浏览...", command=self.browse_output_folder)
self.output_btn.grid(row=0, column=2, padx=5, pady=5)
# 覆盖选项
ttk.Checkbutton(output_frame, text="覆盖原文件", variable=self.overwrite,
command=self.toggle_output_options).grid(row=1, column=0, sticky=tk.W, pady=5)
# 目标格式选择
ttk.Label(output_frame, text="目标格式:").grid(row=1, column=1, sticky=tk.W, pady=5)
format_combo = ttk.Combobox(output_frame, textvariable=self.target_format,
values=self.supported_formats, state="readonly", width=10)
format_combo.grid(row=1, column=2, sticky=tk.W, pady=5)
# 处理模式选择
mode_frame = ttk.LabelFrame(main_frame, text="图片处理模式", padding="10")
mode_frame.grid(row=2, column=0, columnspan=3, sticky=tk.W + tk.E, pady=10)
ttk.Radiobutton(mode_frame, text="调整大小", variable=self.processing_mode,
value=0, command=self.toggle_processing_options).grid(row=0, column=0, sticky=tk.W, padx=10)
ttk.Radiobutton(mode_frame, text="裁切到指定尺寸", variable=self.processing_mode,
value=1, command=self.toggle_processing_options).grid(row=0, column=1, sticky=tk.W, padx=10)
ttk.Radiobutton(mode_frame, text="保持原图尺寸,限制文件大小(KB)", variable=self.processing_mode,
value=2, command=self.toggle_processing_options).grid(row=0, column=2, sticky=tk.W, padx=10)
# 尺寸调整选项
size_frame = ttk.LabelFrame(main_frame, text="尺寸参数", padding="10")
size_frame.grid(row=3, column=0, columnspan=3, sticky=tk.W + tk.E, pady=10)
ttk.Label(size_frame, text="宽度:").grid(row=0, column=0, sticky=tk.W, pady=5)
self.width_entry = ttk.Entry(size_frame, textvariable=self.width, width=10)
self.width_entry.grid(row=0, column=1, pady=5)
ttk.Label(size_frame, text="高度:").grid(row=0, column=2, sticky=tk.W, pady=5, padx=10)
self.height_entry = ttk.Entry(size_frame, textvariable=self.height, width=10)
self.height_entry.grid(row=0, column=3, pady=5)
self.ratio_check = ttk.Checkbutton(size_frame, text="保持宽高比", variable=self.preserve_ratio)
self.ratio_check.grid(row=0, column=4, sticky=tk.W, pady=5, padx=10)
# 文件大小限制选项
filesize_frame = ttk.LabelFrame(main_frame, text="文件大小限制", padding="10")
filesize_frame.grid(row=4, column=0, columnspan=3, sticky=tk.W + tk.E, pady=10)
ttk.Label(filesize_frame, text="最大文件大小 (KB):").grid(row=0, column=0, sticky=tk.W, pady=5)
self.filesize_entry = ttk.Entry(filesize_frame, textvariable=self.max_filesize, width=10)
self.filesize_entry.grid(row=0, column=1, pady=5)
ttk.Label(filesize_frame, text="建议值: JPG(100-800), PNG(200-1500), AVIF(50-500)").grid(row=0, column=2,
sticky=tk.W, pady=5,
padx=10)
# 转换按钮
ttk.Button(main_frame, text="开始转换", command=self.start_conversion, style="Accent.TButton").grid(row=5,
column=0,
columnspan=3,
pady=20)
# 进度条
self.progress_var = tk.DoubleVar()
self.progress_bar = ttk.Progressbar(main_frame, variable=self.progress_var, maximum=100)
self.progress_bar.grid(row=6, column=0, columnspan=3, sticky=tk.W + tk.E, pady=10)
# 状态区域
ttk.Label(main_frame, text="转换状态:").grid(row=7, column=0, sticky=tk.W, pady=5)
self.status_var = tk.StringVar(value="就绪")
ttk.Label(main_frame, textvariable=self.status_var).grid(row=7, column=1, sticky=tk.W, pady=5)
# 日志区域
ttk.Label(main_frame, text="转换日志:").grid(row=8, column=0, sticky=tk.NW, pady=5)
log_frame = ttk.Frame(main_frame)
log_frame.grid(row=8, column=1, columnspan=2, sticky=tk.NSEW, pady=5)
self.log_text = tk.Text(log_frame, height=10, width=60, state=tk.DISABLED)
self.log_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar = ttk.Scrollbar(log_frame, command=self.log_text.yview)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.log_text.config(yscrollcommand=scrollbar.set)
# 配置网格权重,使界面可伸缩
main_frame.columnconfigure(1, weight=1)
output_frame.columnconfigure(1, weight=1)
mode_frame.columnconfigure(2, weight=1)
size_frame.columnconfigure(4, weight=1)
filesize_frame.columnconfigure(2, weight=1)
main_frame.rowconfigure(8, weight=1)
# 初始设置控件状态
self.toggle_output_options()
self.toggle_processing_options()
# 设置按钮样式
style = ttk.Style()
style.configure("Accent.TButton", font=('SimHei', 10, 'bold'))
def toggle_output_options(self):
"""根据是否覆盖选项启用/禁用输出文件夹选项"""
if self.overwrite.get():
self.output_entry.config(state=tk.DISABLED)
self.output_btn.config(state=tk.DISABLED)
else:
self.output_entry.config(state=tk.NORMAL)
self.output_btn.config(state=tk.NORMAL)
def toggle_processing_options(self):
"""根据处理模式切换不同选项的可用性"""
mode = self.processing_mode.get()
if mode in (0, 1): # 调整大小或裁切模式
# 启用尺寸参数,禁用文件大小参数
self.width_entry.config(state=tk.NORMAL)
self.height_entry.config(state=tk.NORMAL)
self.ratio_check.config(state=tk.NORMAL if mode == 0 else tk.DISABLED)
self.filesize_entry.config(state=tk.DISABLED)
elif mode == 2: # 限制文件大小模式
# 禁用尺寸参数,启用文件大小参数
self.width_entry.config(state=tk.DISABLED)
self.height_entry.config(state=tk.DISABLED)
self.ratio_check.config(state=tk.DISABLED)
self.filesize_entry.config(state=tk.NORMAL)
def browse_input_folder(self):
"""选择输入文件夹"""
folder = filedialog.askdirectory(title="选择图片所在文件夹")
if folder:
self.input_folder.set(folder)
# 如果输出文件夹未设置,自动设置为输入文件夹的子文件夹
if not self.output_folder.get():
self.output_folder.set(os.path.join(folder, "converted_images"))
def browse_output_folder(self):
"""选择输出文件夹"""
folder = filedialog.askdirectory(title="选择保存转换后图片的文件夹")
if folder:
self.output_folder.set(folder)
def log(self, message):
"""向日志区域添加消息"""
self.log_text.config(state=tk.NORMAL)
self.log_text.insert(tk.END, message + "\n")
self.log_text.see(tk.END)
self.log_text.config(state=tk.DISABLED)
self.root.update_idletasks() # 更新界面
def get_image_files(self, folder):
"""获取文件夹中所有支持的图片文件"""
image_files = []
for file in os.listdir(folder):
ext = os.path.splitext(file)[1].lower()[1:] # 获取扩展名(不含点)
if ext in self.supported_formats:
image_files.append(os.path.join(folder, file))
return image_files
def process_image(self, image):
"""根据选择的模式处理图片"""
mode = self.processing_mode.get()
if mode == 0: # 调整大小
return self.resize_image(image)
elif mode == 1: # 裁切
return self.crop_image(image)
elif mode == 2: # 限制文件大小模式 - 保持原图尺寸
return image # 直接返回原图,不做尺寸改变
return image
def resize_image(self, image):
"""调整图片大小"""
try:
width = int(self.width.get())
height = int(self.height.get())
if width <= 0 or height <= 0:
raise ValueError("宽度和高度必须为正数")
# 如果保持比例,则计算新尺寸
if self.preserve_ratio.get():
original_width, original_height = image.size
ratio = min(width / original_width, height / original_height)
width = int(original_width * ratio)
height = int(original_height * ratio)
return image.resize((width, height), Image.Resampling.LANCZOS)
except ValueError as e:
self.log(f"尺寸错误: {str(e)},使用原图尺寸")
return image
def crop_image(self, image):
"""裁切图片到指定尺寸"""
try:
target_width = int(self.width.get())
target_height = int(self.height.get())
if target_width <= 0 or target_height <= 0:
raise ValueError("宽度和高度必须为正数")
# 使用中心裁切
return ImageOps.fit(
image,
(target_width, target_height),
method=Image.Resampling.LANCZOS,
bleed=0.0,
centering=(0.5, 0.5) # 中心位置
)
except ValueError as e:
self.log(f"裁切错误: {str(e)},使用原图")
return image
def save_with_filesize_limit(self, image, output_path, target_format):
"""保存图片并限制文件大小,保持原图尺寸"""
max_size_kb = int(self.max_filesize.get())
max_size_bytes = max_size_kb * 1024
# 对于不同格式使用不同的初始质量
if target_format.lower() in ['jpg', 'jpeg']:
quality = 90
format = 'JPEG'
elif target_format.lower() == 'png':
quality = 80
format = 'PNG'
elif target_format.lower() == 'webp':
quality = 80
format = 'WEBP'
elif target_format.lower() == 'avif':
quality = 60 # AVIF格式压缩效率高,初始质量设低一些
format = 'AVIF'
else:
quality = 80
format = target_format.upper()
# 先尝试保存一次看是否符合大小要求
temp_path = output_path + ".temp"
image.save(temp_path, format, quality=quality)
# 检查文件大小
current_size = os.path.getsize(temp_path)
# 如果已经符合要求,直接使用
if current_size <= max_size_bytes:
os.replace(temp_path, output_path)
return True, f"文件大小: {current_size / 1024:.1f}KB"
# 二分法寻找最佳质量
min_quality = 10
max_quality = quality
best_quality = quality
while max_quality - min_quality > 5:
mid_quality = (min_quality + max_quality) // 2
image.save(temp_path, format, quality=mid_quality)
current_size = os.path.getsize(temp_path)
if current_size <= max_size_bytes:
best_quality = mid_quality
min_quality = mid_quality # 尝试提高质量
else:
max_quality = mid_quality # 必须降低质量
# 使用找到的最佳质量保存
image.save(temp_path, format, quality=best_quality)
current_size = os.path.getsize(temp_path)
os.replace(temp_path, output_path)
if current_size > max_size_bytes:
return True, f"已尽力压缩: {current_size / 1024:.1f}KB (超过目标 {max_size_kb}KB)"
return True, f"文件大小: {current_size / 1024:.1f}KB"
def convert_image(self, input_path, output_path, target_format):
"""转换单张图片的格式和大小"""
try:
with Image.open(input_path) as img:
# 记录原图尺寸用于日志
original_size = img.size
# 处理透明通道
if target_format.lower() in ['jpg', 'jpeg', 'bmp'] and img.mode in ('RGBA', 'LA'):
background = Image.new(img.mode[:-1], img.size, (255, 255, 255))
background.paste(img, img.split()[-1])
img = background
elif img.mode == 'P': # 处理调色板模式
img = img.convert('RGB')
# 处理图片(调整大小或裁切)
processed_img = self.process_image(img)
# 保存图片
if self.processing_mode.get() == 2: # 限制文件大小模式
# 明确记录保持原图尺寸
success, size_msg = self.save_with_filesize_limit(processed_img, output_path, target_format)
return success, f"转换成功: {os.path.basename(input_path)} (保持原图尺寸: {original_size[0]}x{original_size[1]}) - {size_msg}"
else: # 其他模式
if target_format.lower() == 'jpg':
processed_img.save(output_path, 'JPEG', quality=95)
elif target_format.lower() == 'avif':
processed_img.save(output_path, 'AVIF', quality=80)
else:
processed_img.save(output_path)
size = os.path.getsize(output_path) / 1024
return True, f"转换成功: {os.path.basename(input_path)} (新尺寸: {processed_img.size[0]}x{processed_img.size[1]}) - 大小: {size:.1f}KB"
except Exception as e:
return False, f"转换失败 {os.path.basename(input_path)}: {str(e)}"
def start_conversion(self):
"""开始批量转换图片"""
input_folder = self.input_folder.get()
target_format = self.target_format.get().lower()
# 验证输入
if not input_folder or not os.path.isdir(input_folder):
messagebox.showerror("错误", "请选择有效的源文件夹")
return
if not self.overwrite.get():
output_folder = self.output_folder.get()
if not output_folder:
messagebox.showerror("错误", "请选择输出文件夹")
return
# 创建输出文件夹(如果不存在)
os.makedirs(output_folder, exist_ok=True)
# 验证文件大小参数(如果是限制文件大小模式)
if self.processing_mode.get() == 2:
try:
max_filesize = int(self.max_filesize.get())
if max_filesize <= 0:
raise ValueError
except ValueError:
messagebox.showerror("错误", "最大文件大小必须是正数")
return
# 获取所有图片文件
image_files = self.get_image_files(input_folder)
if not image_files:
messagebox.showinfo("信息", "所选文件夹中没有支持的图片文件")
return
# 开始转换
total = len(image_files)
success_count = 0
self.progress_var.set(0)
self.log("开始转换...")
self.status_var.set(f"正在转换: 0/{total}")
for i, input_path in enumerate(image_files, 1):
# 构建输出路径
filename = os.path.basename(input_path)
name_without_ext = os.path.splitext(filename)[0]
output_filename = f"{name_without_ext}.{target_format}"
if self.overwrite.get():
output_path = os.path.join(input_folder, output_filename)
else:
output_path = os.path.join(self.output_folder.get(), output_filename)
# 转换图片
success, msg = self.convert_image(input_path, output_path, target_format)
self.log(msg)
if success:
success_count += 1
# 更新进度
progress = (i / total) * 100
self.progress_var.set(progress)
self.status_var.set(f"正在转换: {i}/{total}")
self.root.update_idletasks()
# 转换完成
self.progress_var.set(100)
self.status_var.set("转换完成")
self.log(f"\n转换完成: 成功 {success_count}/{total}")
messagebox.showinfo("完成", f"转换完成!成功转换 {success_count}/{total} 张图片")
if __name__ == "__main__":
# 确保中文显示正常
root = tk.Tk()
app = ImageConverterApp(root)
root.mainloop()图片合成PDF
功能
选择一个文件夹,扫描图片,然后合成为一个pdf文件,一张图片一页。
需要的环境
需要pillow 库
代码
import os
import tkinter as tk
from tkinter import ttk, filedialog, messagebox, scrolledtext
from PIL import Image
class ImageToPDFConverter:
def __init__(self, root):
self.root = root
self.root.title("图片转PDF工具")
self.root.geometry("600x500")
self.root.resizable(True, True)
# 支持的图片格式
self.supported_formats = {'.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.tif', '.webp', '.gif'}
# 存储选择的图片文件
self.image_files = []
self.setup_ui()
def setup_ui(self):
# 主框架
main_frame = ttk.Frame(self.root, padding="10")
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# 配置网格权重
self.root.columnconfigure(0, weight=1)
self.root.rowconfigure(0, weight=1)
main_frame.columnconfigure(1, weight=1)
# 标题
title_label = ttk.Label(main_frame, text="图片转PDF转换器",
font=("Arial", 16, "bold"))
title_label.grid(row=0, column=0, columnspan=3, pady=(0, 20))
# 选择文件夹部分
folder_frame = ttk.LabelFrame(main_frame, text="选择图片文件夹", padding="10")
folder_frame.grid(row=1, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=(0, 10))
folder_frame.columnconfigure(1, weight=1)
ttk.Label(folder_frame, text="文件夹路径:").grid(row=0, column=0, sticky=tk.W, padx=(0, 10))
self.folder_path = tk.StringVar()
self.folder_entry = ttk.Entry(folder_frame, textvariable=self.folder_path, width=50)
self.folder_entry.grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(0, 10))
ttk.Button(folder_frame, text="浏览...",
command=self.browse_folder).grid(row=0, column=2)
ttk.Button(folder_frame, text="扫描图片",
command=self.scan_images).grid(row=1, column=0, columnspan=3, pady=(10, 0))
# 图片列表部分
list_frame = ttk.LabelFrame(main_frame, text="图片列表", padding="10")
list_frame.grid(row=2, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 10))
list_frame.columnconfigure(0, weight=1)
list_frame.rowconfigure(0, weight=1)
main_frame.rowconfigure(2, weight=1)
# 图片列表和滚动条
self.listbox_frame = ttk.Frame(list_frame)
self.listbox_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
self.listbox_frame.columnconfigure(0, weight=1)
self.listbox_frame.rowconfigure(0, weight=1)
self.image_listbox = tk.Listbox(self.listbox_frame, height=8)
self.image_listbox.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
list_scrollbar = ttk.Scrollbar(self.listbox_frame, orient=tk.VERTICAL,
command=self.image_listbox.yview)
list_scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))
self.image_listbox.configure(yscrollcommand=list_scrollbar.set)
# 控制按钮
control_frame = ttk.Frame(list_frame)
control_frame.grid(row=1, column=0, sticky=(tk.W, tk.E), pady=(10, 0))
ttk.Button(control_frame, text="上移",
command=self.move_up).pack(side=tk.LEFT, padx=(0, 5))
ttk.Button(control_frame, text="下移",
command=self.move_down).pack(side=tk.LEFT, padx=(0, 5))
ttk.Button(control_frame, text="删除选中",
command=self.delete_selected).pack(side=tk.LEFT, padx=(0, 5))
ttk.Button(control_frame, text="清空列表",
command=self.clear_list).pack(side=tk.LEFT)
# 输出设置部分
output_frame = ttk.LabelFrame(main_frame, text="输出设置", padding="10")
output_frame.grid(row=3, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=(0, 10))
output_frame.columnconfigure(1, weight=1)
ttk.Label(output_frame, text="PDF文件名:").grid(row=0, column=0, sticky=tk.W, padx=(0, 10))
self.pdf_name = tk.StringVar(value="output.pdf")
self.pdf_entry = ttk.Entry(output_frame, textvariable=self.pdf_name, width=50)
self.pdf_entry.grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(0, 10))
ttk.Button(output_frame, text="选择保存位置...",
command=self.browse_output).grid(row=0, column=2)
# 排序选项
sort_frame = ttk.Frame(output_frame)
sort_frame.grid(row=1, column=0, columnspan=3, sticky=tk.W, pady=(10, 0))
ttk.Label(sort_frame, text="排序方式:").pack(side=tk.LEFT, padx=(0, 10))
self.sort_method = tk.StringVar(value="name")
ttk.Radiobutton(sort_frame, text="按文件名",
variable=self.sort_method, value="name").pack(side=tk.LEFT, padx=(0, 10))
ttk.Radiobutton(sort_frame, text="按创建时间",
variable=self.sort_method, value="date").pack(side=tk.LEFT, padx=(0, 10))
ttk.Radiobutton(sort_frame, text="手动排序",
variable=self.sort_method, value="manual").pack(side=tk.LEFT)
# 转换按钮
button_frame = ttk.Frame(main_frame)
button_frame.grid(row=4, column=0, columnspan=3, pady=(10, 0))
ttk.Button(button_frame, text="开始转换",
command=self.convert_to_pdf,
style="Accent.TButton").pack(side=tk.LEFT, padx=(0, 10))
ttk.Button(button_frame, text="退出",
command=self.root.quit).pack(side=tk.LEFT)
# 状态栏
self.status_var = tk.StringVar(value="就绪")
status_bar = ttk.Label(main_frame, textvariable=self.status_var, relief=tk.SUNKEN)
status_bar.grid(row=5, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=(10, 0))
def browse_folder(self):
"""选择文件夹"""
folder = filedialog.askdirectory(title="选择包含图片的文件夹")
if folder:
self.folder_path.set(folder)
def browse_output(self):
"""选择输出PDF文件位置"""
filename = filedialog.asksaveasfilename(
title="保存PDF文件",
defaultextension=".pdf",
filetypes=[("PDF文件", "*.pdf"), ("所有文件", "*.*")]
)
if filename:
self.pdf_name.set(filename)
def scan_images(self):
"""扫描文件夹中的图片"""
folder = self.folder_path.get()
if not folder or not os.path.exists(folder):
messagebox.showerror("错误", "请选择有效的文件夹路径!")
return
self.image_files = []
for file in os.listdir(folder):
file_path = os.path.join(folder, file)
if os.path.isfile(file_path) and os.path.splitext(file)[1].lower() in self.supported_formats:
self.image_files.append(file_path)
if not self.image_files:
messagebox.showwarning("警告", "在指定文件夹中未找到支持的图片文件!")
self.status_var.set("未找到图片文件")
return
# 根据选择的排序方式排序
if self.sort_method.get() == "name":
self.image_files.sort()
elif self.sort_method.get() == "date":
self.image_files.sort(key=lambda x: os.path.getctime(x))
# manual 不排序
self.update_listbox()
self.status_var.set(f"找到 {len(self.image_files)} 张图片")
def update_listbox(self):
"""更新列表显示"""
self.image_listbox.delete(0, tk.END)
for img_path in self.image_files:
self.image_listbox.insert(tk.END, os.path.basename(img_path))
def move_up(self):
"""上移选中项目"""
selected = self.image_listbox.curselection()
if selected and selected[0] > 0:
index = selected[0]
self.image_files[index], self.image_files[index-1] = self.image_files[index-1], self.image_files[index]
self.update_listbox()
self.image_listbox.select_set(index-1)
def move_down(self):
"""下移选中项目"""
selected = self.image_listbox.curselection()
if selected and selected[0] < len(self.image_files) - 1:
index = selected[0]
self.image_files[index], self.image_files[index+1] = self.image_files[index+1], self.image_files[index]
self.update_listbox()
self.image_listbox.select_set(index+1)
def delete_selected(self):
"""删除选中项目"""
selected = self.image_listbox.curselection()
if selected:
index = selected[0]
del self.image_files[index]
self.update_listbox()
if self.image_files:
new_index = min(index, len(self.image_files) - 1)
self.image_listbox.select_set(new_index)
def clear_list(self):
"""清空列表"""
if messagebox.askyesno("确认", "确定要清空图片列表吗?"):
self.image_files.clear()
self.update_listbox()
self.status_var.set("列表已清空")
def convert_to_pdf(self):
"""转换为PDF"""
if not self.image_files:
messagebox.showerror("错误", "没有可转换的图片!")
return
output_pdf = self.pdf_name.get()
if not output_pdf:
messagebox.showerror("错误", "请输入PDF文件名!")
return
if not output_pdf.lower().endswith('.pdf'):
output_pdf += '.pdf'
try:
self.status_var.set("正在转换中...")
self.root.update()
# 转换为RGB模式并保存为PDF
images = []
for i, img_path in enumerate(self.image_files):
self.status_var.set(f"处理图片 {i+1}/{len(self.image_files)}: {os.path.basename(img_path)}")
self.root.update()
img = Image.open(img_path)
if img.mode != 'RGB':
img = img.convert('RGB')
images.append(img)
# 保存为PDF
if images:
images[0].save(
output_pdf,
save_all=True,
append_images=images[1:],
resolution=100.0
)
self.status_var.set(f"转换完成!共 {len(images)} 页")
messagebox.showinfo("成功", f"PDF文件已生成:\n{output_pdf}\n共 {len(images)} 页")
except Exception as e:
self.status_var.set("转换失败")
messagebox.showerror("错误", f"转换过程中出错:\n{str(e)}")
def main():
# 检查PIL是否安装
try:
from PIL import Image
except ImportError:
print("错误:需要安装Pillow库")
print("请运行: pip install Pillow")
return
root = tk.Tk()
app = ImageToPDFConverter(root)
root.mainloop()
if __name__ == "__main__":
main()
默认评论
Halo系统提供的评论