Files
MeowOS-SDK/sdk/test_meow_img_stream.c
T
AskaEth 6c8a1d3925 fix: 扫描结尾不再丢最后一个 MCU + 畸形 SOF 尺寸硬闸(M23 r2 P1-A/P1-B)
P1-A(像素错误):mj_boundary() 在「over 且 marker 非 RSTn」时 return 0 提前退出
扫描。熵码位缓冲最多 9 位前瞻,会在最后一个 MCU/块**之前**把扫描结束后的 marker
(DHT/EOI)吃掉;此时最后 1~2 个 MCU 的有效比特还在 bitbuf 里,return 0 把它们丢掉,
分量平面 memset(128) 的灰底直接露出来(低熵/纯白渐进式右下角 #808080)。
修:扫描循环本来就知道确定的 MCU/块数 —— 一律继续解完(位耗尽后 mj_eb 返回 0 做
零填充),marker 交给外层主循环。实测(纯白 2000x3000 progressive 无 restart):
修复前全图最小通道 128 / 与 stb 最大差 127 / >40 差值的通道 9 个;修复后
最小通道 253~255、最大差 ≤2、>40 通道 0。

测试加固(评审指出旧断言是抽样蒙混):
- S12「与无 restart 逐字节一致」改为**全图 memcmp**(去掉 64×64 网格 MAD<0.1 的兜底);
  修复后 9 档 restart 全部逐字节一致。
- 新增 S15:纯白 2000x3000 progressive(q50/60/70/80,无 restart)+ mostly-white q95,
  断言全图最小通道/右下角 24×24 最小通道 ≥250(丢 MCU 必露 128)、与 stb 全图逐像素比较。
  用 HEAD(11a4fcd) 的 meow_jpeg.c 重编同一测试:4 档纯白全部 FAIL(min=128,
  >40 通道 9),修复后全 PASS。

P1-B(内存放大/安全):mj_sof() 按 SOF 声明尺寸分配平面,降档循环在 base_n 已为 1
时不再生效,17~23 字节的 `SOI+SOF0(65535×65535)+EOI` 会让公开入口 _dim(720)
分配并 memset 64MiB(1 分量)/96MiB(3 分量)后才返回 NULL。
修:降档循环之后补硬闸 `if (plane_bytes + target_bytes > MJ_PEAK_CAP) return 0;`
→ 回退 stb(同文件 stb 是 0.02MiB 安全失败)。
回归 S16:构造 SOF 65535²(1/3 分量)→ 后端返回 0、公开入口 NULL、峰值 0.02MiB
(修复前 64.00 / 96.00MiB)。

meow_jpeg.h 注明「峰值只与输出尺寸相关」的适用范围:畸形/超大声明尺寸直接拒绝(安全失败)。

验证:test_meow_img_stream ALL PASS(含 ASan 整轮 rc=0 无报告);test_meow_img_budget /
test_meow_img_color ALL PASS;run_tests.sh rc=0(zip 106 / cbz 296 / membudget PASS);
run_webtoon_tests.sh rc=0;build_sdk.sh 零警告,MIPS32 rel2;体积 meow_jpeg.o 17,212B
+ meow_png_stream.o 8,456B。峰值不变(修复不增加分配)。
2026-11-06 11:21:03 +08:00

1183 lines
52 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"
"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;
}