Files
MeowOS-SDK/sdk/test_meow_img_stream.c
T
AskaEth 11a4fcd730 fix: 畸形 DHT 拒绝 + 渐进式 restart 重同步(M23 r1 P1-1/P1-2)
P1-1(内存安全):mj_huff_build 构造 9 位快表前校验规范码空间
(code + bits[l] > (1<<l) → 表非法),填表时再夹一道 <512 边界;mj_dht 失败
即返回 0 → 后端拒绝 → 回退 stb。此前过订阅 DHT(如「长度 2 的码 100 个」)会把
fast[512] 下标推到 12799,28~220 字节的畸形 JPEG 就栈溢出(ASan 实测
stack-buffer-overflow,非 ASan 直接 SIGSEGV/SIGBUS)。
回归:码长 1..4 × 符号数 3..255 共 1012 组畸形 DHT 全部后端拒绝、无崩溃;
「有效 JPEG + 注入坏 DHT」走拒绝 + 回退(不花屏)。

P1-2(能力):三处扫描循环的区间重同步统一到 mj_boundary,**只在 total == rst
(编码器区间长度)时重同步**。原实现一见「over && marker∈D0..D7」就重同步,但熵码
位缓冲有最多 9 位前瞻,over 会在区间真正结束前 1~2 个 MCU 置位——提前重同步会丢掉
bitbuf 里本区间剩余块的有效比特,随即失步(渐进式 DC 扫描每块仅 ~1 bit,前瞻能跨过
整个 MCU)。这正是 Pillow restart_marker_blocks=1/2/3/4/5/8/16/64/200 的渐进式
100% 回退 stb(25.8MiB)的根因。另修 mj_restart 裸扫把 FF 00(数据字节 0xFF 的填充)
里的 00 误当 marker。
回归:9 个渐进式 + restart 夹具全部 BACKEND=1、峰值 1.86MiB(与无 restart 相同)、
与同编码无 restart 文件逐字节一致(mad=0.000);baseline + restart 仍逐字节一致。

附带(评审 P2 两项,改动极小且已验证):
- PNG 8bit gray/RGB 的 tRNS 透明接通(8bit 采样值在 tRNS 低字节),与 stb 逐点 alpha 一致;
- 拒绝 16bit 量化表(只属 12bit 精度;收下会让 F*qt 在 mj_accim 里 int32 溢出,UB)。
- meow_img_load_file_step 的 JPEG 走真缩解后端(PNG/BMP 暂留 stb:阅读器预算模型仍是
  M22「整幅物化」口径,待 M27 同步后放开)。

验证:ASan 构建全 PASS;test_meow_img_stream 111 项全 PASS;test_meow_img_budget /
test_meow_img_color 全 PASS;tuku run_tests.sh rc=0(zip 106 / cbz 296,含 membudget)、
run_webtoon_tests.sh rc=0;build_sdk.sh 零警告,MIPS32 rel2 目标码。
峰值不变(修复不增加分配):2000x3000 baseline/progressive 1.86MiB、PNG 1.33MiB。
体积:meow_jpeg.o 17,284B + meow_png_stream.o 8,456B = 25.1KB。
2026-11-05 22:39:09 +08:00

1013 lines
44 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(不崩)
*
* 夹具:本文件内置生成脚本(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 <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"
"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 *mark = path_join("base2000.jpg");
int have = file_exists(mark);
free(mark);
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;
}
/* ================================================================== */
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;
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)) {
double c2 = 0.0;
double d2 = (im && ref) ? mean_abs_diff(im, ref, 64, &c2) : 1e9;
if (!(d2 < 0.1)) all_equal = 0; /* 允许极个别像素 ±1(编码器分表差异) */
printf(" RST=%-3d 与无 restart 平均差 %.4f\n", rb[i], d2);
}
/* 关键正确性:与本文件自己的 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);
}
/* ---- 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;
}