完成一个主界面开发流程
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
i1_to_png.py — 将 LVGL 模拟器 `--screenshot` 产出的 I1(1bpp) 原始位图转换为 PNG。
|
||||
|
||||
输入格式(模拟器 main.c 的 --screenshot 模式输出):
|
||||
- 1 位/像素,每字节 MSB 在左(bit7=该行最左像素),bit=1 为前景、bit=0 为背景
|
||||
- 每行 stride = 宽 / 8 字节,行内无填充
|
||||
- 模拟器当前已剥离 8 字节调色板,文件即纯像素数据(宽/8 × 高 字节)
|
||||
- 若遇到仍含调色板的旧文件(像素数据 + 8 字节),--auto-palette 会自动识别并跳过
|
||||
|
||||
用法示例:
|
||||
python i1_to_png.py shot.bin # -> shot.png(默认 160x160)
|
||||
python i1_to_png.py shot.bin -o out.png # 指定输出
|
||||
python i1_to_png.py shot.bin --scale 6 # 放大 6 倍便于查看(最近邻,保持像素感)
|
||||
python i1_to_png.py shot.bin -W 320 -H 240 # 非默认分辨率
|
||||
python i1_to_png.py shot.bin --fg 0,0,0 --bg 255,255,255 # 自定义前/背景色(RGB 模式)
|
||||
python i1_to_png.py shot.bin --invert # 前景/背景互换
|
||||
|
||||
依赖:Pillow(pip install pillow)。本仓库环境已验证 PIL 12.2.0。
|
||||
退出码:0 成功;1 参数/数据错误。
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
|
||||
# DTU 屏幕默认分辨率(160x160,1bpp)
|
||||
DEFAULT_W = 160
|
||||
DEFAULT_H = 160
|
||||
# I1 调色板大小(2 色 × 4 字节)。模拟器新版已剥离,旧文件可能残留。
|
||||
I1_PALETTE_BYTES = 2 * 4
|
||||
|
||||
|
||||
def _decode_i1(data, width, height):
|
||||
"""把原始 I1 数据解成 (height, width) 的 0/1 矩阵。MSB 在左。"""
|
||||
stride = width // 8
|
||||
if width % 8 != 0:
|
||||
raise ValueError("宽度必须是 8 的整数倍(I1 按字节打包),当前 %d" % width)
|
||||
need = stride * height
|
||||
if len(data) < need:
|
||||
raise ValueError("数据不足:%d 字节 < 需要 %d 字节(%dx%d I1)"
|
||||
% (len(data), need, width, height))
|
||||
px = []
|
||||
for y in range(height):
|
||||
base = y * stride
|
||||
row = [0] * width
|
||||
for x in range(width):
|
||||
byte_i = x >> 3
|
||||
bit_i = 7 - (x & 7)
|
||||
row[x] = (data[base + byte_i] >> bit_i) & 1
|
||||
px.append(row)
|
||||
return px
|
||||
|
||||
|
||||
def _pick_palette_offset(data, width, height, auto):
|
||||
"""返回像素数据在文件中的起始偏移(跳过调色板)。"""
|
||||
stride = width // 8
|
||||
need = stride * height
|
||||
sizes = [len(data)]
|
||||
if auto:
|
||||
# 像素数据前可能带 I1 调色板(8 字节)
|
||||
if len(data) == need + I1_PALETTE_BYTES:
|
||||
return I1_PALETTE_BYTES
|
||||
if len(data) < need:
|
||||
raise ValueError("数据不足:%d 字节 < 像素所需 %d 字节" % (len(data), need))
|
||||
return 0
|
||||
|
||||
|
||||
def i1_to_png(input_path, output_path=None, width=DEFAULT_W, height=DEFAULT_H,
|
||||
scale=1, invert=False, auto_palette=True,
|
||||
fg=None, bg=None):
|
||||
"""核心转换函数,可被其他脚本直接 import 调用。
|
||||
|
||||
fg / bg 形如 (r,g,b);为 None 时用黑白(L 模式)。
|
||||
返回输出文件路径。
|
||||
"""
|
||||
from PIL import Image
|
||||
|
||||
if not os.path.isfile(input_path):
|
||||
raise FileNotFoundError(input_path)
|
||||
|
||||
with open(input_path, "rb") as f:
|
||||
data = f.read()
|
||||
|
||||
off = _pick_palette_offset(data, width, height, auto_palette)
|
||||
px = _decode_i1(data[off:], width, height)
|
||||
|
||||
nw, nh = width, height
|
||||
|
||||
if fg is None and bg is None:
|
||||
# 灰度:bit=1 -> 白(255),bit=0 -> 黑(0)
|
||||
img = Image.new("L", (nw, nh))
|
||||
ipx = img.load()
|
||||
for y in range(nh):
|
||||
for x in range(nw):
|
||||
bit = px[y][x]
|
||||
if invert:
|
||||
bit ^= 1
|
||||
ipx[x, y] = 255 if bit else 0
|
||||
else:
|
||||
fgc = fg if fg is not None else (255, 255, 255)
|
||||
bgc = bg if bg is not None else (0, 0, 0)
|
||||
if invert:
|
||||
fgc, bgc = bgc, fgc
|
||||
img = Image.new("RGB", (nw, nh), bgc)
|
||||
ipx = img.load()
|
||||
for y in range(nh):
|
||||
for x in range(nw):
|
||||
bit = px[y][x]
|
||||
ipx[x, y] = fgc if bit else bgc
|
||||
|
||||
# 放大(最近邻,保持 1bpp 像素感)
|
||||
if scale and scale > 1:
|
||||
img = img.resize((nw * scale, nh * scale), Image.NEAREST)
|
||||
|
||||
if output_path is None:
|
||||
output_path = os.path.splitext(input_path)[0] + ".png"
|
||||
img.save(output_path, "PNG")
|
||||
return output_path
|
||||
|
||||
|
||||
def _parse_rgb(s):
|
||||
if s is None:
|
||||
return None
|
||||
parts = s.split(",")
|
||||
if len(parts) != 3:
|
||||
raise argparse.ArgumentTypeError("颜色格式应为 R,G,B,如 255,0,0")
|
||||
try:
|
||||
r, g, b = (int(p) for p in parts)
|
||||
except ValueError:
|
||||
raise argparse.ArgumentTypeError("颜色分量必须是整数")
|
||||
for v in (r, g, b):
|
||||
if not (0 <= v <= 255):
|
||||
raise argparse.ArgumentTypeError("颜色分量需在 0~255")
|
||||
return (r, g, b)
|
||||
|
||||
|
||||
def _ensure_utf8_console():
|
||||
"""Windows 控制台默认 GBK,中文会乱码;统一切到 UTF-8(不支持则静默跳过)。"""
|
||||
for stream in (sys.stdout, sys.stderr):
|
||||
try:
|
||||
stream.reconfigure(encoding="utf-8")
|
||||
except (AttributeError, ValueError):
|
||||
pass
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
_ensure_utf8_console()
|
||||
ap = argparse.ArgumentParser(
|
||||
description="将模拟器 I1(1bpp) 原始截图转换为 PNG",
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
)
|
||||
ap.add_argument("input", help="I1 原始位图文件(.bin)")
|
||||
ap.add_argument("-o", "--output", default=None,
|
||||
help="输出 PNG 路径(默认与输入同名 .png)")
|
||||
ap.add_argument("-W", "--width", type=int, default=DEFAULT_W, help="图像宽度")
|
||||
ap.add_argument("-H", "--height", type=int, default=DEFAULT_H, help="图像高度")
|
||||
ap.add_argument("--scale", type=int, default=1,
|
||||
help="放大倍数(最近邻,便于查看小图)")
|
||||
ap.add_argument("--invert", action="store_true", help="前景/背景互换")
|
||||
ap.add_argument("--no-auto-palette", action="store_true",
|
||||
help="不做调色板自动识别(按纯像素数据解析)")
|
||||
ap.add_argument("--fg", type=_parse_rgb, default=None, help="前景色 R,G,B(bit=1)")
|
||||
ap.add_argument("--bg", type=_parse_rgb, default=None, help="背景色 R,G,B(bit=0)")
|
||||
args = ap.parse_args(argv)
|
||||
|
||||
try:
|
||||
out = i1_to_png(
|
||||
args.input,
|
||||
output_path=args.output,
|
||||
width=args.width,
|
||||
height=args.height,
|
||||
scale=args.scale,
|
||||
invert=args.invert,
|
||||
auto_palette=not args.no_auto_palette,
|
||||
fg=args.fg,
|
||||
bg=args.bg,
|
||||
)
|
||||
except (ValueError, FileNotFoundError) as e:
|
||||
print("错误:%s" % e, file=sys.stderr)
|
||||
return 1
|
||||
|
||||
print("已生成:%s" % out)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user