/* 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(不崩) * * 夹具:本文件内置生成脚本(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) */ #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 #include #include #include #include #include /* clock():解码耗时打印 */ #include /* 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; } /* ================================================================== */ 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); } } /* ---- 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; }