fix: 畸形 DHT 拒绝 + 渐进式 restart 重同步(M23 r1 P1-1/P1-2)
P1-1(内存安全):mj_huff_build 构造 9 位快表前校验规范码空间 (code + bits[l] > (1<<l) → 表非法),填表时再夹一道 <512 边界;mj_dht 失败 即返回 0 → 后端拒绝 → 回退 stb。此前过订阅 DHT(如「长度 2 的码 100 个」)会把 fast[512] 下标推到 12799,28~220 字节的畸形 JPEG 就栈溢出(ASan 实测 stack-buffer-overflow,非 ASan 直接 SIGSEGV/SIGBUS)。 回归:码长 1..4 × 符号数 3..255 共 1012 组畸形 DHT 全部后端拒绝、无崩溃; 「有效 JPEG + 注入坏 DHT」走拒绝 + 回退(不花屏)。 P1-2(能力):三处扫描循环的区间重同步统一到 mj_boundary,**只在 total == rst (编码器区间长度)时重同步**。原实现一见「over && marker∈D0..D7」就重同步,但熵码 位缓冲有最多 9 位前瞻,over 会在区间真正结束前 1~2 个 MCU 置位——提前重同步会丢掉 bitbuf 里本区间剩余块的有效比特,随即失步(渐进式 DC 扫描每块仅 ~1 bit,前瞻能跨过 整个 MCU)。这正是 Pillow restart_marker_blocks=1/2/3/4/5/8/16/64/200 的渐进式 100% 回退 stb(25.8MiB)的根因。另修 mj_restart 裸扫把 FF 00(数据字节 0xFF 的填充) 里的 00 误当 marker。 回归:9 个渐进式 + restart 夹具全部 BACKEND=1、峰值 1.86MiB(与无 restart 相同)、 与同编码无 restart 文件逐字节一致(mad=0.000);baseline + restart 仍逐字节一致。 附带(评审 P2 两项,改动极小且已验证): - PNG 8bit gray/RGB 的 tRNS 透明接通(8bit 采样值在 tRNS 低字节),与 stb 逐点 alpha 一致; - 拒绝 16bit 量化表(只属 12bit 精度;收下会让 F*qt 在 mj_accim 里 int32 溢出,UB)。 - meow_img_load_file_step 的 JPEG 走真缩解后端(PNG/BMP 暂留 stb:阅读器预算模型仍是 M22「整幅物化」口径,待 M27 同步后放开)。 验证:ASan 构建全 PASS;test_meow_img_stream 111 项全 PASS;test_meow_img_budget / test_meow_img_color 全 PASS;tuku run_tests.sh rc=0(zip 106 / cbz 296,含 membudget)、 run_webtoon_tests.sh rc=0;build_sdk.sh 零警告,MIPS32 rel2 目标码。 峰值不变(修复不增加分配):2000x3000 baseline/progressive 1.86MiB、PNG 1.33MiB。 体积:meow_jpeg.o 17,284B + meow_png_stream.o 8,456B = 25.1KB。
This commit is contained in:
1 parent
e9c0749609
commit
11a4fcd730
6 files changed
+377
-54
No files matched your search
@@ -31,15 +31,20 @@ int meow_img_probe_file(const char *path, int *w, int *h, int *channels);
|
|||||||
* 「meow_img_load_file + meow_img_downscale(maxdim)」逐字节同口径(公式一致)。
|
* 「meow_img_load_file + meow_img_downscale(maxdim)」逐字节同口径(公式一致)。
|
||||||
*
|
*
|
||||||
* 实现(M23 起):先进真缩解后端(内部 meow_jpeg.c / meow_png_stream.c),解码期只算目标分辨率:
|
* 实现(M23 起):先进真缩解后端(内部 meow_jpeg.c / meow_png_stream.c),解码期只算目标分辨率:
|
||||||
* JPEG:DCT 域缩解(每 8×8 块只解出 N×N 像素,N 由 8·maxdim/长边 取「不欠采样」的最小值),
|
* JPEG:DCT 域缩解(每 8×8 块只解出 N×N 像素,N 由 8·maxdim/长边 取「不欠采样」的最小值;
|
||||||
* 渐进式只解到首个精细化扫描(首个 Ah>0)——低频带已完整,不再整幅驻留系数阵列;
|
* N=1..7,N≥8 交回 stb),渐进式跳过精细化扫描、只解各频带首次扫描 —— 低频带已完整,
|
||||||
|
* 不整幅驻留系数阵列;
|
||||||
* PNG :libz 逐行 inflate + 反滤波,按步长丢行丢列直写目标尺寸(gray/gray+A/RGB/RGBA/
|
* PNG :libz 逐行 inflate + 反滤波,按步长丢行丢列直写目标尺寸(gray/gray+A/RGB/RGBA/
|
||||||
* 调色板/16bit,16bit 取高字节);
|
* 调色板/1-16bit,16bit 取高字节,tRNS 透明含 8bit 灰/RGB);
|
||||||
* BMP :按目标行 seek 到源行 + 行内跳列(8bpp 调色板 / 16bpp RGB555 / 24bpp / 32bpp)。
|
* BMP :按目标行 seek 到源行 + 行内跳列(8bpp 调色板 / 16bpp RGB555 / 24bpp / 32bpp)。
|
||||||
* 峰值只与输出尺寸相关,不随源图分辨率/压缩体积线性增长(2000×3000 页 ≤2.5MB 量级)。
|
* 峰值只与输出尺寸相关,不随源图分辨率/压缩体积线性增长(2000×3000 页 ≤2.5MB 量级)。
|
||||||
* 不支持的特性(CMYK/YCCK 4 分量 JPEG、算术编码、Adam7 交错 PNG、RLE/1-4bpp BMP、几乎等尺寸)
|
* 不支持的特性(CMYK/YCCK 4 分量 JPEG、算术编码、过订阅 Huffman 表、16bit 量化表、Adam7
|
||||||
* 自动回退 stb 旧路径(meow_img_load_file + 缩解/降采样),对外语义完全不变。
|
* 交错 PNG、RLE/1-4bpp BMP、几乎等尺寸)自动回退 stb 旧路径,对外语义完全不变。
|
||||||
* 小图(长边 ≤ maxdim)走旧路径,与原 API 完全一致;maxdim<=0 等价旧入口。失败返回 NULL。 */
|
* 小图(长边 ≤ maxdim)走旧路径,与原 API 完全一致;maxdim<=0 等价旧入口。失败返回 NULL。
|
||||||
|
*
|
||||||
|
* 尺寸口径说明(M23 评审 P2-2):本入口按「原图 + meow_img_downscale」公式算目标尺寸;M12 旧
|
||||||
|
* 实现经 stb 的 ceil(w/s) 缩解再精修,源尺寸不是档位整数倍时可能与本入口差 1px(实测 1999×2999:
|
||||||
|
* 旧 320×480 vs 新 319×480)。阅读器按显示框缩放,无实际影响;PNG/BMP 无此差异。 */
|
||||||
meow_img *meow_img_load_file_dim(const char *path, int maxdim);
|
meow_img *meow_img_load_file_dim(const char *path, int maxdim);
|
||||||
meow_img *meow_img_load_mem_dim(const void *buf, int len, int maxdim);
|
meow_img *meow_img_load_mem_dim(const void *buf, int len, int maxdim);
|
||||||
|
|
||||||
@@ -51,7 +56,9 @@ int meow_img_pick_scale(int longside, int maxdim);
|
|||||||
/* 追加(M22)显式档位解码:语义与 meow_img_load_file_dim 相同(结果长边 ≤ maxdim,
|
/* 追加(M22)显式档位解码:语义与 meow_img_load_file_dim 相同(结果长边 ≤ maxdim,
|
||||||
* 超了就再精修一次),只是缩解档位由调用方决定。内存吃紧时可以选更粗的档位——输出更小、
|
* 超了就再精修一次),只是缩解档位由调用方决定。内存吃紧时可以选更粗的档位——输出更小、
|
||||||
* 峰值更低,代价是细节变软。step <= 1 等价长边约束入口;step > 8 按 8 处理。
|
* 峰值更低,代价是细节变软。step <= 1 等价长边约束入口;step > 8 按 8 处理。
|
||||||
* 非 JPEG 格式 stb 没有解码期缩放,step 被忽略(整幅解码)。 */
|
* M23 起:JPEG 先走真缩解后端(step 折算成目标长边 ceil(长边/step),后端按该长边选 DCT 阶),
|
||||||
|
* 峰值只与输出尺寸相关;不支持的特性回退 stb(step 原样传入)。PNG/BMP 暂仍走 stb —— 阅读器的
|
||||||
|
* 单次解码预算模型(M22)还是「整幅物化」口径,待 M27 同步后放开(见 M23 r1 报告)。 */
|
||||||
meow_img *meow_img_load_file_step(const char *path, int maxdim, int step);
|
meow_img *meow_img_load_file_step(const char *path, int maxdim, int step);
|
||||||
|
|
||||||
/* 追加(M22)精确重采样到 w×h(最近邻,缩小/放大都支持)。阅读器用它把「缩解档位」的
|
/* 追加(M22)精确重采样到 w×h(最近邻,缩小/放大都支持)。阅读器用它把「缩解档位」的
|
||||||
|
|||||||
+68
-38
@@ -148,21 +148,26 @@ typedef struct {
|
|||||||
int valptr[17];
|
int valptr[17];
|
||||||
} mj_huff;
|
} mj_huff;
|
||||||
|
|
||||||
static void mj_huff_build(mj_huff *h)
|
/* 构造 9 位快表 + 规范码表。返回 0 = 表非法(过订阅/码空间越界),调用方必须拒绝该表。
|
||||||
|
* 过订阅输入(如「长度 2 的码有 100 个」)此前会让 code << shift 把快表下标推到 512 之外,
|
||||||
|
* 28~220 字节的畸形 JPEG 就能栈溢出 → 这里在填表前校验码空间,填表时再加一道下标边界。 */
|
||||||
|
static int mj_huff_build(mj_huff *h)
|
||||||
{
|
{
|
||||||
int code = 0, k = 0, l;
|
int code = 0, k = 0, l;
|
||||||
memset(h->fast, 0, sizeof h->fast);
|
memset(h->fast, 0, sizeof h->fast);
|
||||||
h->maxcode[0] = -1;
|
h->maxcode[0] = -1;
|
||||||
for (l = 1; l <= 16; l++) {
|
for (l = 1; l <= 16; l++) {
|
||||||
int i;
|
int i;
|
||||||
|
if (code + h->bits[l] > (1 << l)) return 0; /* 码空间溢出:过订阅表 */
|
||||||
h->valptr[l] = k;
|
h->valptr[l] = k;
|
||||||
h->mincode[l] = code;
|
h->mincode[l] = code;
|
||||||
for (i = 0; i < h->bits[l]; i++) {
|
for (i = 0; i < h->bits[l]; i++) {
|
||||||
if (l <= 9) {
|
if (l <= 9) {
|
||||||
int shift = 9 - l, j;
|
int shift = 9 - l, j;
|
||||||
unsigned short e = (unsigned short)((l << 8) | h->vals[k]);
|
unsigned short e = (unsigned short)((l << 8) | h->vals[k]);
|
||||||
|
int base = code << shift;
|
||||||
for (j = 0; j < (1 << shift); j++)
|
for (j = 0; j < (1 << shift); j++)
|
||||||
h->fast[(code << shift) | j] = e;
|
if ((base | j) < 512) h->fast[base | j] = e;
|
||||||
}
|
}
|
||||||
code++;
|
code++;
|
||||||
k++;
|
k++;
|
||||||
@@ -170,6 +175,7 @@ static void mj_huff_build(mj_huff *h)
|
|||||||
h->maxcode[l] = h->bits[l] ? code - 1 : -1;
|
h->maxcode[l] = h->bits[l] ? code - 1 : -1;
|
||||||
code <<= 1;
|
code <<= 1;
|
||||||
}
|
}
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int mj_hdecode(mj_src *s, const mj_huff *h)
|
static int mj_hdecode(mj_src *s, const mj_huff *h)
|
||||||
@@ -341,16 +347,12 @@ static int mj_dqt(mj_img *img, const unsigned char *d, int len)
|
|||||||
int pq = d[p] >> 4, tq = d[p] & 15, i;
|
int pq = d[p] >> 4, tq = d[p] & 15, i;
|
||||||
p++;
|
p++;
|
||||||
if (tq > 3) return 0;
|
if (tq > 3) return 0;
|
||||||
if (pq == 0) {
|
/* 16bit 量化表只出现在 12bit 精度(本模块不支持);收下会让 F*qt 在 mj_accum 里
|
||||||
if (p + 64 > len) return 0;
|
* int32 溢出(UB)。直接拒绝 → 后端返回 0 → 回退 stb。 */
|
||||||
for (i = 0; i < 64; i++) img->qt[tq][mj_zigzag[i]] = d[p + i];
|
if (pq != 0) return 0;
|
||||||
p += 64;
|
if (p + 64 > len) return 0;
|
||||||
} else {
|
for (i = 0; i < 64; i++) img->qt[tq][mj_zigzag[i]] = d[p + i];
|
||||||
if (p + 128 > len) return 0;
|
p += 64;
|
||||||
for (i = 0; i < 64; i++)
|
|
||||||
img->qt[tq][mj_zigzag[i]] = (d[p + 2 * i] << 8) | d[p + 2 * i + 1];
|
|
||||||
p += 128;
|
|
||||||
}
|
|
||||||
img->have_qt[tq] = 1;
|
img->have_qt[tq] = 1;
|
||||||
}
|
}
|
||||||
return 1;
|
return 1;
|
||||||
@@ -371,7 +373,7 @@ static int mj_dht(mj_img *img, const unsigned char *d, int len)
|
|||||||
if (total > 256 || p + total > len) return 0;
|
if (total > 256 || p + total > len) return 0;
|
||||||
for (i = 0; i < total; i++) h->vals[i] = d[p + i];
|
for (i = 0; i < total; i++) h->vals[i] = d[p + i];
|
||||||
p += total;
|
p += total;
|
||||||
mj_huff_build(h);
|
if (!mj_huff_build(h)) return 0; /* 过订阅/码空间越界:拒绝整段 → 回退 stb */
|
||||||
if (tc) img->have_ac[th] = 1; else img->have_dc[th] = 1;
|
if (tc) img->have_ac[th] = 1; else img->have_dc[th] = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -558,7 +560,10 @@ static void mj_skip_entropy(mj_img *img)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 重启标记:对齐位缓冲并吃掉 RSTn,重置 DC 预测。 */
|
/* 重启标记:对齐位缓冲并吃掉 RSTn,重置 DC 预测。
|
||||||
|
*
|
||||||
|
* 裸扫时必须跳过 FF 00(数据字节 0xFF 的填充):区间末尾的填充字节常常正好是 0xFF,
|
||||||
|
* 它在码流里写成 `FF 00 FF Dn`;把中间那个 00 当成 marker 会让扫描提前结束并失步。 */
|
||||||
static void mj_restart(mj_img *img)
|
static void mj_restart(mj_img *img)
|
||||||
{
|
{
|
||||||
mj_src *s = &img->src;
|
mj_src *s = &img->src;
|
||||||
@@ -573,14 +578,47 @@ static void mj_restart(mj_img *img)
|
|||||||
c = mj_raw(s);
|
c = mj_raw(s);
|
||||||
if (c < 0) { s->over = 1; break; }
|
if (c < 0) { s->over = 1; break; }
|
||||||
if (c != 0xFF) continue;
|
if (c != 0xFF) continue;
|
||||||
do { c = mj_raw(s); if (c < 0) { s->over = 1; break; } } while (c == 0xFF);
|
for (;;) {
|
||||||
if (c < 0) break;
|
c = mj_raw(s);
|
||||||
if (c >= 0xD0 && c <= 0xD7) break;
|
if (c < 0) { s->over = 1; break; }
|
||||||
s->marker = c; s->over = 1;
|
if (c == 0xFF) continue; /* 填充 FF */
|
||||||
break;
|
if (c >= 0xD0 && c <= 0xD7) goto rst_found;
|
||||||
|
if (c == 0x00) break; /* FF00 = 数据字节,继续扫 */
|
||||||
|
s->marker = c; s->over = 1; /* 别的 marker:熵段结束 */
|
||||||
|
goto rst_done;
|
||||||
|
}
|
||||||
|
if (s->over) break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
rst_found:
|
||||||
for (i = 0; i < img->ncomp; i++) img->c[i].pred = 0;
|
for (i = 0; i < img->ncomp; i++) img->c[i].pred = 0;
|
||||||
|
rst_done:
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 区间边界处理(三处扫描循环共用)。返回:0 = 熵段结束(调用方退出扫描);
|
||||||
|
* 1 = 继续;2 = 刚做过区间重同步(AC 扫描需清 EOBRUN)。
|
||||||
|
*
|
||||||
|
* 只在 total == rst(编码器的区间长度)时重同步。**不能**一看到 over&&RSTn 就重同步:
|
||||||
|
* 熵码位缓冲有最多 9 位前瞻,`over` 会在区间真正结束前 1~2 个 MCU 就被置位,
|
||||||
|
* 此时 bitbuf 里还压着本区间剩余块的有效比特——提前重同步会把它们丢掉并失步
|
||||||
|
* (渐进式 DC 扫描每块仅 ~1 bit,前瞻能跨过整个 MCU;这是 rst=2..200 花屏的根因)。
|
||||||
|
* 到达 total==rst 时,RSTn 要么已被前瞻吃掉(over/marker=RSTn,清掉即可),
|
||||||
|
* 要么还在流里(裸扫对齐)——mj_restart 两种都处理。 */
|
||||||
|
static int mj_boundary(mj_img *img, int *total)
|
||||||
|
{
|
||||||
|
mj_src *s = &img->src;
|
||||||
|
if (img->rst && *total == img->rst) {
|
||||||
|
mj_restart(img);
|
||||||
|
*total = 0;
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
if (s->over) {
|
||||||
|
/* 前瞻已吃到 RSTn:本区间剩余块的有效比特还在 bitbuf 里,继续解。
|
||||||
|
* 只有「非 RST 的 marker」才是熵段真的结束(EOI/下一段的 DHT…)。 */
|
||||||
|
return (s->marker >= 0xD0 && s->marker <= 0xD7) ? 1 : 0;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@@ -600,12 +638,7 @@ static void mj_scan_seq(mj_img *img, const mj_scan *sc)
|
|||||||
for (my = 0; my < mcus_y; my++) {
|
for (my = 0; my < mcus_y; my++) {
|
||||||
for (mx = 0; mx < mcus_x; mx++) {
|
for (mx = 0; mx < mcus_x; mx++) {
|
||||||
int j;
|
int j;
|
||||||
if (img->rst && total == img->rst) {
|
if (!mj_boundary(img, &total)) return;
|
||||||
if (img->src.over && !(img->src.marker >= 0xD0 && img->src.marker <= 0xD7)) return;
|
|
||||||
mj_restart(img);
|
|
||||||
total = 0;
|
|
||||||
}
|
|
||||||
if (img->src.over) return;
|
|
||||||
if (single) {
|
if (single) {
|
||||||
mj_comp *c = &img->c[sc->ci[0]];
|
mj_comp *c = &img->c[sc->ci[0]];
|
||||||
c->td = sc->td[0]; c->ta = sc->ta[0];
|
c->td = sc->td[0]; c->ta = sc->ta[0];
|
||||||
@@ -635,12 +668,9 @@ static void mj_scan_dc_prog(mj_img *img, const mj_scan *sc)
|
|||||||
for (my = 0; my < mcus_y; my++) {
|
for (my = 0; my < mcus_y; my++) {
|
||||||
for (mx = 0; mx < mcus_x; mx++) {
|
for (mx = 0; mx < mcus_x; mx++) {
|
||||||
int j;
|
int j;
|
||||||
if (img->rst && total == img->rst) {
|
if (!mj_boundary(img, &total)) {
|
||||||
if (img->src.over && !(img->src.marker >= 0xD0 && img->src.marker <= 0xD7)) return;
|
return;
|
||||||
mj_restart(img);
|
|
||||||
total = 0;
|
|
||||||
}
|
}
|
||||||
if (img->src.over) return;
|
|
||||||
if (single) {
|
if (single) {
|
||||||
mj_comp *c = &img->c[sc->ci[0]];
|
mj_comp *c = &img->c[sc->ci[0]];
|
||||||
c->td = sc->td[0];
|
c->td = sc->td[0];
|
||||||
@@ -669,13 +699,9 @@ static void mj_scan_ac_prog(mj_img *img, const mj_scan *sc)
|
|||||||
c->ta = sc->ta[0];
|
c->ta = sc->ta[0];
|
||||||
for (by = 0; by < c->oh; by++) {
|
for (by = 0; by < c->oh; by++) {
|
||||||
for (bx = 0; bx < c->ow; bx++) {
|
for (bx = 0; bx < c->ow; bx++) {
|
||||||
if (img->rst && total == img->rst) {
|
int rb = mj_boundary(img, &total);
|
||||||
if (img->src.over && !(img->src.marker >= 0xD0 && img->src.marker <= 0xD7)) return;
|
if (!rb) return;
|
||||||
mj_restart(img);
|
if (rb == 2) eobrun = 0; /* 区间重同步:EOB 游程不能跨 RST */
|
||||||
total = 0;
|
|
||||||
eobrun = 0;
|
|
||||||
}
|
|
||||||
if (s->over) return;
|
|
||||||
if (eobrun > 0) {
|
if (eobrun > 0) {
|
||||||
eobrun--;
|
eobrun--;
|
||||||
} else {
|
} else {
|
||||||
@@ -709,7 +735,9 @@ static void mj_scan_ac_prog(mj_img *img, const mj_scan *sc)
|
|||||||
}
|
}
|
||||||
total++;
|
total++;
|
||||||
img->blocks++;
|
img->blocks++;
|
||||||
if (s->over) return;
|
/* 这里**不能**再 `if (s->over) return`:块末的位前瞻可能已吃掉本区间的 RSTn
|
||||||
|
* (over=1),必须留给下一轮 mj_boundary 做区间重同步,否则渐进式 + restart 的
|
||||||
|
* AC 扫描会在第一个区间边界处静默退出、把后续 RST 当独立 marker 跳过 → 失步。 */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -838,6 +866,7 @@ static int mj_decode(mj_img *img, int maxdim, int *tw, int *th, unsigned char **
|
|||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
{
|
||||||
if (!img->progressive) {
|
if (!img->progressive) {
|
||||||
mj_scan_seq(img, &sc);
|
mj_scan_seq(img, &sc);
|
||||||
} else if (sc.ss == 0) {
|
} else if (sc.ss == 0) {
|
||||||
@@ -846,6 +875,7 @@ static int mj_decode(mj_img *img, int maxdim, int *tw, int *th, unsigned char **
|
|||||||
if (sc.ns != 1) return 0;
|
if (sc.ns != 1) return 0;
|
||||||
mj_scan_ac_prog(img, &sc);
|
mj_scan_ac_prog(img, &sc);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
s->bitcnt = 0;
|
s->bitcnt = 0;
|
||||||
s->bitbuf = 0;
|
s->bitbuf = 0;
|
||||||
/* 一个熵段结束:位读取器可能已把下一个 marker 吃掉,回填给主循环,
|
/* 一个熵段结束:位读取器可能已把下一个 marker 吃掉,回填给主循环,
|
||||||
|
|||||||
+12
-5
@@ -128,17 +128,19 @@ static void png_get_px(const png_ctx *c, const unsigned char *row, int x, unsign
|
|||||||
} else {
|
} else {
|
||||||
unsigned char g = (unsigned char)(v * 255 / ((1 << d) - 1));
|
unsigned char g = (unsigned char)(v * 255 / ((1 << d) - 1));
|
||||||
o[0] = o[1] = o[2] = g;
|
o[0] = o[1] = o[2] = g;
|
||||||
o[3] = 255;
|
/* tRNS 存的是原始采样值(0..2^d-1),不是展开后的 0..255 */
|
||||||
|
o[3] = (c->color == 0 && c->have_trns && (unsigned)v == c->trns[0]) ? 0 : 255;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (d == 8) {
|
if (d == 8) {
|
||||||
if (c->color == 0) { /* gray */
|
if (c->color == 0) { /* gray */
|
||||||
o[0] = o[1] = o[2] = row[x];
|
o[0] = o[1] = o[2] = row[x];
|
||||||
o[3] = 255;
|
o[3] = (c->have_trns && row[x] == c->trns[0]) ? 0 : 255;
|
||||||
} else if (c->color == 2) { /* RGB */
|
} else if (c->color == 2) { /* RGB */
|
||||||
o[0] = row[x * 3]; o[1] = row[x * 3 + 1]; o[2] = row[x * 3 + 2];
|
o[0] = row[x * 3]; o[1] = row[x * 3 + 1]; o[2] = row[x * 3 + 2];
|
||||||
o[3] = 255;
|
o[3] = (c->have_trns && row[x * 3] == c->trns[0] &&
|
||||||
|
row[x * 3 + 1] == c->trns[1] && row[x * 3 + 2] == c->trns[2]) ? 0 : 255;
|
||||||
} else if (c->color == 3) { /* palette */
|
} else if (c->color == 3) { /* palette */
|
||||||
int i = row[x];
|
int i = row[x];
|
||||||
o[0] = c->pal[i][0]; o[1] = c->pal[i][1]; o[2] = c->pal[i][2];
|
o[0] = c->pal[i][0]; o[1] = c->pal[i][1]; o[2] = c->pal[i][2];
|
||||||
@@ -333,9 +335,14 @@ int meow_png_decode_stream(FILE *f, const unsigned char *mem, size_t len,
|
|||||||
if (c.color == 3) {
|
if (c.color == 3) {
|
||||||
for (i = 0; i < (int)clen && i < 256; i++) c.trns_a[i] = d[i];
|
for (i = 0; i < (int)clen && i < 256; i++) c.trns_a[i] = d[i];
|
||||||
} else if (c.color == 0 && clen >= 2) {
|
} else if (c.color == 0 && clen >= 2) {
|
||||||
c.trns[0] = d[0]; /* 高字节(已降 8bit) */
|
/* 8bit/低位深:采样值存在低字节;16bit:与像素一样取高字节比较 */
|
||||||
|
c.trns[0] = (c.depth == 16) ? d[0] : d[1];
|
||||||
} else if (c.color == 2 && clen >= 6) {
|
} else if (c.color == 2 && clen >= 6) {
|
||||||
c.trns[0] = d[0]; c.trns[1] = d[2]; c.trns[2] = d[4];
|
if (c.depth == 16) {
|
||||||
|
c.trns[0] = d[0]; c.trns[1] = d[2]; c.trns[2] = d[4];
|
||||||
|
} else {
|
||||||
|
c.trns[0] = d[1]; c.trns[1] = d[3]; c.trns[2] = d[5];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (memcmp(type, "IDAT", 4) == 0) {
|
} else if (memcmp(type, "IDAT", 4) == 0) {
|
||||||
unsigned remain = clen;
|
unsigned remain = clen;
|
||||||
|
|||||||
@@ -110,6 +110,34 @@ meow_img *meow_img_load_file_step(const char *path, int maxdim, int step)
|
|||||||
if (!stbi_info(path, &w, &h, &c)) return NULL;
|
if (!stbi_info(path, &w, &h, &c)) return NULL;
|
||||||
int longside = w > h ? w : h;
|
int longside = w > h ? w : h;
|
||||||
if (step == 1 && maxdim <= 0) return meow_img_load_file(path);
|
if (step == 1 && maxdim <= 0) return meow_img_load_file(path);
|
||||||
|
/* 追加(M23):真缩解后端优先(本入口只接 JPEG)。step 是「输出别比这个档位更细」的提示:
|
||||||
|
* 折算成目标长边 ceil(长边/step) 传给后端(后端按该长边自动选 DCT 阶),与下面
|
||||||
|
* stbi_load_scaled(step) 的语义一致,峰值只与输出尺寸相关;maxdim<=0 时不精修,
|
||||||
|
* 结果仍是档位尺寸。后端不支持(CMYK/算术编码/交错…)时回退 stb,行为与 M22 一致。
|
||||||
|
*
|
||||||
|
* PNG/BMP 在本入口暂不接后端:阅读器的单次解码峰值**预算模型**还是 M22 的「整幅物化」
|
||||||
|
* 口径,实测峰值骤降会让它的 model≤2×measured 自检失真(详见 M23 r1 报告的 M27 待办)。
|
||||||
|
* 待 M27 同步模型后,把这里的 JPEG 判定去掉即可。 */
|
||||||
|
{
|
||||||
|
unsigned char sig[3];
|
||||||
|
int is_jpeg = 0;
|
||||||
|
FILE *fp = fopen(path, "rb");
|
||||||
|
if (fp) {
|
||||||
|
if (fread(sig, 1, 3, fp) == 3 &&
|
||||||
|
sig[0] == 0xFF && sig[1] == 0xD8 && sig[2] == 0xFF)
|
||||||
|
is_jpeg = 1;
|
||||||
|
fclose(fp);
|
||||||
|
}
|
||||||
|
if (is_jpeg) {
|
||||||
|
int eff = maxdim > 0 ? maxdim : longside;
|
||||||
|
int step_cap = (longside + step - 1) / step;
|
||||||
|
unsigned char *bp = NULL;
|
||||||
|
int bw = 0, bh = 0;
|
||||||
|
if (step_cap > 0 && step_cap < eff) eff = step_cap;
|
||||||
|
if (eff > 0 && meow_img_backend_file(path, eff, &bw, &bh, &bp) && bp)
|
||||||
|
return img_finish_dim(bp, bw, bh, maxdim > 0 ? maxdim : longside);
|
||||||
|
}
|
||||||
|
}
|
||||||
unsigned char *p = stbi_load_scaled(path, &w, &h, &c, 4, step);
|
unsigned char *p = stbi_load_scaled(path, &w, &h, &c, 4, step);
|
||||||
/* maxdim<=0:不精修(保留档位输出的原始尺寸)。 */
|
/* maxdim<=0:不精修(保留档位输出的原始尺寸)。 */
|
||||||
return img_finish_dim(p, w, h, maxdim > 0 ? maxdim : longside);
|
return img_finish_dim(p, w, h, maxdim > 0 ? maxdim : longside);
|
||||||
|
|||||||
+254
-4
@@ -180,6 +180,17 @@ static const char *g_gen_py =
|
|||||||
" chunk(b'IDAT', zlib.compress(bytes(raw))) + chunk(b'IEND', b''))\n"
|
" chunk(b'IDAT', zlib.compress(bytes(raw))) + chunk(b'IEND', b''))\n"
|
||||||
" open(path, 'wb').write(data)\n"
|
" open(path, 'wb').write(data)\n"
|
||||||
"adam7(os.path.join(out, 'adam7.png'), 1600, 2400)\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"
|
"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"
|
" bytes((i * 37 + 11) & 0xFF for i in range(256)))\n"
|
||||||
"for n in sorted(os.listdir(out)):\n"
|
"for n in sorted(os.listdir(out)):\n"
|
||||||
@@ -390,6 +401,75 @@ static size_t old_dim_peak(const char *path, int maxdim)
|
|||||||
return peak;
|
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)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
@@ -681,22 +761,192 @@ int main(int argc, char **argv)
|
|||||||
}
|
}
|
||||||
{
|
{
|
||||||
size_t base = peak_reset();
|
size_t base = peak_reset();
|
||||||
meow_img *got = meow_img_load_file_dim(prs, 720);
|
int tw = 0, th = 0;
|
||||||
size_t pk = peak_since(base);
|
unsigned char *px = NULL;
|
||||||
meow_img *ref = ref_thumb(prs, 720);
|
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 corr = 0.0;
|
||||||
double d = (got && ref) ? mean_abs_diff(got, ref, 64, &corr) : 1e9;
|
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);
|
peak_report("restart marker 2000x3000", pk);
|
||||||
|
CHECK(handled == 1, "baseline + restart 走后端(BACKEND=1,不回退 stb)");
|
||||||
CHECK(got && nonblank(got), "restart marker JPEG 解码成功");
|
CHECK(got && nonblank(got), "restart marker JPEG 解码成功");
|
||||||
CHECK(d < 24.0 && corr > 0.85, "restart marker JPEG 与 stb 结构一致(RST 重同步正确)");
|
CHECK(d < 24.0 && corr > 0.85, "restart marker JPEG 与 stb 结构一致(RST 重同步正确)");
|
||||||
CHECK(pk <= PEAK_2500KB, "restart marker JPEG 峰值 ≤ 2.5 MiB");
|
CHECK(pk <= PEAK_2500KB, "restart marker JPEG 峰值 ≤ 2.5 MiB");
|
||||||
meow_img_free(got);
|
meow_img_free(got);
|
||||||
meow_img_free(ref);
|
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(psw);
|
||||||
free(prs);
|
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 内存入口 ---- */
|
/* ---- S8 内存入口 ---- */
|
||||||
printf("[S8] 内存入口 meow_img_load_mem_dim\n");
|
printf("[S8] 内存入口 meow_img_load_mem_dim\n");
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -165,6 +165,7 @@ if [ -f "$SDK/meow_ui_view.c" ] && [ -f "$SDK/meow_ui_config.c" ]; then
|
|||||||
"$SDK"/meow_ui_ime.c "$SDK"/meow_ui_str.c "$SDK"/meow_pinyin.c \
|
"$SDK"/meow_ui_ime.c "$SDK"/meow_ui_str.c "$SDK"/meow_pinyin.c \
|
||||||
"$SDK"/meow_ui_draw.c "$SDK"/meow_ui_view.c "$SDK"/meow_ui_box.c \
|
"$SDK"/meow_ui_draw.c "$SDK"/meow_ui_view.c "$SDK"/meow_ui_box.c \
|
||||||
"$SDK"/meow_ui_anim.c "$SDK"/meow_ui_scroll.c "$SDK"/meow_ui_img.c \
|
"$SDK"/meow_ui_anim.c "$SDK"/meow_ui_scroll.c "$SDK"/meow_ui_img.c \
|
||||||
|
"$SDK"/meow_jpeg.c "$SDK"/meow_png_stream.c "$SDK"/meow_ui_time.c \
|
||||||
"$SDK"/meow_ui_media.c "$SDK"/meow_notify_ipc.c "$SDK"/meow_ui_config.c \
|
"$SDK"/meow_ui_media.c "$SDK"/meow_notify_ipc.c "$SDK"/meow_ui_config.c \
|
||||||
"$SDK"/meow_ui_nav.c "$SDK"/stb_image.c "$SDK"/test_host_stub.c \
|
"$SDK"/meow_ui_nav.c "$SDK"/stb_image.c "$SDK"/test_host_stub.c \
|
||||||
-pthread -lm -lz -o "$OUT"/test_reader_membudget \
|
-pthread -lm -lz -o "$OUT"/test_reader_membudget \
|
||||||
|
|||||||
Reference in new issue
Block a user