公开仓 ~/Proj/MeowOS-SDK 的 sdk/ 与主仓 patch_v2/sdk 无共同 git 历史(subtree split 重写了提交哈希,不做跨仓 merge/subtree pull),按内容逐文件裁决。公开仓侧的真增量只有 commit 21bffa1(图片预算解码)及其依赖;其余差异是公开仓的自包含打包(扁平 include/、 vendored 头与 meow_font.c)或主仓早已超越的旧快照。 一、取公开侧(纯追加,主仓该文件无独有改动) - stb_image.h:新增 stbi_load_scaled / stbi_load_scaled_from_memory——JPEG 输出级按 步长抽取行/列,整幅原尺寸输出缓冲从不物化;进升级 JPEG 各分量系数 IDCT 进像素平面后 立即释放。scale<=1 与旧 API 等价,非 JPEG 忽略。主仓版本的 diff 全部是公开侧新增 (无主仓独有 hunk),故整体替换。 - meow_ui_img.c:追加 img_pick_scale / img_finish_dim / meow_img_probe_file / meow_img_load_file_dim / meow_img_load_mem_dim。既有加载入口一字未改。 - include/meow_ui_image.h:追加上述三个公共声明。 - 新增 test_meow_img_budget.c + test_meow_img_budget_gen.py(--wrap malloc/free 打点 统计瞬时峰值,B1-B9 覆盖矩阵)。 二、取主仓(公开侧为旧快照或自包含适配,并入会回退功能) - meow_ui_ime.c / test_ime_fix.c:主仓 bug3 修复(IME 打开瞬间 200ms TAP 冷却窗 + drain/40ms/再 drain)及其回归用例为公开侧所无;公开侧同名文件不含该修复。 - meow_ui_input.c / meow_ui_internal.h:主仓 meow_input_touch_reset(drain 与叠加抑制 进入沿共用)、FreeType 渲染/字形声明、meow_time_ms_from_parts。公开侧此三处无独有声明 可并,取主仓即并集。 - meow_ui_gfx.c / meow_ui_sys.c / meow_cachegov.c:主仓时钟 ABI 根因修复(o32 time32, meow_ui_time.c 链接期接管 __clock_gettime*)已把这些文件的本地 now_ms 统一为 meow_ui_time_ms();公开侧仍是各自 clock_gettime,回落即回退根因修复。 - meow_ui_dialog.c / meow_select.c:主仓 MD3 后续(对话框按钮胶囊 MEOW_SHAPE_FULL、 bottom sheet 顶角 28)晚于公开侧快照,公开侧等于旧版。 - include/meow_ui_font.h:主仓多出 FreeType 段与 stdint 自洽 include,公开侧缺。 - build_sdk.sh / build_sdk_host.sh:主仓含 FreeType vendor 裁剪构建、meow_ui_time TU、 launcher meow_font.a 打包;公开侧为自包含重写,不回流。 - 其余 test_*.c:差异仅为编译配方注释(公开仓用 -Iinclude、无 meow_ui_time.c、标注 「需主仓组件故公开仓跳过」等),对主仓目录布局不成立,不改。 三、不引入(vendored 副本 / 打包产物) - meow_font.c、include/meow_font.h、include/meow_ipc.h、include/meow_notif.h:公开仓 为自包含 vendored 副本(meow_font.c 与主仓 font/meow_font.c 逐字节相同),主仓已有 权威源,复制会制造双源。 裁决说明(两边都改过的文件): - test_meow_ui_select.c:公开仓把 T1 探针从 x=10 改到 x=12(commit 432875d)。主仓 x=10 在本树实测 6/6 PASS(容器卡左缘 8、文本左缘 24,x=10 已在卡内且避开圆角),无失败 可修,故不跟进该改动,避免无谓 churn。 - 公开侧「meow_media_playctl」一类 API 名:仅出现在公开仓 vendored 头/文档,主仓 sdk 无对应实现,未硬造。 四、验证(worktree / 分支 feat/sdk-absorb-public-deltas) - build_sdk_host.sh --verify:0 warning / 0 error;成员清单 76 个(OBJS 32 + meow_font.o + freetype 43)一致,全部 ELF32 MIPS32 rel2 (o32)。 - nm 确认 meow_ui.a 导出 meow_img_probe_file / meow_img_load_file_dim / meow_img_load_mem_dim / stbi_load_scaled / stbi_load_scaled_from_memory。 - 宿主测试:对「未改动的 main SDK」跑同一套矩阵做基线对比,40 项同名结果逐项完全一致 (22 PASS / 11 FAIL / 7 建链失败,失败项与失败计数一字不差);本提交新增 test_meow_img_budget 为 PASS。补充按各测试正确配方单编:test_meow_img_color、 test_meow_lyric、test_meow_lyric_view、test_meow_ui_ft(显式传入 MiSans 字体)、 test_umbrella 均通过/0 warning。 - 既有失败全部是 Termux 环境问题,与本次合并无关(基线同样失败):/tmp 不可写导致 mkdtemp/fopen/bind EACCES(config/vol/notif_open/notify_ipc/notify_integration/ sb_collapse/overlay_suppress/input_keys),audiod 守护不在场(ui_audio_host), test_sdk_fixes 阻塞循环超时(基线同样 124)。 - hello_app 用新 meow_ui.a 重链成功(0 warning,静态 MIPS32r2),可执行文件 471380→473636 字节(+2256,新解码路径)。 五、影响 meow_ui.a 内容变化(新增 4 个 MEOW_API + stb 解码路径改变)→ 需重编重链 hello / files / reader / ncm 四 App 与 meow_launcherd。
221 lines
8.3 KiB
C
221 lines
8.3 KiB
C
/*
|
||
* meow_ui_img.c — 图片层(B1.2)。基于 stb_image(单头文件、静态编入),
|
||
* 解 PNG/JPEG/BMP → RGBA,统一最近邻缩放/居中裁方。
|
||
*/
|
||
#include "meow_ui.h"
|
||
#include "meow_ui_internal.h"
|
||
#include "stb_image.h"
|
||
|
||
#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),退回整幅解码后精修——此时本就接近目标尺寸,无需牺牲。 */
|
||
static int 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); /* 不限尺寸 = 旧入口 */
|
||
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, img_pick_scale(longside, maxdim));
|
||
return img_finish_dim(p, w, h, maxdim);
|
||
}
|
||
|
||
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);
|
||
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,
|
||
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);
|
||
}
|