Files
MeowOS-For-R3P2/patch_v2/sdk/meow_jpeg.c
T
AskaEth 281f4153ee fix(sdk): M26 r1 P1 — ILP32 尺寸算术溢出(畸形 BMP 经公开入口 SIGSEGV)+ 加 S17 回归
评审在 ARM32(ILP32,与 mipsel 同数据模型)实测:54 字节 BMP 头声明 w=2^27、bpp=32
时 w*bpp 回绕成 0 → rowsize=0 → malloc(0) → 采样索引 row+sx*4 越界读 → SIGSEGV,
经 meow_img_load_file_dim 端到端可触发;同一文件在 LP64 宿主返回 NULL(不溢出),
合并前走 stb 也是 NULL。即这是相对旧路径的鲁棒性回归。

修复(只加闸,正常路径数值不变):

- meow_png_stream.c(P1 本体)
  · 新增 ILP32 守卫 MZP_MAX_DIM=1<<20 与 mzp_bytes_ok()(unsigned long long 验算,
    超过 SIZE_MAX 即拒)
  · BMP:声明尺寸先升 int/long long 校验(w/h 单边 ≤1<<20;h=INT_MIN 用
    -(long long)hi 取绝对值,不再有 -h 溢出);rowsize 用 64 位算并验;
    行偏移 off / 采样下标 sx 改 long long,sx 夹在 [0,w-1];按行 fseek 前加
    off > LONG_MAX 拒绝;目标 RGBA 字节数 64 位验后再 malloc
  · PNG:IHDR 尺寸原已 ≤65535,补上 tw*th*4 的 64 位校验(32768²×4 = 2^32 会回绕),
    emit_row 的 sy/sx 与输出行偏移改 64 位并夹紧
- meow_jpeg.c(顺手审计同类 32 位算术,评审要求「不要只修点名那处」)
  · mj_layout 返回 unsigned long long(单平面/三平面之和都可能越 2^32),
    越界返回 MJ_LAYOUT_BAD → mj_sof 各处重新布局后都检查
  · target_bytes 与分量 plane malloc 改 64 位验算;目标图字节超 MJ_PEAK_CAP 直接拒
  · mj_output:输出缓冲 64 位验;y*vh / x*vw 采样改 long long 并夹紧
  · meow_img_target_dims / mj_pick_n 的 (long)h*maxdim、8*maxdim 改 long long

- test_meow_img_stream.c:新增 S17(10 组畸形 BMP 声明尺寸 + 2 个合法正例 +
  PNG 回绕点),断言「后端拒绝 + 公开入口不崩(越界尺寸另断言 NULL 且不分配)」;
  文件头补 ILP32 交叉编译命令(宿主 LP64 看不到 32 位回绕,测试注释里明写这一点)

验证:同一测试在 zig arm-linux-musleabi(sizeof(long)=4)下 S1-S17 全绿(206 checks,
exit 0);把后端换回 r1 提交的旧实现,同一 ARM32 二进制 exit 139(说明 S17 真能抓);
宿主 LP64 S1-S17 全绿、B1-B9 全绿;静态库 --verify 仍 78 成员 / 全 MIPS32r2。

同步提醒:这些行与公开仓 ~/Proj/MeowOS-SDK/sdk/{meow_png_stream.c,meow_jpeg.c} 逐字节
同源,同一补丁必须由控制塔同步过去,否则立刻产生双边漂移。
2026-11-06 21:27:03 +08:00

