/* test_meow_img_stream.c — M23 真缩解后端宿主验证(峰值 + 像素一致性 + 回退)。 * * 覆盖矩阵(对齐 M23 任务 3/4/5): * S1 探针/入口透明:meow_img_probe_file、load_file_dim / load_mem_dim 对新后端透明; * 小图逐字节等价旧入口;maxdim<=0 等价旧入口 * S2 baseline 2000×3000:maxdim=720/480 峰值 ≤ 2.5MiB(旧入口如实并列打印) * S3 progressive 2000×3000:同一断言(渐进式不再整幅驻留系数) * S4 PNG 1600×2400:峰值 ≤ 2.5MiB(逐行 inflate,无整幅 raw/RGBA) * S5 BMP 1600×2400:峰值 ≤ 2.5MiB(按行 seek,不读全图) * S6 4MP/8MP/12MP JPEG:都能解且峰值 ≤ 2.5MiB;峰值增长远小于像素数增长(非线性) * S7 像素一致性:与「stb 整幅解码 + meow_img_downscale」网格比对(容差内) * S8 内存入口 == 文件入口(逐字节),峰值同级 * S9 回退:CMYK JPEG(4 分量)、1bpp BMP、Adam7 交错 PNG → 后端拒绝,stb 仍成功; * 截断/垃圾 → NULL(不崩) * * 夹具:本文件内置生成脚本(Pillow),首次运行时写到夹具目录并执行。 * 编译(本 sdk 目录下;--wrap 用于给 malloc/free 家族打点): * gcc -Wall -Wextra -O2 -I. -Iinclude -DMEOW_UI_HOST_TEST -o test_meow_img_stream \ * test_meow_img_stream.c meow_ui_img.c meow_jpeg.c meow_png_stream.c stb_image.c \ * -lm -lz \ * -Wl,--wrap=malloc -Wl,--wrap=calloc -Wl,--wrap=realloc -Wl,--wrap=free * 运行:./test_meow_img_stream "$PREFIX/tmp/meow_img_stream" (exit 0=PASS / 1=FAIL) */ #define _GNU_SOURCE #include "meow_ui.h" #include "meow_ui_internal.h" /* meow_gfx_blit_rgba 原型(本测试只给桩) */ #include "meow_jpeg.h" /* 后端入口(内部头):直接比对「后端 vs 回退」 */ #include "stb_image.h" /* 旧 _dim 路径基线(stbi_load_scaled,M23 之前的行为) */ #include #include #include #include #include #include /* clock():解码耗时打印 */ #include /* sqrt:结构相关系数 */ /* ------------------------------------------------------------------ */ /* 分配打点 */ /* ------------------------------------------------------------------ */ #define AHDR 32 static size_t g_live = 0; static size_t g_peak = 0; extern void *__real_malloc(size_t); extern void *__real_calloc(size_t, size_t); extern void *__real_realloc(void *, size_t); extern void __real_free(void *); static void acc_alloc(size_t n) { g_live += n; if (g_live > g_peak) g_peak = g_live; } void *__wrap_malloc(size_t n) { size_t *h = (size_t *)__real_malloc(n + AHDR); if (!h) return NULL; h[0] = n; acc_alloc(n); return (char *)h + AHDR; } void *__wrap_calloc(size_t a, size_t b) { size_t n = a * b; void *p = __wrap_malloc(n); if (p) memset(p, 0, n); return p; } void __wrap_free(void *p) { size_t *h; if (!p) return; h = (size_t *)((char *)p - AHDR); g_live -= h[0]; __real_free(h); } void *__wrap_realloc(void *p, size_t n) { size_t old = p ? ((size_t *)((char *)p - AHDR))[0] : 0; void *q = __wrap_malloc(n); if (!q) return NULL; if (p) { memcpy(q, p, old < n ? old : n); __wrap_free(p); } return q; } static size_t peak_reset(void) { g_peak = g_live; return g_live; } static size_t peak_since(size_t base) { return g_peak - base; } /* meow_ui_img.c 的绘制函数需要的 gfx 符号:本测试只测解码/内存,桩即可。 */ void meow_gfx_blit_rgba(int x, int y, int w, int h, const uint8_t *rgba, int alpha) { (void)x; (void)y; (void)w; (void)h; (void)rgba; (void)alpha; } /* ------------------------------------------------------------------ */ /* 断言与打印 */ /* ------------------------------------------------------------------ */ static int g_fail = 0; #define CHECK(cond, name) do { \ if (cond) printf(" PASS %s\n", name); \ else { printf(" FAIL %s (%s:%d)\n", name, __FILE__, __LINE__); g_fail++; } } while (0) #define PEAK_2500KB ((size_t)2560 * 1024) /* 2.5 MiB */ static void peak_report(const char *tag, size_t bytes) { printf(" peak %-32s %7.2f MiB (%zu B)\n", tag, (double)bytes / (1024.0 * 1024.0), bytes); } /* ------------------------------------------------------------------ */ /* 夹具 */ /* ------------------------------------------------------------------ */ static const char *g_dir = NULL; static const char *g_gen_py = "import os, sys, zlib, struct\n" "from PIL import Image, ImageDraw\n" "out = sys.argv[1]\n" "os.makedirs(out, exist_ok=True)\n" "def pat(w, h, mode='RGB'):\n" " im = Image.new(mode, (w, h), (250, 249, 245) if mode == 'RGB' else 200)\n" " d = ImageDraw.Draw(im)\n" " for k in range(0, min(w, h) - 200, 40):\n" " d.rectangle([40 + k, 40 + k, 200 + k, 180 + k], outline=(12, 12, 14), width=3)\n" " for band in range(14):\n" " y0 = band * h // 14\n" " for x in range(0, w, 7):\n" " d.line([(x, y0), (x + 3, y0 + 5)], fill=(110, 110, 116))\n" " for i, c in enumerate([(220, 30, 40), (30, 180, 60), (40, 60, 220), (240, 200, 20)]):\n" " d.rectangle([w // 2 + i * 30, h // 2, w // 2 + i * 30 + 25, h // 2 + 60], fill=c)\n" " return im\n" "big = pat(2000, 3000)\n" "big.save(os.path.join(out, 'base2000.jpg'), 'JPEG', quality=60, subsampling=2)\n" "big.save(os.path.join(out, 'prog2000.jpg'), 'JPEG', quality=60, subsampling=2, progressive=True)\n" "big.save(os.path.join(out, 'base2000_444.jpg'), 'JPEG', quality=60, subsampling=0)\n" "big.save(os.path.join(out, 'base2000_422.jpg'), 'JPEG', quality=60, subsampling=1)\n" "big.save(os.path.join(out, 'restart2000.jpg'), 'JPEG', quality=60, subsampling=2,\n" " restart_marker_blocks=8)\n" "pat(640, 480).save(os.path.join(out, 'sweep640.jpg'), 'JPEG', quality=70, subsampling=2)\n" "big.convert('L').save(os.path.join(out, 'gray2000.jpg'), 'JPEG', quality=60)\n" "photo = Image.blend(big, Image.effect_noise((2000, 3000), 40).convert('RGB'), 0.35)\n" "photo.save(os.path.join(out, 'photo2000.jpg'), 'JPEG', quality=75, subsampling=2)\n" "photo.save(os.path.join(out, 'photo2000p.jpg'), 'JPEG', quality=75, subsampling=2, progressive=True)\n" "pat(1600, 2400).save(os.path.join(out, 'page1600.png'), 'PNG')\n" "pat(1600, 2400).save(os.path.join(out, 'page1600.bmp'), 'BMP')\n" "pat(2320, 1740).save(os.path.join(out, 'mp4.jpg'), 'JPEG', quality=60, subsampling=2)\n" "pat(3264, 2448).save(os.path.join(out, 'mp8.jpg'), 'JPEG', quality=60, subsampling=2)\n" "pat(4000, 3000).save(os.path.join(out, 'mp12.jpg'), 'JPEG', quality=60, subsampling=2)\n" "pat(64, 64).save(os.path.join(out, 'small64.jpg'), 'JPEG', quality=80)\n" "Image.open(os.path.join(out, 'page1600.png')).convert('L').save(os.path.join(out, 'gray.png'))\n" "pat(800, 600).convert('P', palette=Image.ADAPTIVE, colors=64).save(os.path.join(out, 'pal.png'))\n" "pat(800, 600).convert('RGBA').save(os.path.join(out, 'rgba.png'))\n" "big.convert('CMYK').save(os.path.join(out, 'cmyk.jpg'), 'JPEG', quality=60)\n" "Image.open(os.path.join(out, 'page1600.png')).convert('1').save(os.path.join(out, 'mono1.bmp'))\n" "def chunk(t, d):\n" " c = struct.pack('>I', len(d)) + t + d\n" " return c + struct.pack('>I', zlib.crc32(t + d) & 0xffffffff)\n" "def adam7(path, w, h):\n" " raw = bytearray()\n" " passes = [(0,0,8,8),(4,0,8,8),(0,4,4,8),(2,0,4,4),(0,2,2,4),(1,0,2,2),(0,1,1,2)]\n" " for (x0, y0, dx, dy) in passes:\n" " xs = list(range(x0, w, dx)); ys = list(range(y0, h, dy))\n" " if not xs or not ys:\n" " continue\n" " for y in ys:\n" " raw.append(0)\n" " for x in xs:\n" " raw += bytes(((x * 3) & 255, (y * 3) & 255, ((x + y) * 2) & 255))\n" " ihdr = struct.pack('>IIBBBBB', w, h, 8, 2, 0, 0, 1)\n" " data = (b'\\x89PNG\\r\\n\\x1a\\n' + chunk(b'IHDR', ihdr) +\n" " chunk(b'IDAT', zlib.compress(bytes(raw))) + chunk(b'IEND', b''))\n" " open(path, 'wb').write(data)\n" "adam7(os.path.join(out, 'adam7.png'), 1600, 2400)\n" "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; } /* ================================================================== */ 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(); meow_img *got = meow_img_load_file_dim(prs, 720); size_t pk = peak_since(base); meow_img *ref = ref_thumb(prs, 720); double corr = 0.0; double d = (got && ref) ? mean_abs_diff(got, ref, 64, &corr) : 1e9; peak_report("restart marker 2000x3000", pk); 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); } free(psw); free(prs); } /* ---- 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; }