diff --git a/patch_v2/meow_launcherd/build_launcher.sh b/patch_v2/meow_launcherd/build_launcher.sh index bff4d6e..6b292c8 100755 --- a/patch_v2/meow_launcherd/build_launcher.sh +++ b/patch_v2/meow_launcherd/build_launcher.sh @@ -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 diff --git a/patch_v2/meow_launcherd/now_playing.c b/patch_v2/meow_launcherd/now_playing.c index 6865a7f..3158410 100644 --- a/patch_v2/meow_launcherd/now_playing.c +++ b/patch_v2/meow_launcherd/now_playing.c @@ -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; } diff --git a/patch_v2/sdk/meow_img_color.c b/patch_v2/sdk/meow_img_color.c new file mode 100644 index 0000000..df12b60 --- /dev/null +++ b/patch_v2/sdk/meow_img_color.c @@ -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 + +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; +} diff --git a/patch_v2/sdk/meow_img_color.h b/patch_v2/sdk/meow_img_color.h new file mode 100644 index 0000000..d0c96c3 --- /dev/null +++ b/patch_v2/sdk/meow_img_color.h @@ -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 */ diff --git a/patch_v2/sdk/meow_lyric_view.c b/patch_v2/sdk/meow_lyric_view.c index 0bbb604..16345c9 100644 --- a/patch_v2/sdk/meow_lyric_view.c +++ b/patch_v2/sdk/meow_lyric_view.c @@ -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)。 */ diff --git a/patch_v2/sdk/test_meow_img_color.c b/patch_v2/sdk/test_meow_img_color.c new file mode 100644 index 0000000..433826c --- /dev/null +++ b/patch_v2/sdk/test_meow_img_color.c @@ -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 +#include +#include +#include + +/* 局部补全 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; +}