图库: 门控模型接真缩解后端 + _step 全格式后端(M27)
M23 交付了真缩解后端却「修好了用不上」:_step 只接 JPEG,而阅读器的峰值模型
仍是 stb 口径(PNG/BMP 按 W·H·4、JPEG 按缩解后整幅 RGBA),12MP/PNG 页全被判
超预算占位。本次把两处缝焊上:
- sdk/meow_ui_img.c:meow_img_load_file_step 的 JPEG-only 限制放开到 PNG/BMP;
新增 meow_img_load_file_step_ex 回报实际路径(backend/stb),日志可核对预判。
- tuku/logic/reader/page_source.[ch]:新增 reader_estimate_peak(),成为峰值模型 +
预算 + MemAvailable 门控的唯一实现(reader 不再另写一份)。
* 后端命中(JPEG SOF0/1/2、≤3 分量、8bit、N=1..7;PNG 非交错/标准压缩/合法色型;
BMP DIB≥40/BI_RGB/8·16·24·32bpp)→ 峰值 = 目标图 + 分量平面/行缓冲 + 固定开销,
与源分辨率无关 → 直接放行,不门控;
* 只有回退 stb(CMYK/算术编码、Adam7、RLE/1-4bpp、N≥8 小图等)才按 M22 stb 模型
算峰值,超 3MB 或 MemAvailable <6MB 才占位。Adam7 另补 stb 的整幅暂存上界。
- reader:删掉本地 stb 模型/常量,统一调 reader_estimate_peak;日志加 path=backend|stb。
- 测试:6 类典型页(2000×3000 baseline/渐进/restart、1600×2400 PNG/BMP、4MP、12MP)
断言全部 backend + 不占位 + 估算 ≥ 实测且 ≤1.5×(实测 1.03–1.08×);两条路径输出
尺寸逐像素一致;CMYK/Adam7/1bpp 断言走 stb 并按旧门控占位;连翻 30 页无占位、
单页分配峰值 ≤3MB;membudget 链接补 meow_ui_time.c 与 M23 两个解码 TU。
This commit is contained in:
1 parent
668629bda3
commit
1725af631b
9 files changed
+797
-195
No files matched your search
@@ -200,10 +200,19 @@ static int probe_png(probe_src *s, mr_img_info *in)
|
||||
if (memcmp(hdr, sig, 8) != 0 || memcmp(hdr + 12, "IHDR", 4) != 0) return 0;
|
||||
unsigned ww = probe_be32(hdr + 16), hh = probe_be32(hdr + 20);
|
||||
if (ww == 0 || hh == 0) return 0;
|
||||
/* IHDR 的位深/颜色类型(紧跟在宽高后)。截断头(stb 也读不下去)按 RGB 8bit 记。 */
|
||||
/* IHDR 的位深/颜色类型/压缩/滤波/交错(紧跟宽高之后)。截断头(stb 也读不下去)
|
||||
* 按 RGB 8bit 记;但后端支持字段保持「未知(0)」→ 保守回退 stb 门控。 */
|
||||
int depth = 8, ctype = 2;
|
||||
unsigned char ex[2];
|
||||
if (ps_read(s, ex, sizeof ex)) { depth = ex[0]; ctype = ex[1]; }
|
||||
unsigned char ex[5];
|
||||
if (ps_read(s, ex, sizeof ex)) {
|
||||
depth = ex[0];
|
||||
ctype = ex[1];
|
||||
in->depth = ex[0];
|
||||
in->color = ex[1];
|
||||
in->compression = ex[2];
|
||||
in->filter = ex[3];
|
||||
in->interlace = ex[4];
|
||||
}
|
||||
int ch;
|
||||
switch (ctype) {
|
||||
case 0: ch = 1; break; /* 灰度 */
|
||||
@@ -259,16 +268,20 @@ static int probe_jpeg(probe_src *s, mr_img_info *in)
|
||||
in->codec = MR_IMG_CODEC_JPEG;
|
||||
in->progressive = prog;
|
||||
in->channels = 3;
|
||||
in->sof = m; /* M27:0xC0/0xC1/0xC2 才可能走后端 */
|
||||
in->depth = so[0]; /* 精度(8 才可能走后端) */
|
||||
/* 未知采样因子时按 4:4:4 估(3·W·H,偏保守):宁可多算峰值,不要低估。 */
|
||||
in->planes = (long long)ww * hh * 3;
|
||||
unsigned char nc = 0;
|
||||
if (ps_read(s, &nc, 1) && nc >= 1 && nc <= 4 && len >= 8 + 3 * nc) {
|
||||
int hv[4] = { 0, 0, 0, 0 };
|
||||
int tq[4] = { 0, 0, 0, 0 };
|
||||
int hmax = 1, vmax = 1, ok = 1;
|
||||
for (int i = 0; i < nc && ok; i++) {
|
||||
unsigned char comp[3]; /* id / 采样因子 hv / 量化表 */
|
||||
if (!ps_read(s, comp, sizeof comp)) { ok = 0; break; }
|
||||
hv[i] = comp[1];
|
||||
tq[i] = comp[2];
|
||||
int ch = hv[i] >> 4, cv = hv[i] & 15;
|
||||
if (ch < 1 || ch > 4 || cv < 1 || cv > 4) { ok = 0; break; }
|
||||
if (ch > hmax) hmax = ch;
|
||||
@@ -278,6 +291,13 @@ static int probe_jpeg(probe_src *s, mr_img_info *in)
|
||||
if (hmax % (hv[i] >> 4) != 0 || vmax % (hv[i] & 15) != 0) ok = 0;
|
||||
}
|
||||
if (ok) {
|
||||
/* M27:给后端支持判定留原始采样因子/量化表号(4 分量已在上面排除)。 */
|
||||
in->ncomp = nc;
|
||||
for (int i = 0; i < nc; i++) {
|
||||
in->comp_h[i] = (unsigned char)(hv[i] >> 4);
|
||||
in->comp_v[i] = (unsigned char)(hv[i] & 15);
|
||||
in->comp_tq[i] = (unsigned char)tq[i];
|
||||
}
|
||||
/* 分量面字节 = 与 stb 完全同式的 w2×h2(MCU 对齐 + 采样因子);
|
||||
* 渐进式每个分量还多一张 w2×h2×2 的 short 系数面(M20 实测 +23.5MB 的来源)。 */
|
||||
long long mcu_x = (ww + 8 * hmax - 1) / (8 * hmax);
|
||||
@@ -310,15 +330,24 @@ static int probe_bmp(probe_src *s, mr_img_info *in)
|
||||
((unsigned)hdr[24] << 16) | ((unsigned)hdr[25] << 24));
|
||||
if (hh < 0) hh = -hh; /* 自底向上 BMP */
|
||||
if (ww <= 0 || hh <= 0) return 0;
|
||||
unsigned char ex[4]; /* biPlanes(2) / biBitCount(2) */
|
||||
unsigned char ex[8]; /* biPlanes(2) / biBitCount(2) / biCompression(4) */
|
||||
int bpp = 24;
|
||||
if (ps_read(s, ex, sizeof ex)) bpp = (int)((unsigned)ex[2] | ((unsigned)ex[3] << 8));
|
||||
int have_ex = ps_read(s, ex, sizeof ex);
|
||||
if (have_ex) bpp = (int)((unsigned)ex[2] | ((unsigned)ex[3] << 8));
|
||||
in->w = ww;
|
||||
in->h = hh;
|
||||
in->codec = MR_IMG_CODEC_BMP;
|
||||
in->channels = bpp >= 8 ? bpp / 8 : 4; /* stb 一律解到 RGBA 目标 */
|
||||
in->progressive = 0;
|
||||
in->planes = 0; /* stb 直接写 RGBA 目标,不另开源缓冲 */
|
||||
/* M27:后端支持判定(DIB≥40 / BI_RGB / 8·16·24·32bpp)。字段读不全就保持未知 → stb。 */
|
||||
in->dib = (int)((unsigned)hdr[14] | ((unsigned)hdr[15] << 8) |
|
||||
((unsigned)hdr[16] << 16) | ((unsigned)hdr[17] << 24));
|
||||
if (have_ex) {
|
||||
in->depth = bpp;
|
||||
in->compression = (int)((unsigned)ex[4] | ((unsigned)ex[5] << 8) |
|
||||
((unsigned)ex[6] << 16) | ((unsigned)ex[7] << 24));
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -387,6 +416,259 @@ int mr_probe_size(const char *path, int *w, int *h)
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* ============================================================ */
|
||||
/* 解码峰值模型 + 内存门控(M27:唯一实现) */
|
||||
/* ------------------------------------------------------------
|
||||
* 阅读器与页源都只调 reader_estimate_peak()——模型口径、预算策略、门控顺序都只在这里
|
||||
* 写一份。两个模型:
|
||||
* 后端(M23):峰值 = 目标图字节 + 后端工作面(JPEG 分量平面 / PNG·BMP 行缓冲)+ 固定开销。
|
||||
* 与源图分辨率/压缩体积无关;命中即放行(不做内存门控)。
|
||||
* stb(回退):M22 的真实分配模型(planes + 抽取输出 + 精修/落框临时图)。
|
||||
* 只有这类页参与 3MB 峰值预算与 MemAvailable < 6MB 门控。
|
||||
* ============================================================ */
|
||||
#define MR_PEAK_CAP_BYTES (3LL * 1024 * 1024) /* 常态单次解码峰值上限 */
|
||||
#define MR_PEAK_HARD_BYTES (12LL * 1024 * 1024) /* MemFree 富余时的绝对上限 */
|
||||
#define MR_MEM_FREE_MARGIN_KB (4L * 1024) /* 放宽档:MemFree 需比峰值多这么多 */
|
||||
#define MR_MEM_FLOOR_KB (6L * 1024) /* 回退路径:MemAvailable 下限 */
|
||||
#define MR_DECODE_SLACK (64LL * 1024) /* 结构体/行缓冲/分配器开销余量 */
|
||||
#define MR_BACKEND_SOFT_CAP (2400LL * 1024) /* 与 meow_jpeg.c 的 2.4MB 预算同值 */
|
||||
#define MR_BACKEND_HARD_CAP (8LL * 1024 * 1024) /* meow_jpeg.c 的 MJ_PEAK_CAP */
|
||||
|
||||
long long reader_peak_budget_bytes(long memfree_kb)
|
||||
{
|
||||
long long budget = MR_PEAK_CAP_BYTES;
|
||||
if (memfree_kb >= 0) {
|
||||
long long room = (memfree_kb - MR_MEM_FREE_MARGIN_KB) * 1024;
|
||||
if (room > budget) budget = room;
|
||||
if (budget > MR_PEAK_HARD_BYTES) budget = MR_PEAK_HARD_BYTES;
|
||||
}
|
||||
return budget;
|
||||
}
|
||||
|
||||
/* 与 meow_img_target_dims / meow_img_downscale 同一公式(纯 C 层不得依赖 SDK)。 */
|
||||
static void mr_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;
|
||||
}
|
||||
|
||||
static long long mr_ceil_div(long long a, long long b) { return (a + b - 1) / b; }
|
||||
|
||||
/* 档位折算:与 meow_img_load_file_step 的 eff 计算逐字一致
|
||||
* (eff = min(maxdim, ceil(长边/step)),后端按 eff 选 DCT 阶)。 */
|
||||
static int mr_effective_dim(int longside, int step, int dim)
|
||||
{
|
||||
if (step < 1) step = 1;
|
||||
if (step > 8) step = 8;
|
||||
int eff = dim;
|
||||
int cap = (int)mr_ceil_div(longside, step);
|
||||
if (cap > 0 && cap < eff) eff = cap;
|
||||
return eff;
|
||||
}
|
||||
|
||||
/* PNG 位深 × 颜色类型 → 每像素位数(与 meow_png_stream.c 的 png_bits_of 同式)。 */
|
||||
static int mr_png_bits(int color, int depth)
|
||||
{
|
||||
switch (color) {
|
||||
case 0: return depth;
|
||||
case 2: return depth * 3;
|
||||
case 3: return depth;
|
||||
case 4: return depth * 2;
|
||||
case 6: return depth * 4;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* 后端支持矩阵(镜像 meow_jpeg.c / meow_png_stream.c 的拒绝条件;字段未知一律不支持)。
|
||||
* eff 是后端实际会收到的 maxdim(见 mr_effective_dim)。 */
|
||||
static int mr_backend_supported(const mr_img_info *in, int eff)
|
||||
{
|
||||
if (!in || in->w <= 0 || in->h <= 0 || eff <= 0) return 0;
|
||||
int longside = in->w > in->h ? in->w : in->h;
|
||||
if (longside <= eff) return 0; /* 小图走旧入口 */
|
||||
|
||||
switch (in->codec) {
|
||||
case MR_IMG_CODEC_JPEG: {
|
||||
if (in->sof != 0xC0 && in->sof != 0xC1 && in->sof != 0xC2) return 0;
|
||||
if (in->depth != 8) return 0;
|
||||
if (in->ncomp < 1 || in->ncomp > 3) return 0;
|
||||
for (int i = 0; i < in->ncomp; i++) {
|
||||
if (in->comp_h[i] < 1 || in->comp_h[i] > 4) return 0;
|
||||
if (in->comp_v[i] < 1 || in->comp_v[i] > 4) return 0;
|
||||
if (in->comp_tq[i] > 3) return 0;
|
||||
}
|
||||
int n = (int)mr_ceil_div(8LL * eff, longside);
|
||||
if (n < 1) n = 1;
|
||||
return n <= 7; /* N=8 起几乎没有缩解收益 → stb */
|
||||
}
|
||||
case MR_IMG_CODEC_PNG: {
|
||||
if (in->interlace || in->compression || in->filter) return 0;
|
||||
if (in->w > 65535 || in->h > 65535) return 0;
|
||||
if (mr_png_bits(in->color, in->depth) == 0) return 0;
|
||||
return 1;
|
||||
}
|
||||
case MR_IMG_CODEC_BMP:
|
||||
if (in->dib < 40 || in->dib > 1024) return 0;
|
||||
if (in->compression != 0) return 0; /* RLE/BITFIELDS → stb */
|
||||
return in->depth == 8 || in->depth == 16 || in->depth == 24 || in->depth == 32;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* JPEG 分量平面字节(与 meow_jpeg.c 的 mj_layout 同式,n[] 是各分量的 DCT 阶)。 */
|
||||
static long long mr_jpeg_plane_bytes(const mr_img_info *in, const int n[3])
|
||||
{
|
||||
int hmax = 1, vmax = 1;
|
||||
for (int i = 0; i < in->ncomp; i++) {
|
||||
if (in->comp_h[i] > hmax) hmax = in->comp_h[i];
|
||||
if (in->comp_v[i] > vmax) vmax = in->comp_v[i];
|
||||
}
|
||||
long long mcux = mr_ceil_div(in->w, 8LL * hmax);
|
||||
long long mcuy = mr_ceil_div(in->h, 8LL * vmax);
|
||||
long long total = 0;
|
||||
for (int i = 0; i < in->ncomp; i++) {
|
||||
long long sw = mr_ceil_div(in->w * (long long)in->comp_h[i], hmax);
|
||||
long long sh = mr_ceil_div(in->h * (long long)in->comp_v[i], vmax);
|
||||
long long ow = mr_ceil_div(sw, 8);
|
||||
long long oh = mr_ceil_div(sh, 8);
|
||||
long long bx = mcux * in->comp_h[i];
|
||||
long long by = mcuy * in->comp_v[i];
|
||||
if (bx < ow) bx = ow;
|
||||
if (by < oh) by = oh;
|
||||
total += bx * (long long)n[i] * by * (long long)n[i];
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
/* 后端峰值:目标图 + 工作面 + 固定开销。JPEG 的两步降档(色度减半、2.4MB 预算、
|
||||
* 8MB 硬上限)与 meow_jpeg.c 一致——估算因此能贴着实测,而不是靠放大余量。 */
|
||||
static long long mr_backend_peak_bytes(const mr_img_info *in, int eff, int step,
|
||||
int box_w, int box_h, int *tw, int *th)
|
||||
{
|
||||
int dim = box_w > box_h ? box_w : box_h;
|
||||
mr_target_dims(in->w, in->h, eff, tw, th);
|
||||
long long target = (long long)*tw * (*th) * 4;
|
||||
long long work = 0;
|
||||
|
||||
if (in->codec == MR_IMG_CODEC_JPEG) {
|
||||
int longside = in->w > in->h ? in->w : in->h;
|
||||
int base = (int)mr_ceil_div(8LL * eff, longside);
|
||||
if (base < 1) base = 1;
|
||||
int n[3] = { base, base, base };
|
||||
work = mr_jpeg_plane_bytes(in, n);
|
||||
/* 4:4:4 色度与亮度等大:亮度平面超过 400KB 时色度降一档(mj_sof 同式)。 */
|
||||
if (in->ncomp == 3 && in->comp_h[0] == in->comp_h[1] && in->comp_v[0] == in->comp_v[1] &&
|
||||
work / 3 > 400LL * 1024) {
|
||||
for (int i = 1; i < 3; i++) if (n[i] > 1) n[i] >>= 1;
|
||||
work = mr_jpeg_plane_bytes(in, n);
|
||||
}
|
||||
/* 平面 + 目标图超 2.4MB:先降色度(亮度保持清晰)。 */
|
||||
if (work + target > MR_BACKEND_SOFT_CAP) {
|
||||
int changed = 0;
|
||||
for (int i = 1; i < 3; i++) if (n[i] > 1) { n[i] >>= 1; changed = 1; }
|
||||
if (changed) work = mr_jpeg_plane_bytes(in, n);
|
||||
}
|
||||
/* 最后一道防线:异常巨图整体降档(最多 N=1)。 */
|
||||
while (work + target > MR_BACKEND_HARD_CAP && base > 1) {
|
||||
base >>= 1;
|
||||
for (int i = 0; i < 3; i++) if (n[i] > base) n[i] = base;
|
||||
work = mr_jpeg_plane_bytes(in, n);
|
||||
}
|
||||
} else if (in->codec == MR_IMG_CODEC_PNG) {
|
||||
long long rowbytes = (in->w * (long long)mr_png_bits(in->color, in->depth) + 7) / 8;
|
||||
work = 3 * rowbytes + 1; /* line + prev + cur(png_stream 同式) */
|
||||
work += 48LL * 1024; /* inflate 状态余量(zlib 窗口等) */
|
||||
} else { /* BMP */
|
||||
int bpp = in->depth > 0 ? in->depth : 24;
|
||||
long long rowbytes = (in->w * (long long)bpp + 31) / 32 * 4;
|
||||
work = rowbytes; /* 单行 seek 缓冲 */
|
||||
work += 48LL * 1024; /* 结构体/调色板/stdio 余量 */
|
||||
}
|
||||
|
||||
long long peak = target + work + MR_DECODE_SLACK;
|
||||
|
||||
/* 阅读器在「粗档位 + 输出尺寸与显示框不一致」时还会拷一张显示框大小的图;
|
||||
* 条件与 reader_load_page 逐字对齐,避免把常态误加一倍。 */
|
||||
if (step > 1 && dim > 0 && (in->w >= box_w || in->h >= box_h) &&
|
||||
(*tw != box_w || *th != box_h)) {
|
||||
peak += (long long)box_w * box_h * 4;
|
||||
}
|
||||
return peak;
|
||||
}
|
||||
|
||||
/* stb 回退路径峰值(M22 模型 + M27 Adam7 修正):只有这类页才参与预算门控。 */
|
||||
static long long mr_stb_peak_bytes(const mr_img_info *in, int step, int box_w, int box_h)
|
||||
{
|
||||
long long W = in->w, H = in->h;
|
||||
long long box = (long long)box_w * box_h * 4;
|
||||
long long dst;
|
||||
if (in->codec == MR_IMG_CODEC_JPEG) {
|
||||
if (step < 1) step = 1;
|
||||
dst = mr_ceil_div(W, step) * mr_ceil_div(H, step) * 4;
|
||||
} else {
|
||||
dst = W * H * 4; /* PNG/BMP:stb 无解码期缩放,整幅落地 */
|
||||
}
|
||||
long long peak = in->planes + dst; /* stb 阶段 */
|
||||
/* Adam7 交错 PNG:stb 额外整幅暂存各 pass 的 raw 与一张中间 RGBA(实测 1600×2400
|
||||
* 45MB,M22 模型只算 26MB)。多算 planes+dst 保证是上界;这类页无论预算如何都超过
|
||||
* 12MB 放宽档,结果仍是占位(安全侧)。 */
|
||||
if (in->codec == MR_IMG_CODEC_PNG && in->interlace)
|
||||
peak += in->planes + dst;
|
||||
long long finished = dst > box ? box : dst;
|
||||
if (dst + finished > peak) peak = dst + finished;
|
||||
if (finished + box > peak) peak = finished + box;
|
||||
return peak + MR_DECODE_SLACK;
|
||||
}
|
||||
|
||||
void reader_estimate_peak(const mr_img_info *in, int step, int box_w, int box_h,
|
||||
long memfree_kb, long memavail_kb, mr_decode_decision *out)
|
||||
{
|
||||
if (!out) return;
|
||||
out->path = MR_DECODE_PATH_STB;
|
||||
out->skip = MR_DECODE_OK;
|
||||
out->step = 1;
|
||||
out->tw = out->th = 0;
|
||||
out->peak_bytes = 0;
|
||||
out->budget_bytes = reader_peak_budget_bytes(memfree_kb);
|
||||
if (!in || in->w <= 0 || in->h <= 0 || box_w <= 0 || box_h <= 0) return;
|
||||
|
||||
if (step < 1) step = 1;
|
||||
if (step > 8) step = 8;
|
||||
out->step = step;
|
||||
|
||||
int dim = box_w > box_h ? box_w : box_h;
|
||||
int longside = in->w > in->h ? in->w : in->h;
|
||||
int eff = mr_effective_dim(longside, step, dim);
|
||||
|
||||
/* 1) 先看后端能否接住:命中 → 直接放行(后端峰值与源尺寸无关,不需要门控)。 */
|
||||
if (mr_backend_supported(in, eff)) {
|
||||
out->path = MR_DECODE_PATH_BACKEND;
|
||||
out->peak_bytes = mr_backend_peak_bytes(in, eff, step, box_w, box_h,
|
||||
&out->tw, &out->th);
|
||||
out->skip = MR_DECODE_OK;
|
||||
return;
|
||||
}
|
||||
|
||||
/* 2) 回退 stb:只有这里才做 MemAvailable 与峰值预算门控。 */
|
||||
if (longside <= dim) { out->tw = in->w; out->th = in->h; }
|
||||
else mr_target_dims(in->w, in->h, dim, &out->tw, &out->th);
|
||||
out->peak_bytes = mr_stb_peak_bytes(in, step, box_w, box_h);
|
||||
if (memavail_kb >= 0 && memavail_kb < MR_MEM_FLOOR_KB)
|
||||
out->skip = MR_DECODE_SKIP_LOWMEM;
|
||||
else if (out->peak_bytes > out->budget_bytes)
|
||||
out->skip = MR_DECODE_SKIP_PEAK;
|
||||
else
|
||||
out->skip = MR_DECODE_OK;
|
||||
}
|
||||
|
||||
/* ============================================================ */
|
||||
/* mr_book_ops 适配器(目录册) */
|
||||
/* ============================================================ */
|
||||
|
||||
Reference in new issue
Block a user