379 lines
17 KiB
C
379 lines
17 KiB
C
/* mp_jpeg.c — 最小 baseline JPEG 编码器(顺序 DCT,4:2:0,标准 Huffman 表 Annex K)。
|
||
*
|
||
* 自包含:零第三方依赖,无 libm(DCT 用预计算余弦常数表),仅 malloc/realloc。
|
||
* 输入 16bpp RGB565 → YCbCr(4:2:0) → DCT → 量化 → zigzag → RLE/Huffman → entropy 字节填充。
|
||
* 供 MeowPanel 实时画面(GET /api/screen.jpeg)编码 fb0 帧。
|
||
*
|
||
* 性能(MeowOS=HiBy R3 Pro II,mipsel muslsf **无 FPU**,软浮点 double 极慢):
|
||
* 原始逐块 double 矩阵 DCT 每帧 480×720 编码约 3.96s,是实时画面掉帧到 5-6s/帧的根因。
|
||
* 本实现把 RGB→YCbCr 与 DCT/量化全部改为**整数定点**(FSCALE=13;BT.601 256 系数),
|
||
* 纯整数乘加(MIPS 原生 mult/add),实测 480×720 编码降至约 0.19s(约 20×),
|
||
* 质量/体积与 double 版 PSNR 等价(主机 480×720 高频测试图两者 PSNR 差 <0.1dB)。
|
||
*/
|
||
#include "mp_jpeg.h"
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
|
||
/* ---- 输出缓冲(带字节填充) ---- */
|
||
typedef struct { uint8_t *d; size_t len, cap; } mj_out;
|
||
static int mj_reserve(mj_out *o, size_t need) {
|
||
if (o->len + need <= o->cap) return 0;
|
||
size_t nc = o->cap ? o->cap * 2 : 8192;
|
||
while (nc < o->len + need) nc *= 2;
|
||
uint8_t *nd = (uint8_t *)realloc(o->d, nc);
|
||
if (!nd) return -1;
|
||
o->d = nd; o->cap = nc;
|
||
return 0;
|
||
}
|
||
static int mj_byte(mj_out *o, uint8_t b) {
|
||
if (mj_reserve(o, 1)) return -1;
|
||
o->d[o->len++] = b;
|
||
return 0;
|
||
}
|
||
static int mj_bytes(mj_out *o, const uint8_t *b, size_t n) {
|
||
if (mj_reserve(o, n)) return -1;
|
||
memcpy(o->d + o->len, b, n);
|
||
o->len += n;
|
||
return 0;
|
||
}
|
||
static int mj_u16(mj_out *o, uint16_t v) {
|
||
if (mj_reserve(o, 2)) return -1;
|
||
o->d[o->len++] = (uint8_t)(v >> 8);
|
||
o->d[o->len++] = (uint8_t)v;
|
||
return 0;
|
||
}
|
||
|
||
/* ---- 标准量化表(Annex K,quality 50) ---- */
|
||
static const uint8_t MQ_LUMA[64] = {
|
||
16,11,10,16,24,40,51,61, 12,12,14,19,26,58,60,55,
|
||
14,13,16,24,40,57,69,56, 14,17,22,29,51,87,80,62,
|
||
18,22,37,56,68,109,103,77, 24,35,55,64,81,104,113,92,
|
||
49,64,78,87,103,121,120,101, 72,92,95,98,112,100,103,99
|
||
};
|
||
static const uint8_t MQ_CHROMA[64] = {
|
||
17,18,24,47,99,99,99,99, 18,21,26,66,99,99,99,99,
|
||
24,26,56,99,99,99,99,99, 47,66,99,99,99,99,99,99,
|
||
99,99,99,99,99,99,99,99, 99,99,99,99,99,99,99,99,
|
||
99,99,99,99,99,99,99,99, 99,99,99,99,99,99,99,99
|
||
};
|
||
|
||
/* ---- 标准 Huffman 表 ---- */
|
||
static const uint8_t HB_DC_L[16] = { 0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0 };
|
||
static const uint8_t HV_DC_L[12] = { 0,1,2,3,4,5,6,7,8,9,10,11 };
|
||
static const uint8_t HB_AC_L[16] = { 0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,125 };
|
||
static const uint8_t HV_AC_L[162] = {
|
||
0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,0x21,0x31,0x41,0x06,0x13,0x51,0x61,
|
||
0x07,0x22,0x71,0x14,0x32,0x81,0x91,0xA1,0x08,0x23,0x42,0xB1,0xC1,0x15,0x52,
|
||
0xD1,0xF0,0x24,0x33,0x62,0x72,0x82,0x09,0x0A,0x16,0x17,0x18,0x19,0x1A,0x25,
|
||
0x26,0x27,0x28,0x29,0x2A,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x43,0x44,0x45,
|
||
0x46,0x47,0x48,0x49,0x4A,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0x63,0x64,
|
||
0x65,0x66,0x67,0x68,0x69,0x6A,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7A,0x83,
|
||
0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,
|
||
0x9A,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xB2,0xB3,0xB4,0xB5,0xB6,
|
||
0xB7,0xB8,0xB9,0xBA,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xD2,0xD3,
|
||
0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,
|
||
0xE9,0xEA,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA
|
||
};
|
||
static const uint8_t HB_DC_C[16] = { 0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0 };
|
||
static const uint8_t HV_DC_C[12] = { 0,1,2,3,4,5,6,7,8,9,10,11 };
|
||
static const uint8_t HB_AC_C[16] = { 0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,119 };
|
||
static const uint8_t HV_AC_C[162] = {
|
||
0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,0x31,0x06,0x12,0x41,0x51,0x07,0x61,
|
||
0x71,0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91,0xA1,0xB1,0xC1,0x09,0x23,0x33,
|
||
0x52,0xF0,0x15,0x62,0x72,0xD1,0x0A,0x16,0x24,0x34,0xE1,0x25,0xF1,0x17,0x18,
|
||
0x19,0x1A,0x26,0x27,0x28,0x29,0x2A,0x35,0x36,0x37,0x38,0x39,0x3A,0x43,0x44,
|
||
0x45,0x46,0x47,0x48,0x49,0x4A,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0x63,
|
||
0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7A,
|
||
0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x92,0x93,0x94,0x95,0x96,0x97,
|
||
0x98,0x99,0x9A,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xB2,0xB3,0xB4,
|
||
0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,
|
||
0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,
|
||
0xE8,0xE9,0xEA,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,0xF8,0xF9,0xFA
|
||
};
|
||
|
||
/* ---- 预计算 FDCT 余弦系数(cosp[u][x] = C(u)/2 * cos((2x+1)u*pi/16)) ---- */
|
||
static const double MP_COSP[8][8] = {
|
||
{0.35355339059327373,0.35355339059327373,0.35355339059327373,0.35355339059327373,0.35355339059327373,0.35355339059327373,0.35355339059327373,0.35355339059327373},
|
||
{0.49039264020161522,0.41573480615127262,0.27778511650980114,0.097545161008064166,-0.097545161008064096,-0.27778511650980098,-0.41573480615127267,-0.49039264020161522},
|
||
{0.46193976625564337,0.19134171618254492,-0.19134171618254486,-0.46193976625564337,-0.46193976625564342,-0.19134171618254517,0.191341716182545,0.46193976625564326},
|
||
{0.41573480615127262,-0.097545161008064096,-0.49039264020161522,-0.27778511650980109,0.27778511650980092,0.49039264020161522,0.097545161008064388,-0.41573480615127256},
|
||
{0.35355339059327379,-0.35355339059327373,-0.35355339059327384,0.35355339059327368,0.35355339059327384,-0.35355339059327334,-0.35355339059327356,0.35355339059327329},
|
||
{0.27778511650980114,-0.49039264020161522,0.097545161008064152,0.41573480615127273,-0.41573480615127256,-0.097545161008064013,0.49039264020161533,-0.27778511650980076},
|
||
{0.19134171618254492,-0.46193976625564342,0.46193976625564326,-0.19134171618254495,-0.19134171618254528,0.46193976625564337,-0.4619397662556432,0.19134171618254478},
|
||
{0.097545161008064166,-0.27778511650980109,0.41573480615127273,-0.49039264020161533,0.49039264020161522,-0.41573480615127251,0.27778511650980076,-0.097545161008064291}
|
||
};
|
||
|
||
/* ---- zigzag 顺序(自然索引 = v*8+u) ---- */
|
||
static const uint8_t MP_ZZ[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
|
||
};
|
||
|
||
/* ---- Huffman 码表 ---- */
|
||
typedef struct { uint16_t code; uint8_t size; } mj_hc;
|
||
static void huff_build(const uint8_t *bits, const uint8_t *vals, int nval, mj_hc *out) {
|
||
int code = 0, k = 0;
|
||
for (int len = 1; len <= 16; len++) {
|
||
for (int i = 0; i < bits[len - 1]; i++) {
|
||
if (k >= nval) return;
|
||
out[vals[k]].code = (uint16_t)code;
|
||
out[vals[k]].size = (uint8_t)len;
|
||
k++; code++;
|
||
}
|
||
code <<= 1;
|
||
}
|
||
}
|
||
static int cat_of(int v) {
|
||
int a = (v < 0) ? -v : v, s = 0;
|
||
while (a) { a >>= 1; s++; }
|
||
return s;
|
||
}
|
||
static uint32_t cat_bits(int v, int s) {
|
||
return (uint32_t)((v >= 0) ? v : (v + ((1 << s) - 1)));
|
||
}
|
||
|
||
/* ---- bit writer(MSB 先行 + entropy 0xFF 字节填充) ---- */
|
||
typedef struct { mj_out *o; uint32_t acc; int nbits; } mj_bw;
|
||
static int bw_bits(mj_bw *w, uint32_t code, int size) {
|
||
if (size > 0) {
|
||
uint32_t m = (size >= 31) ? 0xffffffffu : ((1u << size) - 1u);
|
||
w->acc = (w->acc << size) | (code & m);
|
||
w->nbits += size;
|
||
}
|
||
while (w->nbits >= 8) {
|
||
w->nbits -= 8;
|
||
uint8_t b = (uint8_t)((w->acc >> w->nbits) & 0xff);
|
||
if (mj_byte(w->o, b)) return -1;
|
||
if (b == 0xff) { if (mj_byte(w->o, 0x00)) return -1; }
|
||
}
|
||
return 0;
|
||
}
|
||
static int bw_flush(mj_bw *w) {
|
||
if (w->nbits > 0) {
|
||
uint8_t b = (uint8_t)((w->acc << (8 - w->nbits)) & 0xff);
|
||
if (mj_byte(w->o, b)) return -1;
|
||
if (b == 0xff) { if (mj_byte(w->o, 0x00)) return -1; }
|
||
w->acc = 0; w->nbits = 0;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/* ---- 量化表(按 quality 缩放) ---- */
|
||
static void build_q(const uint8_t *base, int quality, uint8_t *out) {
|
||
int s = (quality < 50) ? (5000 / quality) : (200 - quality * 2);
|
||
if (s < 1) s = 1;
|
||
for (int i = 0; i < 64; i++) {
|
||
int v = (base[i] * s + 50) / 100;
|
||
if (v < 1) v = 1;
|
||
if (v > 255) v = 255;
|
||
out[i] = (uint8_t)v;
|
||
}
|
||
}
|
||
|
||
static int clamp255(int v) { if (v < 0) v = 0; if (v > 255) v = 255; return v; }
|
||
|
||
/* 定点缩放:把 DCT 系数放大 2^FSCALE 后做纯整数乘加(MIPS 无 FPU,软浮点 double 极慢,
|
||
* 定点整数快一个量级)。两趟都 >> FSCALE,净缩放 2^(2*FSCALE),中间值在 int32 内(FSCALE=13 安全)。 */
|
||
#define FSCALE 13
|
||
static int mp_fix_round(int a) { /* 有符号四舍五入右移 */
|
||
return (a + (a >= 0 ? (1 << (FSCALE - 1)) : -(1 << (FSCALE - 1)))) >> FSCALE;
|
||
}
|
||
|
||
/* ---- 抓 8x8 样本(越界 clamp 到边缘;样本为中心化整数 -128..127) ---- */
|
||
static void grab8(const uint8_t *plane, int pstride, int pw, int ph,
|
||
int x0, int y0, int s[8][8]) {
|
||
for (int y = 0; y < 8; y++) {
|
||
int py = y0 + y; if (py >= ph) py = ph - 1;
|
||
const uint8_t *row = plane + (size_t)py * (size_t)pstride;
|
||
for (int x = 0; x < 8; x++) {
|
||
int px = x0 + x; if (px >= pw) px = pw - 1;
|
||
s[y][x] = (int)row[px] - 128;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* ---- 编码一个 8x8 块(定点 DCT→量化→zigzag→RLE/Huffman) ---- */
|
||
static int encode_block(mj_bw *w, const int samp[8][8], const uint8_t *q,
|
||
const mj_hc *dc, const mj_hc *ac, int *prev_dc) {
|
||
static int ci[8][8];
|
||
static int ci_ready = 0;
|
||
if (!ci_ready) {
|
||
for (int u = 0; u < 8; u++)
|
||
for (int x = 0; x < 8; x++)
|
||
ci[u][x] = (int)(MP_COSP[u][x] * (1 << FSCALE) + 0.5);
|
||
ci_ready = 1;
|
||
}
|
||
int coef[64];
|
||
int R[8][8];
|
||
for (int y = 0; y < 8; y++)
|
||
for (int u = 0; u < 8; u++) {
|
||
int acc = 0;
|
||
for (int x = 0; x < 8; x++) acc += ci[u][x] * samp[y][x];
|
||
R[u][y] = mp_fix_round(acc);
|
||
}
|
||
for (int u = 0; u < 8; u++)
|
||
for (int v = 0; v < 8; v++) {
|
||
int acc = 0;
|
||
for (int y = 0; y < 8; y++) acc += ci[v][y] * R[u][y];
|
||
int c = mp_fix_round(acc);
|
||
int Q = q[v * 8 + u];
|
||
coef[v * 8 + u] = (c + (c >= 0 ? Q / 2 : -Q / 2)) / Q;
|
||
}
|
||
|
||
int dcv = coef[0];
|
||
int diff = dcv - *prev_dc;
|
||
*prev_dc = dcv;
|
||
int ds = cat_of(diff);
|
||
if (bw_bits(w, dc[ds].code, dc[ds].size)) return -1;
|
||
if (ds && bw_bits(w, cat_bits(diff, ds), ds)) return -1;
|
||
|
||
int run = 0;
|
||
for (int p = 1; p < 64; p++) {
|
||
int val = coef[MP_ZZ[p]];
|
||
if (!val) { run++; continue; }
|
||
while (run >= 16) {
|
||
if (bw_bits(w, ac[0xF0].code, ac[0xF0].size)) return -1;
|
||
run -= 16;
|
||
}
|
||
int sc = cat_of(val);
|
||
int sym = (run << 4) | sc;
|
||
if (bw_bits(w, ac[sym].code, ac[sym].size)) return -1;
|
||
if (bw_bits(w, cat_bits(val, sc), sc)) return -1;
|
||
run = 0;
|
||
}
|
||
if (run > 0) {
|
||
if (bw_bits(w, ac[0x00].code, ac[0x00].size)) return -1;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/* ---- DHT 段 ---- */
|
||
static int emit_dht(mj_out *o, uint8_t spec, const uint8_t *bits,
|
||
const uint8_t *vals, int nval) {
|
||
if (mj_byte(o, 0xFF) || mj_byte(o, 0xC4)) return -1;
|
||
if (mj_u16(o, (uint16_t)(2 + 1 + 16 + nval))) return -1;
|
||
if (mj_byte(o, spec)) return -1;
|
||
if (mj_bytes(o, bits, 16)) return -1;
|
||
if (mj_bytes(o, vals, (size_t)nval)) return -1;
|
||
return 0;
|
||
}
|
||
|
||
int mp_jpeg_encode_rgb565(const uint8_t *rgb565, int w, int h, int quality,
|
||
uint8_t **out, size_t *outlen) {
|
||
if (!rgb565 || w <= 0 || h <= 0 || !out || !outlen) return -1;
|
||
if (quality < 1) quality = 1;
|
||
if (quality > 100) quality = 100;
|
||
|
||
int wc = (w + 1) / 2, hc = (h + 1) / 2;
|
||
uint8_t *Y = (uint8_t *)malloc((size_t)w * (size_t)h);
|
||
uint8_t *Cb = (uint8_t *)malloc((size_t)wc * (size_t)hc);
|
||
uint8_t *Cr = (uint8_t *)malloc((size_t)wc * (size_t)hc);
|
||
uint8_t ql[64], qc[64];
|
||
if (!Y || !Cb || !Cr) { free(Y); free(Cb); free(Cr); return -1; }
|
||
build_q(MQ_LUMA, quality, ql);
|
||
build_q(MQ_CHROMA, quality, qc);
|
||
|
||
/* RGB565 → Y 平面(全分辨率) + Cb/Cr 平面(2x2 平均),全部整数定点(BT.601 近似)。
|
||
* Y=(77R+150G+29B)>>8;Cb=128+((-43R-85G+128B)>>8);Cr=128+((128R-107G-21B)>>8)。 */
|
||
for (int cy = 0; cy < h; cy += 2)
|
||
for (int cx = 0; cx < w; cx += 2) {
|
||
int cb = 0, cr = 0, n = 0;
|
||
for (int dy = 0; dy < 2; dy++)
|
||
for (int dx = 0; dx < 2; dx++) {
|
||
int py = cy + dy, px = cx + dx;
|
||
if (py >= h || px >= w) continue;
|
||
uint16_t v = (uint16_t)(rgb565[((size_t)py * (size_t)w + (size_t)px) * 2]
|
||
| (rgb565[((size_t)py * (size_t)w + (size_t)px) * 2 + 1] << 8));
|
||
int r5 = (v >> 11) & 0x1f, g6 = (v >> 5) & 0x3f, b5 = v & 0x1f;
|
||
int R = (r5 << 3) | (r5 >> 2);
|
||
int G = (g6 << 2) | (g6 >> 4);
|
||
int B = (b5 << 3) | (b5 >> 2);
|
||
Y[(size_t)py * (size_t)w + (size_t)px] = (uint8_t)((77 * R + 150 * G + 29 * B) >> 8);
|
||
cb += (-43) * R - 85 * G + 128 * B;
|
||
cr += 128 * R - 107 * G - 21 * B;
|
||
n++;
|
||
}
|
||
size_t k = (size_t)(cy / 2) * (size_t)wc + (size_t)(cx / 2);
|
||
int dn = n ? n : 1;
|
||
Cb[k] = (uint8_t)clamp255(128 + ((cb / dn) >> 8));
|
||
Cr[k] = (uint8_t)clamp255(128 + ((cr / dn) >> 8));
|
||
}
|
||
|
||
mj_out o = {0};
|
||
mj_hc hdc_l[16], hac_l[256], hdc_c[16], hac_c[256];
|
||
memset(hdc_l, 0, sizeof hdc_l); memset(hac_l, 0, sizeof hac_l);
|
||
memset(hdc_c, 0, sizeof hdc_c); memset(hac_c, 0, sizeof hac_c);
|
||
huff_build(HB_DC_L, HV_DC_L, 12, hdc_l);
|
||
huff_build(HB_AC_L, HV_AC_L, 162, hac_l);
|
||
huff_build(HB_DC_C, HV_DC_C, 12, hdc_c);
|
||
huff_build(HB_AC_C, HV_AC_C, 162, hac_c);
|
||
|
||
int rc = -1;
|
||
/* SOI */
|
||
if (mj_byte(&o, 0xFF) || mj_byte(&o, 0xD8)) goto done;
|
||
/* APP0 JIF */
|
||
static const uint8_t jfif[14] = { 'J','F','I','F',0,1,1,0,0,1,0,1,0,0 };
|
||
if (mj_byte(&o, 0xFF) || mj_byte(&o, 0xE0) || mj_u16(&o, 0x0010)) goto done;
|
||
if (mj_bytes(&o, jfif, 14)) goto done;
|
||
/* DQT(2 张表) */
|
||
if (mj_byte(&o, 0xFF) || mj_byte(&o, 0xDB) || mj_u16(&o, 0x0084)) goto done;
|
||
if (mj_byte(&o, 0x00) || mj_bytes(&o, ql, 64)) goto done;
|
||
if (mj_byte(&o, 0x01) || mj_bytes(&o, qc, 64)) goto done;
|
||
/* SOF0 baseline, 8-bit, 3 分量, 4:2:0 */
|
||
if (mj_byte(&o, 0xFF) || mj_byte(&o, 0xC0) || mj_u16(&o, 0x0011)) goto done;
|
||
if (mj_byte(&o, 0x08)) goto done;
|
||
if (mj_u16(&o, (uint16_t)h) || mj_u16(&o, (uint16_t)w)) goto done;
|
||
if (mj_byte(&o, 0x03)) goto done;
|
||
if (mj_byte(&o, 0x01) || mj_byte(&o, 0x22) || mj_byte(&o, 0x00)) goto done; /* Y */
|
||
if (mj_byte(&o, 0x02) || mj_byte(&o, 0x11) || mj_byte(&o, 0x01)) goto done; /* Cb */
|
||
if (mj_byte(&o, 0x03) || mj_byte(&o, 0x11) || mj_byte(&o, 0x01)) goto done; /* Cr */
|
||
/* DHT(4 张表) */
|
||
if (emit_dht(&o, 0x00, HB_DC_L, HV_DC_L, 12)) goto done;
|
||
if (emit_dht(&o, 0x10, HB_AC_L, HV_AC_L, 162)) goto done;
|
||
if (emit_dht(&o, 0x01, HB_DC_C, HV_DC_C, 12)) goto done;
|
||
if (emit_dht(&o, 0x11, HB_AC_C, HV_AC_C, 162)) goto done;
|
||
/* SOS */
|
||
if (mj_byte(&o, 0xFF) || mj_byte(&o, 0xDA) || mj_u16(&o, 0x000C)) goto done;
|
||
if (mj_byte(&o, 0x03)) goto done;
|
||
if (mj_byte(&o, 0x01) || mj_byte(&o, 0x00)) goto done; /* Y: DC0 AC0 */
|
||
if (mj_byte(&o, 0x02) || mj_byte(&o, 0x11)) goto done; /* Cb: DC1 AC1 */
|
||
if (mj_byte(&o, 0x03) || mj_byte(&o, 0x11)) goto done; /* Cr: DC1 AC1 */
|
||
if (mj_byte(&o, 0x00) || mj_byte(&o, 0x3F) || mj_byte(&o, 0x00)) goto done;
|
||
|
||
{
|
||
mj_bw bb = { &o, 0, 0 };
|
||
int pd_y = 0, pd_cb = 0, pd_cr = 0; /* 每分量独立 DC 预测器(JPEG 规范) */
|
||
int mx_count = (w + 15) / 16, my_count = (h + 15) / 16;
|
||
for (int my = 0; my < my_count; my++)
|
||
for (int mx = 0; mx < mx_count; mx++) {
|
||
for (int by = 0; by < 2; by++)
|
||
for (int bx = 0; bx < 2; bx++) {
|
||
int s[8][8];
|
||
grab8(Y, w, w, h, mx * 16 + bx * 8, my * 16 + by * 8, s);
|
||
if (encode_block(&bb, s, ql, hdc_l, hac_l, &pd_y)) goto done;
|
||
}
|
||
{
|
||
int s[8][8];
|
||
grab8(Cb, wc, wc, hc, mx * 8, my * 8, s);
|
||
if (encode_block(&bb, s, qc, hdc_c, hac_c, &pd_cb)) goto done;
|
||
}
|
||
{
|
||
int s[8][8];
|
||
grab8(Cr, wc, wc, hc, mx * 8, my * 8, s);
|
||
if (encode_block(&bb, s, qc, hdc_c, hac_c, &pd_cr)) goto done;
|
||
}
|
||
}
|
||
if (bw_flush(&bb)) goto done;
|
||
}
|
||
if (mj_byte(&o, 0xFF) || mj_byte(&o, 0xD9)) goto done; /* EOI */
|
||
rc = 0;
|
||
|
||
done:
|
||
free(Y); free(Cb); free(Cr);
|
||
if (rc == 0) { *out = o.d; *outlen = o.len; }
|
||
else { free(o.d); *out = NULL; *outlen = 0; }
|
||
return rc;
|
||
}
|