Files
MeowOS-SDK/tuku/logic/reader/archive_source.c
T
AskaEth 7036020005 图库: 阅读器页槽改 RGB565 常驻,页图内存减半(M32)
- 页槽从 RGBA 改成「已落框的 RGB565 位图」(2B/px):真机 480×853 页 1599KB → 800KB,
  两页常驻 3.2MB → 1.6MB。解码仍在 worker 里产出 RGBA,当场转 565 并释放 RGBA。
- SDK 新增 565 原语(meow_ui_draw.h / meow_ui_gfx.c):meow_gfx_blit_rgb565 命中页每行
  一次 memcpy(单次拷贝、无每帧重采样),缩放兜底走 565 最近邻。量化与 back_buf 用同一个
  rgb565(),565 屏上落下的像素与旧 RGBA 路径逐像素一致(不引入色偏/新增色带)。
- 预算按 565 重算:单页像素上限沿用旧 3MB@RGBA 的同几何口径(=1.5MB@565),
  MR_RESIDENT_CAP = 3 槽 + 1 张在途 = 3.2MB;reader_estimate_peak 新增 convert_bytes
  (落槽暂存 2B/px)与 peak_bytes 一起参与门控,避免实测超预算。
- 结果队列(已解码、未落槽)字节计入总预算(pending_bytes),不再「偷吃」最多 2 页。
- CBZ meta() 改读条目 4KB 前缀(mz_read_prefix;deflate 解到 4KB 即停,输入缓冲走栈),
  500 页排版不再触发 500 次全量解压。
- 测试:B3/B8 的 565 常驻与在途预算断言、CBZ 500 页 stored/deflate 前后对比、
  P12 565 与 RGBA 路径的逐像素颜色对比(量化误差/RMSE/灰阶梯)。
2026-11-06 18:43:11 +08:00

