253 lines
11 KiB
Python
253 lines
11 KiB
Python
import tkinter as tk
|
||
from tkinter import ttk, filedialog, messagebox
|
||
from PIL import Image
|
||
import os
|
||
import tempfile
|
||
import sys
|
||
from LVGLImage import LVGLImage, ColorFormat, CompressMethod
|
||
|
||
HELP_TEXT = """LVGL图片转换工具使用说明:
|
||
|
||
1. 添加文件:点击“添加文件”按钮选择需要转换的图片,支持批量导入
|
||
|
||
2. 移除文件:在列表中选中文件前的复选框“[ ]”(选中后会变成“[√]”),点击“移除选中”可删除选定文件
|
||
|
||
3. 设置分辨率:选择需要的分辨率,如128x128
|
||
建议根据自己的设备的屏幕分辨率来选择。过大和过小都会影响显示效果。
|
||
|
||
4. 颜色格式:选择“自动识别”会根据图片是否透明自动选择,或手动指定
|
||
除非你了解这个选项,否则建议使用自动识别,不然可能会出现一些意想不到的问题……
|
||
|
||
5. 压缩方式:选择NONE或RLE压缩
|
||
除非你了解这个选项,否则建议保持默认NONE不压缩
|
||
|
||
6. 输出目录:设置转换后文件的保存路径
|
||
默认为程序所在目录下的output文件夹
|
||
|
||
7. 转换:点击“转换全部”或“转换选中”开始转换
|
||
"""
|
||
|
||
class ImageConverterApp:
|
||
def __init__(self, root):
|
||
self.root = root
|
||
self.root.title("LVGL图片转换工具")
|
||
self.root.geometry("750x650")
|
||
|
||
# 初始化变量
|
||
self.output_dir = tk.StringVar(value=os.path.abspath("output"))
|
||
self.resolution = tk.StringVar(value="128x128")
|
||
self.color_format = tk.StringVar(value="自动识别")
|
||
self.compress_method = tk.StringVar(value="NONE")
|
||
|
||
# 创建UI组件
|
||
self.create_widgets()
|
||
self.redirect_output()
|
||
|
||
def create_widgets(self):
|
||
# 参数设置框架
|
||
settings_frame = ttk.LabelFrame(self.root, text="转换设置")
|
||
settings_frame.grid(row=0, column=0, padx=10, pady=5, sticky="ew")
|
||
|
||
# 分辨率设置
|
||
ttk.Label(settings_frame, text="分辨率:").grid(row=0, column=0, padx=2)
|
||
ttk.Combobox(settings_frame, textvariable=self.resolution,
|
||
values=["128x128", "64x64", "32x32"], width=8).grid(row=0, column=1, padx=2)
|
||
|
||
# 颜色格式
|
||
ttk.Label(settings_frame, text="颜色格式:").grid(row=0, column=2, padx=2)
|
||
ttk.Combobox(settings_frame, textvariable=self.color_format,
|
||
values=["自动识别", "RGB565", "RGB565A8"], width=10).grid(row=0, column=3, padx=2)
|
||
|
||
# 压缩方式
|
||
ttk.Label(settings_frame, text="压缩方式:").grid(row=0, column=4, padx=2)
|
||
ttk.Combobox(settings_frame, textvariable=self.compress_method,
|
||
values=["NONE", "RLE"], width=8).grid(row=0, column=5, padx=2)
|
||
|
||
# 文件操作框架
|
||
file_frame = ttk.LabelFrame(self.root, text="输入文件")
|
||
file_frame.grid(row=1, column=0, padx=10, pady=5, sticky="nsew")
|
||
|
||
# 文件操作按钮
|
||
btn_frame = ttk.Frame(file_frame)
|
||
btn_frame.pack(fill=tk.X, pady=2)
|
||
ttk.Button(btn_frame, text="添加文件", command=self.select_files).pack(side=tk.LEFT, padx=2)
|
||
ttk.Button(btn_frame, text="移除选中", command=self.remove_selected).pack(side=tk.LEFT, padx=2)
|
||
ttk.Button(btn_frame, text="清空列表", command=self.clear_files).pack(side=tk.LEFT, padx=2)
|
||
|
||
# 文件列表(Treeview)
|
||
self.tree = ttk.Treeview(file_frame, columns=("selected", "filename"),
|
||
show="headings", height=10)
|
||
self.tree.heading("selected", text="选中", anchor=tk.W)
|
||
self.tree.heading("filename", text="文件名", anchor=tk.W)
|
||
self.tree.column("selected", width=60, anchor=tk.W)
|
||
self.tree.column("filename", width=600, anchor=tk.W)
|
||
self.tree.pack(fill=tk.BOTH, expand=True)
|
||
self.tree.bind("<ButtonRelease-1>", self.on_tree_click)
|
||
|
||
# 输出目录
|
||
output_frame = ttk.LabelFrame(self.root, text="输出目录")
|
||
output_frame.grid(row=2, column=0, padx=10, pady=5, sticky="ew")
|
||
ttk.Entry(output_frame, textvariable=self.output_dir, width=60).pack(side=tk.LEFT, padx=5)
|
||
ttk.Button(output_frame, text="浏览", command=self.select_output_dir).pack(side=tk.RIGHT, padx=5)
|
||
|
||
# 转换按钮和帮助按钮
|
||
convert_frame = ttk.Frame(self.root)
|
||
convert_frame.grid(row=3, column=0, padx=10, pady=10)
|
||
ttk.Button(convert_frame, text="转换全部文件", command=lambda: self.start_conversion(True)).pack(side=tk.LEFT, padx=5)
|
||
ttk.Button(convert_frame, text="转换选中文件", command=lambda: self.start_conversion(False)).pack(side=tk.LEFT, padx=5)
|
||
ttk.Button(convert_frame, text="帮助", command=self.show_help).pack(side=tk.RIGHT, padx=5)
|
||
|
||
# 日志区域(新增清空按钮部分)
|
||
log_frame = ttk.LabelFrame(self.root, text="日志")
|
||
log_frame.grid(row=4, column=0, padx=10, pady=5, sticky="nsew")
|
||
|
||
# 添加按钮框架
|
||
log_btn_frame = ttk.Frame(log_frame)
|
||
log_btn_frame.pack(fill=tk.X, side=tk.BOTTOM)
|
||
|
||
# 清空日志按钮
|
||
ttk.Button(log_btn_frame, text="清空日志", command=self.clear_log).pack(side=tk.RIGHT, padx=5, pady=2)
|
||
|
||
self.log_text = tk.Text(log_frame, height=15)
|
||
self.log_text.pack(fill=tk.BOTH, expand=True)
|
||
|
||
# 布局配置
|
||
self.root.columnconfigure(0, weight=1)
|
||
self.root.rowconfigure(1, weight=1)
|
||
self.root.rowconfigure(4, weight=1)
|
||
|
||
def clear_log(self):
|
||
"""清空日志内容"""
|
||
self.log_text.delete(1.0, tk.END)
|
||
|
||
def show_help(self):
|
||
messagebox.showinfo("帮助", HELP_TEXT)
|
||
|
||
def redirect_output(self):
|
||
class StdoutRedirector:
|
||
def __init__(self, text_widget):
|
||
self.text_widget = text_widget
|
||
self.original_stdout = sys.stdout
|
||
|
||
def write(self, message):
|
||
self.text_widget.insert(tk.END, message)
|
||
self.text_widget.see(tk.END)
|
||
self.original_stdout.write(message)
|
||
|
||
def flush(self):
|
||
self.original_stdout.flush()
|
||
|
||
sys.stdout = StdoutRedirector(self.log_text)
|
||
|
||
def on_tree_click(self, event):
|
||
region = self.tree.identify("region", event.x, event.y)
|
||
if region == "cell":
|
||
col = self.tree.identify_column(event.x)
|
||
item = self.tree.identify_row(event.y)
|
||
if col == "#1": # 点击的是选中列
|
||
current_val = self.tree.item(item, "values")[0]
|
||
new_val = "[√]" if current_val == "[ ]" else "[ ]"
|
||
self.tree.item(item, values=(new_val, self.tree.item(item, "values")[1]))
|
||
|
||
def select_output_dir(self):
|
||
path = filedialog.askdirectory()
|
||
if path:
|
||
self.output_dir.set(path)
|
||
|
||
def select_files(self):
|
||
files = filedialog.askopenfilenames(filetypes=[("图片文件", "*.png;*.jpg;*.jpeg;*.bmp;*.gif")])
|
||
for f in files:
|
||
self.tree.insert("", tk.END, values=("[ ]", os.path.basename(f)), tags=(f,))
|
||
|
||
def remove_selected(self):
|
||
to_remove = []
|
||
for item in self.tree.get_children():
|
||
if self.tree.item(item, "values")[0] == "[√]":
|
||
to_remove.append(item)
|
||
for item in reversed(to_remove):
|
||
self.tree.delete(item)
|
||
|
||
def clear_files(self):
|
||
for item in self.tree.get_children():
|
||
self.tree.delete(item)
|
||
|
||
def start_conversion(self, convert_all):
|
||
input_files = [
|
||
self.tree.item(item, "tags")[0]
|
||
for item in self.tree.get_children()
|
||
if convert_all or self.tree.item(item, "values")[0] == "[√]"
|
||
]
|
||
|
||
if not input_files:
|
||
msg = "没有找到可转换的文件" if convert_all else "没有选中任何文件"
|
||
messagebox.showwarning("警告", msg)
|
||
return
|
||
|
||
os.makedirs(self.output_dir.get(), exist_ok=True)
|
||
|
||
# 解析转换参数
|
||
width, height = map(int, self.resolution.get().split('x'))
|
||
compress = CompressMethod.RLE if self.compress_method.get() == "RLE" else CompressMethod.NONE
|
||
|
||
# 执行转换
|
||
self.convert_images(input_files, width, height, compress)
|
||
|
||
def convert_images(self, input_files, width, height, compress):
|
||
success_count = 0
|
||
total_files = len(input_files)
|
||
|
||
for idx, file_path in enumerate(input_files):
|
||
try:
|
||
print(f"正在处理: {os.path.basename(file_path)}")
|
||
|
||
with Image.open(file_path) as img:
|
||
# 调整图片大小
|
||
img = img.resize((width, height), Image.Resampling.LANCZOS)
|
||
|
||
# 处理颜色格式
|
||
color_format_str = self.color_format.get()
|
||
if color_format_str == "自动识别":
|
||
# 检测透明通道
|
||
has_alpha = img.mode in ('RGBA', 'LA') or (img.mode == 'P' and 'transparency' in img.info)
|
||
if has_alpha:
|
||
img = img.convert('RGBA')
|
||
cf = ColorFormat.RGB565A8
|
||
else:
|
||
img = img.convert('RGB')
|
||
cf = ColorFormat.RGB565
|
||
else:
|
||
if color_format_str == "RGB565A8":
|
||
img = img.convert('RGBA')
|
||
cf = ColorFormat.RGB565A8
|
||
else:
|
||
img = img.convert('RGB')
|
||
cf = ColorFormat.RGB565
|
||
|
||
# 保存调整后的图片
|
||
base_name = os.path.splitext(os.path.basename(file_path))[0]
|
||
output_image_path = os.path.join(self.output_dir.get(), f"{base_name}_{width}x{height}.png")
|
||
img.save(output_image_path, 'PNG')
|
||
|
||
# 创建临时文件
|
||
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmpfile:
|
||
temp_path = tmpfile.name
|
||
img.save(temp_path, 'PNG')
|
||
|
||
# 转换为LVGL C数组
|
||
lvgl_img = LVGLImage().from_png(temp_path, cf=cf)
|
||
output_c_path = os.path.join(self.output_dir.get(), f"{base_name}.c")
|
||
lvgl_img.to_c_array(output_c_path, compress=compress)
|
||
|
||
success_count += 1
|
||
os.unlink(temp_path)
|
||
print(f"成功转换: {base_name}.c\n")
|
||
|
||
except Exception as e:
|
||
print(f"转换失败: {str(e)}\n")
|
||
|
||
print(f"转换完成! 成功 {success_count}/{total_files} 个文件\n")
|
||
|
||
if __name__ == "__main__":
|
||
root = tk.Tk()
|
||
app = ImageConverterApp(root)
|
||
root.mainloop() |