Files
MeowOS-For-R3P2/patch_v2/sdk/test_meow_img_stream.c
T
AskaEth 281f4153ee fix(sdk): M26 r1 P1 — ILP32 尺寸算术溢出(畸形 BMP 经公开入口 SIGSEGV)+ 加 S17 回归
评审在 ARM32(ILP32,与 mipsel 同数据模型)实测:54 字节 BMP 头声明 w=2^27、bpp=32
时 w*bpp 回绕成 0 → rowsize=0 → malloc(0) → 采样索引 row+sx*4 越界读 → SIGSEGV,
经 meow_img_load_file_dim 端到端可触发;同一文件在 LP64 宿主返回 NULL(不溢出),
合并前走 stb 也是 NULL。即这是相对旧路径的鲁棒性回归。

修复(只加闸,正常路径数值不变):

- meow_png_stream.c(P1 本体)
  · 新增 ILP32 守卫 MZP_MAX_DIM=1<<20 与 mzp_bytes_ok()(unsigned long long 验算,
    超过 SIZE_MAX 即拒)
  · BMP:声明尺寸先升 int/long long 校验(w/h 单边 ≤1<<20;h=INT_MIN 用
    -(long long)hi 取绝对值,不再有 -h 溢出);rowsize 用 64 位算并验;
    行偏移 off / 采样下标 sx 改 long long,sx 夹在 [0,w-1];按行 fseek 前加
    off > LONG_MAX 拒绝;目标 RGBA 字节数 64 位验后再 malloc
  · PNG:IHDR 尺寸原已 ≤65535,补上 tw*th*4 的 64 位校验(32768²×4 = 2^32 会回绕),
    emit_row 的 sy/sx 与输出行偏移改 64 位并夹紧
- meow_jpeg.c(顺手审计同类 32 位算术,评审要求「不要只修点名那处」)
  · mj_layout 返回 unsigned long long(单平面/三平面之和都可能越 2^32),
    越界返回 MJ_LAYOUT_BAD → mj_sof 各处重新布局后都检查
  · target_bytes 与分量 plane malloc 改 64 位验算;目标图字节超 MJ_PEAK_CAP 直接拒
  · mj_output:输出缓冲 64 位验;y*vh / x*vw 采样改 long long 并夹紧
  · meow_img_target_dims / mj_pick_n 的 (long)h*maxdim、8*maxdim 改 long long

- test_meow_img_stream.c:新增 S17(10 组畸形 BMP 声明尺寸 + 2 个合法正例 +
  PNG 回绕点),断言「后端拒绝 + 公开入口不崩(越界尺寸另断言 NULL 且不分配)」;
  文件头补 ILP32 交叉编译命令(宿主 LP64 看不到 32 位回绕,测试注释里明写这一点)

验证:同一测试在 zig arm-linux-musleabi(sizeof(long)=4)下 S1-S17 全绿(206 checks,
exit 0);把后端换回 r1 提交的旧实现,同一 ARM32 二进制 exit 139(说明 S17 真能抓);
宿主 LP64 S1-S17 全绿、B1-B9 全绿;静态库 --verify 仍 78 成员 / 全 MIPS32r2。

同步提醒:这些行与公开仓 ~/Proj/MeowOS-SDK/sdk/{meow_png_stream.c,meow_jpeg.c} 逐字节
同源,同一补丁必须由控制塔同步过去,否则立刻产生双边漂移。
2026-11-06 21:27:03 +08:00

