feat(lyric): 批5 封面双色渐变取色 — SDK meow_img_color纯整数分桶主色+次色(副采样≤3.6k零分配)+launcher播放页/歌词页set_colors注入渐变主题+无封面回退全局主题+meow_lyric_view灰阶随覆盖cur修正;宿主C1-C7全过,0warning,未并入meow_ui.a
This commit is contained in:
1 parent
a3679e3688
commit
1f6d390e1c
6 files changed
+541
-22
No files matched your search
@@ -29,7 +29,7 @@ SYSROOT="$ROOT/.tools/mipsel-linux-muslsf-cross/mipsel-linux-muslsf"
|
||||
font_theme.c keys.c util.c meow_dep.c \
|
||||
meow_touch.c meow_settings.c meow_notif.c meow_media.c meow_bgplay.c \
|
||||
meow_ams_start.c lock_screen.c lock_screen_ui.c lock_settings.c \
|
||||
"../sdk/meow_lyric.c" "../sdk/meow_lyric_view.c" \
|
||||
"../sdk/meow_lyric.c" "../sdk/meow_lyric_view.c" "../sdk/meow_img_color.c" \
|
||||
"../sdk/meow_ui.a" "$SYSROOT/lib/libz.a" -lpthread -o meow_launcherd
|
||||
echo "== meow_launcherd =="
|
||||
ls -la meow_launcherd
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "meow_launcher_internal.h"
|
||||
#include "np_gesture.h" /* 批4:双页方向感知手势决策(header-only,宿主可单测) */
|
||||
#include "meow_lyric_view.h" /* 批2控件:歌词页渲染(批4接入,数据批3 meow_media_lyric_*) */
|
||||
#include "meow_img_color.h" /* 批5:封面双色取色原语(独立源码,不并入 meow_ui.a) */
|
||||
|
||||
|
||||
#define NP_COVER 300 /* 封面边长(方,居中) */
|
||||
@@ -66,6 +67,56 @@ static void np_cover_load(const meow_media_session_t *s)
|
||||
g_np_cover = meow_img_load_file(path);
|
||||
}
|
||||
|
||||
/* ==================================================================
|
||||
* 批5:封面双色取色(主色+次色)→ 播放页/歌词页动态主题。
|
||||
* - 何时取:进入 Now Playing 页 / 封面路径变化时(np_cover_theme_update 幂等,按路径缓存)。
|
||||
* - 取色成本:meow_img_duocolor 内部自适副采样(等效缩略图,≤~3.6k 样本),不额外解码/分配。
|
||||
* - 缓存:按 artwork 路径;路径未变不重取(g_np_cc_valid + g_np_cc_path)。
|
||||
* - 回退:无封面/取色失败(g_np_cc_valid=0)→ 歌词页 set_colors(NULL) 用 meow_theme 默认;
|
||||
* 播放页背景/文字走 s_ui_theme() 主题面。
|
||||
* ================================================================== */
|
||||
static meow_img_color_theme g_np_cc;
|
||||
static int g_np_cc_valid = 0;
|
||||
static char g_np_cc_path[200] = "";
|
||||
|
||||
/* 校验/刷新取色缓存(封面路径变化才重取;幂等)。 */
|
||||
static void np_cover_theme_update(void)
|
||||
{
|
||||
const meow_media_session_t *s = meow_media_current();
|
||||
const char *art = s ? s->artwork : "";
|
||||
if (g_np_cc_valid && strcmp(g_np_cc_path, art) == 0) return; /* 缓存命中 */
|
||||
snprintf(g_np_cc_path, sizeof g_np_cc_path, "%s", art);
|
||||
g_np_cc_valid = 0;
|
||||
if (!g_np_cover || !art[0]) return; /* 无封面 → 回退主题 */
|
||||
uint32_t mainc = 0, secondc = 0;
|
||||
if (meow_img_duocolor(g_np_cover, &mainc, &secondc) != 0) return; /* 取色失败 → 回退主题 */
|
||||
meow_img_color_derive(mainc, secondc, meow_theme_get(), &g_np_cc);
|
||||
g_np_cc_valid = 1;
|
||||
}
|
||||
|
||||
/* 把封面取色注入歌词页(无取色 → NULL 回退 meow_theme 默认)。 */
|
||||
static void np_apply_lyric_colors(void)
|
||||
{
|
||||
if (!g_np_lyric) return;
|
||||
meow_lyric_view_set_colors(g_np_lyric, g_np_cc_valid ? &g_np_cc.lyr : NULL);
|
||||
}
|
||||
|
||||
/* 竖向双色渐变填充(无 GPU,逐行整数插值;封面主题背景用)。 */
|
||||
static void np_fill_gradient(int x, int y, int w, int h, uint32_t top, uint32_t bottom)
|
||||
{
|
||||
if (w <= 0 || h <= 0) return;
|
||||
int tr = (top >> 16) & 255, tg = (top >> 8) & 255, tb = top & 255;
|
||||
int br = (bottom >> 16) & 255, bg = (bottom >> 8) & 255, bb = bottom & 255;
|
||||
int span = h - 1;
|
||||
for (int yy = 0; yy < h; yy++) {
|
||||
int t = span > 0 ? yy * 255 / span : 0;
|
||||
int r = tr + ((br - tr) * t + 128) / 255;
|
||||
int g = tg + ((bg - tg) * t + 128) / 255;
|
||||
int b = tb + ((bb - tb) * t + 128) / 255;
|
||||
fill_rect(x, y + yy, w, 1, ((uint32_t)r << 16) | ((uint32_t)g << 8) | (uint32_t)b);
|
||||
}
|
||||
}
|
||||
|
||||
#if 0 /* ================================================================
|
||||
* M7.0b 退役:Now Playing 手绘控制钮/进度条改走 SDK 控件(meow_iconbtn +
|
||||
* meow_progress),命中/状态/进度全由 SDK 接管;保留作回退位(删除 #if 0 行恢复旧手绘)。
|
||||
@@ -166,13 +217,14 @@ static void np_widgets_create(void)
|
||||
g_np_init = 1;
|
||||
}
|
||||
|
||||
/* 主题刷新:播放钮底色/图标随当前主题(明暗/风格切换后跟手)。 */
|
||||
/* 主题刷新:播放钮底色/图标随当前主题(明暗/风格切换后跟手);批5 封面取色时随 accent。 */
|
||||
static void np_refresh_theme(void)
|
||||
{
|
||||
if (!g_np_init) return;
|
||||
const meow_theme *th = meow_theme_get();
|
||||
meow_widget_opts op; memset(&op, 0, sizeof op);
|
||||
op.bg = th->primary; op.fg = th->onPrimary;
|
||||
op.bg = g_np_cc_valid ? g_np_cc.accent : th->primary;
|
||||
op.fg = g_np_cc_valid ? g_np_cc.accent_fg : th->onPrimary;
|
||||
if (g_np_play) meow_widget_set_opts(g_np_play, &op);
|
||||
if (g_np_pause) meow_widget_set_opts(g_np_pause, &op);
|
||||
}
|
||||
@@ -201,8 +253,9 @@ static void np_draw_progress(const meow_media_session_t *s, uint32_t pos, const
|
||||
unsigned dm = (s->dur_ms ? s->dur_ms : 0) / 60000u, ds = (s->dur_ms ? s->dur_ms : 0) % 60000u / 1000u;
|
||||
snprintf(l, sizeof l, "%u:%02u", lm, ls);
|
||||
snprintf(rbuf, sizeof rbuf, "%u:%02u", dm, ds);
|
||||
ui_draw(NP_PROG_X, NP_TIME_Y, l, th->on_surface_variant, 16);
|
||||
ui_draw(NP_PROG_X + NP_PROG_W - ui_width(rbuf, 16), NP_TIME_Y, rbuf, th->on_surface_variant, 16);
|
||||
uint32_t tcol = g_np_cc_valid ? g_np_cc.fg_dim : th->on_surface_variant;
|
||||
ui_draw(NP_PROG_X, NP_TIME_Y, l, tcol, 16);
|
||||
ui_draw(NP_PROG_X + NP_PROG_W - ui_width(rbuf, 16), NP_TIME_Y, rbuf, tcol, 16);
|
||||
}
|
||||
|
||||
/* ==================================================================
|
||||
@@ -238,14 +291,20 @@ static void np_lyric_refresh(const meow_media_session_t *s)
|
||||
g_np_lyric_last_valid = valid;
|
||||
}
|
||||
|
||||
/* 歌词页整页绘制:铺 theme surface 底 → lyric_view(自绘 scrim + 歌词/空态)。 */
|
||||
/* 歌词页整页绘制:铺封面取色渐变(或 theme surface)底 → lyric_view(自绘 scrim + 歌词/空态)。 */
|
||||
static void np_draw_lyric_page(int redraw_sb)
|
||||
{
|
||||
const meow_ui_theme_t *th = s_ui_theme();
|
||||
int W = fb_xres, H = fb_yres;
|
||||
if (!g_np_init) np_widgets_create();
|
||||
if (redraw_sb) fill_rect(0, 0, W, SB_BAR_H, th->surface);
|
||||
fill_rect(0, SB_BAR_H, W, H - SB_BAR_H, th->surface);
|
||||
/* 批5:封面取色背景(主→次渐变)或主题面;并注入歌词页 set_colors。 */
|
||||
np_cover_theme_update();
|
||||
if (g_np_cc_valid)
|
||||
np_fill_gradient(0, SB_BAR_H, W, H - SB_BAR_H, g_np_cc.bg_top, g_np_cc.bg_bottom);
|
||||
else
|
||||
fill_rect(0, SB_BAR_H, W, H - SB_BAR_H, th->surface);
|
||||
np_apply_lyric_colors();
|
||||
np_lyric_refresh(meow_media_current());
|
||||
if (g_np_lyric)
|
||||
meow_widget_draw(meow_lyric_view_widget(g_np_lyric));
|
||||
@@ -264,10 +323,10 @@ static void np_draw_page(int redraw_sb)
|
||||
return;
|
||||
}
|
||||
if (redraw_sb) fill_rect(0, 0, W, SB_BAR_H, th->surface);
|
||||
fill_rect(0, SB_BAR_H, W, H - SB_BAR_H, th->surface);
|
||||
const meow_media_session_t *s = meow_media_current();
|
||||
if (!s || s->state == MEOW_MEDIA_STOPPED) {
|
||||
/* 空态占位(M7.0b 文案走 meow_str,未知 id 回退中文) */
|
||||
fill_rect(0, SB_BAR_H, W, H - SB_BAR_H, th->surface);
|
||||
const char *t1 = meow_str_zh("np.not_playing", "未在播放");
|
||||
ui_draw((W - ui_width(t1, 26)) / 2, 300, t1, th->on_surface_variant, 26);
|
||||
const char *t2 = meow_str_zh("np.open_app_hint", "打开播放 App 以显示正在播放");
|
||||
@@ -278,6 +337,14 @@ static void np_draw_page(int redraw_sb)
|
||||
return;
|
||||
}
|
||||
np_cover_load(s);
|
||||
/* 批5:封面取色(主/次色)+ 背景渐变或主题面;并注入歌词页 set_colors。
|
||||
* np_cover_theme_update 幂等(封面路径不变不重取);无封面/取色失败回退 meow_theme。 */
|
||||
np_cover_theme_update();
|
||||
if (g_np_cc_valid)
|
||||
np_fill_gradient(0, SB_BAR_H, W, H - SB_BAR_H, g_np_cc.bg_top, g_np_cc.bg_bottom);
|
||||
else
|
||||
fill_rect(0, SB_BAR_H, W, H - SB_BAR_H, th->surface);
|
||||
np_apply_lyric_colors();
|
||||
/* 封面:解码缓存 + 圆角裁方;无封面回退主题色占位 + 首字 */
|
||||
if (g_np_cover) {
|
||||
meow_gfx_fill_round(NP_COVER_X - 6, NP_COVER_Y - 6, NP_COVER + 12, NP_COVER + 12, 18, th->surface_container);
|
||||
@@ -290,11 +357,11 @@ static void np_draw_page(int redraw_sb)
|
||||
ui_draw(NP_COVER_X + (NP_COVER - cw) / 2, NP_COVER_Y + (NP_COVER - meow_font_line_height(22)) / 2,
|
||||
t, meow_theme_get()->onPrimary, 22);
|
||||
}
|
||||
/* 元数据 */
|
||||
np_center_text(NP_TITLE_Y, s->title, 26, th->on_surface, W - 40);
|
||||
np_center_text(NP_ARTIST_Y, s->artist, 18, th->on_surface_variant, W - 60);
|
||||
/* 元数据(批5:封面取色时文字随 fg/fg_dim,否则主题面) */
|
||||
np_center_text(NP_TITLE_Y, s->title, 26, g_np_cc_valid ? g_np_cc.fg : th->on_surface, W - 40);
|
||||
np_center_text(NP_ARTIST_Y, s->artist, 18, g_np_cc_valid ? g_np_cc.fg_dim : th->on_surface_variant, W - 60);
|
||||
if (s->album[0])
|
||||
np_center_text(NP_ALBUM_Y, s->album, 16, th->on_surface_variant, W - 80);
|
||||
np_center_text(NP_ALBUM_Y, s->album, 16, g_np_cc_valid ? g_np_cc.fg_dim : th->on_surface_variant, W - 80);
|
||||
/* 进度(SDK meow_progress) */
|
||||
uint32_t pos = meow_media_display_pos(s);
|
||||
np_draw_progress(s, pos, th);
|
||||
@@ -564,4 +631,6 @@ void run_now_playing(struct app *apps, int napps)
|
||||
kp_close(&kp_power);
|
||||
if (g_np_cover) { meow_img_free(g_np_cover); g_np_cover = NULL; }
|
||||
g_np_cover_path[0] = 0;
|
||||
g_np_cc_valid = 0; /* 批5:退出清取色缓存,下次进入重新取 */
|
||||
g_np_cc_path[0] = 0;
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* meow_img_color.c — MeowOS SDK 封面双色渐变取色实现(见 meow_img_color.h 契约)。
|
||||
*
|
||||
* 独立源码,不并入 meow_ui.a(铁律:避免并发重编/四 App 重链)。纯函数,无任何 meow_* 调用,
|
||||
* 宿主单测零链接重(仅需本 .c + meow_img_color.h 及其 include 链)。
|
||||
*
|
||||
* 布局耦合说明:meow_img 为 opaque(对外仅前向声明,结构体私有于 meow_ui_img.c)。为在不动
|
||||
* meow_ui.a 二进制的前提下直读封面像素(批5 取色),本 TU 按 meow_ui_img.c 自 A1 起稳定的
|
||||
* `struct meow_img { int w, h; unsigned char *rgba; };` 未布局局部补全。两 TU 各持同一定义,
|
||||
* 链接后字段偏移一致(本项目内 ABI 稳定)。主会话下次重建 SDK 时可上收为 meow_ui_img.c 的
|
||||
* 正式访问器,届时本补全即删除。
|
||||
*/
|
||||
#include "meow_img_color.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
struct meow_img { int w, h; unsigned char *rgba; }; /* 见上:局部补全(与 meow_ui_img.c 一致) */
|
||||
|
||||
#define MIC_BUCKETS 4 /* 每通道色阶数 ⇒ 4×4×4=64 桶 */
|
||||
#define MIC_NBUCKET (MIC_BUCKETS*MIC_BUCKETS*MIC_BUCKETS)
|
||||
#define MIC_ALPHA_MIN 64 /* 跳过近透明(alpha<64) */
|
||||
#define MIC_LUMA_MIN 24 /* 跳过近黑 */
|
||||
#define MIC_LUMA_MAX 244 /* 跳过近白 */
|
||||
#define MIC_MIN_VALID 8 /* 有效采样下限(低于=取色失败回退主题) */
|
||||
#define MIC_TARGET 900 /* 目标采样数(副采样步进自适,×4 作上限) */
|
||||
#define MIC_SEC_FRAC 7U /* 次桶显著门槛:≥主桶 1/7(≈14%) */
|
||||
|
||||
static int mic_luma(uint32_t c)
|
||||
{
|
||||
int r = (int)((c >> 16) & 255), g = (int)((c >> 8) & 255), b = (int)(c & 255);
|
||||
return (r * 299 + g * 587 + b * 114) / 1000;
|
||||
}
|
||||
|
||||
/* 两色线性混合(t=0..255,0=纯 a、255=纯 b;逐通道整数,无浮点)。 */
|
||||
static uint32_t mic_mix(uint32_t a, uint32_t b, int t)
|
||||
{
|
||||
if (t < 0) t = 0;
|
||||
if (t > 255) t = 255;
|
||||
int ar = (a >> 16) & 255, ag = (a >> 8) & 255, ab = a & 255;
|
||||
int br = (b >> 16) & 255, bg = (b >> 8) & 255, bb = b & 255;
|
||||
int r = ar + ((br - ar) * t + 128) / 255;
|
||||
int g = ag + ((bg - ag) * t + 128) / 255;
|
||||
int bl = ab + ((bb - ab) * t + 128) / 255;
|
||||
return ((uint32_t)r << 16) | ((uint32_t)g << 8) | (uint32_t)bl;
|
||||
}
|
||||
|
||||
int meow_img_duocolor_rgba(const uint8_t *rgba, int w, int h,
|
||||
uint32_t *main, uint32_t *second)
|
||||
{
|
||||
if (!rgba || w <= 0 || h <= 0 || !main || !second) return -1;
|
||||
|
||||
/* 副采样步进:使采样数≈MIC_TARGET(×4 上限),大图/缩略图成本恒定。 */
|
||||
int step = 1;
|
||||
while ((long)(w / step) * (long)(h / step) > (long)MIC_TARGET * 4 && step < 64)
|
||||
step++;
|
||||
|
||||
long cnt[MIC_NBUCKET];
|
||||
long sr[MIC_NBUCKET], sg[MIC_NBUCKET], sb[MIC_NBUCKET], sw[MIC_NBUCKET];
|
||||
memset(cnt, 0, sizeof cnt);
|
||||
memset(sr, 0, sizeof sr);
|
||||
memset(sg, 0, sizeof sg);
|
||||
memset(sb, 0, sizeof sb);
|
||||
memset(sw, 0, sizeof sw);
|
||||
|
||||
long total = 0;
|
||||
for (int y = 0; y < h; y += step) {
|
||||
for (int x = 0; x < w; x += step) {
|
||||
const uint8_t *p = rgba + ((size_t)y * (size_t)w + (size_t)x) * 4;
|
||||
int a = p[3];
|
||||
if (a < MIC_ALPHA_MIN) continue;
|
||||
int r = p[0], g = p[1], b = p[2];
|
||||
if ((r * 299 + g * 587 + b * 114) / 1000 < MIC_LUMA_MIN) continue; /* 近黑 */
|
||||
if ((r * 299 + g * 587 + b * 114) / 1000 > MIC_LUMA_MAX) continue; /* 近白 */
|
||||
int mx = r, mn = r;
|
||||
if (g > mx) mx = g;
|
||||
if (b > mx) mx = b;
|
||||
if (g < mn) mn = g;
|
||||
if (b < mn) mn = b;
|
||||
int sat = mx - mn; /* 饱和度 0..255 */
|
||||
long wt = 1 + sat / 24; /* 1..~11:偏向鲜艳(跳过灰蒙蒙) */
|
||||
int qr = r * MIC_BUCKETS / 256, qg = g * MIC_BUCKETS / 256, qb = b * MIC_BUCKETS / 256;
|
||||
int bi = (qr * MIC_BUCKETS + qg) * MIC_BUCKETS + qb;
|
||||
if (bi < 0 || bi >= MIC_NBUCKET) continue;
|
||||
cnt[bi] += wt;
|
||||
sr[bi] += (long)r * wt; sg[bi] += (long)g * wt; sb[bi] += (long)b * wt;
|
||||
sw[bi] += wt;
|
||||
total++;
|
||||
}
|
||||
}
|
||||
if (total < MIC_MIN_VALID) return -1; /* 全透明/近黑白 → 失败回退主题 */
|
||||
|
||||
/* 主色:加权最大桶中心 */
|
||||
int mi = -1;
|
||||
long best = -1;
|
||||
for (int i = 0; i < MIC_NBUCKET; i++)
|
||||
if (cnt[i] > best) { best = cnt[i]; mi = i; }
|
||||
if (mi < 0 || sw[mi] <= 0) return -1;
|
||||
long mr0 = sr[mi] / sw[mi], mg0 = sg[mi] / sw[mi], mb0 = sb[mi] / sw[mi];
|
||||
*main = ((uint32_t)mr0 << 16) | ((uint32_t)mg0 << 8) | (uint32_t)mb0;
|
||||
|
||||
/* 次色:与主色 RGB 色距最大的显著桶;无显著桶→主色缩暗 2/3(渐变互补)。 */
|
||||
long thresh = best > 0 ? best / MIC_SEC_FRAC : 1;
|
||||
int si = -1;
|
||||
long sd = -1;
|
||||
for (int i = 0; i < MIC_NBUCKET; i++) {
|
||||
if (i == mi || cnt[i] < thresh || sw[i] <= 0) continue;
|
||||
long dr = sr[i] / sw[i] - mr0, dg = sg[i] / sw[i] - mg0, db = sb[i] / sw[i] - mb0;
|
||||
long d = dr * dr + dg * dg + db * db;
|
||||
if (d > sd) { sd = d; si = i; }
|
||||
}
|
||||
if (si >= 0)
|
||||
*second = ((uint32_t)(sr[si] / sw[si]) << 16) | ((uint32_t)(sg[si] / sw[si]) << 8) |
|
||||
(uint32_t)(sb[si] / sw[si]);
|
||||
else
|
||||
*second = ((uint32_t)(mr0 * 2 / 3) << 16) | ((uint32_t)(mg0 * 2 / 3) << 8) |
|
||||
(uint32_t)(mb0 * 2 / 3);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int meow_img_duocolor(const meow_img *im, uint32_t *main, uint32_t *second)
|
||||
{
|
||||
if (!im) return -1;
|
||||
/* 局部补全 struct 视图直读像素(布局与 meow_ui_img.c 一致,见顶部说明)。 */
|
||||
return meow_img_duocolor_rgba(im->rgba, im->w, im->h, main, second);
|
||||
}
|
||||
|
||||
void meow_img_color_derive(uint32_t main, uint32_t second, const meow_theme *th,
|
||||
meow_img_color_theme *out)
|
||||
{
|
||||
if (!out) return;
|
||||
uint32_t mainv = main ? main : (th ? th->primary : 0x808080);
|
||||
uint32_t secv = second ? second : (th ? th->secondary : mainv);
|
||||
/* 背景平均亮度 → 文字可读色(黑/白) */
|
||||
int luavg = (mic_luma(mainv) + mic_luma(secv)) / 2;
|
||||
uint32_t fg = (luavg >= 140) ? 0x1C1C1C : 0xFFFFFF;
|
||||
uint32_t fgd = mic_mix(fg, mic_mix(mainv, 0x0A0A0A, 150), 90); /* 次要文字向背景靠(降对比) */
|
||||
/* 背景渐变:主/次各压暗 55%(防刺眼 + 保文字可读) */
|
||||
uint32_t bg_top = mic_mix(mainv, 0x0A0A0A, 150);
|
||||
uint32_t bg_bottom = mic_mix(secv, 0x0A0A0A, 150);
|
||||
/* 强调=次色(与主色底对比);其上图标按 accent 亮度选黑/白 */
|
||||
uint32_t accent = secv;
|
||||
uint32_t accent_fg = (mic_luma(accent) >= 140) ? 0x1C1C1C : 0xFFFFFF;
|
||||
|
||||
out->bg_top = bg_top; out->bg_bottom = bg_bottom;
|
||||
out->fg = fg; out->fg_dim = fgd;
|
||||
out->accent = accent; out->accent_fg = accent_fg;
|
||||
|
||||
/* 歌词页覆盖:scrim=主色压暗、cur=fg(可读)、gray 留 0(lyric_view 以 cur 自动递减)。 */
|
||||
meow_lyric_view_colors *lc = &out->lyr;
|
||||
memset(lc, 0, sizeof *lc);
|
||||
lc->scrim = mic_mix(mainv, 0x000000, 150);
|
||||
lc->cur = fg;
|
||||
lc->scrim_alpha = 176;
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* meow_img_color.h — MeowOS SDK 封面双色渐变取色(批5)。
|
||||
*
|
||||
* 定位:从歌曲封面(已解码 RGBA 像素/meow_img 句柄)提取「主色 + 次色」,供播放页/歌词页动态主题
|
||||
* (背景渐变/文字 accent)。纯 C 分桶统计,无 GPU、无浮点、无堆分配。
|
||||
*
|
||||
* 组织:独立公共头(仿 meow_lyric_view.h 模式,不随 meow_ui.h 伞头发布),取色原语是独立源码
|
||||
* meow_img_color.c——**不并入 meow_ui.a**(铁律:避免并发重编/四 App 重链)。App/launcher 需显式
|
||||
* `#include "meow_img_color.h"` 并在构建时单链 meow_img_color.c(宿主单测同法)。
|
||||
*
|
||||
* 约束(0 warning / 最小侵入):
|
||||
* - 不修改 meow_ui.a 任何成员(meow_img 保持 opaque 对外)。为直读封面像素,本 TU 按
|
||||
* meow_ui_img.c 自 A1 起稳定的 `struct meow_img { int w,h; unsigned char *rgba; }` 布局
|
||||
* 局部补全(见 .c 顶部说明)。主会话下次重建 SDK 时可上收为 meow_ui_img.c 的正式访问器。
|
||||
* - 纯函数:meow_img_duocolor_rgba / meow_img_duocolor / meow_img_color_derive 不调用任何
|
||||
* meow_* 符号,宿主单测可零链接重(仅需本头 + 本 .c)。
|
||||
*
|
||||
* 色彩语义:
|
||||
* - 输入 RGBA(stb_image 输出,4 通道 straight alpha),w×h,最多遍历 ~3.6k 采样(内部自适
|
||||
* 副采样步进,等效缩略图成本,避免全尺寸逐像素)。
|
||||
* - 色域分桶 4×4×4=64(RGB 各 4 段),跳过近黑(luma<24)/近白(luma>244)/透明(alpha<64),
|
||||
* 按「像素数 + 饱和度」加权;主色=加权最大桶中心;次色=与主色色距最大的显著桶(≥主桶 14%)
|
||||
* 中心;无显著桶→次色=主色缩暗 2/3(渐变互补)。
|
||||
* - 返回 0=成功;-1=参数非法 / 有效采样不足(调用方回退全局 meow_theme)。
|
||||
*/
|
||||
#ifndef MEOW_IMG_COLOR_H
|
||||
#define MEOW_IMG_COLOR_H
|
||||
|
||||
#include "meow_ui.h" /* meow_img 句柄 + meow_theme */
|
||||
#include "meow_lyric_view.h" /* meow_lyric_view_colors(派生歌词页覆盖) */
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* 从 RGBA 像素缓冲取主色+次色(纯算法;样本副采样)。返回 0=成功;-1=失败。 */
|
||||
int meow_img_duocolor_rgba(const uint8_t *rgba, int w, int h,
|
||||
uint32_t *main, uint32_t *second);
|
||||
|
||||
/* 从 meow_img 句柄取主色+次色(内部直读 rgba 缓冲,见 .c 布局说明)。返回 0=成功;-1=失败。 */
|
||||
int meow_img_duocolor(const meow_img *im, uint32_t *main, uint32_t *second);
|
||||
|
||||
/* 由主/次色派生播放页 + 歌词页封面主题(纯函数,可宿主单测)。
|
||||
* th 用于默认回退色(main/second 为 0 时);调用方应传 meow_theme_get()。
|
||||
* 派生语义:
|
||||
* - 背景渐变 = 主色(顶)→ 次色(底)各压暗 55%(防刺眼 + 保文字可读);
|
||||
* - fg = 可读文字色(按主次平均亮度选黑/白),fg_dim = 向背景靠拢的次要文字色;
|
||||
* - accent = 次色(与主色底对比),accent_fg = accent 上可读图标色;
|
||||
* - lyr = 歌词页 set_colors 覆盖:scrim=主色压暗(alpha 176)、cur=fg,gray 留 0(由 lyric_view
|
||||
* 以「cur 为基准自动递减」——批5 已修正对覆盖 cur 亦然)。 */
|
||||
typedef struct meow_img_color_theme {
|
||||
uint32_t bg_top; /* 播放页渐变顶 */
|
||||
uint32_t bg_bottom; /* 播放页渐变底 */
|
||||
uint32_t fg; /* 主要文字色(背景上可读) */
|
||||
uint32_t fg_dim; /* 次要文字色 */
|
||||
uint32_t accent; /* 强调色(控件/进度/封面占位) */
|
||||
uint32_t accent_fg; /* accent 上可读前景(图标) */
|
||||
meow_lyric_view_colors lyr; /* 歌词页 set_colors 覆盖 */
|
||||
} meow_img_color_theme;
|
||||
|
||||
void meow_img_color_derive(uint32_t main, uint32_t second, const meow_theme *th,
|
||||
meow_img_color_theme *out);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* MEOW_IMG_COLOR_H */
|
||||
@@ -84,20 +84,24 @@ static void lv_resolve_colors(const meow_lyric_view *lv, const meow_theme *th,
|
||||
/* 内建默认(theme 驱动) */
|
||||
uint32_t scrim = th->scrim;
|
||||
if (scrim == 0) scrim = th->surfaceDim; /* scrim 缺省为黑:用 surfaceDim 兜底(非纯黑) */
|
||||
uint32_t cur = th->onSurface; /* dark=0xFFFFFF 纯白(活动行高亮) */
|
||||
out->scrim = scrim;
|
||||
out->cur = cur;
|
||||
out->cur = th->onSurface; /* dark=0xFFFFFF 纯白(活动行高亮) */
|
||||
out->scrim_alpha = 176;
|
||||
out->gray[0] = cur;
|
||||
for (int i = 1; i < 6; i++) out->gray[i] = lv_scale_col(cur, lv_gray_fac[i]);
|
||||
|
||||
if (!lv->has_colors) return;
|
||||
/* 用户覆盖(0 字段=沿用上面内建默认;scrim_alpha<0=沿用默认 176) */
|
||||
if (!lv->has_colors) {
|
||||
out->gray[0] = out->cur;
|
||||
for (int i = 1; i < 6; i++) out->gray[i] = lv_scale_col(out->cur, lv_gray_fac[i]);
|
||||
return;
|
||||
}
|
||||
/* 用户覆盖(0 字段=沿用内建默认;scrim_alpha<0=沿用默认 176)。
|
||||
* gray 显式字段优先;未给字段以「解析后 cur」为基准自动递减(对齐头文档
|
||||
* "gray[1..5]=对 cur 逐级缩灰"——覆盖 cur 后灰阶也应随 cur,而非随主题默认 onSurface)。 */
|
||||
if (lv->colors.scrim != 0) out->scrim = lv->colors.scrim;
|
||||
if (lv->colors.cur != 0) { out->cur = lv->colors.cur; out->gray[0] = lv->colors.cur; }
|
||||
if (lv->colors.cur != 0) out->cur = lv->colors.cur;
|
||||
if (lv->colors.scrim_alpha >= 0) out->scrim_alpha = lv->colors.scrim_alpha;
|
||||
for (int i = 0; i < 6; i++)
|
||||
if (lv->colors.gray[i] != 0) out->gray[i] = lv->colors.gray[i];
|
||||
out->gray[0] = (lv->colors.gray[0] != 0) ? lv->colors.gray[0] : out->cur;
|
||||
for (int i = 1; i < 6; i++)
|
||||
out->gray[i] = (lv->colors.gray[i] != 0) ? lv->colors.gray[i]
|
||||
: lv_scale_col(out->cur, lv_gray_fac[i]);
|
||||
}
|
||||
|
||||
/* 距活动行 dist 的生效行色(dist 越界 clamp 0..5)。 */
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
/* test_meow_img_color.c — 批5 封面双色渐变取色宿主验证。
|
||||
*
|
||||
* 覆盖矩阵(对齐任务批5):
|
||||
* C1 纯色 → 主色=该色(桶平均精确);无显著次桶 → 次色=主色缩暗 2/3(渐变互补)
|
||||
* C2 两色图 → 主/次正确分离(各桶平均精确)
|
||||
* C3 近黑/近白/透明跳过:近黑+鲜艳 → 主=鲜艳;全近黑/全近白/全透明 → -1(回退主题)
|
||||
* C4 尺寸缩放/副采样:12×12 与 120×120 同内容 → 主色不变
|
||||
* C5 meow_img_duocolor 桥接(局部 struct meow_img 直读 rgba)
|
||||
* C6 meow_img_color_derive:可读 fg/accent_fg、背景渐变、scrim=主色压暗
|
||||
* C7 set_colors 注入/回退(lyric_view 层):注入 derive.lyr → cur=fg、gray 随 cur;
|
||||
* NULL 回退 meow_theme 默认
|
||||
*
|
||||
* 编译(在 patch_v2/sdk 下;链法同 test_meow_lyric_view + 单链 meow_img_color.c):
|
||||
* gcc -Wall -Wextra -o test_meow_img_color test_meow_img_color.c test_host_stub.c \
|
||||
* meow_img_color.c meow_lyric.c meow_lyric_view.c \
|
||||
* meow_ui_gfx.c meow_ui_sys.c meow_ui_theme.c meow_ui_vol.c meow_ui_log.c \
|
||||
* meow_ui_widget.c meow_ui_input.c meow_ui_activity.c meow_ui_dialog.c meow_ui_ime.c meow_ui_str.c \
|
||||
* meow_pinyin.c meow_ui_draw.c meow_ui_view.c meow_ui_box.c meow_ui_anim.c meow_ui_scroll.c \
|
||||
* meow_ui_img.c meow_ui_media.c meow_notify_ipc.c stb_image.c \
|
||||
* -I. -I../meow_launcherd -DMEOW_UI_HOST_TEST -pthread -lm
|
||||
* 运行:./test_meow_img_color
|
||||
*/
|
||||
#include "meow_ui.h"
|
||||
#include "meow_ui_internal.h" /* meow_ui_back_buf/pitch/bpp(如需真实像素断言) */
|
||||
#include "meow_lyric_view.h"
|
||||
#include "meow_img_color.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
/* 局部补全 meow_img(与 meow_ui_img.c / meow_img_color.c 布局一致):构造假封面做桥接测试。 */
|
||||
struct meow_img { int w, h; unsigned char *rgba; };
|
||||
|
||||
static int g_fail = 0;
|
||||
#define CHECK(cond, name) do { \
|
||||
if (cond) printf(" PASS %s\n", name); \
|
||||
else { printf(" FAIL %s (%s:%d)\n", name, __FILE__, __LINE__); g_fail++; } } while (0)
|
||||
|
||||
/* 构造 RGBA 缓冲:rows 描述每行占 w 空格,n 个 (start,count,r,g,b,a) 段。 */
|
||||
static unsigned char *make_rgba(int w, int h)
|
||||
{
|
||||
unsigned char *buf = (unsigned char *)calloc((size_t)w * h * 4, 1);
|
||||
assert(buf);
|
||||
return buf;
|
||||
}
|
||||
|
||||
static void fill_px(unsigned char *buf, int w, int x, int y, int r, int g, int b, int a)
|
||||
{
|
||||
unsigned char *p = buf + ((size_t)y * w + (size_t)x) * 4;
|
||||
p[0] = (unsigned char)r; p[1] = (unsigned char)g; p[2] = (unsigned char)b; p[3] = (unsigned char)a;
|
||||
}
|
||||
|
||||
/* 填充一块实心色(x0,y0,w0,h0)。 */
|
||||
static void fill_rect_px(unsigned char *buf, int W, int x0, int y0, int w0, int h0,
|
||||
int r, int g, int b, int a)
|
||||
{
|
||||
for (int y = y0; y < y0 + h0; y++)
|
||||
for (int x = x0; x < x0 + w0; x++)
|
||||
fill_px(buf, W, x, y, r, g, b, a);
|
||||
}
|
||||
|
||||
static void c1_solid(void)
|
||||
{
|
||||
printf("C1 纯色 → 主色精确,次色=主色缩暗 2/3\n");
|
||||
int W = 12, H = 12;
|
||||
unsigned char *buf = make_rgba(W, H);
|
||||
fill_rect_px(buf, W, 0, 0, W, H, 0x30, 0x60, 0xA0, 0xFF);
|
||||
uint32_t m = 0, s = 0;
|
||||
int rc = meow_img_duocolor_rgba(buf, W, H, &m, &s);
|
||||
CHECK(rc == 0, "纯色取色成功");
|
||||
CHECK(m == 0x3060A0, "主色==0x3060A0(桶中心精确)");
|
||||
CHECK(s == 0x20406A, "次色==0x20406A(主色缩暗 2/3 渐变互补)");
|
||||
free(buf);
|
||||
}
|
||||
|
||||
static void c2_two_colors(void)
|
||||
{
|
||||
printf("C2 两色图 → 主/次分离\n");
|
||||
int W = 16, H = 16;
|
||||
unsigned char *buf = make_rgba(W, H);
|
||||
fill_rect_px(buf, W, 0, 0, W, 8, 0xE0, 0x30, 0x40, 0xFF); /* 上半:红 */
|
||||
fill_rect_px(buf, W, 0, 8, W, 8, 0x20, 0x40, 0xE0, 0xFF); /* 下半:蓝 */
|
||||
uint32_t m = 0, s = 0;
|
||||
int rc = meow_img_duocolor_rgba(buf, W, H, &m, &s);
|
||||
CHECK(rc == 0, "两色取色成功");
|
||||
CHECK(m != s, "主色 != 次色");
|
||||
/* 蓝的饱和权重更高(sat=192 vs 红 176)→ 主色偏蓝,次色偏红 */
|
||||
CHECK(((m >> 16) & 0xFF) < 0x80 && (m & 0xFF) > 0xA0, "主色为蓝系(B 高)");
|
||||
CHECK(((s >> 16) & 0xFF) > 0xA0 && (s & 0xFF) < 0x80, "次色为红系(R 高)");
|
||||
free(buf);
|
||||
}
|
||||
|
||||
static void c3_skip_near(void)
|
||||
{
|
||||
printf("C3 近黑/近白/透明跳过\n");
|
||||
/* 近黑 + 一排鲜艳 → 主=鲜艳(近黑被跳过) */
|
||||
int W = 12, H = 12;
|
||||
unsigned char *buf = make_rgba(W, H);
|
||||
fill_rect_px(buf, W, 0, 0, W, 11, 0x08, 0x08, 0x08, 0xFF); /* 近黑(luma<24) */
|
||||
fill_rect_px(buf, W, 0, 11, W, 1, 0xE0, 0x30, 0x40, 0xFF); /* 鲜艳一行 */
|
||||
uint32_t m = 0, s = 0;
|
||||
int rc = meow_img_duocolor_rgba(buf, W, H, &m, &s);
|
||||
CHECK(rc == 0, "近黑+鲜艳 → 成功");
|
||||
CHECK(m == 0xE03040, "主色==鲜艳色(近黑被跳过)");
|
||||
free(buf);
|
||||
|
||||
/* 全近黑 → -1(回退主题) */
|
||||
buf = make_rgba(W, H);
|
||||
fill_rect_px(buf, W, 0, 0, W, H, 0x10, 0x10, 0x10, 0xFF);
|
||||
CHECK(meow_img_duocolor_rgba(buf, W, H, &m, &s) == -1, "全近黑 → -1");
|
||||
free(buf);
|
||||
|
||||
/* 全近白 → -1 */
|
||||
buf = make_rgba(W, H);
|
||||
fill_rect_px(buf, W, 0, 0, W, H, 0xF8, 0xF8, 0xF8, 0xFF);
|
||||
CHECK(meow_img_duocolor_rgba(buf, W, H, &m, &s) == -1, "全近白 → -1");
|
||||
free(buf);
|
||||
|
||||
/* 全透明 → -1 */
|
||||
buf = make_rgba(W, H);
|
||||
/* 默认 calloc → rgba=0 → alpha=0 全部跳过 */
|
||||
CHECK(meow_img_duocolor_rgba(buf, W, H, &m, &s) == -1, "全透明 → -1");
|
||||
free(buf);
|
||||
}
|
||||
|
||||
static void c4_size_invariant(void)
|
||||
{
|
||||
printf("C4 尺寸缩放/副采样不变性\n");
|
||||
/* 12×12 vs 120×120:同内容上下半红蓝 → 主色(蓝)一致 */
|
||||
uint32_t m1 = 0, s1 = 0, m2 = 0, s2 = 0;
|
||||
unsigned char *b1 = make_rgba(12, 12);
|
||||
fill_rect_px(b1, 12, 0, 0, 12, 6, 0xE0, 0x30, 0x40, 0xFF);
|
||||
fill_rect_px(b1, 12, 0, 6, 12, 6, 0x20, 0x40, 0xE0, 0xFF);
|
||||
CHECK(meow_img_duocolor_rgba(b1, 12, 12, &m1, &s1) == 0, "小图取色成功");
|
||||
unsigned char *b2 = make_rgba(120, 120);
|
||||
fill_rect_px(b2, 120, 0, 0, 120, 60, 0xE0, 0x30, 0x40, 0xFF);
|
||||
fill_rect_px(b2, 120, 0, 60, 120, 60, 0x20, 0x40, 0xE0, 0xFF);
|
||||
CHECK(meow_img_duocolor_rgba(b2, 120, 120, &m2, &s2) == 0, "大图取色成功");
|
||||
CHECK(m1 == m2, "大/小图主色一致(副采样不变性)");
|
||||
free(b1);
|
||||
free(b2);
|
||||
}
|
||||
|
||||
static void c5_img_bridge(void)
|
||||
{
|
||||
printf("C5 meow_img_duocolor 桥接(局部 struct meow_img)\n");
|
||||
int W = 4, H = 4;
|
||||
struct meow_img im;
|
||||
im.w = W; im.h = H;
|
||||
im.rgba = make_rgba(W, H);
|
||||
fill_rect_px(im.rgba, W, 0, 0, W, 2, 0xE0, 0x30, 0x40, 0xFF);
|
||||
fill_rect_px(im.rgba, W, 0, 2, W, 2, 0x20, 0x40, 0xE0, 0xFF);
|
||||
uint32_t m = 0, s = 0;
|
||||
int rc = meow_img_duocolor((const meow_img *)&im, &m, &s);
|
||||
CHECK(rc == 0, "meow_img 句柄取色成功");
|
||||
CHECK(((m >> 16) & 0xFF) < 0x80 && (m & 0xFF) > 0xA0, "桥接主色为蓝系");
|
||||
/* NULL → -1 */
|
||||
CHECK(meow_img_duocolor(NULL, &m, &s) == -1, "NULL 句柄 → -1");
|
||||
free(im.rgba);
|
||||
}
|
||||
|
||||
static void c6_derive(void)
|
||||
{
|
||||
printf("C6 meow_img_color_derive 派生主题\n");
|
||||
meow_theme th; memset(&th, 0, sizeof th);
|
||||
th.primary = 0x204060; th.secondary = 0xE0B050; /* 深蓝主 + 琥珀次 */
|
||||
meow_img_color_theme ct;
|
||||
meow_img_color_derive(0x204060, 0xE0B050, &th, &ct);
|
||||
/* 可读性:主深次亮 → 平均亮度偏中低 → 文字白 */
|
||||
CHECK(ct.fg == 0xFFFFFF, "fg==白色(背景偏暗可读)");
|
||||
CHECK(ct.accent == 0xE0B050, "accent==次色(琥珀)");
|
||||
CHECK(ct.accent_fg == 0x1C1C1C, "accent_fg==深色(琥珀上可读图标)");
|
||||
CHECK(ct.bg_top != ct.bg_bottom, "背景双色渐变(顶!=底)");
|
||||
/* scrim=主色压暗:比原主色更暗(向黑靠) */
|
||||
CHECK(((ct.lyr.scrim >> 16) & 0xFF) < 0x20, "lyr.scrim 主色压暗(R 递减)");
|
||||
CHECK(ct.lyr.cur == 0xFFFFFF, "lyr.cur==fg(可读)");
|
||||
CHECK(ct.lyr.scrim_alpha == 176, "lyr.scrim_alpha==176");
|
||||
}
|
||||
|
||||
static void c7_set_colors_inject(void)
|
||||
{
|
||||
printf("C7 set_colors 注入/回退(lyric_view 层)\n");
|
||||
meow_theme_set_mode(MEOW_THEME_DARK);
|
||||
meow_lyric_view *lv = meow_lyric_view_create(0, 0, 480, 720, NULL, NULL);
|
||||
assert(lv);
|
||||
uint32_t def_cur = meow_lyric_view_cur_color(lv); /* dark=0xFFFFFF 纯白 */
|
||||
/* 注入封面派生主题 */
|
||||
meow_img_color_theme ct;
|
||||
meow_img_color_derive(0x204060, 0xE0B050, meow_theme_get(), &ct);
|
||||
meow_lyric_view_set_colors(lv, &ct.lyr);
|
||||
CHECK(meow_lyric_view_cur_color(lv) == ct.lyr.cur, "注入后 cur==派生 fg(可读)");
|
||||
/* gray 随覆盖 cur 自动递减(非白 cur 亦然:红色 cur → 灰阶=0xBB0000 级) */
|
||||
meow_lyric_view_colors c; memset(&c, 0, sizeof c);
|
||||
c.cur = 0xFF0000;
|
||||
meow_lyric_view_set_colors(lv, &c);
|
||||
CHECK(meow_lyric_view_cur_color(lv) == 0xFF0000, "覆盖 cur=红生效");
|
||||
CHECK(meow_lyric_view_line_color(lv, 1) == 0xBB0000, "gray(1) 随覆盖 cur 递减(0xBB0000)");
|
||||
/* 回退:NULL → 主题默认 */
|
||||
meow_lyric_view_set_colors(lv, NULL);
|
||||
CHECK(meow_lyric_view_cur_color(lv) == def_cur, "set_colors(NULL) 回退主题默认");
|
||||
meow_lyric_view_free(lv);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
setbuf(stdout, NULL);
|
||||
setenv("MEOW_UI_DUMP", "/tmp/imgcolor.ppm", 1);
|
||||
meow_ui_opts o = { 480, 720 };
|
||||
assert(meow_ui_init(&o) == 0);
|
||||
meow_ui_activity_push(0x57);
|
||||
|
||||
c1_solid();
|
||||
c2_two_colors();
|
||||
c3_skip_near();
|
||||
c4_size_invariant();
|
||||
c5_img_bridge();
|
||||
c6_derive();
|
||||
c7_set_colors_inject();
|
||||
|
||||
meow_ui_activity_pop();
|
||||
meow_ui_shutdown();
|
||||
printf(g_fail ? "\nRESULT: %d FAIL(S)\n" : "\nRESULT: ALL PASS\n", g_fail);
|
||||
return g_fail;
|
||||
}
|
||||
Reference in new issue
Block a user