SDK: 图片解码真缩解后端 —— JPEG DCT 域 / PNG 逐行 / BMP 跳行(M23)

stb 的 stbi_load_scaled 是「先整幅解、再重采样」:2000x3000 baseline 峰值 10.07MB、
渐进式 25.84MB、1600x2400 PNG 25.65MB,真机解码余量只有 ~4.2MB。新增解码后端在
解码期只算目标分辨率,峰值只与输出尺寸相关:

- meow_jpeg.c:自写纯 C JPEG 解码(baseline + progressive,Huffman)。按
  ceil(8*maxdim/长边) 选 N 点 DCT 缩解(N=1..7),基函数为「8 点 IDCT 按像素区间
  盒式平均」的 Q12 定点(DC 恒为 F00/8,N 之间亮度一致);系数边解边累加进分量平面,
  不留整幅系数阵列;渐进式跳过精细化扫描(Ah>0)、只解各频带首次扫描 —— 渐进式与
  baseline 内存足迹相同(这是 libjpeg 系列做不到的:它对渐进式必须整幅驻留 JBLOCK)。
  不支持项(4 分量 CMYK/YCCK、算术/无损/分层、12bit 精度)返回 0 回退 stb。
- meow_png_stream.c:libz 逐行 inflate + 反滤波,只把需要的列写进目标行;
  gray/gray+A/RGB/RGBA/调色板,1/2/4/8/16bit(16→8 取高字节);Adam7 等回退 stb。
  BMP 按目标行 seek + 行内跳列(8bpp 调色板/16bpp RGB555/24/32bpp),不读全图。
- meow_ui_img.c:load_file_dim / load_mem_dim 先进后端,失败/不支持回落 stb 旧路径
  (stbi_load_scaled + 精修);输出尺寸与 meow_img_downscale 同公式,对外语义不变。

宿主实测(--wrap malloc,maxdim=720,M23 前后同一夹具):
  2000x3000 baseline     10.07 → 1.86 MiB
  2000x3000 progressive  25.84 → 1.86 MiB
  1600x2400 PNG          25.65 → 1.33 MiB
  1600x2400 BMP          15.97 → 1.32 MiB
  4MP/8MP/12MP JPEG       9.67/13.37/20.12 → 2.30/2.20/2.29 MiB(不随分辨率线性增长)

sdk/test_meow_img_stream.c:峰值断言(≤2.5MiB)、与 stb 整幅+降采样的像素比对
(平均差 4.4-13.7/255,块均值结构相关 ≥0.88)、内存入口逐字节一致、回退矩阵
(CMYK/1bpp BMP/Adam7 PNG)、restart marker、缩解阶扫描 N=4/5/7、坏图容错。
build_sdk.sh 增加 meow_jpeg / meow_png_stream 两个 TU(MIPS32 -Os 零警告)。
体积:目标码 +24.9KB(meow_ui.a +25.5KB),picbrowser 静态二进制 +18.2KB。
This commit is contained in:
AskaEth committed 2026-11-05 21:33:54 +08:00
1 parent 88f3d861b0
commit d84dd949c7
8 files changed
+2320 -15

No files matched your search

+974
View File
@@ -0,0 +1,974 @@
/*
* 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;
static void 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;
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]);
for (j = 0; j < (1 << shift); j++)
h->fast[(code << shift) | j] = e;
}
code++;
k++;
}
h->maxcode[l] = h->bits[l] ? code - 1 : -1;
code <<= 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;
if (pq == 0) {
if (p + 64 > len) return 0;
for (i = 0; i < 64; i++) img->qt[tq][mj_zigzag[i]] = d[p + i];
p += 64;
} else {
if (p + 128 > len) return 0;
for (i = 0; i < 64; i++)
img->qt[tq][mj_zigzag[i]] = (d[p + 2 * i] << 8) | d[p + 2 * i + 1];
p += 128;
}
img->have_qt[tq] = 1;
}
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;
mj_huff_build(h);
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);
}
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 预测。 */
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;
do { c = mj_raw(s); if (c < 0) { s->over = 1; break; } } while (c == 0xFF);
if (c < 0) break;
if (c >= 0xD0 && c <= 0xD7) break;
s->marker = c; s->over = 1;
break;
}
}
for (i = 0; i < img->ncomp; i++) img->c[i].pred = 0;
}
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 (img->rst && total == img->rst) {
if (img->src.over && !(img->src.marker >= 0xD0 && img->src.marker <= 0xD7)) return;
mj_restart(img);
total = 0;
}
if (img->src.over) return;
if (single) {
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 (img->rst && total == img->rst) {
if (img->src.over && !(img->src.marker >= 0xD0 && img->src.marker <= 0xD7)) return;
mj_restart(img);
total = 0;
}
if (img->src.over) return;
if (single) {
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++) {
if (img->rst && total == img->rst) {
if (img->src.over && !(img->src.marker >= 0xD0 && img->src.marker <= 0xD7)) return;
mj_restart(img);
total = 0;
eobrun = 0;
}
if (s->over) return;
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;
}
}
}
/* ------------------------------------------------------------------ */
/* 输出:分量平面 → 目标尺寸 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;
}