P1-1:判定为 backend 的页不再无条件放行 + 后端中途失败不再静默回退 stb。 - meow_img_load_file_step_ex 增加 allow_stb_fallback 参数:0 = 只试后端, 失败返回 NULL(坏图占位),绝不回落 stb。阅读器按 dec.path 传入。 - 旧行为实测:截断 12MP JPEG 18,403kB、3000×3000 24bpp 头-only BMP 36,056kB、 非法位深 PNG 16,409kB(stb 都"成功"出图)。修复后: 截断 JPEG 275kB→NULL、头-only BMP 908kB→NULL、非法位深 PNG 门控占位(0 分配)。 P2-1:PNG 支持矩阵补 png_depth_ok 同式(color 2/4/6 仅 8/16;color 3 无 16; color 0 允许 1/2/4/8/16),不再出现「矩阵说支持、后端拒绝」。 P2-2:后端命中页也保留两道安全阀:MemAvailable<6MB 占位、估算 >12MB 硬上限占位。 P2-3:B8 指标改为「窗口稳定后只 reset 一次」,量整个 30 翻窗口内相对稳态基线的 最大存活增量(淘汰不再掩盖峰值),并打印稳态/驻留/增量。 P2-4:page_source.h 的估算精度口径限定为「后端命中/典型页」。 回归: - B7c 新增截断 12MP JPEG / 24bpp 头-only BMP / 非法位深 PNG,断言 backend-only NULL 且峰值 ≤3MB、或 stb 门控占位;B7 典型页与 B7b 回退矩阵保持全绿。 - test_reader_many_pages 恢复 24bpp 头-only 夹具(1bpp 保留):24bpp 判 failed、 1bpp 判 skipped;断言 failed=2 / skipped=1。 - run_tests.sh / run_webtoon_tests.sh EXIT=0;sdk/test_meow_img_stream ALL PASS; 交叉构建(SDK + tuku)零警告。
135 lines
5.6 KiB
Python
135 lines
5.6 KiB
Python
#!/usr/bin/env python3
|
||
"""make_mem_samples.py — 为 test_reader_membudget.c 生成解码夹具(M22 / M27)。
|
||
|
||
用法:python3 make_mem_samples.py <输出目录>
|
||
依赖:Pillow。没有 Pillow 时测试自动 SKIP JPEG 相关断言(BMP/PNG 小夹具由 C 直接写)。
|
||
|
||
M27 追加「6 类典型页」与「回退路径」夹具,用来断言门控模型与真缩解后端同口径:
|
||
典型(应全部走后端、不占位):
|
||
base2000.jpg 2000x3000 baseline
|
||
prog2000.jpg 2000x3000 progressive
|
||
restart2000.jpg 2000x3000 progressive + restart markers
|
||
page1600.png 1600x2400 PNG
|
||
page1600.bmp 1600x2400 BMP
|
||
mp4.jpg 2320x1740(4MP)
|
||
mp12.jpg 4000x3000(12MP)
|
||
回退(后端拒绝、按旧 stb 门控占位):
|
||
cmyk.jpg 2000x3000 CMYK(4 分量)
|
||
adam7.png 1600x2400 Adam7 交错
|
||
mono1.bmp 1600x2400 1bpp
|
||
M22 旧断言仍用:
|
||
page.jpg / big4.jpg / prog.jpg(1000x1500 / 2000x2000 / 1000x1500 progressive)
|
||
"""
|
||
import os
|
||
import struct
|
||
import sys
|
||
import zlib
|
||
|
||
from PIL import Image, ImageDraw
|
||
|
||
|
||
def pattern(w, h, mode="RGB"):
|
||
im = Image.new(mode, (w, h), (248, 247, 244) if mode == "RGB" else 200)
|
||
d = ImageDraw.Draw(im)
|
||
for k in range(0, min(w, h) - 200, 70):
|
||
d.rectangle([20 + k, 20 + k, 160 + k, 120 + k], outline=(10, 10, 12), width=3)
|
||
return im
|
||
|
||
|
||
def chunk(tag, data):
|
||
return (struct.pack(">I", len(data)) + tag + data +
|
||
struct.pack(">I", zlib.crc32(tag + data) & 0xFFFFFFFF))
|
||
|
||
|
||
def write_adam7(path, img):
|
||
"""手写 Adam7 交错 PNG(Pillow 不支持写交错)。RGB8。"""
|
||
w, h = img.size
|
||
px = img.load()
|
||
raw = bytearray()
|
||
passes = [(0, 0, 8, 8), (4, 0, 8, 8), (0, 4, 4, 8), (2, 0, 4, 4),
|
||
(0, 2, 2, 4), (1, 0, 2, 2), (0, 1, 1, 2)]
|
||
for (x0, y0, dx, dy) in passes:
|
||
xs = list(range(x0, w, dx))
|
||
ys = list(range(y0, h, dy))
|
||
if not xs or not ys:
|
||
continue
|
||
for y in ys:
|
||
raw.append(0)
|
||
for x in xs:
|
||
r, g, b = px[x, y][:3]
|
||
raw += bytes((r, g, b))
|
||
ihdr = struct.pack(">IIBBBBB", w, h, 8, 2, 0, 0, 1)
|
||
data = (b"\x89PNG\r\n\x1a\n" + chunk(b"IHDR", ihdr) +
|
||
chunk(b"IDAT", zlib.compress(bytes(raw))) + chunk(b"IEND", b""))
|
||
with open(path, "wb") as f:
|
||
f.write(data)
|
||
|
||
|
||
def main():
|
||
if len(sys.argv) != 2:
|
||
print(__doc__)
|
||
return 2
|
||
out = sys.argv[1]
|
||
os.makedirs(out, exist_ok=True)
|
||
|
||
# ---- M22 旧断言夹具 ----
|
||
pattern(1000, 1500).save(os.path.join(out, "page.jpg"), "JPEG", quality=60, subsampling=2)
|
||
pattern(2000, 2000).save(os.path.join(out, "big4.jpg"), "JPEG", quality=60, subsampling=2)
|
||
pattern(1000, 1500).save(os.path.join(out, "prog.jpg"), "JPEG", quality=60, subsampling=2,
|
||
progressive=True)
|
||
|
||
# ---- M27 6 类典型页 ----
|
||
big = pattern(2000, 3000)
|
||
big.save(os.path.join(out, "base2000.jpg"), "JPEG", quality=60, subsampling=2)
|
||
big.save(os.path.join(out, "prog2000.jpg"), "JPEG", quality=60, subsampling=2, progressive=True)
|
||
big.save(os.path.join(out, "restart2000.jpg"), "JPEG", quality=60, subsampling=2,
|
||
progressive=True, restart_marker_blocks=8)
|
||
pattern(1600, 2400).save(os.path.join(out, "page1600.png"), "PNG")
|
||
pattern(1600, 2400).save(os.path.join(out, "page1600.bmp"), "BMP")
|
||
pattern(2320, 1740).save(os.path.join(out, "mp4.jpg"), "JPEG", quality=60, subsampling=2)
|
||
pattern(4000, 3000).save(os.path.join(out, "mp12.jpg"), "JPEG", quality=60, subsampling=2)
|
||
|
||
# ---- M27 回退路径 ----
|
||
big.convert("CMYK").save(os.path.join(out, "cmyk.jpg"), "JPEG", quality=60)
|
||
write_adam7(os.path.join(out, "adam7.png"), pattern(1600, 2400))
|
||
pattern(1600, 2400).convert("1").save(os.path.join(out, "mono1.bmp"), "BMP")
|
||
|
||
# ---- M27 r1(P1-1):头部判支持但解码中途失败/后端拒绝的坏文件 ----
|
||
# 1) 截断的 12MP JPEG:SOF 仍在(probe/矩阵判 backend),熵段被腰斩 → 后端失败;
|
||
# 旧实现会静默回退 stb 并分配 ~18MB。
|
||
with open(os.path.join(out, "mp12.jpg"), "rb") as f:
|
||
blob = f.read(200000)
|
||
with open(os.path.join(out, "trunc2000.jpg"), "wb") as f:
|
||
f.write(blob)
|
||
# 2) 3000x3000 24bpp BMP,只有 54 字节头(无像素):矩阵判 backend,后端失败;
|
||
# 旧实现回退 stb 会 malloc 36MB 并"解出"一张全黑图。
|
||
hdr = bytearray(54)
|
||
hdr[0:2] = b"BM"
|
||
struct.pack_into("<I", hdr, 2, 54) # 谎报文件大小
|
||
struct.pack_into("<I", hdr, 10, 54) # 像素数据偏移
|
||
struct.pack_into("<I", hdr, 14, 40) # BITMAPINFOHEADER
|
||
struct.pack_into("<i", hdr, 18, 3000) # width
|
||
struct.pack_into("<i", hdr, 22, 3000) # height
|
||
struct.pack_into("<H", hdr, 26, 1) # planes
|
||
struct.pack_into("<H", hdr, 28, 24) # bpp
|
||
with open(os.path.join(out, "hdr3000.bmp"), "wb") as f:
|
||
f.write(hdr)
|
||
# 3) 非法位深 PNG(color=2 RGB + depth=1):后端 png_depth_ok 拒绝 → 必须判 stb;
|
||
# 旧矩阵只按 color 算位宽 → 误判 backend,失败后回退 stb 分配 ~16MB。
|
||
w, h = 1600, 2400
|
||
rowbytes = (w * 3 + 7) // 8 # 600 字节/行(depth=1 RGB)
|
||
raw = bytearray()
|
||
for y in range(h):
|
||
raw.append(0)
|
||
raw += bytes((x * 7 + y) & 0xFF for x in range(rowbytes))
|
||
ihdr = struct.pack(">IIBBBBB", w, h, 1, 2, 0, 0, 0)
|
||
data = (b"\x89PNG\r\n\x1a\n" + chunk(b"IHDR", ihdr) +
|
||
chunk(b"IDAT", zlib.compress(bytes(raw))) + chunk(b"IEND", b""))
|
||
with open(os.path.join(out, "baddepth.png"), "wb") as f:
|
||
f.write(data)
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(main())
|