Files
lucky-cat/tools/preview_fortune_expression.py
T

65 lines
4.0 KiB
Python

#!/usr/bin/env python3
"""调用实际固件 C 解码与光栅函数生成 GIF/五签预览;不模拟 SPI 或扫描撕裂。"""
import argparse
import ctypes as C
from pathlib import Path
import subprocess
import tempfile
from PIL import Image
from compress_idle_expression import pixels565
ROOT=Path(__file__).resolve().parents[1]
class Asset(C.Structure):
_fields_=[("width",C.c_uint16),("height",C.c_uint16),("x",C.c_uint16),("y",C.c_uint16),
("size",C.c_size_t),("data",C.POINTER(C.c_uint8))]
class Texture(C.Structure):
_fields_=[("width",C.c_uint16),("height",C.c_uint16),("pixels",C.POINTER(C.c_uint8))]
def image565(raw,width,height):
pixels=[]
for i in range(0,len(raw),2):
c=raw[i]<<8|raw[i+1];r=(c>>11)&31;g=(c>>5)&63;b=c&31
pixels.append(((r<<3)|(r>>2),(g<<2)|(g>>4),(b<<3)|(b>>2)))
image=Image.new("RGB",(width,height));image.putdata(pixels);return image
def main():
parser=argparse.ArgumentParser();parser.add_argument("--output",type=Path,default=Path("/tmp/lucky-fortune-preview"))
parser.add_argument("--result",type=int,choices=range(5),default=2);args=parser.parse_args()
args.output.mkdir(parents=True,exist_ok=True)
with tempfile.TemporaryDirectory(prefix="fortune-preview-") as tmp:
library=Path(tmp)/"fortune.dylib"
subprocess.run(["clang","-std=c11","-shared","-fPIC","-O2","-I",str(ROOT/"components/platform/internal"),
str(ROOT/"components/platform/src/lcd_emote_fortune.c"),
str(ROOT/"components/platform/src/generated/emote_gfx/fortune_assets.c"),"-o",str(library)],check=True)
lib=C.CDLL(str(library));shared=(Asset*6).in_dll(lib,"lcd_fortune_shared");texts=(Asset*5).in_dll(lib,"lcd_fortune_text")
decode=lib.lcd_emote_fortune_decode;decode.argtypes=[C.POINTER(C.c_uint8),C.c_size_t,C.POINTER(C.c_uint8),C.c_size_t];decode.restype=C.c_bool
build=lib.lcd_emote_fortune_build_frame;build.argtypes=[C.POINTER(C.c_uint8),C.c_size_t,C.POINTER(Texture),C.POINTER(Texture),C.c_uint32];build.restype=C.c_bool
buffers=[]
def texture(a,name):
count=a.width*a.height;buffer=(C.c_uint8*(count*3))();buffers.append(buffer)
assert decode(a.data,a.size,buffer,len(buffer))
pixels=pixels565(Image.open(ROOT/"components/platform/assets/lcd_layers/chouqian"/(name+".png")))
expected=bytes(v for hi,lo,alpha in pixels for v in (hi,lo))+bytes(alpha for hi,lo,alpha in pixels)
assert bytes(buffer)==expected,name # 验证 C 解压与图层完整 A8 一致。
return Texture(a.width,a.height,buffer)
names=("pile","back","edge","front","question","rod")
textures=(Texture*6)(*(texture(a,n) for a,n in zip(shared,names)))
results=("buyun","heping","shangshang","xiaoji","zhongshang")
text_textures=[texture(a,"text_"+n) for a,n in zip(texts,results)]
dst=(C.c_uint8*(208*220*2))()
rod=Image.open(ROOT/"components/platform/assets/lcd_layers/chouqian/rod.png").convert("RGBA")
def frame(ms,index):
assert build(dst,len(dst),textures,C.byref(text_textures[index]),ms)
image=Image.new("RGB",(320,240))
if ms<600:image.paste(rod,(0,40),rod)
image.paste(image565(bytes(dst),208,220),(56,20));return image
frames=[frame(t,args.result) for t in range(0,2500,40)]+[frame(2500,args.result)]
frames[0].save(args.output/"preview.gif",save_all=True,append_images=frames[1:],duration=[40]*(len(frames)-1)+[1500],loop=0)
sheet=Image.new("RGB",(320*5,240))
for i in range(5):
f=frame(2500,i);sheet.paste(f,(320*i,0));f.save(args.output/(results[i]+".png"))
sheet.save(args.output/"results.png")
phases=(0,600,750,900,1050,1200,1800,2200,2500)
stages=Image.new("RGB",(320*3,240*3))
for i,t in enumerate(phases):stages.paste(frame(t,args.result),(i%3*320,i//3*240))
stages.save(args.output/"stages.png")
print("PASS: actual C decode == RGB565+A8 inputs; actual C GIF, five results and motion stages:",args.output)
if __name__=="__main__":main()