65 lines
4.0 KiB
Python
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()
|