菜单支持,所有功能都测试成功
This commit is contained in:
@@ -1,14 +1,10 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
STM32 ST7735S LCD 字模生成工具(Pillow 版)
|
||||
- ASCII 8×16:生成 lcd_data.c
|
||||
- CJK 16×16:生成 hz16_data.c + hz16_data.h(含二分查找函数)
|
||||
STM32 ST7735S LCD CJK 字模生成工具(Pillow 版)
|
||||
生成 hz16_data.c + hz16_data.h(含二分查找函数)
|
||||
|
||||
用法:
|
||||
# ASCII
|
||||
python font_gen.py --font simsun --size 56 --threshold 64
|
||||
|
||||
# CJK 常用汉字(GB2312 一级,约 3755 字)
|
||||
python font_gen.py --cjk --font simsun --size 32 --threshold 64
|
||||
|
||||
@@ -19,7 +15,6 @@ STM32 ST7735S LCD 字模生成工具(Pillow 版)
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
import struct
|
||||
|
||||
try:
|
||||
from PIL import Image, ImageFont, ImageDraw
|
||||
@@ -27,12 +22,44 @@ except ImportError:
|
||||
print("请安装 Pillow: pip install Pillow", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# ── Otsu 自适应阈值 ──
|
||||
|
||||
def otsu_threshold(pixels: list) -> int:
|
||||
"""Otsu 算法自动计算最佳二值化阈值"""
|
||||
hist = [0] * 256
|
||||
for v in pixels:
|
||||
hist[v] += 1
|
||||
total = len(pixels)
|
||||
sum_all = sum(i * hist[i] for i in range(256))
|
||||
sum_bg = 0
|
||||
w_bg = 0
|
||||
w_fg = 0
|
||||
max_var = 0
|
||||
best = 128
|
||||
for t in range(256):
|
||||
w_bg += hist[t]
|
||||
if w_bg == 0:
|
||||
continue
|
||||
w_fg = total - w_bg
|
||||
if w_fg == 0:
|
||||
break
|
||||
sum_bg += t * hist[t]
|
||||
mean_bg = sum_bg / w_bg
|
||||
mean_fg = (sum_all - sum_bg) / w_fg
|
||||
between_var = w_bg * w_fg * (mean_bg - mean_fg) * (mean_bg - mean_fg)
|
||||
if between_var > max_var:
|
||||
max_var = between_var
|
||||
best = t
|
||||
return best
|
||||
|
||||
|
||||
# ── 通用渲染 ──
|
||||
|
||||
def render_char(font, char: str, w: int, h: int, scale: int = 1,
|
||||
threshold: int = 64) -> list:
|
||||
threshold: int = None) -> list:
|
||||
"""
|
||||
在 w*scale × h*scale 画布上渲染字符,缩放到 w×h
|
||||
threshold=None 时自动使用 Otsu 算法
|
||||
返回逐行字节列表(MSB 优先)
|
||||
"""
|
||||
bw, bh = w * scale, h * scale
|
||||
@@ -42,13 +69,20 @@ def render_char(font, char: str, w: int, h: int, scale: int = 1,
|
||||
bbox = draw.textbbox((0, 0), char, font=font)
|
||||
tw = bbox[2] - bbox[0]
|
||||
th = bbox[3] - bbox[1]
|
||||
cx = (bw - tw) // 2 - bbox[0]
|
||||
cy = (bh - th) // 2 - bbox[1]
|
||||
|
||||
# 防止 bbox[0]/bbox[1] 为负导致偏移错误
|
||||
left = bbox[0] if bbox[0] > 0 else 0
|
||||
top = bbox[1] if bbox[1] > 0 else 0
|
||||
cx = (bw - tw) // 2 - left
|
||||
cy = (bh - th) // 2 - top
|
||||
draw.text((cx, cy), char, font=font, fill=255)
|
||||
|
||||
small = img.resize((w, h), Image.LANCZOS)
|
||||
pixels = list(small.getdata())
|
||||
|
||||
if threshold is None:
|
||||
threshold = otsu_threshold(pixels)
|
||||
|
||||
bytes_per_row = (w + 7) // 8
|
||||
result = []
|
||||
for r in range(h):
|
||||
@@ -62,57 +96,6 @@ def render_char(font, char: str, w: int, h: int, scale: int = 1,
|
||||
return result
|
||||
|
||||
|
||||
# ── ASCII 8×16 ──
|
||||
|
||||
def generate_ascii(output_path: str, font_path: str, size: int,
|
||||
threshold: int, scale: int = 4):
|
||||
font = ImageFont.truetype(font_path, size)
|
||||
chars = list(range(32, 127))
|
||||
N = len(chars)
|
||||
|
||||
lines = [
|
||||
'#include "lcd_data.h"',
|
||||
'/*ASCII字模数据*********************/',
|
||||
'',
|
||||
'/*宽8像素,高16像素*/',
|
||||
f'/* 字体: {os.path.basename(font_path)}, 字号: {size}, 阈值: {threshold} */',
|
||||
'const uint8_t LCD_F8x16[][16] =',
|
||||
'{',
|
||||
]
|
||||
|
||||
for idx, ch in enumerate(chars):
|
||||
if ch == ord('_'):
|
||||
bitmap = [0x00] * 15 + [0x7F]
|
||||
else:
|
||||
bitmap = render_char(font, chr(ch), 8, 16, scale, threshold)
|
||||
|
||||
show = chr(ch) if 32 <= ch <= 126 else '?'
|
||||
comma = ',' if idx < N - 1 else ''
|
||||
line1 = ', '.join(f'0x{b:02X}' for b in bitmap[:8])
|
||||
line2 = ', '.join(f'0x{b:02X}' for b in bitmap[8:])
|
||||
lines.append(f' {line1},')
|
||||
lines.append(f' {line2}{comma} // {show} {idx}')
|
||||
if (idx + 1) % 10 == 0:
|
||||
print(f" ASCII 进度: {idx+1}/{N}")
|
||||
|
||||
lines.append('};')
|
||||
lines.append('')
|
||||
lines.append('/*宽6像素,高8像素(未使用)*/')
|
||||
lines.append('const uint8_t LCD_F6x8[][6] =')
|
||||
lines.append('{')
|
||||
for idx, ch in enumerate(chars):
|
||||
show = chr(ch) if 32 <= ch <= 126 else '?'
|
||||
comma = ',' if idx < N - 1 else ''
|
||||
lines.append(f' 0x00,0x00,0x00,0x00,0x00,0x00{comma} // {show} {idx}')
|
||||
lines.append('};')
|
||||
lines.append('')
|
||||
|
||||
content = '\n'.join(lines)
|
||||
with open(output_path, 'w', encoding='utf-8', newline='\n') as f:
|
||||
f.write(content)
|
||||
print(f"已生成 ASCII 字模: {output_path}")
|
||||
|
||||
|
||||
# ── CJK 16×16 ──
|
||||
|
||||
CJK_W, CJK_H = 16, 16
|
||||
@@ -344,50 +327,42 @@ def _resolve_font(name_or_path: str) -> str:
|
||||
|
||||
|
||||
def main():
|
||||
p = argparse.ArgumentParser(description='字模生成工具')
|
||||
p = argparse.ArgumentParser(description='CJK 字模生成工具')
|
||||
p.add_argument('--font', default='simsun', help='字体名称或路径')
|
||||
p.add_argument('--size', type=int, default=64, help='渲染字号')
|
||||
p.add_argument('--threshold', type=int, default=50, help='二值化阈值(默认50)')
|
||||
p.add_argument('--scale', type=int, default=4, help='缩放倍率')
|
||||
|
||||
g = p.add_argument_group('ASCII 8×16')
|
||||
g.add_argument('--ascii', action='store_true', help='生成 ASCII 字模(默认)')
|
||||
|
||||
g = p.add_argument_group('CJK 16×16')
|
||||
g.add_argument('--cjk', action='store_true', help='生成 CJK 汉字字模')
|
||||
g.add_argument('--common', action='store_true', help='CJK:仅常用汉字(GB2312 一级)')
|
||||
g.add_argument('--gb2312', action='store_true', help='CJK:GB2312 全部汉字')
|
||||
g.add_argument('--punct', action='store_true', help='CJK:加入中文标点/全角字符')
|
||||
g.add_argument('--string', type=str, default=None, help='CJK:仅生成指定字符串中的字符(节省空间)')
|
||||
g.add_argument('--output', '-o', default=None, help='CJK 输出基名(不含扩展名)')
|
||||
p.add_argument('--size', type=int, default=None, help='渲染字号')
|
||||
p.add_argument('--threshold', type=int, default=None, help='二值化阈值(默认 Otsu 自适应)')
|
||||
p.add_argument('--scale', type=int, default=None, help='缩放倍率(越大边缘越平滑)')
|
||||
p.add_argument('--cjk', action='store_true', help='生成 CJK 汉字字模(必须指定)')
|
||||
p.add_argument('--common', action='store_true', help='CJK:仅常用汉字(GB2312 一级)')
|
||||
p.add_argument('--gb2312', action='store_true', help='CJK:GB2312 全部汉字')
|
||||
p.add_argument('--punct', action='store_true', help='CJK:加入中文标点/全角字符')
|
||||
p.add_argument('--string', type=str, default=None, help='CJK:仅生成指定字符串中的字符(节省空间)')
|
||||
p.add_argument('--output', '-o', default=None, help='CJK 输出基名(不含扩展名)')
|
||||
|
||||
args = p.parse_args()
|
||||
|
||||
font_path = _resolve_font(args.font)
|
||||
print(f"字体: {font_path}")
|
||||
|
||||
if not args.cjk:
|
||||
print("请指定 --cjk 来生成 CJK 字库,ASCII 字库已不再由此工具生成")
|
||||
return
|
||||
|
||||
proj_dir = os.path.abspath(os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
'..', 'STM32F103C8T6', 'Drivers', 'BSP', 'LCD'))
|
||||
|
||||
if args.cjk:
|
||||
size = args.size or 48
|
||||
scale = args.scale or 3
|
||||
out_base = args.output or os.path.join(proj_dir, 'hz16_data')
|
||||
size = args.size if args.size is not None else 48
|
||||
scale = args.scale if args.scale is not None else 3
|
||||
out_base = args.output or os.path.join(proj_dir, 'hz16_data')
|
||||
|
||||
if args.string is not None:
|
||||
unicodes = sorted(set(ord(c) for c in args.string))
|
||||
generate_cjk_direct(out_base, font_path, size, args.threshold, scale, unicodes)
|
||||
else:
|
||||
generate_cjk(out_base, font_path, size, args.threshold, scale,
|
||||
mode='common' if args.common else ('gb2312' if args.gb2312 else 'common'),
|
||||
punct=args.punct)
|
||||
if args.string is not None:
|
||||
unicodes = sorted(set(ord(c) for c in args.string))
|
||||
generate_cjk_direct(out_base, font_path, size, args.threshold, scale, unicodes)
|
||||
else:
|
||||
# 默认 ASCII
|
||||
size = args.size or 56
|
||||
scale = args.scale or 4
|
||||
out = os.path.join(proj_dir, 'lcd_data.c')
|
||||
generate_ascii(out, font_path, size, args.threshold, scale)
|
||||
generate_cjk(out_base, font_path, size, args.threshold, scale,
|
||||
mode='common' if args.common else ('gb2312' if args.gb2312 else 'common'),
|
||||
punct=args.punct)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
Reference in New Issue
Block a user