Files
MeowOS-SDK/sdk/meow_png_stream.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

493 lines
18 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_png_stream.c — M23 真缩解后端(二):PNG 逐行 inflate + BMP 按行跳采样。
*
* 为什么不用 stb 的 PNG 路径:stb 先整幅 inflate + 反滤波成 raw,再展开成 RGBA 整幅,
* 最后才降采样——1600×2400 PNG 实测 +26.2MB。这里改成「按行流式」:
* - IDAT 边到边 inflate,一次只持有一行(filter 字节 + rowbytes)与上一行;
* - 反滤波后**只把需要的列**写进目标行(最近邻,与 meow_img_downscale 同口径);
* - 不需要的行整行丢弃(但仍要反滤波,filter 依赖上一行);
* - 峰值 ≈ 两行缓冲 + zlib 窗口(32KB) + 目标 RGBA,与源图分辨率无关。
* 支持:gray / gray+A / RGB / RGBA / 调色板,bit depth 1/2/4/8/16(16bit 取高字节降 8bit),
* tRNS 透明。不支持 → 返回 0 回退 stb:Adam7 交错、非 0 压缩/滤波方法、异常色型组合。
*
* BMP:文件流按目标行直接 seek 到源行(不读全图),行内跳列采样。支持 8bpp 调色板 /
* 16bpp RGB555 / 24bpp / 32bpp(BI_RGB);RLE、BITFIELDS、1/2/4bpp → 回退 stb。
*
* 许可证:MeowOS 自写(MIT,随 SDK 发布)。PNG/zlib 语义参考 RFC 2083 / zlib 手册;
* BMP 布局参考 stb_image 的解析口径(同为本仓已有依赖,行为对齐)。
*/
#include "meow_jpeg.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
/* ---------------- zlib ABI ---------------- */
#if defined(__has_include)
# if __has_include(<zlib.h>)
# include <zlib.h>
# define MZP_HAVE_H 1
# endif
#endif
#ifndef MZP_HAVE_H
/* 设备 musl sysroot 没有 zlib.h(meow_font.c 同此口径):按 libz.a 的 ABI 声明最小 API。
* 指针/unsigned long 交给编译器定尺寸,宿主 64 位与设备 32 位都成立。 */
typedef unsigned char mzp_Bytef;
typedef unsigned int mzp_uInt;
typedef unsigned long mzp_uLong;
typedef void *mzp_voidpf;
typedef struct mzp_stream_s {
mzp_Bytef *next_in; mzp_uInt avail_in; mzp_uLong total_in;
mzp_Bytef *next_out; mzp_uInt avail_out; mzp_uLong total_out;
char *msg;
void *state;
void *zalloc;
void *zfree;
mzp_voidpf opaque;
int data_type;
mzp_uLong adler;
mzp_uLong reserved;
} mzp_stream;
extern int inflateInit_(mzp_stream *, const char *, int);
extern int inflate(mzp_stream *, int);
extern int inflateEnd(mzp_stream *);
extern const char *zlibVersion(void);
# define z_stream mzp_stream
# define Z_OK 0
# define Z_STREAM_END 1
# define Z_NO_FLUSH 0
#endif
/* ---------------- 字节源 ---------------- */
typedef struct {
FILE *f;
const unsigned char *mem;
size_t memlen, mempos;
} png_src;
static int ps_read(png_src *s, void *dst, size_t n)
{
if (s->mem) {
if (s->mempos + n > s->memlen) return 0;
memcpy(dst, s->mem + s->mempos, n);
s->mempos += n;
return 1;
}
return fread(dst, 1, n, s->f) == n;
}
static int ps_skip(png_src *s, size_t n)
{
if (s->mem) {
if (s->mempos + n > s->memlen) return 0;
s->mempos += n;
return 1;
}
return fseek(s->f, (long)n, SEEK_CUR) == 0;
}
/* ================================================================== */
/* PNG */
/* ================================================================== */
typedef struct {
png_src src;
z_stream z;
int z_ready, z_end;
unsigned w, h;
int depth, color;
int bits; /* 每像素位数 */
size_t rowbytes;
unsigned char *line; /* 1 filter 字节 + rowbytes */
unsigned char *prev; /* 上一行的像素字节 */
unsigned char *cur; /* 本行的像素字节 */
size_t need, got;
unsigned char pal[256][3];
unsigned char trns_a[256]; /* 调色板透明度 */
int pal_n;
int have_trns;
unsigned trns[3]; /* gray / RGB 的 tRNS 值(已降到 8bit) */
unsigned char *out;
int tw, th;
int next_ty;
unsigned y;
} png_ctx;
/* 从行里取 px 列的 8bit 通道;返回通道数(写入 o[0..3] 的 RGB(A) 语义由调用方定)。 */
static void png_get_px(const png_ctx *c, const unsigned char *row, int x, unsigned char *o)
{
int d = c->depth;
if (d < 8) {
int per = 8 / d; /* 每字节像素数 */
int byte = x / per;
int shift = 8 - d * (x % per + 1);
int v = (row[byte] >> shift) & ((1 << d) - 1);
if (c->color == 3) { /* 调色板:子字节值即索引 */
o[0] = c->pal[v][0]; o[1] = c->pal[v][1]; o[2] = c->pal[v][2];
o[3] = c->trns_a[v];
} else {
unsigned char g = (unsigned char)(v * 255 / ((1 << d) - 1));
o[0] = o[1] = o[2] = g;
o[3] = 255;
}
return;
}
if (d == 8) {
if (c->color == 0) { /* gray */
o[0] = o[1] = o[2] = row[x];
o[3] = 255;
} else if (c->color == 2) { /* RGB */
o[0] = row[x * 3]; o[1] = row[x * 3 + 1]; o[2] = row[x * 3 + 2];
o[3] = 255;
} else if (c->color == 3) { /* palette */
int i = row[x];
o[0] = c->pal[i][0]; o[1] = c->pal[i][1]; o[2] = c->pal[i][2];
o[3] = c->trns_a[i];
} else if (c->color == 4) { /* gray + A */
o[0] = o[1] = o[2] = row[x * 2];
o[3] = row[x * 2 + 1];
} else { /* RGBA */
o[0] = row[x * 4]; o[1] = row[x * 4 + 1];
o[2] = row[x * 4 + 2]; o[3] = row[x * 4 + 3];
}
return;
}
/* 16bit:取高字节 */
if (c->color == 0) {
o[0] = o[1] = o[2] = row[x * 2];
o[3] = (c->have_trns && row[x * 2] == c->trns[0]) ? 0 : 255;
} else if (c->color == 2) {
o[0] = row[x * 6]; o[1] = row[x * 6 + 2]; o[2] = row[x * 6 + 4];
o[3] = (c->have_trns && row[x * 6] == c->trns[0] &&
row[x * 6 + 2] == c->trns[1] && row[x * 6 + 4] == c->trns[2]) ? 0 : 255;
} else if (c->color == 4) {
o[0] = o[1] = o[2] = row[x * 4];
o[3] = row[x * 4 + 2];
} else {
o[0] = row[x * 8]; o[1] = row[x * 8 + 2];
o[2] = row[x * 8 + 4]; o[3] = row[x * 8 + 6];
}
}
/* 一行反滤波完成后:按需写目标行。 */
static int png_emit_row(png_ctx *c)
{
unsigned char ft = c->line[0];
const unsigned char *src = c->line + 1;
unsigned char *dst = c->cur;
size_t x, bpp = (size_t)((c->bits + 7) / 8);
if (bpp < 1) bpp = 1;
if (ft > 4) return 0;
for (x = 0; x < c->rowbytes; x++) {
unsigned char left = (x >= bpp) ? dst[x - bpp] : 0;
unsigned char up = c->prev ? c->prev[x] : 0;
unsigned char ul = (c->prev && x >= bpp) ? c->prev[x - bpp] : 0;
unsigned char v = src[x];
switch (ft) {
case 0: dst[x] = v; break;
case 1: dst[x] = (unsigned char)(v + left); break;
case 2: dst[x] = (unsigned char)(v + up); break;
case 3: dst[x] = (unsigned char)(v + (((unsigned)left + (unsigned)up) >> 1)); break;
default: {
int p = (int)left + (int)up - (int)ul;
int pa = p - (int)left; if (pa < 0) pa = -pa;
int pb = p - (int)up; if (pb < 0) pb = -pb;
int pc = p - (int)ul; if (pc < 0) pc = -pc;
unsigned char pred = (pa <= pb && pa <= pc) ? left : (pb <= pc ? up : ul);
dst[x] = (unsigned char)(v + pred);
break;
}
}
}
if (c->next_ty < c->th) {
long sy = (long)c->next_ty * (long)c->h / c->th;
if (sy == (long)c->y) {
unsigned char *o = c->out + (size_t)c->next_ty * c->tw * 4;
int tx;
for (tx = 0; tx < c->tw; tx++) {
int sx = (int)((long)tx * (long)c->w / c->tw);
unsigned char px[4];
png_get_px(c, dst, sx, px);
o[tx * 4] = px[0]; o[tx * 4 + 1] = px[1];
o[tx * 4 + 2] = px[2]; o[tx * 4 + 3] = px[3];
}
c->next_ty++;
}
}
/* 交换 prev/cur(prev 直接成为反滤波基准) */
{
unsigned char *t = c->prev;
c->prev = c->cur;
c->cur = t;
}
c->y++;
return 1;
}
#define PNG_NEED_MORE 2
static int png_pump(png_ctx *c)
{
while (c->got < c->need) {
int ret;
c->z.next_out = c->line + c->got;
c->z.avail_out = (unsigned)(c->need - c->got);
ret = inflate(&c->z, Z_NO_FLUSH);
c->got = c->need - c->z.avail_out;
if (ret == Z_STREAM_END) c->z_end = 1;
else if (ret != Z_OK) return 0;
if (c->got < c->need && (c->z.avail_in == 0 || c->z_end))
return c->z_end ? 0 : PNG_NEED_MORE;
}
if (!png_emit_row(c)) return 0;
c->got = 0;
return 1;
}
static int png_bits_of(int color, int depth)
{
switch (color) {
case 0: return depth; /* gray */
case 2: return depth * 3; /* rgb */
case 3: return depth; /* palette */
case 4: return depth * 2; /* gray + a */
case 6: return depth * 4; /* rgba */
default: return 0;
}
}
static int png_depth_ok(int color, int depth)
{
if (color == 0) return depth == 1 || depth == 2 || depth == 4 || depth == 8 || depth == 16;
if (color == 3) return depth == 1 || depth == 2 || depth == 4 || depth == 8;
if (color == 2 || color == 4 || color == 6) return depth == 8 || depth == 16;
return 0;
}
int meow_png_decode_stream(FILE *f, const unsigned char *mem, size_t len,
int maxdim, int *tw, int *th, unsigned char **rgba)
{
png_ctx c;
unsigned char sig[8], hdr[8];
unsigned char ibuf[4096];
int have_ihdr = 0, i, ok = 0;
if (maxdim <= 0) return 0;
memset(&c, 0, sizeof c);
c.src.f = f;
c.src.mem = mem;
c.src.memlen = len;
if (!ps_read(&c.src, sig, 8)) return 0;
if (memcmp(sig, "\x89PNG\r\n\x1a\n", 8) != 0) return 0;
while (ps_read(&c.src, hdr, 8)) {
unsigned clen = ((unsigned)hdr[0] << 24) | ((unsigned)hdr[1] << 16) |
((unsigned)hdr[2] << 8) | hdr[3];
const unsigned char *type = hdr + 4;
if (clen > 0x7FFFFFFFu) return 0;
if (memcmp(type, "IHDR", 4) == 0) {
unsigned char d[13];
if (clen < 13 || have_ihdr) return 0;
if (!ps_read(&c.src, d, 13)) return 0;
c.w = ((unsigned)d[0] << 24) | ((unsigned)d[1] << 16) | ((unsigned)d[2] << 8) | d[3];
c.h = ((unsigned)d[4] << 24) | ((unsigned)d[5] << 16) | ((unsigned)d[6] << 8) | d[7];
c.depth = d[8];
c.color = d[9];
if (d[10] != 0 || d[11] != 0 || d[12] != 0) return 0; /* 只支持非交错 + 标准压缩/滤波 */
if (c.w == 0 || c.h == 0 || c.w > 65535u || c.h > 65535u) return 0;
if (!png_depth_ok(c.color, c.depth)) return 0;
c.bits = png_bits_of(c.color, c.depth);
c.rowbytes = ((size_t)c.w * c.bits + 7) / 8;
if (c.rowbytes == 0) return 0;
/* 小图不动:与 meow_img_load_file_dim 的「旧入口」判定一致 */
if ((long)(c.w > c.h ? c.w : c.h) <= maxdim) return 0;
if (clen > 13 && !ps_skip(&c.src, clen - 13)) return 0;
have_ihdr = 1;
c.need = c.rowbytes + 1;
c.line = (unsigned char *)malloc(c.need);
c.prev = (unsigned char *)malloc(c.rowbytes);
c.cur = (unsigned char *)malloc(c.rowbytes);
if (!c.line || !c.prev || !c.cur) goto done;
memset(c.prev, 0, c.rowbytes);
memset(c.cur, 0, c.rowbytes);
for (i = 0; i < 256; i++) c.trns_a[i] = 255;
meow_img_target_dims((int)c.w, (int)c.h, maxdim, &c.tw, &c.th);
c.out = (unsigned char *)malloc((size_t)c.tw * c.th * 4);
if (!c.out) goto done;
memset(c.out, 0, (size_t)c.tw * c.th * 4);
memset(&c.z, 0, sizeof c.z);
if (inflateInit_(&c.z, zlibVersion(), (int)sizeof c.z) != Z_OK) goto done;
c.z_ready = 1;
} else if (memcmp(type, "PLTE", 4) == 0) {
unsigned char d[256 * 3];
if (clen > sizeof d || clen % 3) return 0;
if (!ps_read(&c.src, d, clen)) return 0;
c.pal_n = (int)(clen / 3);
memcpy(c.pal, d, clen);
} else if (memcmp(type, "tRNS", 4) == 0) {
unsigned char d[256];
if (clen > sizeof d) return 0;
if (!ps_read(&c.src, d, clen)) return 0;
c.have_trns = 1;
if (c.color == 3) {
for (i = 0; i < (int)clen && i < 256; i++) c.trns_a[i] = d[i];
} else if (c.color == 0 && clen >= 2) {
c.trns[0] = d[0]; /* 高字节(已降 8bit) */
} else if (c.color == 2 && clen >= 6) {
c.trns[0] = d[0]; c.trns[1] = d[2]; c.trns[2] = d[4];
}
} else if (memcmp(type, "IDAT", 4) == 0) {
unsigned remain = clen;
if (!have_ihdr || !c.out) return 0;
while (remain) {
unsigned n = remain > sizeof ibuf ? (unsigned)sizeof ibuf : remain;
if (!ps_read(&c.src, ibuf, n)) goto done;
remain -= n;
c.z.next_in = ibuf;
c.z.avail_in = n;
while (c.z.avail_in && c.next_ty < c.th) {
int r = png_pump(&c);
if (r == 0) goto done;
if (r == PNG_NEED_MORE) break;
}
}
} else if (memcmp(type, "IEND", 4) == 0) {
break;
} else {
if (clen && !ps_skip(&c.src, clen)) return 0;
}
/* 跳过 CRC */
{
unsigned char crc[4];
if (!ps_read(&c.src, crc, 4)) break;
}
if (c.out && c.next_ty >= c.th) break; /* 目标行都拿到了,不必读完 */
}
if (!have_ihdr || !c.out || c.next_ty < c.th) goto done;
if (c.color == 3 && c.pal_n <= 0) goto done;
*tw = c.tw;
*th = c.th;
*rgba = c.out;
c.out = NULL;
ok = 1;
done:
if (c.z_ready) inflateEnd(&c.z);
free(c.line);
free(c.prev);
free(c.cur);
free(c.out);
return ok;
}
/* ================================================================== */
/* BMP */
/* ================================================================== */
static unsigned le16(const unsigned char *p) { return (unsigned)p[0] | ((unsigned)p[1] << 8); }
static unsigned le32(const unsigned char *p)
{
return (unsigned)p[0] | ((unsigned)p[1] << 8) | ((unsigned)p[2] << 16) | ((unsigned)p[3] << 24);
}
int meow_bmp_decode_stream(FILE *f, const unsigned char *mem, size_t len,
int maxdim, int *tw, int *th, unsigned char **rgba)
{
png_src src;
unsigned char fh[14], dh[4];
unsigned char dib[1024];
unsigned dibsize, dataoff, bpp, compression, colors;
long w, h, rowsize;
int topdown, i;
unsigned char pal[256][3];
unsigned char *row = NULL, *out = NULL;
int tw_ = 0, th_ = 0, ok = 0;
if (maxdim <= 0) return 0;
memset(&src, 0, sizeof src);
src.f = f;
src.mem = mem;
src.memlen = len;
if (!ps_read(&src, fh, 14)) return 0;
if (fh[0] != 'B' || fh[1] != 'M') return 0;
dataoff = le32(fh + 10);
if (!ps_read(&src, dh, 4)) return 0;
dibsize = le32(dh);
if (dibsize < 40 || dibsize > sizeof dib) return 0; /* BITMAPCOREHEADER/OS2 → stb */
memcpy(dib, dh, 4);
if (!ps_read(&src, dib + 4, dibsize - 4)) return 0;
w = (long)(int)le32(dib + 4);
h = (long)(int)le32(dib + 8);
bpp = le16(dib + 14);
compression = le32(dib + 16);
colors = (dibsize >= 36) ? le32(dib + 32) : 0;
if (compression != 0) return 0; /* RLE/BITFIELDS → stb */
if (w <= 0 || h == 0) return 0;
if (bpp != 8 && bpp != 16 && bpp != 24 && bpp != 32) return 0;
topdown = h < 0;
if (h < 0) h = -h;
if ((long)(w > h ? w : h) <= maxdim) return 0; /* 小图走旧入口 */
if (bpp <= 8) {
unsigned n = colors ? colors : 256;
unsigned char e[4];
if (n > 256) n = 256;
for (i = 0; (unsigned)i < n; i++) {
if (!ps_read(&src, e, 4)) return 0;
pal[i][0] = e[2]; pal[i][1] = e[1]; pal[i][2] = e[0];
}
for (; i < 256; i++) pal[i][0] = pal[i][1] = pal[i][2] = 0;
}
rowsize = (w * bpp + 31) / 32 * 4;
meow_img_target_dims((int)w, (int)h, maxdim, &tw_, &th_);
row = (unsigned char *)malloc((size_t)rowsize);
out = (unsigned char *)malloc((size_t)tw_ * th_ * 4);
if (!row || !out) goto done;
for (int ty = 0; ty < th_; ty++) {
int sy = (int)((long)ty * h / th_);
long file_row = topdown ? sy : (h - 1 - sy);
long off = (long)dataoff + file_row * rowsize;
unsigned char *o = out + (size_t)ty * tw_ * 4;
if (src.mem) {
if (off < 0 || (size_t)off + (size_t)rowsize > src.memlen) goto done;
memcpy(row, src.mem + off, (size_t)rowsize);
} else {
if (fseek(src.f, off, SEEK_SET) != 0) goto done;
if (fread(row, 1, (size_t)rowsize, src.f) != (size_t)rowsize) goto done;
}
for (int tx = 0; tx < tw_; tx++) {
int sx = (int)((long)tx * w / tw_);
unsigned char *p = o + (size_t)tx * 4;
if (bpp == 24) {
const unsigned char *q = row + sx * 3;
p[0] = q[2]; p[1] = q[1]; p[2] = q[0]; p[3] = 255;
} else if (bpp == 32) {
const unsigned char *q = row + sx * 4;
p[0] = q[2]; p[1] = q[1]; p[2] = q[0]; p[3] = 255; /* BI_RGB:alpha 不作为语义 */
} else if (bpp == 16) {
unsigned v = le16(row + sx * 2);
unsigned r = (v >> 10) & 31, g = (v >> 5) & 31, b = v & 31;
p[0] = (unsigned char)((r << 3) | (r >> 2));
p[1] = (unsigned char)((g << 3) | (g >> 2));
p[2] = (unsigned char)((b << 3) | (b >> 2));
p[3] = 255;
} else {
unsigned idx = row[sx];
p[0] = pal[idx & 255][0]; p[1] = pal[idx & 255][1];
p[2] = pal[idx & 255][2]; p[3] = 255;
}
}
}
*tw = tw_;
*th = th_;
*rgba = out;
out = NULL;
ok = 1;
done:
free(row);
free(out);
return ok;
}