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。峰值不变(修复不增加分配)。
1006 lines
38 KiB
C
1006 lines
38 KiB
C
/*
|
||
* 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;
|
||
}
|