feat(lcd): animate fortune results with shared GFX layers
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
#!/usr/bin/env python3
|
||||
"""从保留的抽签源 PNG 拆分共享图层、五个签文,生成 LZ4;--check 不写文件。
|
||||
|
||||
会覆盖 lcd_layers/chouqian 和生成 C;先保存手工美术编辑。背景杂点归零、
|
||||
清除背面残留钓线、抽取并共享正面;RGB565+A8 编码往返无损不等于拆分原画无损。
|
||||
"""
|
||||
import argparse
|
||||
from collections import deque
|
||||
import json
|
||||
from pathlib import Path
|
||||
from PIL import Image
|
||||
from compress_idle_expression import pixels565
|
||||
|
||||
def compress(raw):
|
||||
"""LZ4 block:字面量和 16-bit 回指;编码器自包含,不下载库。"""
|
||||
out=bytearray();table={};anchor=pos=0
|
||||
def length(value):
|
||||
while value>=255: out.append(255);value-=255
|
||||
out.append(value)
|
||||
while pos+12<=len(raw):
|
||||
key=raw[pos:pos+4];previous=table.get(key);table[key]=pos
|
||||
if previous is None or pos-previous>65535:
|
||||
pos+=1;continue
|
||||
size=4
|
||||
while pos+size<len(raw)-5 and raw[previous+size]==raw[pos+size]: size+=1
|
||||
literals=pos-anchor;match=size-4
|
||||
out.append((min(literals,15)<<4)|min(match,15))
|
||||
if literals>=15: length(literals-15)
|
||||
out.extend(raw[anchor:pos]);offset=pos-previous;out.extend((offset&255,offset>>8))
|
||||
if match>=15: length(match-15)
|
||||
end=pos+size
|
||||
for p in range(pos+1,end):
|
||||
if p+4<=len(raw):table[raw[p:p+4]]=p
|
||||
pos=anchor=end
|
||||
literals=len(raw)-anchor;out.append(min(literals,15)<<4)
|
||||
if literals>=15:length(literals-15)
|
||||
out.extend(raw[anchor:])
|
||||
assert decompress(out)==raw
|
||||
return out
|
||||
|
||||
def decompress(data):
|
||||
out=bytearray();pos=0
|
||||
def length(value):
|
||||
nonlocal pos
|
||||
if value==15:
|
||||
while True:
|
||||
v=data[pos];pos+=1;value+=v
|
||||
if v!=255:break
|
||||
return value
|
||||
while pos<len(data):
|
||||
token=data[pos];pos+=1;literals=length(token>>4)
|
||||
out.extend(data[pos:pos+literals]);pos+=literals
|
||||
if pos==len(data):break
|
||||
offset=data[pos]|data[pos+1]<<8;pos+=2;match=length(token&15)+4
|
||||
assert 0<offset<=len(out)
|
||||
for _ in range(match):out.append(out[-offset])
|
||||
return bytes(out)
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SOURCE = ROOT / "components/platform/assets/lcd_source"
|
||||
LAYERS = ROOT / "components/platform/assets/lcd_layers/chouqian"
|
||||
GENERATED = ROOT / "components/platform/src/generated/emote_gfx/fortune_assets.c"
|
||||
RESULTS = ("buyun", "heping", "shangshang", "xiaoji", "zhongshang")
|
||||
|
||||
def clean(name, box, largest=False):
|
||||
image = Image.open(SOURCE / name).convert("RGBA").crop(box)
|
||||
pixels = list(image.getdata())
|
||||
candidates = {(i % image.width, i // image.width) for i, (r,g,b,a) in enumerate(pixels)
|
||||
if max(r,g,b)>35 and a}
|
||||
components=[]
|
||||
while candidates:
|
||||
seed=candidates.pop(); queue=deque([seed]); group={seed}
|
||||
while queue:
|
||||
x,y=queue.popleft()
|
||||
for nx in range(max(0,x-1),min(image.width,x+2)):
|
||||
for ny in range(max(0,y-1),min(image.height,y+2)):
|
||||
if (nx,ny) in candidates:
|
||||
candidates.remove((nx,ny));group.add((nx,ny));queue.append((nx,ny))
|
||||
if len(group)>=8: components.append(group)
|
||||
selected=max(components,key=len) if largest else set().union(*components)
|
||||
image.putdata([(r,g,b,a) if (i%image.width,i//image.width) in selected else (0,0,0,0)
|
||||
for i,(r,g,b,a) in enumerate(pixels)])
|
||||
return image
|
||||
|
||||
def text_layer(name):
|
||||
image=Image.open(SOURCE/f"chouqian_{name}2.png").convert("RGBA").crop((136,94,184,198))
|
||||
# 只保留原签文橙红笔画及边缘,不把黄色签底一起叠加。
|
||||
image.putdata([(r,g,b,a) if r>150 and g<175 and b<100 and r>g*1.35 else (0,0,0,0)
|
||||
for r,g,b,a in image.getdata()])
|
||||
assert image.getbbox(), name
|
||||
return image
|
||||
|
||||
def layers():
|
||||
pile=clean("chouqian_qianzhi1.png",(56,164,264,240))
|
||||
back=clean("chouqian_qianzhi1.png",(130,37,193,150),True)
|
||||
for y in range(18,32):
|
||||
for x in range(8):
|
||||
r,g,b,a=back.getpixel((x,y))
|
||||
if r>g*1.3 and g>b*1.5: back.putpixel((x,y),(0,0,0,0))
|
||||
edge=clean("chouqian_qianzhi2.png",(139,38,180,148),True)
|
||||
front=clean("chouqian_shangshang2.png",(110,24,210,214),True)
|
||||
# 原签文所在内部签底做逐行双色插值;边框、肉垫、外轮廓保持原图。
|
||||
for y in range(70,174):
|
||||
l=front.getpixel((26,y));r=front.getpixel((73,y))
|
||||
assert l[3] and r[3], y
|
||||
for x in range(26,74):
|
||||
f=(x-26)/47
|
||||
front.putpixel((x,y),tuple(round(l[c]*(1-f)+r[c]*f) for c in range(3))+(255,))
|
||||
question=Image.open(SOURCE/"chouqian_shangshang0.png").convert("RGBA").crop((147,83,174,133))
|
||||
question.putdata([(r,g,b,a) if r>150 and g<215 and b<170 and r>g*1.10 else (0,0,0,0)
|
||||
for r,g,b,a in question.getdata()])
|
||||
assert question.getbbox()
|
||||
rod=clean("chouqian_qianzhi0.png",(0,40,169,96))
|
||||
return [("pile",pile,56,164),("back",back,0,0),("edge",edge,0,0),
|
||||
("front",front,0,0),("question",question,0,0),("rod",rod,0,40)] + [
|
||||
("text_"+name,text_layer(name),0,0) for name in RESULTS]
|
||||
|
||||
def main():
|
||||
args=argparse.ArgumentParser();args.add_argument("--check",action="store_true");args=args.parse_args()
|
||||
items=layers(); out=['/* Generated by tools/prepare_fortune_expression.py; do not edit. */',
|
||||
'#include "lcd_emote_fortune.h"','']
|
||||
entries=[]; report=[]
|
||||
if not args.check: LAYERS.mkdir(parents=True,exist_ok=True)
|
||||
for name,image,x,y in items:
|
||||
pixels=pixels565(image)
|
||||
raw=bytes(v for hi,lo,a in pixels for v in (hi,lo))+bytes(a for hi,lo,a in pixels)
|
||||
data=compress(raw) # 对 RGB565 高低字节与独立 A8 平面逐字节往返检查。
|
||||
out += [f'static const uint8_t fortune_{name}[] = {{']
|
||||
out += [' '+', '.join(f'0x{v:02x}' for v in data[i:i+24])+',' for i in range(0,len(data),24)]
|
||||
out += ['};','']
|
||||
entries.append(f' {{{image.width}, {image.height}, {x}, {y}, sizeof(fortune_{name}), fortune_{name}}},')
|
||||
report.append(dict(name=name,width=image.width,height=image.height,x=x,y=y,
|
||||
raw_bytes=image.width*image.height*3,lz4_bytes=len(data)))
|
||||
if args.check:
|
||||
with Image.open(LAYERS/f"{name}.png") as old:
|
||||
assert old.convert("RGBA").tobytes()==image.tobytes(), name
|
||||
else: image.save(LAYERS/f"{name}.png",optimize=True)
|
||||
out += ['const lcd_fortune_asset_t lcd_fortune_shared[LCD_FORTUNE_SHARED_COUNT] = {',*entries[:6],'};',
|
||||
'const lcd_fortune_asset_t lcd_fortune_text[LCD_FORTUNE_RESULT_COUNT] = {',*entries[6:],'};',
|
||||
'const char *const lcd_fortune_result_dirs[LCD_FORTUNE_RESULT_COUNT] = {',
|
||||
' '+', '.join('"chouqian_'+s+'"' for s in RESULTS)+',','};','']
|
||||
source='\n'.join(out)
|
||||
metadata=json.dumps(dict(canvas=[320,240],window=[56,20,208,220],layers=report,
|
||||
note="黑背景归零;共享爪子,签文字形来自原图;旧 PNG 保留"),ensure_ascii=False,indent=2)+'\n'
|
||||
if args.check:
|
||||
assert GENERATED.read_text()==source
|
||||
assert (LAYERS/"layout.json").read_text()==metadata
|
||||
else:
|
||||
GENERATED.write_text(source);(LAYERS/"layout.json").write_text(metadata)
|
||||
total=sum(s["lz4_bytes"] for s in report)
|
||||
peak=208*220*2+sum(s["raw_bytes"] for s in report[:6])+max(s["raw_bytes"] for s in report[6:])
|
||||
assert total<70*1024, ("为代码/描述保留10 KiB预算",total)
|
||||
assert peak<300*1024,peak
|
||||
print(json.dumps(dict(layers=report,payload_bytes=total,pixel_psram_peak=peak),ensure_ascii=False,indent=2))
|
||||
|
||||
if __name__=="__main__": main()
|
||||
@@ -0,0 +1,64 @@
|
||||
#!/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()
|
||||
@@ -0,0 +1,148 @@
|
||||
#!/usr/bin/env python3
|
||||
"""抽签实际 C 解码/几何、五种资源、分配失败/取消/释放主机回归,非设备 FPS。"""
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
import tempfile
|
||||
ROOT=Path(__file__).resolve().parents[1]
|
||||
SRC=(ROOT/"components/platform/src/lcd_direct.c").read_text()
|
||||
def extract(name):
|
||||
match=re.search(r"static [^;{}]+\b"+name+r"\([^;{}]*\)\s*\{",SRC)
|
||||
assert match,name
|
||||
start=match.start();end=SRC.index("{",start)+1;depth=1
|
||||
while depth:
|
||||
if SRC[end]=="{":depth+=1
|
||||
if SRC[end]=="}":depth-=1
|
||||
end+=1
|
||||
return SRC[start:end]
|
||||
PREFIX=r'''
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "lcd_emote_fortune.h"
|
||||
#define ESP_OK 0
|
||||
#define ESP_ERR_INVALID_STATE 1
|
||||
#define ESP_ERR_NO_MEM 2
|
||||
#define ESP_ERR_INVALID_SIZE 3
|
||||
#define ESP_ERR_NOT_SUPPORTED 4
|
||||
#define ESP_ERR_TIMEOUT 5
|
||||
#define PLATFORM_LCD_PRESENTATION_NO_ICON 255
|
||||
#define MALLOC_CAP_SPIRAM 1
|
||||
#define MALLOC_CAP_8BIT 2
|
||||
#define GFX_COLOR_FORMAT_RGB565 0
|
||||
#define GFX_COLOR_FORMAT_RGB565A8 1
|
||||
#define C_ARRAY_HEADER_MAGIC 0
|
||||
#define ESP_LOGI(...) ((void)0)
|
||||
#define ESP_LOGE(...) ((void)0)
|
||||
#define portENTER_CRITICAL(x) ((void)0)
|
||||
#define portEXIT_CRITICAL(x) ((void)0)
|
||||
typedef int esp_err_t;
|
||||
typedef struct {const char *asset_dir;unsigned frame_index,icon_id,delay_ms;} platform_lcd_presentation_step_t;
|
||||
typedef struct {size_t step_count;platform_lcd_presentation_step_t steps[6];} lcd_direct_request_t;
|
||||
typedef struct { struct {int cf,magic,w,h;} header;size_t data_size;const uint8_t *data;} gfx_image_dsc_t;
|
||||
typedef struct {bool visible;int x,y;const gfx_image_dsc_t *src;} gfx_obj_t;
|
||||
static void *s_emote_handle=(void*)1,*s_emote_disp=(void*)1;
|
||||
static gfx_obj_t mesh,closed,cache,listening,bg,scene_obj;
|
||||
static gfx_obj_t *s_emote_eyes_open=&mesh,*s_emote_closed_eyes=&closed,*s_emote_cached_eyes=&cache;
|
||||
static gfx_obj_t *s_listening_eyes=&listening,*s_emote_background=&bg;
|
||||
static bool s_emote_active,s_emote_transfer_fault,stop;
|
||||
static int lock_depth,allocs,live,fail_alloc,fail_obj,fail_lock,fail_unlock,fail_delete;
|
||||
static int completions,waits,cancel_wait,refreshes,fail_refresh;
|
||||
static int64_t now,deadlines[100];
|
||||
static int64_t esp_timer_get_time(void){return now;}
|
||||
static bool lcd_direct_should_stop_idle(void){return stop;}
|
||||
static void *heap_caps_malloc(size_t n,int cap){assert(cap==3);if(++allocs==fail_alloc)return NULL;void *p=malloc(n);if(p)++live;return p;}
|
||||
static void *heap_caps_calloc(size_t n,size_t s,int cap){void *p=heap_caps_malloc(n*s,cap);if(p)memset(p,0,n*s);return p;}
|
||||
static void test_free(void *p){if(p)--live;free(p);}
|
||||
#define free test_free
|
||||
static int gfx_emote_lock(void *h){if(fail_lock){--fail_lock;return 5;}assert(!lock_depth);++lock_depth;return 0;}
|
||||
static int gfx_emote_unlock(void *h){assert(lock_depth==1);--lock_depth;if(fail_unlock){--fail_unlock;return 5;}return 0;}
|
||||
static gfx_obj_t *gfx_img_create(void *d){assert(lock_depth==1);return fail_obj?NULL:&scene_obj;}
|
||||
static int gfx_obj_delete(gfx_obj_t *o){assert(lock_depth==1);if(fail_delete)return 5;o->src=NULL;return 0;}
|
||||
static int gfx_img_set_src(gfx_obj_t *o,void *src){assert(lock_depth==1);o->src=src;return 0;}
|
||||
static int gfx_obj_set_pos(gfx_obj_t *o,int x,int y){assert(lock_depth==1);o->x=x;o->y=y;return 0;}
|
||||
static int gfx_obj_set_visible(gfx_obj_t *o,bool v){assert(lock_depth==1);o->visible=v;return 0;}
|
||||
static int lcd_direct_emote_update_hud(bool force,bool *change){assert(lock_depth==1);return 0;}
|
||||
static int lcd_direct_emote_select_face(bool face){assert(lock_depth==1);bg.src=NULL;return 0;}
|
||||
static void gfx_disp_refresh_all(void *d){assert(lock_depth==1);}
|
||||
static int lcd_direct_emote_refresh(void){assert(lock_depth==1 && s_emote_active);++refreshes;now+=13000;return refreshes==fail_refresh?5:0;}
|
||||
static int lcd_direct_emote_wait_until(int64_t deadline){assert(!lock_depth && waits<100);deadlines[waits++]=deadline;
|
||||
if(waits==cancel_wait){stop=true;return 1;} if(now<deadline)now=deadline;return stop?1:0;}
|
||||
static void lcd_direct_signal_completion(void){++completions;}
|
||||
static uint64_t lcd_direct_emote_fps_x100(uint32_t n,uint64_t t){return t?n*100000000ULL/t:0;}
|
||||
static const char *esp_err_to_name(int e){return "mock";}
|
||||
'''
|
||||
TEST=r'''
|
||||
static void reset(void){assert(live==0 && !lock_depth && !s_fortune_orphan);
|
||||
allocs=fail_alloc=fail_obj=fail_lock=fail_unlock=fail_delete=waits=refreshes=cancel_wait=fail_refresh=completions=0;
|
||||
now=0;stop=s_emote_active=s_emote_transfer_fault=false;}
|
||||
int main(void){
|
||||
uint8_t out[64];
|
||||
assert(!lcd_emote_fortune_decode(NULL,1,out,1));
|
||||
const uint8_t invalid[][5]={{0x10},{0x00,0,0},{0x00,1,0},{0xf0,255,255},{0x11,5}};
|
||||
for(size_t i=0;i<5;++i)assert(!lcd_emote_fortune_decode(invalid[i],sizeof(invalid[i]),out,sizeof(out)));
|
||||
for(size_t r=0;r<5;++r){
|
||||
reset();lcd_direct_fortune_scene_t *s=lcd_direct_fortune_prepare(r);assert(s && live==9);
|
||||
const size_t bytes=LCD_FORTUNE_WIDTH*LCD_FORTUNE_HEIGHT*2;
|
||||
uint8_t *guard=malloc(bytes+2);guard[0]=guard[bytes+1]=0xa5;
|
||||
for(unsigned ms=0;ms<=4000;ms+=17){
|
||||
assert(lcd_emote_fortune_build_frame(guard+1,bytes,s->shared,&s->text,ms));
|
||||
assert(guard[0]==0xa5 && guard[bytes+1]==0xa5);
|
||||
}
|
||||
assert(!lcd_emote_fortune_build_frame(guard+1,bytes-1,s->shared,&s->text,0));
|
||||
assert(!lcd_emote_fortune_build_frame(NULL,bytes,s->shared,&s->text,0));
|
||||
assert(!lcd_emote_fortune_build_frame(guard+1,bytes,NULL,&s->text,0));
|
||||
lcd_emote_fortune_build_frame(guard+1,bytes,s->shared,&s->text,4000);
|
||||
lcd_emote_fortune_build_frame(s->frame,bytes,s->shared,&s->text,2500);
|
||||
assert(!memcmp(guard+1,s->frame,bytes));
|
||||
/* 最终签文输出供回归查看;五种结果不能全都变成同一张。 */
|
||||
char path[100];snprintf(path,sizeof(path),"/tmp/lucky-fortune-%zu.rgb565",r);
|
||||
FILE *f=fopen(path,"wb");assert(f);fwrite(s->frame,1,bytes,f);fclose(f);
|
||||
/* 截断输入和不足的解压空间不允许写越界。 */
|
||||
for(size_t i=0;i<6;++i){const lcd_fortune_asset_t *a=&lcd_fortune_shared[i];size_t n=(size_t)a->width*a->height*3;
|
||||
uint8_t *bad=malloc(n+2);bad[0]=bad[n+1]=0xa5;
|
||||
assert(!lcd_emote_fortune_decode(a->data,a->compressed_size-1,bad+1,n));
|
||||
assert(!lcd_emote_fortune_decode(a->data,a->compressed_size,bad+1,n-1));
|
||||
assert(bad[0]==0xa5 && bad[n+1]==0xa5);
|
||||
/* 普通测试内存不计入设备分配追踪。 */
|
||||
++live;free(bad);
|
||||
}
|
||||
++live;free(guard);lcd_direct_fortune_free(s);assert(live==0);
|
||||
lcd_direct_request_t req={.step_count=6};
|
||||
for(size_t i=0;i<6;++i)req.steps[i]=(platform_lcd_presentation_step_t){i<3?"chouqian_qianzhi":lcd_fortune_result_dirs[r],i%3,255,i==5?1500:500};
|
||||
assert(lcd_direct_fortune_sequence_index(&req)==r);
|
||||
req.steps[4].delay_ms=100;assert(lcd_direct_fortune_sequence_index(&req)==-1);
|
||||
req.steps[4].delay_ms=500;req.steps[3].asset_dir=NULL;assert(lcd_direct_fortune_sequence_index(&req)==-1);
|
||||
reset();assert(lcd_direct_play_fortune(r)==0 && live==0 && completions==1 && !s_emote_active);
|
||||
assert(waits==75 && refreshes==75 && now==4013000 && deadlines[74]==4013000);
|
||||
for(int i=0;i<73;++i)assert(deadlines[i]==(i+1)*34000);
|
||||
assert(deadlines[73]==2500000);
|
||||
}
|
||||
for(int n=1;n<=9;++n){reset();fail_alloc=n;assert(lcd_direct_play_fortune(0)==4 && !live && !completions);}
|
||||
reset();fail_obj=1;assert(lcd_direct_play_fortune(0)==4 && !live);
|
||||
reset();fail_lock=1;assert(lcd_direct_play_fortune(0)==5 && !live);
|
||||
for(int n=1;n<=75;++n){reset();cancel_wait=n;assert(lcd_direct_play_fortune(0)==1 && !live && !completions && !s_emote_active);}
|
||||
reset();fail_refresh=3;assert(lcd_direct_play_fortune(0)==5 && !live && !completions);
|
||||
reset();fail_delete=1;assert(lcd_direct_play_fortune(0)==5 && s_fortune_orphan && live==9 && !completions);
|
||||
assert(lcd_direct_play_fortune(0)==1);fail_delete=0;
|
||||
assert(!lcd_direct_fortune_detach(s_fortune_orphan));lcd_direct_fortune_free(s_fortune_orphan);s_fortune_orphan=NULL;
|
||||
reset();s_emote_transfer_fault=true;assert(lcd_direct_play_fortune(0)==1 && !live);
|
||||
puts("PASS: five assets/results, LZ4 corruption/bounds, 1180 geometry frames, nine allocation failures, 75 cancellation points, anchored 4s playback, cleanup/transfer fault");
|
||||
}
|
||||
'''
|
||||
start=SRC.index("typedef struct {\n lcd_fortune_texture_t shared")
|
||||
end=SRC.index("static int lcd_direct_fortune_sequence_index",start)
|
||||
names=["lcd_direct_fortune_sequence_index","lcd_direct_fortune_free","lcd_direct_fortune_prepare",
|
||||
"lcd_direct_fortune_detach","lcd_direct_play_fortune"]
|
||||
with tempfile.TemporaryDirectory(prefix="fortune-test-") as tmp:
|
||||
path=Path(tmp);c=path/"test.c";exe=path/"test"
|
||||
c.write_text(PREFIX+SRC[start:end]+"\n"+"\n".join(extract(n) for n in names)+TEST)
|
||||
for profile in (0,1):
|
||||
subprocess.run(["clang","-std=c11","-O1","-g","-fsanitize=address,undefined",
|
||||
f"-DCONFIG_TQ_LCD_EMOTE_FPS_LOG={profile}","-I",str(ROOT/"components/platform/internal"),
|
||||
str(c),str(ROOT/"components/platform/src/lcd_emote_fortune.c"),
|
||||
str(ROOT/"components/platform/src/generated/emote_gfx/fortune_assets.c"),"-o",str(exe)],check=True)
|
||||
subprocess.run([str(exe)],check=True)
|
||||
assert "DOMAIN_EXPRESSION_" not in (ROOT/"components/platform/src/lcd_emote_fortune.c").read_text()
|
||||
assert "if (err != ESP_ERR_NOT_SUPPORTED) return err;" in SRC
|
||||
Reference in New Issue
Block a user