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

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

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

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

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

1006 lines
38 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)h * maxdim / w);
} else {
*th = maxdim;
*tw = (int)((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)
{
int n = (8 * maxdim + longside - 1) / longside;
if (n < 1) n = 1;
if (n > 7) return 0;
return n;
}
/* 按各分量当前的 n 计算块网格与平面尺寸,返回平面总字节。 */
static unsigned mj_layout(mj_img *img)
{
unsigned 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);
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;
total += (unsigned)c->pw * (unsigned)c->ph;
}
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 int 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);
target_bytes = (unsigned)tw * (unsigned)th * 4u;
for (i = 0; i < n; i++) img->c[i].n = base_n;
plane_bytes = mj_layout(img);
/* 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);
}
/* 预算 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);
}
/* 最后一道防线:异常巨图整体降档(最多降到 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);
}
/* 硬闸:降到 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];
size_t bytes = (size_t)c->pw * (size_t)c->ph;
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 char *out = (unsigned char *)malloc((size_t)tw * th * 4);
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 (!out) return NULL;
for (y = 0; y < th; y++) {
int sy = (int)((long)y * cy->vh / th);
const unsigned char *ry = py + (size_t)sy * cy->pw;
const unsigned char *rb = pb ? pb + (size_t)((long)y * cb->vh / th) * cb->pw : NULL;
const unsigned char *rc = pc ? pc + (size_t)((long)y * cr->vh / th) * cr->pw : NULL;
unsigned char *dst = out + (size_t)y * tw * 4;
for (x = 0; x < tw; x++) {
int yv = ry[(int)((long)x * cy->vw / tw)];
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) {
o[0] = (unsigned char)yv;
o[1] = rb[(int)((long)x * cb->vw / tw)];
o[2] = rc[(int)((long)x * cr->vw / tw)];
o[3] = 255;
} else {
int u = (int)rb[(int)((long)x * cb->vw / tw)] - 128;
int v = (int)rc[(int)((long)x * cr->vw / tw)] - 128;
int r = yv + ((MJ_FIX_R * v) >> 12);
int g = yv - ((MJ_FIX_G * u + MJ_FIX_G2 * v) >> 12);
int 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;
}