1340 lines
60 KiB
C
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.
/* test_meow_img_stream.c — M23 真缩解后端宿主验证(峰值 + 像素一致性 + 回退)。
*
* 覆盖矩阵(对齐 M23 任务 3/4/5):
* S1 探针/入口透明:meow_img_probe_file、load_file_dim / load_mem_dim 对新后端透明;
* 小图逐字节等价旧入口;maxdim<=0 等价旧入口
* S2 baseline 2000×3000:maxdim=720/480 峰值 ≤ 2.5MiB(旧入口如实并列打印)
* S3 progressive 2000×3000:同一断言(渐进式不再整幅驻留系数)
* S4 PNG 1600×2400:峰值 ≤ 2.5MiB(逐行 inflate,无整幅 raw/RGBA)
* S5 BMP 1600×2400:峰值 ≤ 2.5MiB(按行 seek,不读全图)
* S6 4MP/8MP/12MP JPEG:都能解且峰值 ≤ 2.5MiB;峰值增长远小于像素数增长(非线性)
* S7 像素一致性:与「stb 整幅解码 + meow_img_downscale」网格比对(容差内)
* S8 内存入口 == 文件入口(逐字节),峰值同级
* S9 回退:CMYK JPEG(4 分量)、1bpp BMP、Adam7 交错 PNG → 后端拒绝,stb 仍成功;
* 截断/垃圾 → NULL(不崩)
* S10-S16 缩解阶/restart/DHT/tRNS/step/低熵/progressive-restart 等专项
* S17 32 位尺寸算术:畸形 BMP 声明尺寸(w=2^27…、24bpp 回绕点、h=INT_MIN)与 PNG
* tw*th*4 回绕点 → 一律 NULL 不崩(ILP32 崩溃回归,M26 r1 P1)
*
* 夹具:本文件内置生成脚本(Pillow),首次运行时写到夹具目录并执行。
* 编译(本 sdk 目录下;--wrap 用于给 malloc/free 家族打点):
* gcc -Wall -Wextra -O2 -I. -Iinclude -DMEOW_UI_HOST_TEST -o test_meow_img_stream \
* test_meow_img_stream.c meow_ui_img.c meow_jpeg.c meow_png_stream.c stb_image.c \
* -lm -lz \
* -Wl,--wrap=malloc -Wl,--wrap=calloc -Wl,--wrap=realloc -Wl,--wrap=free
* 运行:./test_meow_img_stream "$PREFIX/tmp/meow_img_stream" (exit 0=PASS / 1=FAIL)
*
* ILP32 回归(S17 的真覆盖;宿主 LP64 的 long/size_t 是 64 位,看不到 32 位回绕崩溃):
* 用 zig 的 arm-linux-musleabi 目标(与 mipsel 同数据模型:long/size_t 均 4 字节),
* 需要一份 32 位 libz.a(设备上的 libz.a 就是 mipsel 32 位;本机可从 zlib 源码
* 用同一 zig cc --target=arm-linux-musleabi -c 各 .c 后 ar 打包)。命令:
* zig cc --target=arm-linux-musleabi -static -O2 -I. -Iinclude -DMEOW_UI_HOST_TEST \
* -o test_meow_img_stream_arm test_meow_img_stream.c meow_ui_img.c meow_jpeg.c \
* meow_png_stream.c stb_image.c -lm <libz_arm.a> \
* -Wl,--wrap=malloc -Wl,--wrap=calloc -Wl,--wrap=realloc -Wl,--wrap=free
* ./test_meow_img_stream_arm "$PREFIX/tmp/meow_img_stream"
* (aarch64 Android 设备可直跑 32 位 ARM 静态二进制;2026-11-06 实测 S1-S17 全绿。)
*/
#define _GNU_SOURCE
#include "meow_ui.h"
#include "meow_ui_internal.h" /* meow_gfx_blit_rgba 原型(本测试只给桩) */
#include "meow_jpeg.h" /* 后端入口(内部头):直接比对「后端 vs 回退」 */
#include "stb_image.h" /* 旧 _dim 路径基线(stbi_load_scaled,M23 之前的行为) */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <sys/stat.h>
#include <time.h> /* clock():解码耗时打印 */
#include <math.h> /* sqrt:结构相关系数 */
/* ------------------------------------------------------------------ */
/* 分配打点 */
/* ------------------------------------------------------------------ */
#define AHDR 32
static size_t g_live = 0;
static size_t g_peak = 0;
extern void *__real_malloc(size_t);
extern void *__real_calloc(size_t, size_t);
extern void *__real_realloc(void *, size_t);
extern void __real_free(void *);
static void acc_alloc(size_t n)
{
g_live += n;
if (g_live > g_peak) g_peak = g_live;
}
void *__wrap_malloc(size_t n)
{
size_t *h = (size_t *)__real_malloc(n + AHDR);
if (!h) return NULL;
h[0] = n;
acc_alloc(n);
return (char *)h + AHDR;
}
void *__wrap_calloc(size_t a, size_t b)
{
size_t n = a * b;
void *p = __wrap_malloc(n);
if (p) memset(p, 0, n);
return p;
}
void __wrap_free(void *p)
{
size_t *h;
if (!p) return;
h = (size_t *)((char *)p - AHDR);
g_live -= h[0];
__real_free(h);
}
void *__wrap_realloc(void *p, size_t n)
{
size_t old = p ? ((size_t *)((char *)p - AHDR))[0] : 0;
void *q = __wrap_malloc(n);
if (!q) return NULL;
if (p) {
memcpy(q, p, old < n ? old : n);
__wrap_free(p);
}
return q;
}
static size_t peak_reset(void) { g_peak = g_live; return g_live; }
static size_t peak_since(size_t base) { return g_peak - base; }
/* meow_ui_img.c 的绘制函数需要的 gfx 符号:本测试只测解码/内存,桩即可。 */
void meow_gfx_blit_rgba(int x, int y, int w, int h, const uint8_t *rgba, int alpha)
{
(void)x; (void)y; (void)w; (void)h; (void)rgba; (void)alpha;
}
/* ------------------------------------------------------------------ */
/* 断言与打印 */
/* ------------------------------------------------------------------ */
static int g_fail = 0;
#define CHECK(cond, name) do { \
if (cond) printf(" PASS %s\n", name); \
else { printf(" FAIL %s (%s:%d)\n", name, __FILE__, __LINE__); g_fail++; } } while (0)
#define PEAK_2500KB ((size_t)2560 * 1024) /* 2.5 MiB */
static void peak_report(const char *tag, size_t bytes)
{
printf(" peak %-32s %7.2f MiB (%zu B)\n", tag, (double)bytes / (1024.0 * 1024.0), bytes);
}
/* ------------------------------------------------------------------ */
/* 夹具 */
/* ------------------------------------------------------------------ */
static const char *g_dir = NULL;
static const char *g_gen_py =
"import os, sys, zlib, struct\n"
"from PIL import Image, ImageDraw\n"
"out = sys.argv[1]\n"
"os.makedirs(out, exist_ok=True)\n"
"def pat(w, h, mode='RGB'):\n"
" im = Image.new(mode, (w, h), (250, 249, 245) if mode == 'RGB' else 200)\n"
" d = ImageDraw.Draw(im)\n"
" for k in range(0, min(w, h) - 200, 40):\n"
" d.rectangle([40 + k, 40 + k, 200 + k, 180 + k], outline=(12, 12, 14), width=3)\n"
" for band in range(14):\n"
" y0 = band * h // 14\n"
" for x in range(0, w, 7):\n"
" d.line([(x, y0), (x + 3, y0 + 5)], fill=(110, 110, 116))\n"
" for i, c in enumerate([(220, 30, 40), (30, 180, 60), (40, 60, 220), (240, 200, 20)]):\n"
" d.rectangle([w // 2 + i * 30, h // 2, w // 2 + i * 30 + 25, h // 2 + 60], fill=c)\n"
" return im\n"
"big = pat(2000, 3000)\n"
"big.save(os.path.join(out, 'base2000.jpg'), 'JPEG', quality=60, subsampling=2)\n"
"big.save(os.path.join(out, 'prog2000.jpg'), 'JPEG', quality=60, subsampling=2, progressive=True)\n"
"big.save(os.path.join(out, 'base2000_444.jpg'), 'JPEG', quality=60, subsampling=0)\n"
"big.save(os.path.join(out, 'base2000_422.jpg'), 'JPEG', quality=60, subsampling=1)\n"
"big.save(os.path.join(out, 'restart2000.jpg'), 'JPEG', quality=60, subsampling=2,\n"
" restart_marker_blocks=8)\n"
"pat(640, 480).save(os.path.join(out, 'sweep640.jpg'), 'JPEG', quality=70, subsampling=2)\n"
"big.convert('L').save(os.path.join(out, 'gray2000.jpg'), 'JPEG', quality=60)\n"
"photo = Image.blend(big, Image.effect_noise((2000, 3000), 40).convert('RGB'), 0.35)\n"
"photo.save(os.path.join(out, 'photo2000.jpg'), 'JPEG', quality=75, subsampling=2)\n"
"photo.save(os.path.join(out, 'photo2000p.jpg'), 'JPEG', quality=75, subsampling=2, progressive=True)\n"
"pat(1600, 2400).save(os.path.join(out, 'page1600.png'), 'PNG')\n"
"pat(1600, 2400).save(os.path.join(out, 'page1600.bmp'), 'BMP')\n"
"pat(2320, 1740).save(os.path.join(out, 'mp4.jpg'), 'JPEG', quality=60, subsampling=2)\n"
"pat(3264, 2448).save(os.path.join(out, 'mp8.jpg'), 'JPEG', quality=60, subsampling=2)\n"
"pat(4000, 3000).save(os.path.join(out, 'mp12.jpg'), 'JPEG', quality=60, subsampling=2)\n"
"pat(64, 64).save(os.path.join(out, 'small64.jpg'), 'JPEG', quality=80)\n"
"Image.open(os.path.join(out, 'page1600.png')).convert('L').save(os.path.join(out, 'gray.png'))\n"
"pat(800, 600).convert('P', palette=Image.ADAPTIVE, colors=64).save(os.path.join(out, 'pal.png'))\n"
"pat(800, 600).convert('RGBA').save(os.path.join(out, 'rgba.png'))\n"
"big.convert('CMYK').save(os.path.join(out, 'cmyk.jpg'), 'JPEG', quality=60)\n"
"Image.open(os.path.join(out, 'page1600.png')).convert('1').save(os.path.join(out, 'mono1.bmp'))\n"
"def chunk(t, d):\n"
" c = struct.pack('>I', len(d)) + t + d\n"
" return c + struct.pack('>I', zlib.crc32(t + d) & 0xffffffff)\n"
"def adam7(path, w, h):\n"
" raw = bytearray()\n"
" 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)]\n"
" for (x0, y0, dx, dy) in passes:\n"
" xs = list(range(x0, w, dx)); ys = list(range(y0, h, dy))\n"
" if not xs or not ys:\n"
" continue\n"
" for y in ys:\n"
" raw.append(0)\n"
" for x in xs:\n"
" raw += bytes(((x * 3) & 255, (y * 3) & 255, ((x + y) * 2) & 255))\n"
" ihdr = struct.pack('>IIBBBBB', w, h, 8, 2, 0, 0, 1)\n"
" data = (b'\\x89PNG\\r\\n\\x1a\\n' + chunk(b'IHDR', ihdr) +\n"
" chunk(b'IDAT', zlib.compress(bytes(raw))) + chunk(b'IEND', b''))\n"
" open(path, 'wb').write(data)\n"
"adam7(os.path.join(out, 'adam7.png'), 1600, 2400)\n"
"for rb in (1, 2, 3, 4, 5, 8, 16, 64, 200):\n"
" big.save(os.path.join(out, 'prog_rst%d.jpg' % rb), 'JPEG', quality=60,\n"
" subsampling=2, progressive=True, restart_marker_blocks=rb)\n"
"white = Image.new('RGB', (2000, 3000), (255, 255, 255))\n"
"white.save(os.path.join(out, 'white2000p.jpg'), 'JPEG', quality=60,\n"
" subsampling=2, progressive=True)\n"
"for _q in (50, 70, 80):\n"
" white.save(os.path.join(out, 'white2000p%d.jpg' % _q), 'JPEG', quality=_q,\n"
" subsampling=2, progressive=True)\n"
"mw = Image.new('RGB', (2000, 3000), (255, 255, 255))\n"
"dmw = ImageDraw.Draw(mw)\n"
"dmw.rectangle([60, 60, 260, 180], fill=(0, 0, 0))\n"
"dmw.line([(300, 200), (1900, 200)], fill=(20, 20, 20), width=2)\n"
"mw.save(os.path.join(out, 'mostlywhite2000p.jpg'), 'JPEG', quality=95,\n"
" subsampling=2, progressive=True)\n"
"g8 = Image.new('L', (640, 480), 0)\n"
"dg = ImageDraw.Draw(g8)\n"
"dg.rectangle([100, 100, 400, 300], fill=255)\n"
"g8.save(os.path.join(out, 'gray8_trns.png'), 'PNG', transparency=0)\n"
"r8 = Image.new('RGB', (640, 480), (255, 0, 255))\n"
"dr = ImageDraw.Draw(r8)\n"
"dr.rectangle([100, 100, 400, 300], fill=(10, 200, 30))\n"
"r8.save(os.path.join(out, 'rgb8_trns.png'), 'PNG', transparency=(255, 0, 255))\n"
"open(os.path.join(out, 'broken.jpg'), 'wb').write(b'\\xff\\xd8\\xff\\xe0' +\n"
" bytes((i * 37 + 11) & 0xFF for i in range(256)))\n"
"for n in sorted(os.listdir(out)):\n"
" print(' %-14s %9d B' % (n, os.path.getsize(os.path.join(out, n))))\n";
static char *path_join(const char *name)
{
size_t n = strlen(g_dir) + strlen(name) + 2;
char *p = (char *)malloc(n);
if (!p) { printf("OOM\n"); exit(1); }
snprintf(p, n, "%s/%s", g_dir, name);
return p;
}
static int file_exists(const char *p)
{
struct stat st;
return stat(p, &st) == 0 && st.st_size > 0;
}
static void ensure_fixtures(void)
{
/* 标记文件:新增夹具后改这里,旧夹具目录会自动补齐(生成脚本幂等) */
char *m1 = path_join("base2000.jpg");
char *m2 = path_join("white2000p.jpg");
int have = file_exists(m1) && file_exists(m2);
free(m1);
free(m2);
if (have) return;
mkdir(g_dir, 0755);
printf("夹具缺失,生成到 %s(Pillow)\n", g_dir);
{
char *py = path_join("gen_stream_fixtures.py");
char cmd[1200];
FILE *f = fopen(py, "w");
int rc;
if (!f) { printf("FATAL 无法写 %s\n", py); exit(1); }
fputs(g_gen_py, f);
fclose(f);
snprintf(cmd, sizeof cmd, "python3 '%s' '%s'", py, g_dir);
rc = system(cmd);
free(py);
if (rc != 0) {
printf("FATAL 夹具生成失败(缺 Pillow?)\n");
exit(1);
}
}
}
static uint8_t *read_whole(const char *p, size_t *len)
{
FILE *f = fopen(p, "rb");
uint8_t *buf;
long n;
if (!f) return NULL;
fseek(f, 0, SEEK_END);
n = ftell(f);
fseek(f, 0, SEEK_SET);
if (n <= 0) { fclose(f); return NULL; }
buf = (uint8_t *)malloc((size_t)n);
if (!buf) { fclose(f); return NULL; }
if (fread(buf, 1, (size_t)n, f) != (size_t)n) { free(buf); fclose(f); return NULL; }
*len = (size_t)n;
fclose(f);
return buf;
}
/* ------------------------------------------------------------------ */
/* 图像辅助 */
/* ------------------------------------------------------------------ */
static int nonblank(const meow_img *im)
{
int w = meow_img_w(im), h = meow_img_h(im);
const unsigned char *p = meow_img_pixels(im);
long sum = 0;
if (!p || w <= 0 || h <= 0) return 0;
for (long i = 0; i < (long)w * h; i += 97)
sum += p[i * 4] + p[i * 4 + 1] + p[i * 4 + 2];
return sum > 0;
}
/* 求矩形区域的亮度均值(用于块均值相关:消除采样相位差与高频噪声,只看结构)。 */
static double block_mean(const meow_img *im, int x0, int y0, int x1, int y1)
{
int w = meow_img_w(im), h = meow_img_h(im);
const unsigned char *p = meow_img_pixels(im);
double s = 0;
long n = 0;
if (!p) return 0;
if (x1 > w) x1 = w;
if (y1 > h) y1 = h;
for (int y = y0; y < y1; y++)
for (int x = x0; x < x1; x++) {
const unsigned char *q = p + ((size_t)y * w + x) * 4;
s += (q[0] + q[1] + q[2]) / 3.0;
n++;
}
return n ? s / n : 0;
}
/* 与「整幅 stb 解码 + meow_img_downscale」比对:按比例网格采样,返回各通道平均绝对差;
* corr 非空时同时给出「网格块均值」的亮度相关系数(结构一致性)。 */
static double mean_abs_diff(const meow_img *a, const meow_img *b, int grid, double *corr)
{
int wa = meow_img_w(a), ha = meow_img_h(a);
int wb = meow_img_w(b), hb = meow_img_h(b);
const unsigned char *pa = meow_img_pixels(a), *pb = meow_img_pixels(b);
long acc = 0, cnt = 0;
if (!pa || !pb || wa <= 0 || hb <= 0) return 1e9;
for (int gy = 0; gy < grid; gy++) {
int ya = (int)((long)gy * ha / grid);
int yb = (int)((long)gy * hb / grid);
for (int gx = 0; gx < grid; gx++) {
int xa = (int)((long)gx * wa / grid);
int xb = (int)((long)gx * wb / grid);
const unsigned char *qa = pa + ((size_t)ya * wa + xa) * 4;
const unsigned char *qb = pb + ((size_t)yb * wb + xb) * 4;
for (int k = 0; k < 3; k++) {
int d = (int)qa[k] - (int)qb[k];
acc += d < 0 ? -d : d;
cnt++;
}
}
}
if (corr && cnt) {
double s1 = 0, s2 = 0, s11 = 0, s22 = 0, s12 = 0;
long n = 0;
for (int gy = 0; gy < grid; gy++) {
int ya0 = (int)((long)gy * ha / grid), ya1 = (int)((long)(gy + 1) * ha / grid);
int yb0 = (int)((long)gy * hb / grid), yb1 = (int)((long)(gy + 1) * hb / grid);
if (ya1 <= ya0) ya1 = ya0 + 1;
if (yb1 <= yb0) yb1 = yb0 + 1;
for (int gx = 0; gx < grid; gx++) {
int xa0 = (int)((long)gx * wa / grid), xa1 = (int)((long)(gx + 1) * wa / grid);
int xb0 = (int)((long)gx * wb / grid), xb1 = (int)((long)(gx + 1) * wb / grid);
double la, lb;
if (xa1 <= xa0) xa1 = xa0 + 1;
if (xb1 <= xb0) xb1 = xb0 + 1;
la = block_mean(a, xa0, ya0, xa1, ya1);
lb = block_mean(b, xb0, yb0, xb1, yb1);
s1 += la; s2 += lb; s11 += la * la; s22 += lb * lb; s12 += la * lb;
n++;
}
}
if (n > 1) {
double va = s11 - s1 * s1 / n, vb = s22 - s2 * s2 / n;
double cv = s12 - s1 * s2 / n;
*corr = (va > 1e-9 && vb > 1e-9) ? cv / sqrt(va * vb) : 1.0;
} else {
*corr = 1.0;
}
}
return cnt ? (double)acc / (double)cnt : 1e9;
}
/* 旧入口整幅解码 → 参考缩略图(downscale 到 maxdim),用于像素比对。 */
static meow_img *ref_thumb(const char *path, int maxdim)
{
meow_img *full = meow_img_load_file(path);
meow_img *small;
if (!full) return NULL;
if (meow_img_w(full) <= maxdim && meow_img_h(full) <= maxdim) return full;
small = meow_img_downscale(full, maxdim);
meow_img_free(full);
return small;
}
/* 一次「新入口」解码的峰值。 */
static size_t peak_of_dim(const char *path, int maxdim, meow_img **out)
{
size_t base = peak_reset();
meow_img *im = meow_img_load_file_dim(path, maxdim);
size_t peak = peak_since(base);
if (out) *out = im; else meow_img_free(im);
return peak;
}
/* 旧 _dim 路径基线(M23 之前的实现):stbi_load_scaled + 最近邻精修到 maxdim 的瞬时峰值。
* 只用于报告里的「前后对比表」——这就是审计里 +10.1MB / +23.5MB / +26.2MB 的那条路径。 */
static size_t old_dim_peak(const char *path, int maxdim)
{
int w = 0, h = 0, c = 0, s = 1, longside;
size_t base, peak;
unsigned char *p, *small = NULL;
if (!stbi_info(path, &w, &h, &c)) return 0;
longside = w > h ? w : h;
base = peak_reset();
while (s < 8 && (longside / (s * 2)) >= maxdim) s *= 2;
p = stbi_load_scaled(path, &w, &h, &c, 4, s);
if (!p) { peak = peak_since(base); return peak; }
if (w > maxdim || h > maxdim) {
int dw, dh;
if (w >= h) { dw = maxdim; dh = (int)((long)h * maxdim / w); }
else { dh = maxdim; dw = (int)((long)w * maxdim / h); }
if (dw < 1) dw = 1;
if (dh < 1) dh = 1;
small = (unsigned char *)malloc((size_t)dw * dh * 4);
if (small) {
for (int dy = 0; dy < dh; dy++) {
int sy = (int)((long)dy * h / dh);
for (int dx = 0; dx < dw; dx++) {
int sx = (int)((long)dx * w / dw);
memcpy(small + ((size_t)dy * dw + dx) * 4, p + ((size_t)sy * w + sx) * 4, 4);
}
}
}
}
peak = peak_since(base);
free(p);
free(small);
return peak;
}
static long alpha_zero_count(const meow_img *im)
{
int w = meow_img_w(im), h = meow_img_h(im);
const unsigned char *p = meow_img_pixels(im);
long n = 0;
if (!p) return 0;
for (long i = 0; i < (long)w * h; i++) if (p[i * 4 + 3] == 0) n++;
return n;
}
/* 目标图 vs 整幅参考图的 alpha 一致性:按 nearest 反向映射采样源像素,返回不一致像素数
* (-1 = 参数错误)。PNG 的采样公式与后端一致(tx*W/tw、ty*H/th),应逐点相等。 */
static long alpha_mismatch(const meow_img *got, const meow_img *ref)
{
int gw = meow_img_w(got), gh = meow_img_h(got);
int rw = meow_img_w(ref), rh = meow_img_h(ref);
const unsigned char *pg = meow_img_pixels(got), *pr = meow_img_pixels(ref);
long bad = 0;
if (!pg || !pr || gw <= 0 || gh <= 0) return -1;
for (int y = 0; y < gh; y++) {
int sy = (int)((long)y * rh / gh);
for (int x = 0; x < gw; x++) {
int sx = (int)((long)x * rw / gw);
if (pg[((size_t)y * gw + x) * 4 + 3] != pr[((size_t)sy * rw + sx) * 4 + 3])
bad++;
}
}
return bad;
}
/* ------------------------------------------------------------------ */
/* P1-1 回归:过订阅 Huffman 表(畸形 DHT) */
/* ------------------------------------------------------------------ */
/* 构造「SOI + DHT(码长 lc 有 nsym 个符号) + EOI」的最小 JPEG(≤ 280B)。 */
static size_t build_badhuff(unsigned char *buf, int lc, int nsym)
{
size_t p = 0;
int i;
int seglen = 2 + 1 + 16 + nsym; /* DHT 长度字段计入自身 2 字节 */
buf[p++] = 0xFF; buf[p++] = 0xD8; /* SOI */
buf[p++] = 0xFF; buf[p++] = 0xC4; /* DHT */
buf[p++] = (unsigned char)(seglen >> 8);
buf[p++] = (unsigned char)(seglen & 0xFF);
buf[p++] = 0x00; /* Tc=0, Th=0 */
for (i = 1; i <= 16; i++)
buf[p++] = (unsigned char)(i == lc ? nsym : 0);
for (i = 0; i < nsym; i++) buf[p++] = (unsigned char)i;
buf[p++] = 0xFF; buf[p++] = 0xD9; /* EOI */
return p;
}
/* 把同一段坏 DHT 插到真实 JPEG 的 SOI 之后(其余字节原样):验证「有效文件 + 畸形
* 码表」走的是「拒绝 + 回退」,而不是花屏/崩溃。 */
static unsigned char *inject_badhuff(const unsigned char *orig, size_t n,
int lc, int nsym, size_t *outn)
{
unsigned char seg[320];
size_t all = build_badhuff(seg, lc, nsym);
size_t seglen = all - 4; /* 去掉首 FFD8 与尾 FFD9 */
size_t total = n + seglen;
unsigned char *out = (unsigned char *)malloc(total);
if (!out) return NULL;
memcpy(out, orig, 2); /* SOI */
memcpy(out + 2, seg + 2, seglen); /* 坏 DHT 段 */
memcpy(out + 2 + seglen, orig + 2, n - 2); /* 原文件其余部分 */
*outn = total;
return out;
}
/* 全图逐像素比较(不是抽样):返回平均绝对差;*maxd = 最大单通道差;
* *big = 差值 > thr 的通道数。尺寸必须一致。 */
static double full_diff(const meow_img *a, const meow_img *b, int *maxd, long *big, int thr)
{
int w = meow_img_w(a), h = meow_img_h(a);
const unsigned char *pa = meow_img_pixels(a), *pb = meow_img_pixels(b);
long acc = 0, cnt = 0, nb = 0;
int mx = 0;
if (!pa || !pb || w <= 0 || h <= 0 || meow_img_w(b) != w || meow_img_h(b) != h) {
if (maxd) *maxd = 999;
if (big) *big = -1;
return 1e9;
}
for (long i = 0; i < (long)w * h; i++)
for (int k = 0; k < 3; k++) {
int d = (int)pa[i * 4 + k] - (int)pb[i * 4 + k];
if (d < 0) d = -d;
if (d > mx) mx = d;
if (d > thr) nb++;
acc += d;
cnt++;
}
if (maxd) *maxd = mx;
if (big) *big = nb;
return cnt ? (double)acc / (double)cnt : 1e9;
}
/* 全图最小通道值(丢 MCU 会露出 128 灰底,纯白图上立刻可见)。 */
static int min_channel(const meow_img *im)
{
int w = meow_img_w(im), h = meow_img_h(im);
const unsigned char *p = meow_img_pixels(im);
int mn = 255;
if (!p || w <= 0 || h <= 0) return -1;
for (long i = 0; i < (long)w * h; i++)
for (int k = 0; k < 3; k++)
if (p[i * 4 + k] < mn) mn = p[i * 4 + k];
return mn;
}
/* 矩形区域的最小通道值(丢 MCU 的灰块只出现在最后一块附近,看角落最灵敏)。 */
static int region_min(const meow_img *im, int x0, int y0, int x1, int y1)
{
int w = meow_img_w(im), h = meow_img_h(im);
const unsigned char *p = meow_img_pixels(im);
int mn = 255;
if (!p) return -1;
if (x0 < 0) x0 = 0;
if (y0 < 0) y0 = 0;
if (x1 > w) x1 = w;
if (y1 > h) y1 = h;
for (int y = y0; y < y1; y++)
for (int x = x0; x < x1; x++)
for (int k = 0; k < 3; k++)
if (p[((size_t)y * w + x) * 4 + k] < mn) mn = p[((size_t)y * w + x) * 4 + k];
return mn;
}
/* 构造「SOI + SOF0(W×H, ncomp) + EOI」的最小 JPEG(无扫描数据)。 */
static size_t build_sof_only(unsigned char *buf, int w, int h, int ncomp)
{
size_t p = 0;
int i;
int seglen = 2 + 6 + 3 * ncomp;
buf[p++] = 0xFF; buf[p++] = 0xD8; /* SOI */
buf[p++] = 0xFF; buf[p++] = 0xC0; /* SOF0 */
buf[p++] = (unsigned char)(seglen >> 8);
buf[p++] = (unsigned char)(seglen & 0xFF);
buf[p++] = 8; /* precision */
buf[p++] = (unsigned char)((h >> 8) & 0xFF); buf[p++] = (unsigned char)(h & 0xFF);
buf[p++] = (unsigned char)((w >> 8) & 0xFF); buf[p++] = (unsigned char)(w & 0xFF);
buf[p++] = (unsigned char)ncomp;
for (i = 0; i < ncomp; i++) {
buf[p++] = (unsigned char)(i + 1);
buf[p++] = (unsigned char)((i == 0 && ncomp > 1) ? 0x22 : 0x11); /* 4:2:0 / 4:4:4 */
buf[p++] = (unsigned char)(i ? 1 : 0); /* 量化表号 */
}
buf[p++] = 0xFF; buf[p++] = 0xD9; /* EOI */
return p;
}
/* ---- S17 用的畸形头构造器(54 字节 BMP / 仅 IHDR 的 PNG) ---- */
static size_t bmp_build(unsigned char *b, unsigned w, unsigned hbits, unsigned bpp)
{
size_t off = 54;
memset(b, 0, off);
b[0] = 'B'; b[1] = 'M';
b[2] = (unsigned char)(off & 0xFF); b[3] = (unsigned char)((off >> 8) & 0xFF);
b[10] = (unsigned char)(off & 0xFF); b[11] = (unsigned char)((off >> 8) & 0xFF);
b[14] = 40; /* BITMAPINFOHEADER */
b[18] = (unsigned char)(w & 0xFF); b[19] = (unsigned char)((w >> 8) & 0xFF);
b[20] = (unsigned char)((w >> 16) & 0xFF); b[21] = (unsigned char)((w >> 24) & 0xFF);
b[22] = (unsigned char)(hbits & 0xFF); b[23] = (unsigned char)((hbits >> 8) & 0xFF);
b[24] = (unsigned char)((hbits >> 16) & 0xFF); b[25] = (unsigned char)((hbits >> 24) & 0xFF);
b[26] = 1; /* planes */
b[28] = (unsigned char)(bpp & 0xFF);
return off;
}
/* 正例对照:合法的 64×64 32bpp BMP(topdown=1 时 h 写成负数) */
static size_t bmp_build_64(unsigned char *b, int topdown)
{
const unsigned w = 64, h = 64, bpp = 32;
size_t n = bmp_build(b, w, topdown ? (unsigned)(-64) : h, bpp);
unsigned x, y;
b[2] = (unsigned char)((n + w * h * 4) & 0xFF); /* 文件大小(解析器不校验) */
b[3] = (unsigned char)(((n + w * h * 4) >> 8) & 0xFF);
for (y = 0; y < h; y++)
for (x = 0; x < w; x++, n += 4) {
b[n + 0] = (unsigned char)(x * 4);
b[n + 1] = (unsigned char)(y * 4);
b[n + 2] = 0x40;
b[n + 3] = 0xFF;
}
return n;
}
static size_t png_ihdr_only(unsigned char *b, unsigned w, unsigned h)
{
static const unsigned char sig[8] = { 0x89, 'P', 'N', 'G', '\r', '\n', 0x1a, '\n' };
size_t p = 0;
memcpy(b, sig, 8);
p = 8;
b[p++] = 0; b[p++] = 0; b[p++] = 0; b[p++] = 13; /* IHDR 长度 */
memcpy(b + p, "IHDR", 4); p += 4;
b[p++] = (unsigned char)(w >> 24); b[p++] = (unsigned char)((w >> 16) & 0xFF);
b[p++] = (unsigned char)((w >> 8) & 0xFF); b[p++] = (unsigned char)(w & 0xFF);
b[p++] = (unsigned char)(h >> 24); b[p++] = (unsigned char)((h >> 16) & 0xFF);
b[p++] = (unsigned char)((h >> 8) & 0xFF); b[p++] = (unsigned char)(h & 0xFF);
b[p++] = 8; /* bit depth */
b[p++] = 2; /* color type RGB */
b[p++] = 0; b[p++] = 0; b[p++] = 0;
return p; /* 故意不带后续块 */
}
/* ================================================================== */
int main(int argc, char **argv)
{
char *p_base, *p_prog, *p_png, *p_bmp, *p_mp4, *p_mp8, *p_mp12;
char *p_small, *p_gray, *p_pal, *p_rgba, *p_cmyk, *p_mono, *p_adam7, *p_broken;
char *p_444, *p_422, *p_grayjpg, *p_photo, *p_photop;
if (argc > 1) {
g_dir = argv[1];
} else {
static char dflt[600];
const char *tmp = getenv("TMPDIR");
const char *pre = getenv("PREFIX");
if (tmp && *tmp) snprintf(dflt, sizeof dflt, "%s/meow_img_stream", tmp);
else if (pre && *pre) snprintf(dflt, sizeof dflt, "%s/tmp/meow_img_stream", pre);
else snprintf(dflt, sizeof dflt, "/tmp/meow_img_stream");
g_dir = dflt;
}
ensure_fixtures();
p_base = path_join("base2000.jpg");
p_prog = path_join("prog2000.jpg");
p_png = path_join("page1600.png");
p_bmp = path_join("page1600.bmp");
p_mp4 = path_join("mp4.jpg");
p_mp8 = path_join("mp8.jpg");
p_mp12 = path_join("mp12.jpg");
p_small = path_join("small64.jpg");
p_gray = path_join("gray.png");
p_pal = path_join("pal.png");
p_rgba = path_join("rgba.png");
p_cmyk = path_join("cmyk.jpg");
p_mono = path_join("mono1.bmp");
p_adam7 = path_join("adam7.png");
p_broken = path_join("broken.jpg");
p_444 = path_join("base2000_444.jpg");
p_422 = path_join("base2000_422.jpg");
p_grayjpg = path_join("gray2000.jpg");
p_photo = path_join("photo2000.jpg");
p_photop = path_join("photo2000p.jpg");
if (!file_exists(p_base)) { printf("FATAL 夹具 %s 缺失\n", p_base); return 1; }
/* ---- S1 入口透明 ---- */
printf("[S1] 探针 / 小图 / maxdim<=0\n");
{
int w = 0, h = 0, c = 0;
meow_img *a = meow_img_load_file(p_small);
meow_img *b = meow_img_load_file_dim(p_small, 64);
meow_img *z = meow_img_load_file_dim(p_small, 0);
CHECK(meow_img_probe_file(p_base, &w, &h, &c) == 1 && w == 2000 && h == 3000,
"probe base2000 → 2000x3000");
CHECK(a && b && z, "小图三条路径都成功");
CHECK(a && b && memcmp(meow_img_pixels(a), meow_img_pixels(b),
(size_t)meow_img_w(a) * meow_img_h(a) * 4) == 0, "小图逐字节与旧入口一致");
CHECK(a && z && memcmp(meow_img_pixels(a), meow_img_pixels(z),
(size_t)meow_img_w(a) * meow_img_h(a) * 4) == 0, "maxdim=0 等价旧入口");
CHECK(meow_img_probe_file(p_broken, NULL, NULL, NULL) == 0, "截断图 probe → 0");
meow_img_free(a); meow_img_free(b); meow_img_free(z);
}
/* ---- S2 baseline 2000×3000 ---- */
printf("[S2] baseline 2000x3000(maxdim=720/480)\n");
{
meow_img *im = NULL;
size_t pk720 = peak_of_dim(p_base, 720, &im);
size_t pk480, pk_old;
peak_report("新入口 _dim(720)", pk720);
CHECK(im && meow_img_w(im) == 480 && meow_img_h(im) == 720 && nonblank(im),
"结果 480x720 且非空");
CHECK(pk720 <= PEAK_2500KB, "峰值 ≤ 2.5 MiB");
meow_img_free(im);
pk480 = peak_of_dim(p_base, 480, NULL);
peak_report("新入口 _dim(480)", pk480);
CHECK(pk480 <= PEAK_2500KB, "_dim(480) 峰值 ≤ 2.5 MiB");
{
size_t base = peak_reset();
meow_img *old = meow_img_load_file(p_base);
pk_old = peak_since(base);
peak_report("旧入口 meow_img_load_file", pk_old);
CHECK(old && pk_old > 6 * pk720, "新入口峰值 < 旧入口 1/6(整幅不再物化)");
meow_img_free(old);
}
}
/* ---- S2b 其它色度采样/灰度/照片:峰值与压缩体积无关 ---- */
printf("[S2b] 4:4:4 / 4:2:2 / 灰度 / 照片级噪声(同一 2000x3000, maxdim=720)\n");
{
char *pe = path_join("base2000_444.jpg");
char *p4 = path_join("base2000_422.jpg");
char *pg = path_join("gray2000.jpg");
char *pp = path_join("photo2000.jpg");
char *pq = path_join("photo2000p.jpg");
struct { const char *tag; const char *path; } fx[] = {
{ "4:4:4", pe }, { "4:2:2", p4 }, { "gray", pg },
{ "photo baseline", pp }, { "photo progressive", pq },
};
for (size_t i = 0; i < sizeof fx / sizeof fx[0]; i++) {
meow_img *im = NULL;
size_t base = peak_reset();
size_t pk;
long t0 = (long)clock();
im = meow_img_load_file_dim(fx[i].path, 720);
pk = peak_since(base);
{
char name[128];
struct stat st;
stat(fx[i].path, &st);
peak_report(fx[i].tag, pk);
printf(" 源文件 %ld KB,解码 %ld ms,输出 %dx%d\n",
(long)st.st_size / 1024,
(long)((clock() - t0) * 1000 / CLOCKS_PER_SEC),
meow_img_w(im), meow_img_h(im));
snprintf(name, sizeof name, "%s 峰值 ≤ 2.5 MiB 且非空", fx[i].tag);
CHECK(im && nonblank(im) && pk <= PEAK_2500KB, name);
}
meow_img_free(im);
}
free(pe); free(p4); free(pg); free(pp); free(pq);
}
/* ---- S3 progressive 2000×3000 ---- */
printf("[S3] progressive 2000x3000\n");
{
meow_img *im = NULL;
size_t pk = peak_of_dim(p_prog, 720, &im);
peak_report("新入口 _dim(720) progressive", pk);
CHECK(im && meow_img_w(im) == 480 && meow_img_h(im) == 720 && nonblank(im),
"结果 480x720 且非空");
CHECK(pk <= PEAK_2500KB, "渐进式峰值 ≤ 2.5 MiB(不整幅驻留系数)");
{
size_t base = peak_reset();
meow_img *old = meow_img_load_file(p_prog);
size_t pk_old = peak_since(base);
peak_report("旧入口 progressive", pk_old);
CHECK(old && pk_old > 8 * pk, "渐进式新入口峰值 < 旧入口 1/8");
meow_img_free(old);
}
meow_img_free(im);
}
/* ---- S4 PNG ---- */
printf("[S4] PNG 1600x2400\n");
{
meow_img *im = NULL;
size_t pk = peak_of_dim(p_png, 720, &im);
peak_report("新入口 _dim(720) PNG", pk);
CHECK(im && meow_img_w(im) == 480 && meow_img_h(im) == 720 && nonblank(im),
"结果 480x720 且非空");
CHECK(pk <= PEAK_2500KB, "PNG 峰值 ≤ 2.5 MiB(逐行 inflate)");
{
size_t base = peak_reset();
meow_img *old = meow_img_load_file(p_png);
size_t pk_old = peak_since(base);
peak_report("旧入口 PNG", pk_old);
CHECK(old && pk_old > 6 * pk, "PNG 新入口峰值 < 旧入口 1/6");
meow_img_free(old);
}
meow_img_free(im);
}
/* ---- S5 BMP ---- */
printf("[S5] BMP 1600x2400\n");
{
meow_img *im = NULL;
size_t pk = peak_of_dim(p_bmp, 720, &im);
peak_report("新入口 _dim(720) BMP", pk);
CHECK(im && meow_img_w(im) == 480 && meow_img_h(im) == 720 && nonblank(im),
"结果 480x720 且非空");
CHECK(pk <= PEAK_2500KB, "BMP 峰值 ≤ 2.5 MiB(按行 seek)");
meow_img_free(im);
}
/* ---- S6 4/8/12MP:峰值不随分辨率线性增长 ---- */
printf("[S6] 4MP/8MP/12MP JPEG(同一 maxdim=720)\n");
{
size_t p4 = peak_of_dim(p_mp4, 720, NULL);
size_t p8 = peak_of_dim(p_mp8, 720, NULL);
size_t p12 = peak_of_dim(p_mp12, 720, NULL);
meow_img *big = NULL;
size_t pb = peak_of_dim(p_mp12, 720, &big);
(void)pb;
peak_report("4MP _dim(720)", p4);
peak_report("8MP _dim(720)", p8);
peak_report("12MP _dim(720)", p12);
CHECK(big && nonblank(big), "12MP 能解且非空");
meow_img_free(big);
CHECK(p4 && p8 && p12, "4/8/12MP 全部解码成功");
CHECK(p4 <= PEAK_2500KB && p8 <= PEAK_2500KB && p12 <= PEAK_2500KB,
"4/8/12MP 峰值均 ≤ 2.5 MiB");
/* 像素数 3× 增长(4→12MP),线性则峰值 3×;DCT 缩解下应远小于。 */
CHECK(p12 < p4 * 2, "12MP/4MP 峰值 < 2×(远非线性 3×)");
printf(" 12MP/4MP 峰值比 = %.2f(像素比 3.00,旧入口约同上)\n",
(double)p12 / (double)p4);
}
/* ---- S6b 前后对比表 ---- */
printf("[S6b] 峰值前后对比(旧 _dim 路径 = stbi_load_scaled + 精修)\n");
{
struct { const char *tag; const char *path; int md; } fx[] = {
{ "2000x3000 baseline @720", p_base, 720 },
{ "2000x3000 progressive @720", p_prog, 720 },
{ "2000x3000 baseline @480", p_base, 480 },
{ "1600x2400 PNG @720", p_png, 720 },
{ "1600x2400 BMP @720", p_bmp, 720 },
{ "4MP JPEG @720", p_mp4, 720 },
{ "8MP JPEG @720", p_mp8, 720 },
{ "12MP JPEG @720", p_mp12, 720 },
};
for (size_t i = 0; i < sizeof fx / sizeof fx[0]; i++) {
size_t old = old_dim_peak(fx[i].path, fx[i].md);
meow_img *im = NULL;
size_t nw = peak_of_dim(fx[i].path, fx[i].md, &im);
char name[128];
printf(" %-28s 旧 %6.2f MiB → 新 %5.2f MiB(÷%.1f)\n", fx[i].tag,
(double)old / 1048576.0, (double)nw / 1048576.0,
nw ? (double)old / (double)nw : 0.0);
snprintf(name, sizeof name, "%s 新峰值 ≤ 旧 1/3", fx[i].tag);
CHECK(old && nw && nw * 3 <= old, name);
meow_img_free(im);
}
}
/* ---- S7 像素一致性 ---- */
printf("[S7] 像素一致性(vs stb 整幅 + meow_img_downscale)\n");
{
struct { const char *tag; const char *path; int md; double tol; double corr_min; } cases[] = {
{ "baseline JPEG", p_base, 720, 24.0, 0.85 },
{ "progressive JPEG", p_prog, 720, 24.0, 0.85 },
{ "baseline JPEG", p_base, 300, 26.0, 0.85 },
{ "4:4:4 JPEG", p_444, 720, 24.0, 0.85 },
{ "4:2:2 JPEG", p_422, 720, 24.0, 0.85 },
{ "gray JPEG", p_grayjpg, 720, 24.0, 0.85 },
{ "photo baseline", p_photo, 720, 24.0, 0.85 },
{ "photo progressive", p_photop, 720, 26.0, 0.85 },
{ "PNG RGB", p_png, 720, 8.0, 0.999 },
{ "PNG gray", p_gray, 300, 8.0, 0.999 },
{ "PNG palette", p_pal, 300, 8.0, 0.999 },
{ "PNG RGBA", p_rgba, 300, 8.0, 0.999 },
{ "BMP", p_bmp, 720, 8.0, 0.999 },
{ "12MP JPEG", p_mp12, 720, 24.0, 0.85 },
};
for (size_t i = 0; i < sizeof cases / sizeof cases[0]; i++) {
meow_img *got = meow_img_load_file_dim(cases[i].path, cases[i].md);
meow_img *ref = ref_thumb(cases[i].path, cases[i].md);
char name[128];
if (!got || !ref) {
snprintf(name, sizeof name, "%s 两侧都成功", cases[i].tag);
CHECK(0, name);
meow_img_free(got); meow_img_free(ref);
continue;
}
{
double corr = 0.0;
double d = mean_abs_diff(got, ref, 64, &corr);
snprintf(name, sizeof name, "%s _dim(%d) 平均差 %.2f < %.1f",
cases[i].tag, cases[i].md, d, cases[i].tol);
CHECK(d < cases[i].tol, name);
snprintf(name, sizeof name, "%s _dim(%d) 结构相关 %.3f > %.3f(非错位/反色/错位通道)",
cases[i].tag, cases[i].md, corr, cases[i].corr_min);
CHECK(corr > cases[i].corr_min, name);
snprintf(name, sizeof name, "%s _dim(%d) 尺寸与旧入口精修一致",
cases[i].tag, cases[i].md);
CHECK(meow_img_w(got) == meow_img_w(ref) && meow_img_h(got) == meow_img_h(ref),
name);
}
meow_img_free(got);
meow_img_free(ref);
}
}
/* ---- S10 缩解阶扫描 + 重启标记(验证 N=4/5/7 基表与 RST 重同步) ---- */
printf("[S10] 缩解阶扫描(N=4/5/7)与 restart marker\n");
{
char *psw = path_join("sweep640.jpg");
char *prs = path_join("restart2000.jpg");
static const int dims[] = { 320, 400, 560 };
for (size_t i = 0; i < sizeof dims / sizeof dims[0]; i++) {
meow_img *got = meow_img_load_file_dim(psw, dims[i]);
meow_img *ref = ref_thumb(psw, dims[i]);
char name[128];
double corr = 0.0;
double d = (got && ref) ? mean_abs_diff(got, ref, 32, &corr) : 1e9;
snprintf(name, sizeof name, "640x480 _dim(%d) → %dx%d 平均差 %.2f 相关 %.3f",
dims[i], meow_img_w(got), meow_img_h(got), d, corr);
CHECK(got && ref && d < 24.0 && corr > 0.85 && nonblank(got), name);
meow_img_free(got);
meow_img_free(ref);
}
{
size_t base = peak_reset();
int tw = 0, th = 0;
unsigned char *px = NULL;
int handled = meow_img_backend_file(prs, 720, &tw, &th, &px);
meow_img *got;
size_t pk;
meow_img *ref;
double corr = 0.0;
double d;
if (px) free(px);
base = peak_reset();
got = meow_img_load_file_dim(prs, 720);
pk = peak_since(base);
ref = ref_thumb(prs, 720);
d = (got && ref) ? mean_abs_diff(got, ref, 64, &corr) : 1e9;
peak_report("restart marker 2000x3000", pk);
CHECK(handled == 1, "baseline + restart 走后端(BACKEND=1,不回退 stb)");
CHECK(got && nonblank(got), "restart marker JPEG 解码成功");
CHECK(d < 24.0 && corr > 0.85, "restart marker JPEG 与 stb 结构一致(RST 重同步正确)");
CHECK(pk <= PEAK_2500KB, "restart marker JPEG 峰值 ≤ 2.5 MiB");
meow_img_free(got);
meow_img_free(ref);
}
/* baseline + restart 与同参数无 restart 文件:RST 只切分熵段,解码结果应逐字节相同 */
{
char *pbase = path_join("base2000.jpg");
meow_img *a = meow_img_load_file_dim(prs, 720);
meow_img *b = meow_img_load_file_dim(pbase, 720);
CHECK(a && b && meow_img_w(a) == meow_img_w(b) && meow_img_h(a) == meow_img_h(b) &&
memcmp(meow_img_pixels(a), meow_img_pixels(b),
(size_t)meow_img_w(a) * meow_img_h(a) * 4) == 0,
"baseline restart 与无 restart 结果逐字节一致(mad=0)");
meow_img_free(a);
meow_img_free(b);
free(pbase);
}
free(psw);
free(prs);
}
/* ---- S11 畸形 DHT(过订阅 Huffman 表):拒绝 + 回退,不崩 ---- */
printf("[S11] 畸形 DHT 矩阵(码长 1..4 × 符号数 3..255)\n");
{
unsigned char buf[320];
int cases = 0, rejected = 0;
int lc, nsym;
for (lc = 1; lc <= 4; lc++) {
for (nsym = 3; nsym <= 255; nsym++) {
size_t n = build_badhuff(buf, lc, nsym);
int tw = 0, th = 0;
unsigned char *px = NULL;
cases++;
/* 崩溃即测试进程死;返回 1 说明过订阅表被当成合法表收下(缺陷) */
if (meow_img_backend_mem(buf, n, 720, &tw, &th, &px) == 0) rejected++;
if (px) free(px);
}
}
{
char name[128];
snprintf(name, sizeof name, "过订阅 DHT 全部拒绝(%d/%d,无崩溃)", rejected, cases);
CHECK(rejected == cases, name);
}
/* 有效 JPEG + 注入坏 DHT:后端必须拒绝;公共入口不得崩(stb 兜底或返回 NULL) */
{
size_t flen = 0, ilen = 0;
uint8_t *real = read_whole(p_base, &flen);
CHECK(real != NULL, "读入 baseline JPEG 用于注入");
if (real) {
uint8_t *inj = inject_badhuff(real, flen, 2, 100, &ilen);
int tw = 0, th = 0;
unsigned char *px = NULL;
meow_img *im;
CHECK(inj && meow_img_backend_mem(inj, ilen, 720, &tw, &th, &px) == 0,
"有效 JPEG + 注入过订阅 DHT → 后端拒绝");
if (px) free(px);
im = inj ? meow_img_load_mem_dim(inj, (int)ilen, 720) : NULL;
CHECK(im == NULL || nonblank(im), "注入坏表后公共入口不崩、不花屏");
meow_img_free(im);
free(inj);
free(real);
}
}
}
/* ---- S12 渐进式 + restart marker(P1-2 回归) ---- */
printf("[S12] progressive + restart marker(BACKEND 必须为 1,峰值 ≤2.5MiB)\n");
{
static const int rb[] = { 1, 2, 3, 4, 5, 8, 16, 64, 200 };
meow_img *ref = meow_img_load_file_dim(p_prog, 720);
int all_backend = 1, all_peak = 1, all_equal = 1;
for (size_t i = 0; i < sizeof rb / sizeof rb[0]; i++) {
char fname[48];
char *p;
int tw = 0, th = 0;
unsigned char *px = NULL;
meow_img *im;
size_t base, pk;
snprintf(fname, sizeof fname, "prog_rst%d.jpg", rb[i]);
p = path_join(fname);
if (meow_img_backend_file(p, 720, &tw, &th, &px) != 1) all_backend = 0;
if (px) free(px);
base = peak_reset();
im = meow_img_load_file_dim(p, 720);
pk = peak_since(base);
if (!(im && nonblank(im) && pk <= PEAK_2500KB)) all_peak = 0;
/* 严格全图逐字节比较(不是网格抽样:64×64=4096 个采样点看不到右下角)。
* restart 只切分熵段、不改系数流,任何差异都是解码器错误。 */
if (!(im && ref && meow_img_w(im) == meow_img_w(ref) && meow_img_h(im) == meow_img_h(ref) &&
memcmp(meow_img_pixels(im), meow_img_pixels(ref),
(size_t)meow_img_w(ref) * meow_img_h(ref) * 4) == 0)) {
all_equal = 0;
printf(" RST=%-3d 全图 memcmp 失败(与无 restart 不同)\n", rb[i]);
}
/* 关键正确性:与本文件自己的 stb 整幅解码比对(不同编码器脚本下 restart 文件的
* 系数可能不同,所以拿「无 restart 文件」当基准只能作参考) */
{
meow_img *rr = ref_thumb(p, 720);
double c3 = 0.0;
double d3 = (im && rr) ? mean_abs_diff(im, rr, 64, &c3) : 1e9;
if (!(d3 < 24.0 && c3 > 0.85)) all_peak = 0;
printf(" RST=%-3d vs stb 平均差 %.2f 相关 %.3f\n", rb[i], d3, c3);
meow_img_free(rr);
}
printf(" RST=%-3d → %dx%d peak %.2f MiB\n", rb[i],
meow_img_w(im), meow_img_h(im), (double)pk / 1048576.0);
free(p);
meow_img_free(im);
}
CHECK(all_backend == 1, "9 个 progressive+restart 全部走后端(BACKEND=1)");
CHECK(all_peak == 1, "9 个 progressive+restart 全部解码成功且峰值 ≤2.5MiB");
CHECK(all_equal == 1, "与无 restart 的同编码 progressive 结果逐字节一致(mad=0)");
meow_img_free(ref);
}
/* ---- S13 8bit gray/RGB PNG 的 tRNS 透明(P2-1 回归) ---- */
printf("[S13] PNG 8bit tRNS(gray / RGB)\n");
{
struct { const char *tag; const char *name; } fx[] = {
{ "gray8 tRNS", "gray8_trns.png" },
{ "rgb8 tRNS", "rgb8_trns.png" },
};
for (size_t i = 0; i < sizeof fx / sizeof fx[0]; i++) {
char *p = path_join(fx[i].name);
int tw = 0, th = 0;
unsigned char *px = NULL;
int handled = meow_img_backend_file(p, 320, &tw, &th, &px);
meow_img *got;
meow_img *full;
char name[128];
if (px) free(px);
got = meow_img_load_file_dim(p, 320);
full = meow_img_load_file(p); /* stb 整幅(tRNS 处理正确) */
snprintf(name, sizeof name, "%s 走后端(BACKEND=1)", fx[i].tag);
CHECK(handled == 1, name);
snprintf(name, sizeof name, "%s 目标图有透明像素(%ld 个)", fx[i].tag,
alpha_zero_count(got));
CHECK(got && alpha_zero_count(got) > 0, name);
snprintf(name, sizeof name, "%s alpha 与 stb 整幅逐点一致(不一致 %ld)", fx[i].tag,
alpha_mismatch(got, full));
CHECK(got && full && alpha_mismatch(got, full) == 0, name);
meow_img_free(got);
meow_img_free(full);
free(p);
}
}
/* ---- S14 M22 显式档位入口 _step:JPEG 走真缩解(PNG/BMP 待 M27 同步预算模型) ---- */
printf("[S14] meow_img_load_file_step(M22 档位入口)JPEG 走后端\n");
{
size_t base = peak_reset();
meow_img *im = meow_img_load_file_step(p_base, 720, 8);
size_t pk = peak_since(base);
char name[128];
snprintf(name, sizeof name, "_step(720,8) → %dx%d(长边 ≤720,非空)",
meow_img_w(im), meow_img_h(im));
CHECK(im && nonblank(im) && meow_img_w(im) <= 720 && meow_img_h(im) <= 720, name);
peak_report("_step(720, 8)", pk);
CHECK(pk <= PEAK_2500KB, "_step 峰值 ≤2.5MiB(走后端,不整幅物化)");
meow_img_free(im);
base = peak_reset();
im = meow_img_load_file_step(p_base, 720, 1);
pk = peak_since(base);
CHECK(im && nonblank(im) && meow_img_w(im) <= 720, "_step(720,1) 成功且长边 ≤720");
CHECK(pk <= PEAK_2500KB, "_step(720,1) 峰值 ≤2.5MiB");
meow_img_free(im);
}
/* ---- S15 低熵/纯白 progressive(P1-A 回归:最后一个 MCU 不许丢) ---- */
printf("[S15] 低熵/纯白 progressive 全图逐像素(丢 MCU 会露出 128 灰块)\n");
{
struct { const char *tag; const char *name; int all_white; } fx[] = {
{ "pure white q60", "white2000p.jpg", 1 },
{ "pure white q50", "white2000p50.jpg", 1 },
{ "pure white q70", "white2000p70.jpg", 1 },
{ "pure white q80", "white2000p80.jpg", 1 },
{ "mostly white q95", "mostlywhite2000p.jpg", 0 },
};
for (size_t i = 0; i < sizeof fx / sizeof fx[0]; i++) {
char *p = path_join(fx[i].name);
meow_img *im = meow_img_load_file_dim(p, 720);
meow_img *ref = ref_thumb(p, 720); /* stb 整幅 + 同公式降采样 */
char name[128];
int mn = min_channel(im);
int cw = meow_img_w(im), ch = meow_img_h(im);
int corner = region_min(im, cw - 24, ch - 24, cw, ch); /* 最后一个 MCU 落点 */
int maxd = 0;
long big = -1;
double mean = (im && ref) ? full_diff(im, ref, &maxd, &big, 40) : 1e9;
snprintf(name, sizeof name, "%s 解码成功且 480x720", fx[i].tag);
CHECK(im && nonblank(im) && cw == 480 && ch == 720, name);
/* 丢 MCU 会露出平面 memset(128) 的灰底:右下角与全图最小值都会掉到 128 附近。
* 纯白图全图最小值 ≥250;mostly-white 图有黑色矩形,只看右下角这块。 */
if (fx[i].all_white) {
snprintf(name, sizeof name, "%s 全图最小通道 = %d(丢块会露 128)", fx[i].tag, mn);
CHECK(mn >= 250, name);
}
snprintf(name, sizeof name, "%s 右下角 24x24 最小通道 = %d(丢块会露 128)",
fx[i].tag, corner);
CHECK(corner >= 250, name);
snprintf(name, sizeof name, "%s 与 stb 全图比较:平均差 %.2f,最大差 %d,>40 的通道 %ld",
fx[i].tag, mean, maxd, big);
CHECK(mean < 24.0 && big >= 0 && big * 200 < (long)720 * 480 * 3, name);
meow_img_free(im);
meow_img_free(ref);
free(p);
}
}
/* ---- S16 畸形 SOF 声明尺寸(P1-B 回归:不得按声明尺寸分配) ---- */
printf("[S16] 畸形 SOF 65535x65535(1/3 分量)\n");
{
unsigned char buf[64];
static const int nc[2] = { 1, 3 };
for (size_t i = 0; i < sizeof nc / sizeof nc[0]; i++) {
size_t n = build_sof_only(buf, 65535, 65535, nc[i]);
int tw = 0, th = 0;
unsigned char *px = NULL;
char name[128];
char *fp = path_join(nc[i] == 1 ? "sof65535_1.jpg" : "sof65535_3.jpg");
FILE *f;
size_t base, pk;
meow_img *im;
snprintf(name, sizeof name, "SOF 65535² ncomp=%d → 后端拒绝(%zu 字节)", nc[i], n);
CHECK(meow_img_backend_mem(buf, n, 720, &tw, &th, &px) == 0, name);
if (px) free(px);
f = fopen(fp, "wb");
CHECK(f != NULL, "写临时畸形 SOF 文件");
if (f) { fwrite(buf, 1, n, f); fclose(f); }
base = peak_reset();
im = meow_img_load_file_dim(fp, 720);
pk = peak_since(base);
peak_report("_dim(720) SOF 65535²", pk);
snprintf(name, sizeof name, "ncomp=%d 公开入口返回 NULL(安全失败)", nc[i]);
CHECK(im == NULL, name);
snprintf(name, sizeof name, "ncomp=%d 峰值 < 2.5MiB(不按声明尺寸分配)", nc[i]);
CHECK(pk < PEAK_2500KB, name);
meow_img_free(im);
free(fp);
}
}
/* ---- S17 32 位尺寸算术(ILP32 崩溃回归,M26 r1 P1) ----
* 54 字节 BMP 头声明 w=2^27、bpp=32 时,ILP32 上 w*bpp = 2^32 → rowsize 回绕成 0 →
* malloc(0) → 采样索引 row + sx*4 越界读 → SIGSEGV。LP64 宿主不会回绕,所以本节在
* 宿主的价值是「契约固定」:这些声明尺寸必须一律返回 NULL(且不崩);真正的 32 位
* 覆盖用法见文件头「ILP32 回归」那条交叉编译命令。 */
printf("[S17] 32 位尺寸算术:BMP 声明尺寸 / 采样偏移溢出(ILP32 崩溃回归)\n");
{
static const struct { const char *tag; unsigned w, hbits, bpp; int must_null; } sc[] = {
{ "w=0x07FFFFFF bpp=32(回绕点前一点)", 0x07FFFFFFu, 1u, 32u, 1 },
{ "w=0x08000000 bpp=32(2^27,旧代码崩点)", 0x08000000u, 1u, 32u, 1 },
{ "w=0x10000000 bpp=32", 0x10000000u, 1u, 32u, 1 },
{ "w=0x40000000 bpp=32", 0x40000000u, 1u, 32u, 1 },
{ "w=0x0AAAAAAA bpp=24(w*24 回绕点前)", 0x0AAAAAAAu, 1u, 24u, 1 },
{ "w=0x0AAAAAAB bpp=24(w*24 ≥ 2^32)", 0x0AAAAAABu, 1u, 24u, 1 },
/* 合法尺寸(≤1<<20)但无像素数据:后端必须拒绝;公开入口按设计回退 stb,
* stb 对截断 BMP 的行为不受本单约束,故只断言「不崩」,不要求 NULL。 */
{ "w=1000000 bpp=32(合法尺寸但无像素数据)", 1000000u, 1u, 32u, 0 },
{ "w=0x00100000 bpp=8(单边上限 1<<20)", 0x00100000u, 1u, 8u, 0 },
{ "w=0x00100001 bpp=8(超单边上限)", 0x00100001u, 1u, 8u, 1 },
{ "h=0x80000000(-INT_MIN 溢出点)", 0x01000000u, 0x80000000u, 32u, 1 },
};
for (size_t i = 0; i < sizeof sc / sizeof sc[0]; i++) {
unsigned char buf[54];
int tw = 0, th = 0;
unsigned char *px = NULL;
meow_img *im;
char name[200];
size_t base, pk;
char *fp = path_join("evil_dims.bmp");
FILE *f;
bmp_build(buf, sc[i].w, sc[i].hbits, sc[i].bpp);
snprintf(name, sizeof name, "%s → 后端拒绝", sc[i].tag);
base = peak_reset();
CHECK(meow_bmp_decode_stream(NULL, buf, sizeof buf, 720, &tw, &th, &px) == 0, name);
pk = peak_since(base);
CHECK(px == NULL, "拒绝时不返回像素缓冲");
if (sc[i].must_null) CHECK(pk < PEAK_2500KB, "越界声明在算术前就被拒(不分配)");
f = fopen(fp, "wb");
CHECK(f != NULL, "写畸形 BMP 到夹具目录");
if (f) { fwrite(buf, 1, sizeof buf, f); fclose(f); }
im = meow_img_load_file_dim(fp, 720);
snprintf(name, sizeof name, "%s → _dim 不崩%s", sc[i].tag,
sc[i].must_null ? "且返回 NULL" : "");
CHECK(im == NULL || !sc[i].must_null, name);
meow_img_free(im);
im = meow_img_load_mem_dim(buf, (int)sizeof buf, 720);
snprintf(name, sizeof name, "%s → _mem_dim 不崩%s", sc[i].tag,
sc[i].must_null ? "且返回 NULL" : "");
CHECK(im == NULL || !sc[i].must_null, name);
meow_img_free(im);
free(fp);
}
/* 正例对照:合法 64×64 32bpp 必须仍能解(含 h<0 的正上方行序) */
{
unsigned char *big = (unsigned char *)malloc(54 + 64 * 64 * 4);
int tw = 0, th = 0;
unsigned char *px = NULL;
size_t n;
CHECK(big != NULL, "分配正例 BMP 缓冲");
if (big) {
n = bmp_build_64(big, 0);
CHECK(meow_bmp_decode_stream(NULL, big, n, 32, &tw, &th, &px) == 1 &&
tw == 32 && th == 32, "正例:64x64 32bpp 正序 → 32x32");
free(px); px = NULL; tw = th = 0;
n = bmp_build_64(big, 1);
CHECK(meow_bmp_decode_stream(NULL, big, n, 32, &tw, &th, &px) == 1 &&
tw == 32 && th == 32, "正例:64x64 32bpp 倒序(h<0)→ 32x32");
free(px);
free(big);
}
}
/* PNG:tw*th*4 在 ILP32 上回绕成 0 的坐标点(65535² 头 + maxdim=32768) */
{
unsigned char pb[64];
int tw = 0, th = 0;
unsigned char *px = NULL;
size_t pn = png_ihdr_only(pb, 65535, 65535);
CHECK(meow_png_decode_stream(NULL, pb, pn, 32768, &tw, &th, &px) == 0,
"PNG 65535² maxdim=32768 → 拒绝(目标图字节数会回绕)");
CHECK(px == NULL, "PNG 拒绝时不返回像素缓冲");
}
}
/* ---- S8 内存入口 ---- */
printf("[S8] 内存入口 meow_img_load_mem_dim\n");
{
size_t len = 0;
uint8_t *buf = read_whole(p_base, &len);
CHECK(buf != NULL, "读入 baseline JPEG");
if (buf) {
size_t base = peak_reset();
meow_img *m = meow_img_load_mem_dim(buf, (int)len, 720);
size_t pk = peak_since(base);
meow_img *f = meow_img_load_file_dim(p_base, 720);
peak_report("_mem_dim(720)", pk);
CHECK(m && f && meow_img_w(m) == meow_img_w(f) && meow_img_h(m) == meow_img_h(f),
"内存入口尺寸一致");
CHECK(m && f && memcmp(meow_img_pixels(m), meow_img_pixels(f),
(size_t)meow_img_w(f) * meow_img_h(f) * 4) == 0, "内存入口逐字节一致");
CHECK(pk <= PEAK_2500KB, "内存入口峰值 ≤ 2.5 MiB");
meow_img_free(m);
meow_img_free(f);
free(buf);
}
}
/* ---- S9 回退与容错 ---- */
printf("[S9] 回退(新后端拒绝 → stb)与坏图\n");
{
int tw = 0, th = 0;
unsigned char *px = NULL;
meow_img *im;
/* CMYK(4 分量):后端必须拒绝,stb 必须兜住 */
CHECK(meow_img_backend_file(p_cmyk, 720, &tw, &th, &px) == 0 && px == NULL,
"CMYK JPEG → 后端拒绝");
im = meow_img_load_file_dim(p_cmyk, 720);
CHECK(im && nonblank(im), "CMYK JPEG → stb 回退成功");
meow_img_free(im);
/* 1bpp BMP */
CHECK(meow_img_backend_file(p_mono, 720, &tw, &th, &px) == 0 && px == NULL,
"1bpp BMP → 后端拒绝");
im = meow_img_load_file_dim(p_mono, 720);
CHECK(im && nonblank(im), "1bpp BMP → stb 回退成功");
meow_img_free(im);
/* Adam7 交错 PNG */
CHECK(meow_img_backend_file(p_adam7, 720, &tw, &th, &px) == 0 && px == NULL,
"Adam7 PNG → 后端拒绝");
im = meow_img_load_file_dim(p_adam7, 720);
CHECK(im && nonblank(im), "Adam7 PNG → stb 回退成功");
meow_img_free(im);
/* 坏图 */
CHECK(meow_img_load_file_dim(p_broken, 480) == NULL, "截断 JPEG → NULL");
CHECK(meow_img_load_file_dim("/no/such/file.jpg", 480) == NULL, "不存在路径 → NULL");
CHECK(meow_img_load_file_dim(NULL, 480) == NULL, "NULL 路径 → NULL");
CHECK(meow_img_load_mem_dim("x", 3, 480) == NULL, "垃圾内存 → NULL");
}
if (g_fail) printf("\nRESULT: FAIL (%d)\n", g_fail);
else printf("\nRESULT: ALL PASS\n");
free(p_base); free(p_prog); free(p_png); free(p_bmp);
free(p_mp4); free(p_mp8); free(p_mp12); free(p_small);
free(p_gray); free(p_pal); free(p_rgba); free(p_cmyk);
free(p_mono); free(p_adam7); free(p_broken);
free(p_444); free(p_422); free(p_grayjpg); free(p_photo); free(p_photop);
return g_fail ? 1 : 0;
}