417 lines
14 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.
/* logic/reader/archive_source.c — 档案册(CBZ/zip 当一本书):页源契约的第二种实现。
*
* 契约、条目过滤与缓存纪律见 page_source.h / page_cache.h。实现要点:
* - 页表在 init 时一次建好(mz_open 只读 central directory;只看名字,不解压);
* - open(i) 才解压单条目:mz_read_alloc → page_cache 落盘 → 注入的解码回调按路径解码。
* 因此翻页/条漫两套消费代码与目录册完全一致(meta 走原始像素尺寸,open 走解码);
* - meta(i) 只读该条目的**前 4KB**(mz_read_prefix)取图片头(不落缓存),结果缓存在
* meta_w/meta_h;M32 前是整条目解压,500 页排版 = 500 次全量解压;
* - 单个 mr_zip_book 的 mz_read* 由自带的互斥量串行化(解码线程与主线程 meta 会碰面)。
*
* 线程:count/page_name 只读页表,可任意线程;open 可由解码线程调用;meta 按 M6
* 用法在主线程调用(条漫排版/当前页几何)。两个 mr_zip_book 实例互不影响,
* 解压缓存(page_cache)是进程级共享的。
*/
#include "logic/reader/page_source.h"
#include "logic/reader/page_cache.h"
#include "logic/zip/zip.h"
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
/* ============================================================ */
/* 条目过滤:图片后缀 + 路径安全 + 非隐藏 + 非目录 */
/* ============================================================ */
static int has_control(const char *name, unsigned len)
{
for (unsigned i = 0; i < len; i++) {
unsigned char c = (unsigned char)name[i];
if (c < 0x20 || c == 0x7F) return 1;
}
return 0;
}
/* 逐段检查:拒绝 ".." 段与隐藏段("._resource" 之类不参与成册)。 */
static int components_safe(const char *name)
{
const char *p = name;
while (*p) {
const char *end = strchr(p, '/');
size_t n = end ? (size_t)(end - p) : strlen(p);
if (n == 2 && p[0] == '.' && p[1] == '.') return 0;
if (n > 0 && p[0] == '.') return 0;
if (!end) break;
p = end + 1;
}
return 1;
}
int mr_zip_entry_is_page(const char *name, unsigned name_len)
{
if (!name || name_len == 0 || name_len >= MR_ZIP_MAX_ENTRY_NAME) return 0;
if (strlen(name) != name_len) return 0; /* 内嵌 NUL:拒绝 */
if (name[0] == '/' || name[0] == '\\') return 0; /* 绝对路径 */
if (name_len >= 2 && name[1] == ':') return 0; /* Windows 盘符 */
if (strchr(name, '\\')) return 0; /* 反斜杠分隔符:一律拒绝 */
if (has_control(name, name_len)) return 0;
if (name[name_len - 1] == '/') return 0; /* 目录条目 */
if (!components_safe(name)) return 0;
return mr_path_is_image(name);
}
/* 页表排序:跨子目录按完整条目名自然序(中文按 UTF-8 字节序,数字段按数值)。 */
typedef struct page_ref {
const char *name;
int eidx;
} page_ref;
static int page_ref_cmp(const void *pa, const void *pb)
{
const page_ref *a = (const page_ref *)pa;
const page_ref *b = (const page_ref *)pb;
int c = mr_natural_cmp(a->name, b->name);
if (c != 0) return c;
return a->eidx < b->eidx ? -1 : (a->eidx > b->eidx ? 1 : 0);
}
/* ============================================================ */
/* 生命周期 */
/* ============================================================ */
static int entry_is_page(const mz_entry *e)
{
if (!e || mz_entry_is_dir(e)) return 0;
if (e->flags & 0x0001u) return 0; /* 加密条目:读不了,不列 */
if (!mr_zip_entry_is_page(e->name, e->name_len)) return 0;
if (e->uncomp_size == 0) return 0;
if (e->uncomp_size > (uint64_t)MR_ZIP_PAGE_MAX_BYTES) return 0; /* zip 炸弹 */
return 1;
}
void mr_zip_book_free(mr_zip_book *zb)
{
if (!zb) return;
if (zb->arch) {
mz_close((mz_archive *)zb->arch);
zb->arch = NULL;
}
if (zb->lock) {
pthread_mutex_destroy((pthread_mutex_t *)zb->lock);
free(zb->lock);
zb->lock = NULL;
}
free(zb->order);
zb->order = NULL;
free(zb->meta_w);
zb->meta_w = NULL;
free(zb->meta_h);
zb->meta_h = NULL;
zb->count = 0;
}
int mr_zip_book_init(mr_zip_book *zb, const char *path, int maxdim,
mr_book_load_fn load, mr_book_unload_fn unload, void *ud)
{
if (!zb) return -1;
memset(zb, 0, sizeof *zb);
if (!path || !path[0]) return -1;
if (strlen(path) >= sizeof zb->path) return -1;
if (maxdim <= 0) maxdim = 720;
mz_archive *z = NULL;
if (mz_open(path, &z) != MZ_OK || !z) return -1;
pthread_mutex_t *lk = (pthread_mutex_t *)malloc(sizeof *lk);
if (!lk) {
mz_close(z);
return -1;
}
pthread_mutex_init(lk, NULL);
snprintf(zb->path, sizeof zb->path, "%s", path);
zb->arch = z;
zb->lock = lk;
zb->maxdim = maxdim;
zb->load = load;
zb->unload = unload;
zb->ud = ud;
struct stat st;
if (stat(path, &st) == 0) {
zb->mtime = (long long)st.st_mtime;
zb->size = (long long)st.st_size;
}
int n = mz_count(z);
page_ref *refs = NULL;
int nref = 0, cap = 0;
for (int i = 0; i < n && nref < MR_BOOK_MAX_PAGES; i++) {
const mz_entry *e = mz_entry_at(z, i);
if (!entry_is_page(e)) continue;
if (nref >= cap) {
int nc = cap ? cap * 2 : 32;
page_ref *nr = (page_ref *)realloc(refs, (size_t)nc * sizeof *nr);
if (!nr) {
free(refs);
mr_zip_book_free(zb);
return -1;
}
refs = nr;
cap = nc;
}
refs[nref].name = e->name;
refs[nref].eidx = i;
nref++;
}
if (nref > 1) qsort(refs, (size_t)nref, sizeof *refs, page_ref_cmp);
if (nref > 0) {
zb->order = (int *)malloc((size_t)nref * sizeof *zb->order);
if (!zb->order) {
free(refs);
mr_zip_book_free(zb);
return -1;
}
for (int i = 0; i < nref; i++) zb->order[i] = refs[i].eidx;
}
free(refs);
zb->count = nref;
return nref;
}
/* ============================================================ */
/* 页 → 缓存文件 */
/* ============================================================ */
static const mz_entry *page_entry(mr_zip_book *zb, int index)
{
if (!zb || !zb->arch || !zb->order) return NULL;
if (index < 0 || index >= zb->count) return NULL;
return mz_entry_at((mz_archive *)zb->arch, zb->order[index]);
}
int mr_zip_book_page_path(mr_zip_book *zb, int index, char *out, int outsz)
{
if (!out || outsz <= 0) return -1;
out[0] = '\0';
const mz_entry *e = page_entry(zb, index);
if (!e) return -1;
char path[MR_CACHE_FILE_MAX];
if (mr_cache_path(zb->path, zb->mtime, zb->size, e->name, index,
path, (int)sizeof path) != 0)
return -1;
if (mr_cache_touch(path)) {
snprintf(out, (size_t)outsz, "%s", path);
return 0;
}
pthread_mutex_lock((pthread_mutex_t *)zb->lock);
void *buf = NULL;
size_t len = 0;
int rc = mz_read_alloc((mz_archive *)zb->arch, e, &buf, &len);
pthread_mutex_unlock((pthread_mutex_t *)zb->lock);
if (rc != MZ_OK || !buf) {
free(buf);
return -1;
}
int ok = mr_cache_store(path, buf, len);
free(buf);
if (!ok) return -1;
snprintf(out, (size_t)outsz, "%s", path);
return 0;
}
void mr_zip_book_page_name(const mr_zip_book *zb, int index, char *out, int outsz)
{
if (!out || outsz <= 0) return;
out[0] = '\0';
const mz_entry *e = page_entry((mr_zip_book *)zb, index);
if (!e) return;
snprintf(out, (size_t)outsz, "%s", e->name);
}
/* ============================================================ */
/* 书库辅助:数页 / 取封面 */
/* ============================================================ */
int mr_zip_count_pages(const char *path)
{
if (!path || !path[0]) return 0;
mz_archive *z = NULL;
if (mz_open(path, &z) != MZ_OK || !z) return 0;
int n = mz_count(z), pages = 0;
for (int i = 0; i < n && pages < MR_BOOK_MAX_PAGES; i++)
if (entry_is_page(mz_entry_at(z, i))) pages++;
mz_close(z);
return pages;
}
int mr_zip_cover_path(const char *path, char *out, int outsz)
{
if (!out || outsz <= 0) return 0;
out[0] = '\0';
if (!path || !path[0]) return 0;
mr_zip_book zb;
int n = mr_zip_book_init(&zb, path, 0, NULL, NULL, NULL);
int ok = 0;
if (n > 0) ok = (mr_zip_book_page_path(&zb, 0, out, outsz) == 0);
mr_zip_book_free(&zb);
return ok;
}
/* ============================================================ */
/* mr_book_ops 适配器(档案册) */
/* ============================================================ */
static int zip_ops_count(void *ctx)
{
mr_zip_book *zb = (mr_zip_book *)ctx;
return zb ? zb->count : 0;
}
/* meta 只读条目**前缀**(≤4KB)取图片头(M32):改前是 mz_read_alloc 整条目解压后再探
* (500 页排版 = 500 次全量解压)。PNG IHDR 33 字节、JPEG SOF 在头几百字节、BMP 在
* 54 字节 + 调色板内,4KB 足够;前缀探测失败且确实被截断时才回退整条目读(罕见/病态文件)。 */
#define MR_ZIP_META_PREFIX 4096
static int zip_meta_probe_prefix(mr_zip_book *zb, const mz_entry *e, int *w, int *h)
{
unsigned char head[MR_ZIP_META_PREFIX];
size_t head_len = 0;
pthread_mutex_lock((pthread_mutex_t *)zb->lock);
int rc = mz_read_prefix((mz_archive *)zb->arch, e, head, sizeof head, &head_len);
pthread_mutex_unlock((pthread_mutex_t *)zb->lock);
if (rc != MZ_OK) return 0;
if (mr_probe_size_mem(head, head_len, w, h)) return 1;
if (head_len < sizeof head) return 0; /* 整个条目都读到了还探不出 → 坏头 */
/* 前缀被截断且探不出尺寸(病态长的头部):回退整条目读,保持旧语义。 */
void *buf = NULL;
size_t len = 0;
pthread_mutex_lock((pthread_mutex_t *)zb->lock);
rc = mz_read_alloc((mz_archive *)zb->arch, e, &buf, &len);
pthread_mutex_unlock((pthread_mutex_t *)zb->lock);
if (rc != MZ_OK || !buf) {
free(buf);
return 0;
}
int known = mr_probe_size_mem(buf, len, w, h);
free(buf);
return known;
}
/* meta 按需探测图片头;结果缓存,每页只读一次(前缀 4KB,不再整页解压)。主线程调用(M6 用法)。 */
static int zip_ops_meta(void *ctx, int index, int *w, int *h)
{
mr_zip_book *zb = (mr_zip_book *)ctx;
if (w) *w = 0;
if (h) *h = 0;
if (!zb || index < 0 || index >= zb->count) return 0;
if (!zb->meta_w) {
int *mw = (int *)calloc((size_t)zb->count, sizeof *mw);
int *mh = (int *)calloc((size_t)zb->count, sizeof *mh);
if (!mw || !mh) {
free(mw);
free(mh);
return 0;
}
zb->meta_w = mw;
zb->meta_h = mh;
}
if (zb->meta_w[index] > 0 && zb->meta_h[index] > 0) {
if (w) *w = zb->meta_w[index];
if (h) *h = zb->meta_h[index];
return 1;
}
const mz_entry *e = page_entry(zb, index);
if (!e) return 0;
int pw = 0, ph = 0;
if (!zip_meta_probe_prefix(zb, e, &pw, &ph)) return 0;
zb->meta_w[index] = pw;
zb->meta_h[index] = ph;
if (w) *w = pw;
if (h) *h = ph;
return 1;
}
static meow_img *zip_ops_open(void *ctx, int index)
{
mr_zip_book *zb = (mr_zip_book *)ctx;
if (!zb || !zb->load) return NULL;
char path[MR_CACHE_FILE_MAX];
if (mr_zip_book_page_path(zb, index, path, (int)sizeof path) != 0) return NULL;
return zb->load(path, zb->ud, zb->maxdim);
}
static void zip_ops_close(void *ctx, meow_img *img)
{
mr_zip_book *zb = (mr_zip_book *)ctx;
if (zb && zb->unload && img) zb->unload(img);
}
static void zip_ops_destroy(void *ctx)
{
mr_zip_book *zb = (mr_zip_book *)ctx;
if (zb) mr_zip_book_free(zb);
}
static const mr_book_ops g_zip_ops = {
zip_ops_count, zip_ops_meta, zip_ops_open, zip_ops_close, zip_ops_destroy,
};
void mr_zip_book_bind(mr_book *b, mr_zip_book *zb)
{
if (!b) return;
b->ops = zb ? &g_zip_ops : NULL;
b->ctx = zb;
}
/* ============================================================ */
/* 统一开册 */
/* ============================================================ */
int mr_book_open_auto(mr_book *b, mr_book_handle *h, const char *path, int maxdim,
mr_book_load_fn load, mr_book_unload_fn unload, void *ud)
{
if (b) {
b->ops = NULL;
b->ctx = NULL;
}
if (!b || !h) return -1;
memset(h, 0, sizeof *h);
if (!path || !path[0]) return -1;
int n;
/* 目录名也可能以 .cbz 结尾(罕见):只有普通文件才当档案册。 */
int is_archive = mr_path_is_archive(path);
if (is_archive) {
struct stat st;
if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) is_archive = 0;
}
if (is_archive) {
n = mr_zip_book_init(&h->u.zip, path, maxdim, load, unload, ud);
if (n < 0) return -1;
h->kind = MR_BOOK_KIND_ZIP;
if (n > 0) mr_zip_book_bind(b, &h->u.zip);
} else {
n = mr_dir_book_init(&h->u.dir, path, maxdim, load, unload, ud);
if (n < 0) return -1;
h->kind = MR_BOOK_KIND_DIR;
if (n > 0) mr_dir_book_bind(b, &h->u.dir);
}
return n;
}
void mr_book_close_auto(mr_book *b, mr_book_handle *h)
{
if (b) mr_book_destroy(b);
if (!h) return;
if (h->kind == MR_BOOK_KIND_ZIP) mr_zip_book_free(&h->u.zip);
else if (h->kind == MR_BOOK_KIND_DIR) mr_dir_book_free(&h->u.dir);
memset(h, 0, sizeof *h);
}