Files
MeowOS-SDK/sdk/test_meow_img_budget_gen.py
T
AskaEth 21bffa1aeb sdk: 图片层追加预算解码入口,JPEG 解码期缩解(峰值 43.7→14.0 MiB)
M6 finding(vuln/high):meow_img_load_file 必先整幅 RGBA 解码,1600×2400 漫画页
瞬时峰值 20.2 MiB(15.4 MiB 输出 + 5.5 MiB YCbCr 平面),超设备 ~14MB 口径,
App 侧无法规避。

调研(vendored stb v2.30):无解码期缩放能力——JPEG 走固定 hs/vs 重采样到全尺寸,
PNG 一次性 inflate 整幅 raw_image,公共 API 只有 load/load_16/loadf/info。
完整缩小 IDCT 需改编 1/2、1/4、1/8 IDCT + 进升级系数存储 + MCU 尺寸,改动面大且
易出静默像素错误,故不重写解码器。

改为受控的最小追加(sdk/stb_image.h):
- 新增 stbi_load_scaled / stbi_load_scaled_from_memory:JPEG 输出级按步长 s 抽取
  行/列(resample 状态机逐行推进,仅采样行做颜色转换),整幅原尺寸输出缓冲从不
  物化;scale<=1 与旧 API 逐字节等价,非 JPEG 忽略。
- 进升级 JPEG:各分量系数 IDCT 进像素平面后立即释放(原来拖到输出分配之后)。
- 旧公共 API 行为不变。

SDK 追加式入口(旧 API 一字未改):
- meow_img_load_file_dim / meow_img_load_mem_dim(path/buf, maxdim):stbi_info 头探测
  选 1/2、1/4、1/8 整数档(取「长边/s ≥ maxdim」最大档,不欠采样),再交既有
  meow_img_downscale 精修;小图/maxdim<=0 走旧路径。
- meow_img_probe_file:只读头取尺寸,供 App 在 OOM 前提示。

实测(宿主 test_meow_img_budget,--wrap malloc 打点,全部 PASS):
  8.32MP JPEG   43.70 → 13.96 MiB(新/旧 0.32)
  1600×2400 页  20.16 →  9.18 MiB
  progressive   43.70 → 35.83 MiB(系数阵列扫描期驻留,残余上限)
  PNG 2400×1600 25.65 → 25.65 MiB(stb 无 PNG 缩解,如实记录)
  BMP 2400×1600 14.65 → 15.23 MiB(同上,多一次降采样小缓冲)
缩解结果与「整幅解码+downscale」网格平均差 6.28;对 PIL 逐像素比对与全幅解码
同误差量级(mean ≤0.02, max ≤3)。

验证:build_sdk.sh 重编 meow_ui.a 0 warning;examples/hello 重链 0 warning;
tuku 用 MEOW_SDK_DIR 指向本分支 sdk 重链 0 warning;既有 test_meow_img_color 仍全 PASS。
2026-11-04 21:02:39 +08:00

70 lines
2.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""test_meow_img_budget_gen.py — 为宿主测试 test_meow_img_budget.c 生成图片夹具。
用法:python3 test_meow_img_budget_gen.py <输出目录>
依赖:Pillow(PIL)。夹具只含结构化图案(白底/黑线/灰块),体积极小。
生成:
big.jpg 3200x2600 baseline 4:2:0 JPEG(8.32MP,峰值对比主用例)
bigprog.jpg 3200x2600 progressive JPEG(验证进升级解码走同一缩解路径)
page.jpg 1600x2400 baseline JPEG(finding 里 15.4MB 的漫画页原尺寸)
small.jpg 64x64 baseline JPEG(「小图不动」用例)
big.png 2400x1600 RGB PNG(stb 无解码期缩放:峰值如实记录,不作改善断言)
big.bmp 2400x1600 24bpp BMP(同上)
broken.jpg JPEG 魔数 + 垃圾字节(头部解析失败,坏图用例)
"""
import os
import sys
from PIL import Image, ImageDraw
def page_pattern(w, h):
"""漫画页近似:白底 + 分镜框线 + 灰阶网点带。内容稀疏 → JPEG 体积小。"""
im = Image.new("RGB", (w, h), (250, 249, 245))
d = ImageDraw.Draw(im)
for k in range(0, min(w, h) - 400, 60):
d.rectangle([100 + k, 100 + k, 400 + k, 300 + k], outline=(12, 12, 14), width=5)
for band in range(14):
y0 = band * h // 14
for x in range(0, w, 9):
d.line([(x, y0), (x + 4, y0 + 7)], fill=(110, 110, 116))
return im
def main():
if len(sys.argv) != 2:
print(__doc__)
return 2
out = sys.argv[1]
os.makedirs(out, exist_ok=True)
big = page_pattern(3200, 2600)
big.save(os.path.join(out, "big.jpg"), "JPEG", quality=55, subsampling=2)
big.save(os.path.join(out, "bigprog.jpg"), "JPEG", quality=55, subsampling=2,
progressive=True)
small = page_pattern(64, 64)
small.save(os.path.join(out, "small.jpg"), "JPEG", quality=80, subsampling=2)
page = page_pattern(1600, 2400)
page.save(os.path.join(out, "page.jpg"), "JPEG", quality=55, subsampling=2)
mid = page_pattern(2400, 1600)
mid.save(os.path.join(out, "big.png"), "PNG")
mid.save(os.path.join(out, "big.bmp"), "BMP")
# 坏图:JPEG 魔数 + 垃圾字节 → 头部解析失败(stb 对「截断但头部完整」很宽容,不能只截断)
garbage = bytes((i * 37 + 11) & 0xFF for i in range(256))
with open(os.path.join(out, "broken.jpg"), "wb") as f:
f.write(b"\xff\xd8\xff\xe0" + garbage)
for name in ("big.jpg", "bigprog.jpg", "page.jpg", "small.jpg", "big.png",
"big.bmp", "broken.jpg"):
print(" %-12s %8d B" % (name, os.path.getsize(os.path.join(out, name))))
return 0
if __name__ == "__main__":
sys.exit(main())