1057 lines
41 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* meow_jpeg.c — M23 真缩解后端(一):JPEG DCT 域缩解 + 格式分派。
*
* 背景(M20 审计实测):stb 的 stbi_load_scaled 是「先整幅解、再重采样」——baseline 分量面
* 1.5·W·H、渐进式再加 3·W·H,2000×3000 baseline 峰值 +10.1MB、渐进 +23.5MB,真机 kill 点
* 只剩 ~4.2MB 余量。本文件换成「解码期只算目标分辨率」:
*
* - 按目标显示尺寸选 DCT 缩解档 N ∈ {1,2,4}(1/8、1/4、1/2 分辨率),每 8×8 块只解出
* N×N 个像素——N 点基函数是「8 点 IDCT 按像素区间盒式平均」的定点近似,DC 项恒为 F00/8,
* 与「整幅解完再降采样」结构一致(差异只在高频细节)。
* - 系数边解边累加进分量平面,不留整幅系数阵列:baseline/渐进式内存足迹相同。
* - 渐进式只解到首个精细化扫描(首个 Ah>0)为止:此时各频带的首次扫描(Ah=0)已到齐,
* 低频信息完整;再往后只是同一批系数的量化精度细化。因此**不需要**整幅系数的
* successive-approximation 缓存——这正是 libjpeg 系列做不到 2.5MB 的根因
* (渐进式必须整幅驻留 JBLOCK,≈1 字节/源像素/分量)。
* - 输出直接是目标尺寸 RGBA,不再「缩解结果 + 精修输出」两份同时驻留。
*
* 不支持 → 返回 0 让 meow_ui_img.c 回退 stb(不改变对外语义):4 分量 CMYK/YCCK、
* 算术编码(SOF9/10)、无损(SOF3)、分层(SOF5-7,13-15)、12bit 精度。
*
* 许可证:MeowOS 自写(MIT,随 SDK 发布);未引入第三方解码器源码。
* 参考资料:ITU-T T.81 (JPEG) 语法与 Huffman 解码、libjpeg 的 marker/DQT/DHT 布局;
* 缩解基函数见文件内 mj_basis_* 的推导注释。
*/
#include "meow_jpeg.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
/* ================================================================== */
/* 公共:目标尺寸(与 meow_img_downscale 同一公式,保证对外表现不变) */
/* ================================================================== */
void meow_img_target_dims(int w, int h, int maxdim, int *tw, int *th)
{
if (w >= h) {
*tw = maxdim;
*th = (int)((long long)h * maxdim / w); /* 64 位:ILP32 上 h*maxdim 会溢出 */
} else {
*th = maxdim;
*tw = (int)((long long)w * maxdim / h);
}
if (*tw < 1) *tw = 1;
if (*th < 1) *th = 1;
}
/* ================================================================== */
/* 字节源:文件流(4KB 环形读)或内存(零拷贝,直接用调用方缓冲) */
/* ================================================================== */
#define MJ_IOBUF 4096
typedef struct {
FILE *f;
const unsigned char *mem;
size_t memlen, mempos;
unsigned char buf[MJ_IOBUF];
int buflen, bufpos;
unsigned int bitbuf; /* 熵流位缓冲(MSB 在前) */
int bitcnt;
int over; /* 本熵段已到尽头(遇到 marker 或 EOF) */
int marker; /* over 时已吃掉的 marker 码(0=无) */
} mj_src;
static int mj_raw(mj_src *s)
{
if (s->mem) {
if (s->mempos >= s->memlen) return -1;
return s->mem[s->mempos++];
}
if (s->bufpos >= s->buflen) {
s->buflen = (int)fread(s->buf, 1, MJ_IOBUF, s->f);
s->bufpos = 0;
if (s->buflen <= 0) { s->buflen = 0; return -1; }
}
return s->buf[s->bufpos++];
}
static int mj_u16(mj_src *s)
{
int a = mj_raw(s), b = mj_raw(s);
return (a < 0 || b < 0) ? -1 : ((a << 8) | b);
}
static int mj_read(mj_src *s, unsigned char *dst, size_t n)
{
while (n--) {
int c = mj_raw(s);
if (c < 0) return 0;
*dst++ = (unsigned char)c;
}
return 1;
}
static int mj_skip(mj_src *s, long n)
{
while (n-- > 0) if (mj_raw(s) < 0) return 0;
return 1;
}
/* 熵段内的字节:FF00 → FF;遇到真 marker(含 RSTn)→ 置 over 并记录,返回 0。 */
static int mj_eb(mj_src *s)
{
int c;
if (s->over) return 0;
c = mj_raw(s);
if (c < 0) { s->over = 1; return 0; }
if (c != 0xFF) return c;
for (;;) {
int d = mj_raw(s);
if (d < 0) { s->over = 1; return 0; }
if (d == 0x00) return 0xFF;
if (d == 0xFF) continue; /* 填充 FF */
s->marker = d; s->over = 1; return 0;
}
}
static void mj_fill(mj_src *s, int n)
{
while (s->bitcnt < n) {
s->bitbuf = (s->bitbuf << 8) | (unsigned)mj_eb(s);
s->bitcnt += 8;
}
}
static int mj_getbits(mj_src *s, int n)
{
if (n <= 0) return 0;
if (s->bitcnt < n) mj_fill(s, n);
s->bitcnt -= n;
return (int)((s->bitbuf >> s->bitcnt) & ((1u << n) - 1u));
}
static int mj_extend(int v, int n)
{
return v < (1 << (n - 1)) ? v - (1 << n) + 1 : v;
}
/* ================================================================== */
/* Huffman 表:9 位快表 + 慢路径(最长码 16 位) */
/* ================================================================== */
typedef struct {
unsigned char bits[17]; /* 各码长符号数 */
unsigned char vals[256];
unsigned short fast[512]; /* (len<<8)|sym,0 = 走慢路径 */
int maxcode[17];
int mincode[17];
int valptr[17];
} mj_huff;
/* 构造 9 位快表 + 规范码表。返回 0 = 表非法(过订阅/码空间越界),调用方必须拒绝该表。
* 过订阅输入(如「长度 2 的码有 100 个」)此前会让 code << shift 把快表下标推到 512 之外,
* 28~220 字节的畸形 JPEG 就能栈溢出 → 这里在填表前校验码空间,填表时再加一道下标边界。 */
static int mj_huff_build(mj_huff *h)
{
int code = 0, k = 0, l;
memset(h->fast, 0, sizeof h->fast);
h->maxcode[0] = -1;
for (l = 1; l <= 16; l++) {
int i;
if (code + h->bits[l] > (1 << l)) return 0; /* 码空间溢出:过订阅表 */
h->valptr[l] = k;
h->mincode[l] = code;
for (i = 0; i < h->bits[l]; i++) {
if (l <= 9) {
int shift = 9 - l, j;
unsigned short e = (unsigned short)((l << 8) | h->vals[k]);
int base = code << shift;
for (j = 0; j < (1 << shift); j++)
if ((base | j) < 512) h->fast[base | j] = e;
}
code++;
k++;
}
h->maxcode[l] = h->bits[l] ? code - 1 : -1;
code <<= 1;
}
return 1;
}
static int mj_hdecode(mj_src *s, const mj_huff *h)
{
if (s->bitcnt < 9) mj_fill(s, 9);
if (s->bitcnt >= 9) {
unsigned idx = (s->bitbuf >> (s->bitcnt - 9)) & 511u;
unsigned e = h->fast[idx];
if (e) {
s->bitcnt -= (int)(e >> 8);
return (int)(e & 0xFFu);
}
}
{
int code = 0, l;
for (l = 1; l <= 16; l++) {
code = (code << 1) | mj_getbits(s, 1);
if (code <= h->maxcode[l])
return h->vals[h->valptr[l] + code - h->mincode[l]];
if (s->over) return 0;
}
}
s->over = 1;
return 0;
}
/* zigzag 序号 → 自然序(行优先 v*8+u) */
static const unsigned char mj_zigzag[64] = {
0, 1, 8, 16, 9, 2, 3, 10,
17, 24, 32, 25, 18, 11, 4, 5,
12, 19, 26, 33, 40, 48, 41, 34,
27, 20, 13, 6, 7, 14, 21, 28,
35, 42, 49, 56, 57, 50, 43, 36,
29, 22, 15, 23, 30, 37, 44, 51,
58, 59, 52, 45, 38, 31, 39, 46,
53, 60, 61, 54, 47, 55, 62, 63
};
/* ================================================================== */
/* 缩解基(Q12):B(u,x) = 「8 点 IDCT 在输出像素区间的盒式平均」 */
/* B(u,y) = (N/8)·Σ_{x} w(y,x)·(1/2)·C(u)·cos((2x+1)uπ/16), C(0)=1/√2 */
/* w(y,x) = 像素 x 的单位区间与输出区间 [y·8/N, (y+1)·8/N) 的重叠长度 */
/* 于是:DC 单频重建恒为 F00/8(与 8×8 IDCT 的均值一致);N=8 时退化为 */
/* 精确 IDCT;任意 N 之间的亮度/对比度一致(同一套离散平均口径)。 */
/* 只列 N=1..7:N=8(≈不缩解)交回 stb 旧路径,保持「档位」对外表现。 */
/* ================================================================== */
static const short mj_basis_1[1] = { 1448 };
static const short mj_basis_2[4] = {
1448, 1448,
1312, -1312
};
static const short mj_basis_3[9] = {
1448, 1448, 1448,
1676, 0, -1676,
808, -1615, 808
};
static const short mj_basis_4[16] = {
1448, 1448, 1448, 1448,
1856, 769, -769, -1856,
1338, -1338, -1338, 1338,
652, -1573, 1573, -652
};
static const short mj_basis_5[25] = {
1448, 1448, 1448, 1448, 1448,
1894, 1187, 0, -1187, -1894,
1476, -530, -1892, -530, 1476,
914, -1498, 0, 1498, -914,
362, -1086, 1448, -1086, 362
};
static const short mj_basis_6[36] = {
1448, 1448, 1448, 1448, 1448, 1448,
1932, 1420, 584, -584, -1420, -1932,
1615, 0, -1615, -1615, 0, 1615,
1177, -1204, -1356, 1356, 1204, -1177,
724, -1448, 724, 724, -1448, 724,
351, -805, 1377, -1377, 805, -351
};
static const short mj_basis_7[49] = {
1448, 1448, 1448, 1448, 1448, 1448, 1448,
1970, 1562, 861, 0, -861, -1562, -1970,
1754, 392, -1199, -1892, -1199, 392, 1754,
1440, -802, -1682, 0, 1682, 802, -1440,
1086, -1448, -362, 1448, -362, -1448, 1086,
745, -1407, 888, 0, -888, 1407, -745,
449, -946, 889, -784, 889, -946, 449
};
static const short *mj_basis(int n)
{
switch (n) {
case 1: return mj_basis_1;
case 2: return mj_basis_2;
case 3: return mj_basis_3;
case 4: return mj_basis_4;
case 5: return mj_basis_5;
case 6: return mj_basis_6;
default: return mj_basis_7;
}
}
/* ================================================================== */
/* 分量与图像 */
/* ================================================================== */
typedef struct {
int id, h, v, tq;
int n; /* 缩解阶:每块 n×n 像素 */
int pw, ph; /* 平面(含 MCU padding)尺寸 */
int vw, vh; /* 有效像素尺寸(最近邻采样用) */
int ow, oh; /* 本分量自己的块网格(非交织扫描用) */
int td, ta; /* 当前扫描的 Huffman 表号 */
int pred; /* DC 预测值(量化域) */
unsigned char *plane;
} mj_comp;
typedef struct {
int width, height;
int ncomp;
int hmax, vmax;
int mcus_x, mcus_y;
int progressive;
int rst; /* 重启间隔(MCU 数),0 = 无 */
int adobe_transform; /* -1 未声明;0=RGB/CMYK;1=YCbCr */
int blocks; /* 已解块数(判「有没有解出东西」) */
mj_comp c[3];
int qt[4][64];
int have_qt[4];
mj_huff dc[4], ac[4];
int have_dc[4], have_ac[4];
mj_src src;
} mj_img;
#define MJ_PEAK_CAP (8u * 1024u * 1024u) /* 防御性上限:异常巨图不至于一次吃掉几十 MB */
static void mj_free(mj_img *img)
{
int i;
for (i = 0; i < img->ncomp; i++) {
free(img->c[i].plane);
img->c[i].plane = NULL;
}
}
/* 把一个(已反量化的)系数按 DCT 缩解累加进平面。k 为自然序下标。 */
static void mj_accum(mj_comp *c, int bx, int by, int k, int32_t F)
{
int u = k & 7, v = k >> 3, n = c->n, x, y;
const short *W;
unsigned char *base;
if (!F || u >= n || v >= n) return;
W = mj_basis(n);
base = c->plane + (size_t)(by * n) * c->pw + (size_t)(bx * n);
for (y = 0; y < n; y++) {
int32_t t = (F * W[v * n + y]) >> 12;
unsigned char *row = base + (size_t)y * c->pw;
for (x = 0; x < n; x++) {
int nv = (int)row[x] + (int)((t * W[u * n + x]) >> 12);
row[x] = (unsigned char)(nv < 0 ? 0 : (nv > 255 ? 255 : nv));
}
}
}
/* ------------------------------------------------------------------ */
/* 表解析 */
/* ------------------------------------------------------------------ */
static int mj_dqt(mj_img *img, const unsigned char *d, int len)
{
int p = 0;
while (p < len) {
int pq = d[p] >> 4, tq = d[p] & 15, i;
p++;
if (tq > 3) return 0;
/* 16bit 量化表只出现在 12bit 精度(本模块不支持);收下会让 F*qt 在 mj_accum 里
* int32 溢出(UB)。直接拒绝 → 后端返回 0 → 回退 stb。 */
if (pq != 0) return 0;
if (p + 64 > len) return 0;
for (i = 0; i < 64; i++) img->qt[tq][mj_zigzag[i]] = d[p + i];
p += 64;
img->have_qt[tq] = 1;
}
return 1;
}
static int mj_dht(mj_img *img, const unsigned char *d, int len)
{
int p = 0;
while (p < len) {
int tc, th, i, total = 0;
if (p + 17 > len) return 0;
tc = d[p] >> 4; th = d[p] & 15; p++;
if (th > 3 || tc > 1) return 0;
{
mj_huff *h = tc ? &img->ac[th] : &img->dc[th];
for (i = 1; i <= 16; i++) { h->bits[i] = d[p + i - 1]; total += d[p + i - 1]; }
p += 16;
if (total > 256 || p + total > len) return 0;
for (i = 0; i < total; i++) h->vals[i] = d[p + i];
p += total;
if (!mj_huff_build(h)) return 0; /* 过订阅/码空间越界:拒绝整段 → 回退 stb */
if (tc) img->have_ac[th] = 1; else img->have_dc[th] = 1;
}
}
return 1;
}
/* ------------------------------------------------------------------ */
/* SOF:尺寸/采样因子/分量的平面分配 */
/* ------------------------------------------------------------------ */
static int mj_ceil_div(int a, int b) { return (a + b - 1) / b; }
/* 缩解阶 N:每 8×8 块只解出 N×N 像素(N/8 = 输出/源分辨率)。
* 取「不欠采样」的最小 N:ceil(8·maxdim/longside)。N=8 表示几乎不缩解(≈原尺寸),
* 这种图上 stb 旧路径的峰值本来就受控,直接交回它,档位对外表现不变。 */
static int mj_pick_n(int longside, int maxdim)
{
long long n = ((long long)8 * maxdim + longside - 1) / longside; /* 64 位:maxdim 可到 INT_MAX */
if (n < 1) n = 1;
if (n > 7) return 0;
return (int)n;
}
/* 按各分量当前的 n 计算块网格与平面尺寸,返回平面总字节。
* 返回 64 位:ILP32 上单平面 pw*ph 与 3 个平面之和都可能越过 2^32。
* MJ_LAYOUT_BAD 表示该乘积在 32 位 size_t 上无法表示(调用方直接拒绝)。 */
#define MJ_LAYOUT_BAD ((unsigned long long)-1)
static unsigned long long mj_layout(mj_img *img)
{
unsigned long long total = 0;
int i;
for (i = 0; i < img->ncomp; i++) {
mj_comp *c = &img->c[i];
int sw = mj_ceil_div(img->width * c->h, img->hmax);
int sh = mj_ceil_div(img->height * c->v, img->vmax);
unsigned long long plane;
c->ow = mj_ceil_div(sw, 8);
c->oh = mj_ceil_div(sh, 8);
c->pw = (img->mcus_x * c->h > c->ow ? img->mcus_x * c->h : c->ow) * c->n;
c->ph = (img->mcus_y * c->v > c->oh ? img->mcus_y * c->v : c->oh) * c->n;
c->vw = mj_ceil_div(sw * c->n, 8);
c->vh = mj_ceil_div(sh * c->n, 8);
if (c->vw > c->pw) c->vw = c->pw;
if (c->vh > c->ph) c->vh = c->ph;
plane = (unsigned long long)c->pw * (unsigned long long)c->ph;
if (plane > (unsigned long long)(size_t)-1) return MJ_LAYOUT_BAD;
total += plane;
}
return total;
}
static int mj_sof(mj_img *img, int marker, const unsigned char *d, int len, int maxdim)
{
int i, n, longside, base_n;
unsigned long long plane_bytes, target_bytes;
int tw, th;
if (marker != 0xC0 && marker != 0xC1 && marker != 0xC2) return 0; /* 算术/无损/差分 */
if (len < 6) return 0;
if (d[0] != 8) return 0; /* 12bit 精度不支持 */
img->height = (d[1] << 8) | d[2];
img->width = (d[3] << 8) | d[4];
n = d[5];
if (img->width <= 0 || img->height <= 0 || n < 1 || n > 3) return 0; /* 4 分量 CMYK → stb */
if (len < 6 + n * 3) return 0;
img->ncomp = n;
img->progressive = (marker == 0xC2);
longside = img->width > img->height ? img->width : img->height;
if (longside <= maxdim) return 0; /* 小图走旧入口 */
base_n = mj_pick_n(longside, maxdim);
if (!base_n) return 0; /* 几乎等尺寸:交回 stb 旧路径 */
img->hmax = img->vmax = 1;
for (i = 0; i < n; i++) {
const unsigned char *p = d + 6 + i * 3;
mj_comp *c = &img->c[i];
c->id = p[0];
c->h = p[1] >> 4;
c->v = p[1] & 15;
c->tq = p[2];
if (c->h < 1 || c->v < 1 || c->h > 4 || c->v > 4 || c->tq > 3) return 0;
if (c->h > img->hmax) img->hmax = c->h;
if (c->v > img->vmax) img->vmax = c->v;
}
img->mcus_x = mj_ceil_div(img->width, 8 * img->hmax);
img->mcus_y = mj_ceil_div(img->height, 8 * img->vmax);
meow_img_target_dims(img->width, img->height, maxdim, &tw, &th);
/* 64 位算目标图字节:ILP32 上 tw*th*4 在 tw=th=32768 时正好回绕成 0,
* 会让下面的预算闸失效并去 malloc(0)(M26 r1 顺手审计)。 */
target_bytes = (unsigned long long)(unsigned)tw * (unsigned long long)(unsigned)th * 4u;
if (target_bytes > MJ_PEAK_CAP) return 0;
for (i = 0; i < n; i++) img->c[i].n = base_n;
plane_bytes = mj_layout(img);
if (plane_bytes == MJ_LAYOUT_BAD) return 0;
/* 4:4:4 的色度平面与亮度等大(正常 4:2:0 只 1/4):亮度平面超过 400KB 时色度降一档,
* 视觉近无损,省下的正是色度要多占的两份。 */
if (n == 3 && img->c[0].h == img->c[1].h && img->c[0].v == img->c[1].v &&
plane_bytes / 3 > 400u * 1024u) {
for (i = 1; i < n; i++) if (img->c[i].n > 1) img->c[i].n >>= 1;
plane_bytes = mj_layout(img);
if (plane_bytes == MJ_LAYOUT_BAD) return 0;
}
/* 预算 2.4MB(平面 + 目标图):超了先降色度(亮度保持清晰),再整体降档。
* 12MP 4:2:0 一档:1.13MB→0.94MB,输出仍是 480×720。 */
if (plane_bytes + target_bytes > 2400u * 1024u) {
int ch = 0;
for (i = 1; i < n; i++) if (img->c[i].n > 1) { img->c[i].n >>= 1; ch = 1; }
if (ch) {
plane_bytes = mj_layout(img);
if (plane_bytes == MJ_LAYOUT_BAD) return 0;
}
}
/* 最后一道防线:异常巨图整体降档(最多降到 N=1),不至于一次吃掉几十 MB。 */
while (plane_bytes + target_bytes > MJ_PEAK_CAP && base_n > 1) {
base_n >>= 1;
for (i = 0; i < n; i++) if (img->c[i].n > base_n) img->c[i].n = base_n;
plane_bytes = mj_layout(img);
if (plane_bytes == MJ_LAYOUT_BAD) return 0;
}
/* 硬闸:降到 N=1 仍超上限(畸形/超大声明尺寸,如 23 字节的 SOF 65535² → 亮度面
* 8192² = 64MiB)直接拒绝 → 返回 0 → 回退 stb(stb 对这类文件是 KB 级安全失败)。
* 没有这道闸,公开入口 meow_img_load_file_dim(720) 会被一个头文件打爆设备内存。 */
if (plane_bytes + target_bytes > MJ_PEAK_CAP) return 0;
for (i = 0; i < n; i++) {
mj_comp *c = &img->c[i];
unsigned long long bytes64 = (unsigned long long)c->pw * (unsigned long long)c->ph;
size_t bytes;
if (bytes64 == 0 || bytes64 > (unsigned long long)(size_t)-1) return 0;
bytes = (size_t)bytes64;
c->plane = (unsigned char *)malloc(bytes);
if (!c->plane) return 0;
memset(c->plane, 128, bytes); /* 中性灰底,系数累加即 IDCT */
c->pred = 0;
}
return 1;
}
/* ------------------------------------------------------------------ */
/* 熵解码:块 */
/* ------------------------------------------------------------------ */
static int mj_block_seq(mj_img *img, mj_comp *c, int bx, int by)
{
mj_src *s = &img->src;
int t = mj_hdecode(s, &img->dc[c->td]);
int k;
if (t > 16) return 0;
c->pred += t ? mj_extend(mj_getbits(s, t), t) : 0;
if (c->pred > (1 << 20) || c->pred < -(1 << 20)) c->pred = 0;
mj_accum(c, bx, by, 0, (int32_t)c->pred * img->qt[c->tq][0]);
for (k = 1; k < 64; ) {
int rs = mj_hdecode(s, &img->ac[c->ta]);
int r = rs >> 4, sz = rs & 15;
if (sz == 0) {
if (r < 15) break; /* EOB */
k += 16; /* ZRL */
continue;
}
k += r;
if (k > 63) break;
{
int v = mj_extend(mj_getbits(s, sz), sz);
int nk = mj_zigzag[k];
if (v > 1023) v = 1023;
if (v < -1023) v = -1023;
mj_accum(c, bx, by, nk, (int32_t)v * img->qt[c->tq][nk]);
k++;
}
}
img->blocks++;
return 1;
}
static int mj_block_dc_prog(mj_img *img, mj_comp *c, int bx, int by, int al)
{
mj_src *s = &img->src;
int t = mj_hdecode(s, &img->dc[c->td]);
if (t > 16) return 0;
c->pred += t ? (mj_extend(mj_getbits(s, t), t) << al) : 0;
if (c->pred > (1 << 20) || c->pred < -(1 << 20)) c->pred = 0;
mj_accum(c, bx, by, 0, (int32_t)c->pred * img->qt[c->tq][0]);
img->blocks++;
return 1;
}
/* 跳过一段熵编码数据(不解码),停在下一个非 RST 的 marker 上。
* 用途:渐进式的精细化扫描(Ah>0)对缩略图无价值,但编码器可能把频带扫描与精细化交错,
* 所以只跳过、不中止——后续的 Ah=0 扫描仍会解码。 */
static void mj_skip_entropy(mj_img *img)
{
mj_src *s = &img->src;
s->bitcnt = 0;
s->bitbuf = 0;
for (;;) {
while (!s->over) (void)mj_eb(s);
if (s->over && s->marker >= 0xD0 && s->marker <= 0xD7) {
s->over = 0; /* RSTn:重同步后继续扫 */
s->marker = 0;
continue;
}
break;
}
}
/* 重启标记:对齐位缓冲并吃掉 RSTn,重置 DC 预测。
*
* 裸扫时必须跳过 FF 00(数据字节 0xFF 的填充):区间末尾的填充字节常常正好是 0xFF,
* 它在码流里写成 `FF 00 FF Dn`;把中间那个 00 当成 marker 会让扫描提前结束并失步。 */
static void mj_restart(mj_img *img)
{
mj_src *s = &img->src;
int i;
s->bitcnt = 0;
s->bitbuf = 0;
if (s->over) {
if (s->marker >= 0xD0 && s->marker <= 0xD7) { s->over = 0; s->marker = 0; }
} else {
int c;
for (;;) {
c = mj_raw(s);
if (c < 0) { s->over = 1; break; }
if (c != 0xFF) continue;
for (;;) {
c = mj_raw(s);
if (c < 0) { s->over = 1; break; }
if (c == 0xFF) continue; /* 填充 FF */
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;
rst_done:
;
}
/* 区间边界处理(三处扫描循环共用)。返回:1 = 继续;2 = 刚做过区间重同步(AC 需清 EOBRUN)。
*
* 只在 total == rst(编码器的区间长度)时重同步。**不能**一看到 over&&RSTn 就重同步:
* 熵码位缓冲有最多 9 位前瞻,`over` 会在区间/扫描真正结束前 1~2 个 MCU 就被置位,
* 此时 bitbuf 里还压着剩余块的有效比特——提前重同步会把它们丢掉并失步
* (渐进式 DC 扫描每块仅 ~1 bit,前瞻能跨过整个 MCU;这是 rst=2..200 花屏的根因)。
* 同理,over 且 marker 非 RSTn(扫描结束后的 DHT/EOI 已被前瞻吃掉)时**也不能退出**:
* 扫描循环本来就知道确定的 MCU/块数,把剩余的解完即可(位耗尽后 mj_eb 返回 0 做零填充),
* marker 交给外层主循环处理。曾在此 `return 0` 提前退出,丢掉最后一个 MCU,
* 平面 memset(128) 的灰底直接露出来(低熵/纯白渐进式右下角 3 像素 #808080)。 */
static int mj_boundary(mj_img *img, int *total)
{
mj_src *s = &img->src;
(void)s;
if (img->rst && *total == img->rst) {
mj_restart(img);
*total = 0;
return 2;
}
return 1;
}
typedef struct {
int ns;
int ci[4];
int td[4], ta[4];
int ss, se, ah, al;
} mj_scan;
/* 顺序(baseline / SOF1)扫描 */
static void mj_scan_seq(mj_img *img, const mj_scan *sc)
{
int single = (sc->ns == 1);
int mcus_x = single ? img->c[sc->ci[0]].ow : img->mcus_x;
int mcus_y = single ? img->c[sc->ci[0]].oh : img->mcus_y;
int mx, my, total = 0;
for (my = 0; my < mcus_y; my++) {
for (mx = 0; mx < mcus_x; mx++) {
int j;
if (!mj_boundary(img, &total)) return;
if (single) {
mj_comp *c = &img->c[sc->ci[0]];
c->td = sc->td[0]; c->ta = sc->ta[0];
mj_block_seq(img, c, mx, my);
} else {
for (j = 0; j < sc->ns; j++) {
mj_comp *c = &img->c[sc->ci[j]];
int by, bx;
c->td = sc->td[j]; c->ta = sc->ta[j];
for (by = 0; by < c->v; by++)
for (bx = 0; bx < c->h; bx++)
mj_block_seq(img, c, mx * c->h + bx, my * c->v + by);
}
}
total++;
}
}
}
/* 渐进式 DC 扫描(Ah=0) */
static void mj_scan_dc_prog(mj_img *img, const mj_scan *sc)
{
int single = (sc->ns == 1);
int mcus_x = single ? img->c[sc->ci[0]].ow : img->mcus_x;
int mcus_y = single ? img->c[sc->ci[0]].oh : img->mcus_y;
int mx, my, total = 0;
for (my = 0; my < mcus_y; my++) {
for (mx = 0; mx < mcus_x; mx++) {
int j;
if (!mj_boundary(img, &total)) {
return;
}
if (single) {
mj_comp *c = &img->c[sc->ci[0]];
c->td = sc->td[0];
mj_block_dc_prog(img, c, mx, my, sc->al);
} else {
for (j = 0; j < sc->ns; j++) {
mj_comp *c = &img->c[sc->ci[j]];
int by, bx;
c->td = sc->td[j];
for (by = 0; by < c->v; by++)
for (bx = 0; bx < c->h; bx++)
mj_block_dc_prog(img, c, mx * c->h + bx, my * c->v + by, sc->al);
}
}
total++;
}
}
}
/* 渐进式 AC 扫描(Ah=0,单分量) */
static void mj_scan_ac_prog(mj_img *img, const mj_scan *sc)
{
mj_comp *c = &img->c[sc->ci[0]];
mj_src *s = &img->src;
int eobrun = 0, bx, by, total = 0;
c->ta = sc->ta[0];
for (by = 0; by < c->oh; by++) {
for (bx = 0; bx < c->ow; bx++) {
int rb = mj_boundary(img, &total);
if (!rb) return;
if (rb == 2) eobrun = 0; /* 区间重同步:EOB 游程不能跨 RST */
if (eobrun > 0) {
eobrun--;
} else {
int k = sc->ss;
while (k <= sc->se) {
int rs = mj_hdecode(s, &img->ac[c->ta]);
int r = rs >> 4, sz = rs & 15;
if (sz == 0) {
if (r < 15) {
eobrun = (1 << r) - 1;
if (r) eobrun += mj_getbits(s, r);
break;
}
k += 16;
continue;
}
k += r;
if (k > sc->se) break;
{
int v = mj_extend(mj_getbits(s, sz), sz);
int nk = mj_zigzag[k];
int32_t sv;
if (v > 1023) v = 1023;
if (v < -1023) v = -1023;
sv = (int32_t)v * img->qt[c->tq][nk];
if (sc->al > 6) sv <<= 6; else sv <<= sc->al; /* Al 上限防御,防 int32 溢出 */
mj_accum(c, bx, by, nk, sv);
}
k++;
}
}
total++;
img->blocks++;
/* 这里**不能**再 `if (s->over) return`:块末的位前瞻可能已吃掉本区间的 RSTn
* (over=1),必须留给下一轮 mj_boundary 做区间重同步,否则渐进式 + restart 的
* AC 扫描会在第一个区间边界处静默退出、把后续 RST 当独立 marker 跳过 → 失步。 */
}
}
}
/* ------------------------------------------------------------------ */
/* 输出:分量平面 → 目标尺寸 RGBA(最近邻,与 meow_img_downscale 同口径)*/
/* ------------------------------------------------------------------ */
#define MJ_FIX_R 5743 /* 1.402 * 4096 */
#define MJ_FIX_G 1410 /* 0.34414 * 4096 */
#define MJ_FIX_G2 2925 /* 0.71414 * 4096 */
#define MJ_FIX_B 7258 /* 1.772 * 4096 */
static unsigned char *mj_output(mj_img *img, int tw, int th, int rgb_mode)
{
unsigned long long ob = (unsigned long long)tw * (unsigned long long)th * 4u;
unsigned char *out;
mj_comp *cy = &img->c[0];
mj_comp *cb = img->ncomp >= 3 ? &img->c[1] : NULL;
mj_comp *cr = img->ncomp >= 3 ? &img->c[2] : NULL;
const unsigned char *py = cy->plane;
const unsigned char *pb = cb ? cb->plane : NULL;
const unsigned char *pc = cr ? cr->plane : NULL;
int x, y;
if (ob == 0 || ob > (unsigned long long)(size_t)-1) return NULL;
out = (unsigned char *)malloc((size_t)ob);
if (!out) return NULL;
for (y = 0; y < th; y++) {
/* 64 位:ILP32 上 y*vh、x*vw 都可能越过 2^31(th/vh 各 3 万量级即可) */
long long syl = (long long)y * cy->vh / th;
int sy = (int)(syl < 0 ? 0 : (syl >= (long long)cy->vh ? (long long)cy->vh - 1 : syl));
const unsigned char *ry = py + (size_t)sy * cy->pw;
const unsigned char *rb = NULL, *rc = NULL;
if (pb) {
long long s = (long long)y * cb->vh / th;
if (s >= (long long)cb->vh) s = (long long)cb->vh - 1;
if (s < 0) s = 0;
rb = pb + (size_t)s * cb->pw;
}
if (pc) {
long long s = (long long)y * cr->vh / th;
if (s >= (long long)cr->vh) s = (long long)cr->vh - 1;
if (s < 0) s = 0;
rc = pc + (size_t)s * cr->pw;
}
unsigned char *dst = out + (size_t)((unsigned long long)y * (unsigned long long)tw * 4u);
for (x = 0; x < tw; x++) {
long long yxl = (long long)x * cy->vw / tw;
int yv;
if (yxl < 0) yxl = 0;
if (yxl >= (long long)cy->vw) yxl = (long long)cy->vw - 1;
yv = ry[(int)yxl];
unsigned char *o = dst + (size_t)x * 4;
if (!rb) {
o[0] = o[1] = o[2] = (unsigned char)yv;
o[3] = 255;
} else if (rgb_mode) {
long long ux = (long long)x * cb->vw / tw;
long long vx = (long long)x * cr->vw / tw;
if (ux < 0) ux = 0;
if (ux >= (long long)cb->vw) ux = (long long)cb->vw - 1;
if (vx < 0) vx = 0;
if (vx >= (long long)cr->vw) vx = (long long)cr->vw - 1;
o[0] = (unsigned char)yv;
o[1] = rb[(int)ux];
o[2] = rc[(int)vx];
o[3] = 255;
} else {
long long ux = (long long)x * cb->vw / tw;
long long vx = (long long)x * cr->vw / tw;
int u, v, r, g, b;
if (ux < 0) ux = 0;
if (ux >= (long long)cb->vw) ux = (long long)cb->vw - 1;
if (vx < 0) vx = 0;
if (vx >= (long long)cr->vw) vx = (long long)cr->vw - 1;
u = (int)rb[(int)ux] - 128;
v = (int)rc[(int)vx] - 128;
r = yv + ((MJ_FIX_R * v) >> 12);
g = yv - ((MJ_FIX_G * u + MJ_FIX_G2 * v) >> 12);
b = yv + ((MJ_FIX_B * u) >> 12);
o[0] = (unsigned char)(r < 0 ? 0 : (r > 255 ? 255 : r));
o[1] = (unsigned char)(g < 0 ? 0 : (g > 255 ? 255 : g));
o[2] = (unsigned char)(b < 0 ? 0 : (b > 255 ? 255 : b));
o[3] = 255;
}
}
}
return out;
}
/* ------------------------------------------------------------------ */
/* 主解析循环 */
/* ------------------------------------------------------------------ */
static int mj_decode(mj_img *img, int maxdim, int *tw, int *th, unsigned char **rgba)
{
mj_src *s = &img->src;
int have_sof = 0, rgb_mode = 0;
int pending = -1; /* 熵段结束时被位读取器吃掉的 marker(回填给主循环) */
if (mj_u16(s) != 0xFFD8) return 0; /* SOI */
for (;;) {
int code, len;
if (pending >= 0) {
code = pending;
pending = -1;
} else {
/* 找下一个 marker(跳过 FF 填充字节) */
do { code = mj_raw(s); if (code < 0) return 0; } while (code != 0xFF);
do { code = mj_raw(s); if (code < 0) return 0; } while (code == 0xFF);
}
if (code == 0xD9) break; /* EOI */
if (code == 0xD8 || code == 0x01 || (code >= 0xD0 && code <= 0xD7)) continue;
len = mj_u16(s);
if (len < 2) return 0;
len -= 2;
if (code == 0xDA) {
int ns, i;
mj_scan sc;
unsigned char hdr[64];
if (len > (int)sizeof hdr) return 0;
if (!mj_read(s, hdr, (size_t)len)) return 0;
if (len < 1) return 0;
ns = hdr[0];
if (ns < 1 || ns > 3 || len < 1 + ns * 2 + 3) return 0;
memset(&sc, 0, sizeof sc);
sc.ns = ns;
for (i = 0; i < ns; i++) {
int cid = hdr[1 + i * 2], tt = hdr[2 + i * 2], j, found = -1;
for (j = 0; j < img->ncomp; j++)
if (img->c[j].id == cid) { found = j; break; }
if (found < 0) return 0;
sc.ci[i] = found;
sc.td[i] = tt >> 4;
sc.ta[i] = tt & 15;
if (sc.td[i] > 3 || sc.ta[i] > 3) return 0;
}
sc.ss = hdr[1 + ns * 2];
sc.se = hdr[2 + ns * 2];
sc.ah = hdr[3 + ns * 2] >> 4;
sc.al = hdr[3 + ns * 2] & 15;
if (!have_sof) return 0;
if (sc.ss < 0 || sc.se > 63 || sc.ss > sc.se || sc.al > 13) return 0;
for (i = 0; i < ns; i++) {
mj_comp *c = &img->c[sc.ci[i]];
if (!img->have_qt[c->tq]) return 0;
if (sc.ss == 0 && !img->have_dc[sc.td[i]]) return 0;
if (sc.se > 0 && !img->have_ac[sc.ta[i]]) return 0;
}
if (img->progressive && sc.ah > 0) {
/* 精细化扫描:跳过(不解码),继续找后续 Ah=0 扫描。低频带首次扫描到齐
* 就够出一张清晰缩略图;整幅系数的 successive-approximation 缓存正是
* 渐进式峰值失控的根因,这里根本不留。 */
mj_skip_entropy(img);
s->bitcnt = 0;
s->bitbuf = 0;
if (s->over) {
if (s->marker) pending = s->marker;
s->marker = 0;
s->over = 0;
}
continue;
}
{
if (!img->progressive) {
mj_scan_seq(img, &sc);
} else if (sc.ss == 0) {
mj_scan_dc_prog(img, &sc);
} else {
if (sc.ns != 1) return 0;
mj_scan_ac_prog(img, &sc);
}
}
s->bitcnt = 0;
s->bitbuf = 0;
/* 一个熵段结束:位读取器可能已把下一个 marker 吃掉,回填给主循环,
* 否则紧跟其后的 DHT/DQT/SOS 会被漏掉。 */
if (s->over) {
if (s->marker) pending = s->marker;
s->marker = 0;
s->over = 0;
}
continue;
}
if (len > 0 && code != 0xDA) {
/* APPn/COM 只跳过(APP14 取 Adobe transform,很短);其它段读进 2KB 栈缓冲。
* 不在 marker 循环里 malloc:EXIF 缩略图/ICC 这类段可能有几百 KB,会把解码峰值抬上去。 */
if (code >= 0xE0 && code <= 0xEF) {
if (code == 0xEE && len >= 12 && len <= 64) {
unsigned char ad[64];
if (!mj_read(s, ad, (size_t)len)) return 0;
if (memcmp(ad, "Adobe", 5) == 0) img->adobe_transform = ad[11];
} else if (!mj_skip(s, len)) {
return 0;
}
continue;
}
if (code == 0xFE) { /* COM */
if (!mj_skip(s, len)) return 0;
continue;
}
{
unsigned char pbuf[2048];
if (len > (int)sizeof pbuf) return 0; /* DQT/DHT/SOF 不可能这么长 */
if (!mj_read(s, pbuf, (size_t)len)) return 0;
switch (code) {
case 0xC0: case 0xC1: case 0xC2:
if (have_sof) return 0;
if (!mj_sof(img, code, pbuf, len, maxdim)) return 0;
have_sof = 1;
break;
case 0xC4:
if (!mj_dht(img, pbuf, len)) return 0;
break;
case 0xDB:
if (!mj_dqt(img, pbuf, len)) return 0;
break;
case 0xDD:
if (len >= 2) img->rst = (pbuf[0] << 8) | pbuf[1];
break;
default:
break;
}
}
continue;
}
if (len > 0) {
if (!mj_skip(s, len)) return 0;
}
}
if (!have_sof || img->blocks <= 0) return 0;
/* 色彩空间:3 分量默认 YCbCr;Adobe transform=0 或分量 ID 为 'R','G','B' 时按 RGB。 */
if (img->ncomp == 3) {
if (img->adobe_transform == 0) rgb_mode = 1;
if (img->c[0].id == 'R' && img->c[1].id == 'G' && img->c[2].id == 'B') rgb_mode = 1;
}
meow_img_target_dims(img->width, img->height, maxdim, tw, th);
*rgba = mj_output(img, *tw, *th, rgb_mode);
return *rgba ? 1 : 0;
}
/* ================================================================== */
/* 后端分派 */
/* ================================================================== */
static int mj_is_jpeg(const unsigned char *p, size_t n)
{
return n >= 3 && p[0] == 0xFF && p[1] == 0xD8 && p[2] == 0xFF;
}
int meow_img_backend_mem(const void *buf, size_t len, int maxdim, int *tw, int *th, unsigned char **rgba)
{
const unsigned char *p = (const unsigned char *)buf;
if (!buf || len < 8 || maxdim <= 0) return 0;
if (mj_is_jpeg(p, len)) {
mj_img img;
int ok;
memset(&img, 0, sizeof img);
img.adobe_transform = -1;
img.src.mem = p;
img.src.memlen = len;
ok = mj_decode(&img, maxdim, tw, th, rgba);
mj_free(&img);
return ok;
}
if (p[0] == 0x89 && p[1] == 'P' && p[2] == 'N' && p[3] == 'G')
return meow_png_decode_stream(NULL, p, len, maxdim, tw, th, rgba);
if (p[0] == 'B' && p[1] == 'M')
return meow_bmp_decode_stream(NULL, p, len, maxdim, tw, th, rgba);
return 0;
}
int meow_img_backend_file(const char *path, int maxdim, int *tw, int *th, unsigned char **rgba)
{
FILE *f;
unsigned char sig[8];
size_t n;
int ok = 0;
if (!path || maxdim <= 0) return 0;
f = fopen(path, "rb");
if (!f) return 0;
n = fread(sig, 1, sizeof sig, f);
if (n < 4 || fseek(f, 0, SEEK_SET) != 0) { fclose(f); return 0; }
if (mj_is_jpeg(sig, n)) {
mj_img img;
memset(&img, 0, sizeof img);
img.adobe_transform = -1;
img.src.f = f;
ok = mj_decode(&img, maxdim, tw, th, rgba);
mj_free(&img);
} else if (sig[0] == 0x89 && sig[1] == 'P' && sig[2] == 'N' && sig[3] == 'G') {
ok = meow_png_decode_stream(f, NULL, 0, maxdim, tw, th, rgba);
} else if (sig[0] == 'B' && sig[1] == 'M') {
ok = meow_bmp_decode_stream(f, NULL, 0, maxdim, tw, th, rgba);
}
fclose(f);
return ok;
}