Files
MeowOS-SDK/sdk/meow_ui_img.c
T
AskaEth 1725af631b 图库: 门控模型接真缩解后端 + _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。
2026-11-06 11:46:16 +08:00

294 lines
12 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_ui_img.c — 图片层(B1.2)。解码:M23 真缩解后端(meow_jpeg.c / meow_png_stream.c)
* 优先,不支持/失败回退 stb_image(单头文件、静态编入);统一输出 RGBA,最近邻缩放/居中裁方。
*/
#include "meow_ui.h"
#include "meow_ui_internal.h"
#include "stb_image.h"
#include "meow_jpeg.h" /* M23 真缩解后端(内部头,不对外导出) */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct meow_img {
int w, h;
unsigned char *rgba; /* 4 通道 straight alpha */
};
static meow_img *img_alloc(int w, int h, unsigned char *rgba)
{
if (w <= 0 || h <= 0 || !rgba) { if (rgba) stbi_image_free(rgba); return NULL; }
meow_img *im = (meow_img *)malloc(sizeof *im);
if (!im) { stbi_image_free(rgba); return NULL; }
im->w = w; im->h = h; im->rgba = rgba;
return im;
}
meow_img *meow_img_load_mem(const void *buf, int len)
{
if (!buf || len <= 0) return NULL;
int w = 0, h = 0, c = 0;
unsigned char *p = stbi_load_from_memory((const unsigned char *)buf, len, &w, &h, &c, 4);
if (!p) return NULL;
return img_alloc(w, h, p);
}
meow_img *meow_img_load_file(const char *path)
{
if (!path) return NULL;
int w = 0, h = 0, c = 0;
unsigned char *p = stbi_load(path, &w, &h, &c, 4);
if (!p) return NULL;
return img_alloc(w, h, p);
}
/* 追加(M12):预算解码。选整数缩解档位 s ∈ {1,2,4,8}——取满足「长边/s ≥ maxdim」的最大档,
* 保证缩解结果不欠采样(不丢细节),再由 meow_img_downscale 精修到 maxdim。长边 < 2*maxdim
* 时无档可用(s=1),退回整幅解码后精修——此时本就接近目标尺寸,无需牺牲。 */
int meow_img_pick_scale(int longside, int maxdim)
{
int s = 1;
while (s < 8 && (longside / (s * 2)) >= maxdim) s *= 2;
return s;
}
/* 追加(M12):缩解结果落地 + 需要时精修。p 所有权转交。 */
static meow_img *img_finish_dim(unsigned char *p, int w, int h, int maxdim)
{
if (!p) return NULL;
meow_img *im = img_alloc(w, h, p);
if (!im) return NULL;
if (im->w <= maxdim && im->h <= maxdim) return im;
meow_img *small = meow_img_downscale(im, maxdim);
if (!small) return im; /* 精修失败:缩解结果已小于原图,仍可用 */
meow_img_free(im);
return small;
}
int meow_img_probe_file(const char *path, int *w, int *h, int *channels)
{
int pw = 0, ph = 0, pc = 0;
if (!path || !stbi_info(path, &pw, &ph, &pc)) return 0;
if (w) *w = pw;
if (h) *h = ph;
if (channels) *channels = pc;
return 1;
}
meow_img *meow_img_load_file_dim(const char *path, int maxdim)
{
if (!path) return NULL;
if (maxdim <= 0) return meow_img_load_file(path); /* 不限尺寸 = 旧入口 */
/* 追加(M23):真缩解后端直接产出目标尺寸(JPEG 按 DCT 档位;PNG 逐行 inflate 丢行丢列;
* BMP 按行 seek 跳采样)。峰值只与输出尺寸相关。
* 不支持/失败/几乎等尺寸(4 分量 CMYK、算术编码、交错 PNG、RLE BMP、小图…)返回 0 →
* 落回下面的 stb 旧路径,对外语义与档位表现不变。 */
{
unsigned char *bp = NULL;
int bw = 0, bh = 0;
if (meow_img_backend_file(path, maxdim, &bw, &bh, &bp) && bp)
return img_finish_dim(bp, bw, bh, maxdim);
}
int w = 0, h = 0, c = 0;
if (!stbi_info(path, &w, &h, &c)) return NULL;
int longside = w > h ? w : h;
if (longside <= maxdim) return meow_img_load_file(path); /* 小图不动:旧路径 */
unsigned char *p = stbi_load_scaled(path, &w, &h, &c, 4, meow_img_pick_scale(longside, maxdim));
return img_finish_dim(p, w, h, maxdim);
}
/* 追加(M22):档位由调用方指定。与 meow_img_load_file_dim 同语义(结果长边 ≤ maxdim,
* 超了再精修一次),只是档位不再由 maxdim 反推 —— 内存预算紧时可选更粗的档位。
* M27:真缩解后端优先(step 折算成目标长边 ceil(长边/step),后端按该长边选 DCT 阶,
* 峰值只与输出尺寸相关);后端不支持时回退 stbi_load_scaled(step),语义与 M22 一致。 */
meow_img *meow_img_load_file_step_ex(const char *path, int maxdim, int step, int *used_backend)
{
if (used_backend) *used_backend = 0;
if (!path) return NULL;
if (step <= 1 && maxdim <= 0) return meow_img_load_file(path);
if (step <= 0) step = 1;
if (step > 8) step = 8;
int w = 0, h = 0, c = 0;
if (!stbi_info(path, &w, &h, &c)) return NULL;
int longside = w > h ? w : h;
if (step == 1 && maxdim <= 0) return meow_img_load_file(path);
/* 追加(M23/M27):真缩解后端优先(JPEG/PNG/BMP 全部格式)。step 是「输出别比这个档位
* 更细」的提示:折算成目标长边 ceil(长边/step) 传给后端(后端按该长边自动选 DCT 阶),
* 与下面 stbi_load_scaled(step) 的语义一致,峰值只与输出尺寸相关;maxdim<=0 时不精修,
* 结果仍是档位尺寸。后端不支持(CMYK/算术编码/交错/RLE…)时回退 stb,行为与 M22 一致。
* M27 起阅读器的峰值模型已与后端同口径(page_source 的 reader_estimate_peak),
* 所以 PNG/BMP 也一并接后端——这正是「修好了却用不上」的最后一处缝。 */
{
int eff = maxdim > 0 ? maxdim : longside;
int step_cap = (longside + step - 1) / step;
unsigned char *bp = NULL;
int bw = 0, bh = 0;
if (step_cap > 0 && step_cap < eff) eff = step_cap;
if (eff > 0 && meow_img_backend_file(path, eff, &bw, &bh, &bp) && bp) {
if (used_backend) *used_backend = 1;
return img_finish_dim(bp, bw, bh, maxdim > 0 ? maxdim : longside);
}
}
unsigned char *p = stbi_load_scaled(path, &w, &h, &c, 4, step);
/* maxdim<=0:不精修(保留档位输出的原始尺寸)。 */
return img_finish_dim(p, w, h, maxdim > 0 ? maxdim : longside);
}
meow_img *meow_img_load_file_step(const char *path, int maxdim, int step)
{
return meow_img_load_file_step_ex(path, maxdim, step, NULL);
}
/* 追加(M22):精确重采样到目标尺寸(最近邻,双向)。阅读器把缩解档位的结果一次性
* 落到显示框,之后命中页就是单次原生 blit(M17 的热路径纪律不被粗档位破坏)。 */
static unsigned char *scale_buf(const meow_img *im, int dw, int dh, int cover);
meow_img *meow_img_resample(meow_img *im, int w, int h)
{
if (!im || w <= 0 || h <= 0) return NULL;
if (im->w == w && im->h == h) return NULL;
unsigned char *tmp = scale_buf(im, w, h, 0);
if (!tmp) return NULL;
return img_alloc(w, h, tmp);
}
meow_img *meow_img_load_mem_dim(const void *buf, int len, int maxdim)
{
if (!buf || len <= 0) return NULL;
if (maxdim <= 0) return meow_img_load_mem(buf, len);
/* 追加(M23):同文件入口,后端直接从调用方缓冲解码(不再整幅解 + 重采样)。 */
{
unsigned char *bp = NULL;
int bw = 0, bh = 0;
if (meow_img_backend_mem(buf, (size_t)len, maxdim, &bw, &bh, &bp) && bp)
return img_finish_dim(bp, bw, bh, maxdim);
}
int w = 0, h = 0, c = 0;
if (!stbi_info_from_memory((const unsigned char *)buf, len, &w, &h, &c)) return NULL;
int longside = w > h ? w : h;
if (longside <= maxdim) return meow_img_load_mem(buf, len);
unsigned char *p = stbi_load_scaled_from_memory((const unsigned char *)buf, len,
&w, &h, &c, 4,
meow_img_pick_scale(longside, maxdim));
return img_finish_dim(p, w, h, maxdim);
}
void meow_img_free(meow_img *im)
{
if (!im) return;
if (im->rgba) stbi_image_free(im->rgba);
free(im);
}
int meow_img_w(const meow_img *im) { return im ? im->w : 0; }
int meow_img_h(const meow_img *im) { return im ? im->h : 0; }
/* 访问器:取 RGBA 像素指针(meow_img 对外 opaque,本函数为唯一像素读入口;供取色/直读像素)。
* NULL 返回 NULL。 */
const unsigned char *meow_img_pixels(const meow_img *im) { return im ? im->rgba : NULL; }
/* 最近邻采样缩放(cover=1 居中裁方),返回 dw*dh*4 临时 RGBA 缓冲 */
static unsigned char *scale_buf(const meow_img *im, int dw, int dh, int cover)
{
if (!im || dw <= 0 || dh <= 0) return NULL;
unsigned char *out = (unsigned char *)malloc((size_t)dw * dh * 4);
if (!out) return NULL;
float s = 1.0f;
if (cover) {
/* 真正铺满(cover):每目标像素对应 s 个源像素,s = min(W/dw, H/dh)——缩放后图像
* 宽/高两维都不小于目标(可能外溢),居中裁方。居中裁剪偏移(源像素)= (W-dw*s)/2
* 恒 ≥ 0。曾误作 `s = max(sx,sy)` 且采样 `(dx+ox)/s`(除以缩放比):把整块输出映射到
* 源左上角 ~dw/s 像素的透明角——图标(透明角 logo)整块透明、不绘制,即
* 「icon: ... OK 但渲染不出(只显首字母占位)」根因。正确:源索引 = dx*s + 裁剪偏移
* (与下方非 cover 分支 `dx*im->w/dw` 同一乘向)。 */
float sx = (float)im->w / dw, sy = (float)im->h / dh;
s = sx < sy ? sx : sy; /* 源像素/目标像素(铺满 → 取小) */
float oxs = ((float)im->w - (float)dw * s) * 0.5f; /* 居中裁剪起点(源像素,≥0) */
float oys = ((float)im->h - (float)dh * s) * 0.5f;
if (oxs < 0) oxs = 0;
if (oys < 0) oys = 0;
for (int dy = 0; dy < dh; dy++) {
int syi = (int)((float)dy * s + oys);
if (syi < 0) syi = 0;
if (syi >= im->h) syi = im->h - 1;
for (int dx = 0; dx < dw; dx++) {
int sxi = (int)((float)dx * s + oxs);
if (sxi < 0) sxi = 0;
if (sxi >= im->w) sxi = im->w - 1;
memcpy(out + ((size_t)dy * dw + dx) * 4,
im->rgba + ((size_t)syi * im->w + sxi) * 4, 4);
}
}
} else {
for (int dy = 0; dy < dh; dy++) {
int syi = (int)((float)dy * im->h / dh);
if (syi >= im->h) syi = im->h - 1;
for (int dx = 0; dx < dw; dx++) {
int sxi = (int)((float)dx * im->w / dw);
if (sxi >= im->w) sxi = im->w - 1;
memcpy(out + ((size_t)dy * dw + dx) * 4,
im->rgba + ((size_t)syi * im->w + sxi) * 4, 4);
}
}
}
return out;
}
/* 内存瘦身:把大图按最近邻等比缩放到「最长边 ≤ maxdim」,返回新图;调用方负责
* meow_img_free 旧图 + 新图。原图已 ≤maxdim → 返回 NULL(无需缩放)。
* 典型:网易云全尺寸封面 1448×1448 RGBA=8MB,显示只需 ≤480px(屏幕 480 宽)→
* 480×480=0.9MB,省 ~7MB anon(设备 RAM 仅 56MB,OOM 真机被杀)。
* 复用 scale_buf(cover=0,最近邻锁比例),不做插值(App 封面/背景无需高质量缩放)。 */
meow_img *meow_img_downscale(meow_img *im, int maxdim)
{
if (!im || maxdim <= 0) return NULL;
if (im->w <= maxdim && im->h <= maxdim) return NULL; /* 已够小,不缩放 */
int dw, dh;
if (im->w >= im->h) {
dw = maxdim;
dh = (int)((long)im->h * maxdim / im->w);
} else {
dh = maxdim;
dw = (int)((long)im->w * maxdim / im->h);
}
if (dw < 1) dw = 1;
if (dh < 1) dh = 1;
unsigned char *tmp = scale_buf(im, dw, dh, 0);
if (!tmp) return NULL;
return img_alloc(dw, dh, tmp);
}
void meow_ui_img(int x, int y, meow_img *im)
{
if (!im) return;
meow_gfx_blit_rgba(x, y, im->w, im->h, im->rgba, 255);
}
void meow_ui_img_scaled(int x, int y, int w, int h, meow_img *im)
{
if (!im) return;
unsigned char *tmp = scale_buf(im, w, h, 0);
if (!tmp) return;
meow_gfx_blit_rgba(x, y, w, h, tmp, 255);
free(tmp);
}
void meow_ui_img_alpha(int x, int y, int w, int h, meow_img *im, int alpha)
{
if (!im) return;
unsigned char *tmp = scale_buf(im, w, h, 0);
if (!tmp) return;
meow_gfx_blit_rgba(x, y, w, h, tmp, alpha);
free(tmp);
}
void meow_ui_img_cover(int x, int y, int size, meow_img *im)
{
if (!im) return;
unsigned char *tmp = scale_buf(im, size, size, 1);
if (!tmp) return;
meow_gfx_blit_rgba(x, y, size, size, tmp, 255);
free(tmp);
}