/* * meow_img_color.c — MeowOS SDK 封面双色渐变取色实现(见 meow_img_color.h 契约)。 * * 独立源码,不并入 meow_ui.a(铁律:避免并发重编/四 App 重链)。取色算法纯函数(无 GPU/浮点/堆)。 * 唯一 meow_* 依赖:仅 meow_img_pixels(SDK 正式访问器,meow_ui_img.c §4),并经 meow_img_w/h 取宽高 * ——三者读 meow_img 的唯一入口,本 TU 不再局部补全其结构体(agent-402 遗留点已上收)。App/宿主 * 链入 meow_img_color.c 时需能解析 meow_img_pixels/w/h(宿主测试已链 meow_ui_img.c,实际 App 恒链 * meow_ui.a)。 */ #include "meow_img_color.h" #include #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; /* 经 SDK 正式访问器读像素(meow_img opaque;meow_img_pixels 为唯一读入口)。 */ const unsigned char *rgba = meow_img_pixels(im); if (!rgba) return -1; return meow_img_duocolor_rgba(rgba, meow_img_w(im), meow_img_h(im), 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; }