Files
MeowOS-SDK/sdk/meow_jpeg.c
T
AskaEth d84dd949c7 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。
2026-11-05 21:33:54 +08:00

975 lines
36 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;
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;
}