/* * 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 #include #include #include /* ---------------- zlib ABI ---------------- */ #if defined(__has_include) # if __has_include() # include # 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; /* tRNS 存的是原始采样值(0..2^d-1),不是展开后的 0..255 */ o[3] = (c->color == 0 && c->have_trns && (unsigned)v == c->trns[0]) ? 0 : 255; } return; } if (d == 8) { if (c->color == 0) { /* gray */ o[0] = o[1] = o[2] = row[x]; o[3] = (c->have_trns && row[x] == c->trns[0]) ? 0 : 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] = (c->have_trns && row[x * 3] == c->trns[0] && row[x * 3 + 1] == c->trns[1] && row[x * 3 + 2] == c->trns[2]) ? 0 : 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) { /* 8bit/低位深:采样值存在低字节;16bit:与像素一样取高字节比较 */ c.trns[0] = (c.depth == 16) ? d[0] : d[1]; } else if (c.color == 2 && clen >= 6) { if (c.depth == 16) { c.trns[0] = d[0]; c.trns[1] = d[2]; c.trns[2] = d[4]; } else { c.trns[0] = d[1]; c.trns[1] = d[3]; c.trns[2] = d[5]; } } } 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; }