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。
1013 lines
44 KiB
C
1013 lines
44 KiB
C
/* 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;
|
||
}
|