from tkinterdnd2 import DND_FILES, TkinterDnD
import tkinter as tk
from tkinter import filedialog, ttk
from PIL import Image, ImageTk
import os
from datetime import datetime
import sys
class ImageToIcoConverter:
def __init__(self, root):
self.root = root
self.root.title("图片格式转换器1.01 - by silent 【吾爱破解】")
# 设置窗口图标
try:
application_path = os.path.dirname(os.path.abspath(__file__))
icon_path = os.path.join(application_path, 'app.ico')
self.root.iconbitmap(icon_path)
except:
pass # 如果设置图标失败,忽略错误
# 增加窗口高度以容纳所有控件
window_width = 500
window_height = 550
# 获取屏幕尺寸
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
# 计算窗口居中位置
center_x = int((screen_width - window_width) / 2)
center_y = int((screen_height - window_height) / 2)
# 设置窗口大小和位置
self.root.geometry(f"{window_width}x{window_height}+{center_x}+{center_y}")
# 禁止调整窗口大小
self.root.resizable(False, False)
# 创建左右分栏
self.left_frame = ttk.Frame(root, padding="10")
self.left_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
self.right_frame = ttk.Frame(root, padding="10")
self.right_frame.grid(row=0, column=1, sticky=(tk.W, tk.E, tk.N, tk.S))
# 为整个窗口注册拖放功能
self.root.drop_target_register(DND_FILES)
self.root.dnd_bind('<<Drop>>', self.handle_drop)
# 为左右框架注册拖放功能
self.left_frame.drop_target_register(DND_FILES)
self.left_frame.dnd_bind('<<Drop>>', self.handle_drop)
self.right_frame.drop_target_register(DND_FILES)
self.right_frame.dnd_bind('<<Drop>>', self.handle_drop)
# 创建拖放提示标签
self.drop_label = ttk.Label(
self.left_frame,
text="拖放图片到窗口任意位置\n或点击选择图片按钮",
justify=tk.CENTER
)
self.drop_label.grid(row=0, column=0, pady=5)
# 为提示标签注册拖放功能
self.drop_label.drop_target_register(DND_FILES)
self.drop_label.dnd_bind('<<Drop>>', self.handle_drop)
# 选择文件按钮
self.select_btn = ttk.Button(self.left_frame, text="选择图片", command=self.select_file)
self.select_btn.grid(row=1, column=0, pady=5)
# 显示选中的文件路径
self.file_label = ttk.Label(self.left_frame, text="未选择文件")
self.file_label.grid(row=2, column=0, pady=3)
# 尺寸选择框架
self.size_frame = ttk.LabelFrame(self.left_frame, text="选择输出尺寸", padding="5")
self.size_frame.grid(row=3, column=0, pady=5)
# 预设尺寸选项
self.sizes = ["16x16", "32x32", "48x48", "64x64", "128x128", "256x256", "512x512", "1024x1024"]
self.size_var = tk.StringVar(value="32x32")
for i, size in enumerate(self.sizes):
ttk.Radiobutton(self.size_frame, text=size, value=size,
variable=self.size_var).grid(row=i//2, column=i%2, padx=10, pady=5)
# 添加格式选择框架
self.format_frame = ttk.LabelFrame(self.left_frame, text="选择输出格式", padding="5")
self.format_frame.grid(row=4, column=0, pady=5)
# 预设格式选项
self.formats = [
("ICO", "ico"),
("ICNS", "icns"),
("PNG", "png"),
("JPG", "jpg"),
("BMP", "bmp")
]
self.format_var = tk.StringVar(value="ico")
# 创建格式单选按钮
for i, (format_name, format_value) in enumerate(self.formats):
ttk.Radiobutton(
self.format_frame,
text=format_name,
value=format_value,
variable=self.format_var
).grid(row=i//2, column=i%2, padx=10, pady=5)
# 打开输出目录选项
self.open_dir_var = tk.BooleanVar(value=True)
self.open_dir_check = ttk.Checkbutton(
self.left_frame,
text="转换后打开输出目录",
variable=self.open_dir_var
)
self.open_dir_check.grid(row=5, column=0, pady=3)
# 转换按钮
self.convert_btn = ttk.Button(self.left_frame, text="转换", command=self.convert)
self.convert_btn.grid(row=6, column=0, pady=5)
# 状态标签
self.status_label = ttk.Label(self.left_frame, text="")
self.status_label.grid(row=7, column=0, pady=3)
# 添加预览框架
self.preview_frame = ttk.LabelFrame(self.right_frame, text="预览", padding="5")
self.preview_frame.grid(row=0, column=0, padx=10, pady=10)
# 预览图片标签
self.preview_label = ttk.Label(self.preview_frame)
self.preview_label.grid(row=0, column=0, padx=10, pady=10)
# 预览信息标签:文件名、尺寸、大小
self.info_label = ttk.Label(self.preview_frame, text="")
self.info_label.grid(row=1, column=0, padx=10, pady=(0, 10))
self.selected_file = None
self.current_dir = os.path.dirname(os.path.abspath(__file__))
self.preview_image = None # 保存预览图片的引用
def update_preview(self, image_path):
try:
# 打开图片
img = Image.open(image_path)
file_stat = os.stat(image_path)
# 计算预览图大小(保持比例,最大150x150)
preview_size = 150
width, height = img.size
ratio = min(preview_size/width, preview_size/height)
new_size = (int(width*ratio), int(height*ratio))
# 调整图片大小
preview_img = img.resize(new_size, Image.Resampling.LANCZOS)
# 转换为PhotoImage
self.preview_image = ImageTk.PhotoImage(preview_img)
# 更新预览标签
self.preview_label.configure(image=self.preview_image)
# 显示文件名、尺寸和大小
file_name = os.path.basename(image_path)
file_size_kb = file_stat.st_size / 1024
self.info_label.configure(text=f"{file_name} | {width}x{height} | {file_size_kb:.1f} KB")
except Exception as e:
self.status_label.config(text=f"预览失败:{str(e)}")
def select_file(self):
filetypes = (
('图片文件', '*.png *.jpg *.jpeg *.bmp'),
('所有文件', '*.*')
)
filename = filedialog.askopenfilename(
title='选择图片',
filetypes=filetypes,
initialdir=self.current_dir # 设置默认打开目录为软件所在目录
)
if filename:
self.selected_file = filename
self.file_label.config(text=os.path.basename(filename))
self.status_label.config(text="")
self.current_dir = os.path.dirname(filename)
# 更新预览图
self.update_preview(filename)
def convert(self):
if not self.selected_file:
self.status_label.config(text="请先选择图片文件!")
return
try:
# 打开原图
img = Image.open(self.selected_file)
# 获取选择的格式
output_format = self.format_var.get().upper()
# 获取用户选择的尺寸
target_size = tuple(map(int, self.size_var.get().split('x')))
# 根据输出格式处理图像
if output_format == 'ICO':
# ICO格式特殊处理
if img.mode != 'RGBA':
img = img.convert('RGBA')
elif output_format == 'ICNS':
# ICNS格式特殊处理,需要RGBA模式
if img.mode != 'RGBA':
img = img.convert('RGBA')
elif output_format == 'JPG':
# JPG不支持透明度,也不支持调色板等模式,统一转为 RGB
if img.mode != 'RGB':
img = img.convert('RGB')
# 设置默认文件名和文件类型
default_filename = 'favicon' if output_format == 'ICO' else ('icon' if output_format == 'ICNS' else 'image')
file_extension = f'.{self.format_var.get().lower()}'
# 保存文件对话框
save_path = filedialog.asksaveasfilename(
defaultextension=file_extension,
filetypes=[(f'{output_format}文件', f'*{file_extension}')],
initialfile=f'{default_filename}{file_extension}'
)
if save_path:
if output_format == 'ICNS':
# ICNS 仅生成用户选择尺寸的 144 DPI(Retina,对应 2x 像素)
original_width, original_height = img.size
original_max_size = max(original_width, original_height)
logical_max = max(target_size)
pixel_size = logical_max * 2 # 144 DPI 约等于 2x 像素
# 若原图不足以支撑 2x,使用原图最大边并提示
if original_max_size < pixel_size:
pixel_size = original_max_size
self.status_label.config(text=f"原图较小,已按 {pixel_size}x{pixel_size} 生成")
resized_img = img.resize((pixel_size, pixel_size), Image.Resampling.LANCZOS)
resized_img.save(save_path, format='ICNS')
print(f"已生成 ICNS:{pixel_size}x{pixel_size} (目标 {logical_max}@144DPI)")
elif output_format == 'ICO':
# 调整图片大小
resized_img = img.resize(target_size, Image.Resampling.LANCZOS)
resized_img.save(save_path, format='ICO', sizes=[target_size])
else:
# PNG / JPG / BMP 等普通位图,依靠扩展名自动识别格式
resized_img = img.resize(target_size, Image.Resampling.LANCZOS)
resized_img.save(save_path)
# 获取当前时间并格式化
current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
self.status_label.config(text=f"转换成功! {current_time}")
# 如果选择了打开输出目录选项,则打开目录
if self.open_dir_var.get():
output_dir = os.path.dirname(save_path)
if os.name == 'nt': # Windows
os.startfile(output_dir)
else: # macOS
import subprocess, sys
if sys.platform == 'darwin':
subprocess.run(['open', output_dir])
else:
subprocess.run(['xdg-open', output_dir])
except Exception as e:
self.status_label.config(text=f"转换失败:{str(e)}")
def handle_drop(self, event):
# 获取拖放的文件路径
file_path = event.data
# 处理Windows路径中的花括号
if file_path.startswith('{') and file_path.endswith('}'):
file_path = file_path[1:-1]
# 检查是否是支持的图片格式
supported_formats = ('.png', '.jpg', '.jpeg', '.bmp')
if file_path.lower().endswith(supported_formats):
self.selected_file = file_path
self.file_label.config(text=os.path.basename(file_path))
self.status_label.config(text="")
self.current_dir = os.path.dirname(file_path)
# 更新预览图
self.update_preview(file_path)
else:
self.status_label.config(text="不支持的文件格式!")
if __name__ == "__main__":
root = TkinterDnD.Tk() # 使用TkinterDnD.Tk替代tk.Tk
app = ImageToIcoConverter(root)
root.mainloop()