220 lines
6.4 KiB
Python
220 lines
6.4 KiB
Python
#!/usr/bin/env python3
|
|
|
|
import os
|
|
import sys
|
|
import struct
|
|
import hashlib
|
|
import json
|
|
from pathlib import Path
|
|
try:
|
|
from PIL import Image
|
|
HAS_PIL = True
|
|
except ImportError:
|
|
HAS_PIL = False
|
|
|
|
def sanitize_var_name(path_str):
|
|
var_name = path_str.replace('/', '_').replace('\\', '_').replace('.', '_').replace('-', '_')
|
|
var_name = ''.join(c if c.isalnum() or c == '_' else '_' for c in var_name)
|
|
if var_name[0].isdigit():
|
|
var_name = '_' + var_name
|
|
return var_name
|
|
|
|
def get_file_hash(file_path):
|
|
hash_md5 = hashlib.md5()
|
|
with open(file_path, 'rb') as f:
|
|
for chunk in iter(lambda: f.read(4096), b""):
|
|
hash_md5.update(chunk)
|
|
return hash_md5.hexdigest()
|
|
|
|
def load_cache(cache_file):
|
|
if cache_file.exists():
|
|
try:
|
|
with open(cache_file, 'r') as f:
|
|
return json.load(f)
|
|
except:
|
|
return {}
|
|
return {}
|
|
|
|
def save_cache(cache_file, cache_data):
|
|
with open(cache_file, 'w') as f:
|
|
json.dump(cache_data, f, indent=2)
|
|
|
|
def get_png_dimensions(data):
|
|
if len(data) < 24:
|
|
return 0, 0
|
|
if data[0:8] != b'\x89PNG\r\n\x1a\n':
|
|
return 0, 0
|
|
width = struct.unpack('>I', data[16:20])[0]
|
|
height = struct.unpack('>I', data[20:24])[0]
|
|
return width, height
|
|
|
|
def get_jpeg_dimensions(data):
|
|
if len(data) < 2:
|
|
return 0, 0
|
|
if data[0:2] != b'\xff\xd8':
|
|
return 0, 0
|
|
|
|
i = 2
|
|
while i < len(data) - 1:
|
|
if data[i] != 0xff:
|
|
return 0, 0
|
|
marker = data[i + 1]
|
|
i += 2
|
|
|
|
if marker == 0xd8 or marker == 0xd9 or marker == 0x01:
|
|
continue
|
|
if marker >= 0xd0 and marker <= 0xd7:
|
|
continue
|
|
|
|
if i + 2 > len(data):
|
|
return 0, 0
|
|
length = struct.unpack('>H', data[i:i+2])[0]
|
|
|
|
if marker >= 0xc0 and marker <= 0xcf and marker not in [0xc4, 0xc8, 0xcc]:
|
|
if i + 5 <= len(data):
|
|
height = struct.unpack('>H', data[i+3:i+5])[0]
|
|
width = struct.unpack('>H', data[i+5:i+7])[0]
|
|
return width, height
|
|
|
|
i += length
|
|
|
|
return 0, 0
|
|
|
|
def get_image_info(file_path, data):
|
|
ext = file_path.suffix.lower()
|
|
|
|
if ext == '.png':
|
|
width, height = get_png_dimensions(data)
|
|
return 'LV_IMG_CF_TRUE_COLOR_ALPHA', width, height
|
|
elif ext in ['.jpg', '.jpeg']:
|
|
width, height = get_jpeg_dimensions(data)
|
|
return 'LV_IMG_CF_TRUE_COLOR_ALPHA', width, height
|
|
|
|
return 'LV_IMG_CF_RAW', 0, 0
|
|
|
|
def file_to_c_file(file_path, var_name):
|
|
with open(file_path, 'rb') as f:
|
|
data = f.read()
|
|
|
|
color_format, width, height = get_image_info(file_path, data)
|
|
|
|
c_content = '#include "lvgl.h"\n\n'
|
|
|
|
c_content += f'static const unsigned char {var_name}_data[] = {{\n'
|
|
|
|
for i in range(0, len(data), 16):
|
|
chunk = data[i:i+16]
|
|
hex_values = ', '.join(f'0x{b:02x}' for b in chunk)
|
|
c_content += f' {hex_values},\n'
|
|
|
|
c_content += '};\n\n'
|
|
|
|
c_content += f'const lv_img_dsc_t {var_name} = {{\n'
|
|
c_content += ' .header = {\n'
|
|
c_content += f' .cf = {color_format},\n'
|
|
c_content += ' .always_zero = 0,\n'
|
|
c_content += ' .reserved = 0,\n'
|
|
c_content += f' .w = {width},\n'
|
|
c_content += f' .h = {height},\n'
|
|
c_content += ' },\n'
|
|
c_content += f' .data_size = {len(data)},\n'
|
|
c_content += f' .data = {var_name}_data,\n'
|
|
c_content += '};\n'
|
|
|
|
return c_content
|
|
|
|
def generate_assets(root_dir, output_dir):
|
|
root_path = Path(root_dir)
|
|
output_path = Path(output_dir)
|
|
|
|
if not root_path.exists():
|
|
print(f"Error: Directory {root_dir} does not exist")
|
|
return
|
|
|
|
output_path.mkdir(parents=True, exist_ok=True)
|
|
|
|
cache_file = output_path / '.assets_cache.json'
|
|
cache_data = load_cache(cache_file)
|
|
|
|
file_count = 0
|
|
updated_count = 0
|
|
skipped_count = 0
|
|
var_names = []
|
|
current_files = {}
|
|
|
|
for file_path in sorted(root_path.rglob('*')):
|
|
if file_path.is_file() and file_path.suffix not in ['.py', '.pyc']:
|
|
rel_path = file_path.relative_to(root_path)
|
|
rel_path_str = str(rel_path)
|
|
var_name = sanitize_var_name(rel_path_str)
|
|
|
|
output_c_file = output_path / (var_name + '.c')
|
|
|
|
file_hash = get_file_hash(file_path)
|
|
current_files[rel_path_str] = file_hash
|
|
|
|
cached_hash = cache_data.get(rel_path_str)
|
|
|
|
if cached_hash == file_hash and output_c_file.exists():
|
|
# print(f"Skipping (unchanged): {rel_path}")
|
|
skipped_count += 1
|
|
else:
|
|
# print(f"Processing: {rel_path} -> {output_c_file.name}")
|
|
|
|
c_content = file_to_c_file(file_path, var_name)
|
|
|
|
with open(output_c_file, 'w') as f:
|
|
f.write(c_content)
|
|
|
|
updated_count += 1
|
|
|
|
var_names.append(var_name)
|
|
file_count += 1
|
|
|
|
for old_file in cache_data.keys():
|
|
if old_file not in current_files:
|
|
var_name = sanitize_var_name(old_file)
|
|
old_c_file = output_path / (var_name + '.c')
|
|
if old_c_file.exists():
|
|
old_c_file.unlink()
|
|
print(f"Removed: {old_c_file.name}")
|
|
|
|
header_file = output_path / 'lisa_ui_assets.h'
|
|
h_content = '#ifndef __LISA_UI_ASSETS_H__\n'
|
|
h_content += '#define __LISA_UI_ASSETS_H__\n\n'
|
|
h_content += '#include "lvgl.h"\n\n'
|
|
|
|
for var_name in var_names:
|
|
h_content += f'LV_IMG_DECLARE({var_name});\n'
|
|
|
|
h_content += '\n#endif\n'
|
|
|
|
header_needs_update = True
|
|
if header_file.exists():
|
|
with open(header_file, 'r') as f:
|
|
old_content = f.read()
|
|
header_needs_update = (old_content != h_content)
|
|
|
|
if header_needs_update:
|
|
with open(header_file, 'w') as f:
|
|
f.write(h_content)
|
|
print(f"Updated header: {header_file.name}")
|
|
else:
|
|
print(f"Header unchanged: {header_file.name}")
|
|
|
|
save_cache(cache_file, current_files)
|
|
|
|
print(f"\nTotal: {file_count} files, Updated: {updated_count}, Skipped: {skipped_count}")
|
|
print(f"Output directory: {output_dir}")
|
|
|
|
if __name__ == '__main__':
|
|
if len(sys.argv) < 2:
|
|
print("Usage: python assets.py <source_directory> [output_directory]")
|
|
print("Example: python assets.py emoji ./generated")
|
|
sys.exit(1)
|
|
|
|
root_dir = sys.argv[1]
|
|
output_dir = sys.argv[2] if len(sys.argv) > 2 else './generated'
|
|
|
|
generate_assets(root_dir, output_dir)
|