真机 dmesg 实锤:OOM Kill ncm (score 460, anon-rss 26.9MB, 设备 56MB)。 画像:封面全分辨率 RGBA 8.28MB(1448x1448 原生解码驻留) 是最大可控块。 瘦身(按收益排序): 1. sdk meow_ui_img 新增纯增量 meow_img_downscale + cover.c cover_cap(≤480px) → 封面 8.28MB→0.78MB,-7.65MB(mem_sim 实测 PASS)。 2. ncm_api.c 瞬态响应 rbuf:song_url→64KB、lyric/song_detail/cloudsearch/user_playlist→256KB (detail/track_all 保 1MB)。 3. ncm_app_internal.h 新增 ncm_thread_create(显式 128KB 栈),替换全部 14 处 pthread_create。 4. 滑动窗口/meta 复核零回退(window_sim:608B/首,6000 首 280KB 恒定)。 验证:mem_sim 封面收益 PASS + host home/player 渲染 OK + stream_sim/player_flow_sim/ window_sim/search_sim/cache_evict_sim 全 PASS + test_ncm_core host+mipsel(qemu) PASS + build_ncm.sh -Wall -Wextra 0 warning。真机 scp 播放在跑确认(缓存曲,0 播放请求)。 新 ncm md5 44fa16fe(text 1015150/bss 1362500)。正式部署留主会话。
155 lines
5.6 KiB
C
155 lines
5.6 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);
|
||
}
|
||
|
||
void meow_img_free(meow_img *im)
|
||
{
|
||
if (!im) return;
|
||
if (im->rgba) stbi_image_free(im->rgba);
|
||
free(im);
|
||
}
|
||
|
||
int meow_img_w(meow_img *im) { return im ? im->w : 0; }
|
||
int meow_img_h(meow_img *im) { return im ? im->h : 0; }
|
||
|
||
/* 最近邻采样缩放(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);
|
||
}
|