/* * 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 #include #include #include /* ================================================================== */ /* 公共:目标尺寸(与 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; }