#!/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()