跨端: 语义色/三级文字在深色下没提亮(设备扫出 12 处)+ 判据取样法修到第三版
## 一、判据基建先修对,否则是在听噪声
深色可读性扫描的**取样法迭代了三版**,每版都因为**假红**才改的
(记在这里,因为"判据自己错"比"界面错"更费时间):
1. **取中心一个像素** ⇒ 落在笔画之间、读到的是底色 ⇒ 一片 ratio=1.00 假红。
2. **取全局最暗/最亮** ⇒ 会采到**两个不同的东西**:细字的抗锯齿中间色。
实例:segmented「日」报 ink `rgb(32,34,35)` / bg `rgb(98,100,101)` 2.69:1 ——
而截图里它要么蓝底白字、要么深底浅字,两种都远高于 3:1。
**反复核对才确认是判据的错,不是界面的错。**
3. **步长采样 + 众数** ⇒ 仍然假红:周表头「一」报 2.03:1,
而逐像素的真实墨是 `rgb(166,167,167)`(**6.6:1**)——
稀疏网格**整个跳过了笔画**。这不是调参能修好的(字越小越糟)。
4. **逐像素 + 众数=底 + 离底最远者=墨**(最终版)。
理由:一个文字框里**面积最大的一定是底**,而"离底色最远"就是笔画最实那部分。
⚠️ 逐像素是**负担得起的**(整屏已解码在内存,一个框最多 9 万像素)——
**不要再为了省开销退回抽样**,前两版都因此假红。
## 二、真 bug 三批(都是"深色下没提亮")
### ① 语义色前景(12 处 → 修完设备复扫 0)
WebUI 的语义色也是**双通道**,`.dark` 段把 `red/green/amber-700` 换成浅色
(`248/118/249 → 164/219/195` 那组)。鸿蒙只有一个深色值:
- 「归档」按钮 `Theme.danger` #B91C1C 压深面 ⇒ **2.47:1**(联系人页 7 处)
- 「今天」/「日」等 ⇒ **2.38–2.77:1**
加 `dangerDark/approveDark/warnFgDark`(逐字对齐 WebUI `.dark`)
+ `dangerFor()/approveFor()/warnFgFor()` 入口,22 处接入。
### ② 日历工具条整块配色抄错(不是"深色档选错")
对照 WebUI `CalendarView.tsx:313-331`:
| 元素 | WebUI | 鸿蒙(改前) |
|---|---|---|
| 「今天」 | `border-gray-300` + `text-gray-700`(**中性**) | `accentSoft` 底 + `accentStrong` 字 |
| 月/周/日 未选中 | `bg-white` + `text-gray-700` | `accentSoft` 底 |
鸿蒙把**次要的中性按钮**画成了**品牌色按钮** —— 视觉重量跟主操作一样,
三个档看起来"都像选中"。已按 WebUI 改成中性。
### ③ 三级文字在深色下 2.82:1(**71 处**,全局最大的一个)
WebUI 的 `.dark` 段注释逐字写着这个坑:
> 「`gray-400/500`(次要文字)→ **提亮**。深底上的浅色 gray-400
> 只有约 2:1 对比度,远低于 WCAG AA 的 4.5:1 —— **看得见但读不动**。」
鸿蒙用的系统三级色 `ohos_id_color_text_tertiary` 深色下是 `rgb(102,103,105)`
压深面 **2.82–3.05:1**,而它被用在 10–11px 的**小字**上
(时间戳、农历、空态、辅助说明)。
**不换掉系统令牌**("系统拥有的维度"纪律照旧),加 `textSubtleFor()`:
深色下改用二级色(实测 `rgb(166,167,167)` ≈ 7.15:1,
且与 WebUI 的 `gray-500` 同档位)。71 处接入。
**设备复扫(修后)**:通信/日历/联系三页 **176 段文字全部 ≥3:1**。
## 三、欠账与底本
- `baseline.sha` 第 4 次重算。仍**先逐个取证**:
`git diff --quiet HEAD -- <f> && echo STALE || echo INTENTIONAL`
⇒ 三个全是 `INTENTIONAL`(我改的),不是变异残留。
把这条取证命令也写进文件,下次照抄。
- `dangerDark/approveDark/warnFgDark` 已登记进 `SELF_OWNED_COLORS`
+ `Theme.ets` 登记表(各带理由与 WebUI 出处)。
`run-all.mjs` → `checks=514 pass=514 fail=0 skip=0 red=0 broken=0 unreported=0`。
**未验**:①「我的」页(底部头像按钮,扫描脚本没找到入口)没扫;
② 浅色下的对比度没扫(本轮全部针对深色)。
This commit is contained in:
@ -245,6 +245,16 @@ const SELF_OWNED_COLORS = [
|
||||
* 各条取值的理由都已写在 `Theme.ets` 各自的注释里(本判据的要求)。
|
||||
*/
|
||||
'navActiveBg', 'navBrandFg', 'badgePlain', 'accentSoftDark', 'accentDark',
|
||||
/*
|
||||
* 语义色的深色档(2026-09-19 加)—— 与 `accentDark` 同一个问题、同一批证据:
|
||||
* WebUI 的语义色也是**双通道**,`.dark` 段把 red/green/amber-700 换成浅色,
|
||||
* 否则深底上的红字/绿字读不动(设备实测「归档」按钮 2.47:1)。
|
||||
* · `dangerDark` #F8A4A4 ← WebUI `.dark --c-red-700`(`248 164 164`)
|
||||
* · `approveDark` #76DB9B ← WebUI `.dark --c-green-700`(`118 219 155`)
|
||||
* · `warnFgDark` #F9C368 ← WebUI `.dark --c-amber-700`(`249 195 104`)
|
||||
* 入口是 `Theme.dangerFor()` / `approveFor()` / `warnFgFor()`。
|
||||
*/
|
||||
'dangerDark', 'approveDark', 'warnFgDark',
|
||||
/*
|
||||
* SSE 连接指示器的四个状态色(`WideSidebar.ets` 的 `sseColorOf`)。
|
||||
* 逐档对齐 WebUI 的 Tailwind 类(`ConnectionIndicator.tsx:22-25`):
|
||||
@ -1295,23 +1305,69 @@ test('★ 设备:深色下短文本必须都读得动(扫一屏,不只查
|
||||
if (w < 20 || h < 14 || w > 700 || h > 130) continue;
|
||||
if (x2 > img0.w - 4 || y2 > img0.h - 4) continue;
|
||||
|
||||
let lo = null;
|
||||
let hi = null;
|
||||
const sx = Math.max(2, Math.floor(w / 14));
|
||||
const sy = Math.max(2, Math.floor(h / 7));
|
||||
for (let y = y1 + 1; y < y2; y += sy) {
|
||||
for (let x = x1 + 1; x < x2; x += sx) {
|
||||
/*
|
||||
* ★★ 第三版取样法(前两版都错,记下来):
|
||||
*
|
||||
* ① 只取**中心一个像素** ⇒ 多半落在笔画之间,读到的是底色 ⇒
|
||||
* ratio≈1.00 的假红一片。
|
||||
* ② **全局最暗/最亮**(我上一版)⇒ 会采到**两个不同东西**:
|
||||
* 细字的抗锯齿**中间色**(既非墨也非底)。
|
||||
* 实测假红:「日」按钮报 ink `rgb(32,34,35)` / bg `rgb(98,100,101)`
|
||||
* 2.69:1 —— 而截图显示那个按钮要么是蓝底白字、要么是深底浅字,
|
||||
* 两种都远高于 3:1。`rgb(98,100,101)` 是白字压在深色上的**边缘混色**。
|
||||
* ★ 反复核对才确认是**判据自己的错**,不是界面的错 ——
|
||||
* 这正是"假红比漏报更费时间"的又一例。
|
||||
* ③(本版)**众数 = 底色**,再找离它最远的颜色当墨。
|
||||
* 理由:一个文字框里**面积最大的一定是底**(笔画只占少数像素),
|
||||
* 而"离底色最远"就是笔画最实的那部分 —— 不会再被边缘混色骗。
|
||||
* 这一版对"细字 + 粗采样"稳,且不依赖步长调得多细。
|
||||
*/
|
||||
/*
|
||||
* ★★ 第四版:**逐像素**(不再抽步长)。
|
||||
*
|
||||
* 第三版虽然避免了"取中心/取全局极值",但仍然**步长采样** ——
|
||||
* 于是第三次假红:周表头的「一」报 2.03:1,而逐像素一看,
|
||||
* 真正的墨是 `rgb(166,167,167)`(**6.6:1,完全没问题**),
|
||||
* 我的稀疏网格**整个跳过了笔画**,只采到抗锯齿的中间色。
|
||||
*
|
||||
* ⇒ 教训:**小字 + 稀疏采样 = 采不到字**。笔画宽度可能只有 2–3px,
|
||||
* 而步长 9 会稳定地跨过去。这不是调参能修好的(字越小越糟),
|
||||
* 只能逐像素。
|
||||
*
|
||||
* ★ 逐像素现在是**负担得起的**:整屏已经解码在内存里(`readPixels`),
|
||||
* 一个框最大 700×130=9.1 万像素,几十个框也就几百万次数组下标读取 ——
|
||||
* 毫秒级。**不要**为了省这点开销再退回抽样(前两版都因此假红)。
|
||||
*/
|
||||
const counts = new Map();
|
||||
for (let y = y1 + 1; y < y2; y++) {
|
||||
for (let x = x1 + 1; x < x2; x++) {
|
||||
const p = img0.at(x, y);
|
||||
if (!p) continue;
|
||||
const L = lum(p);
|
||||
if (!lo || L < lo.L) lo = { L, p };
|
||||
if (!hi || L > hi.L) hi = { L, p };
|
||||
/* 量化到 8 级,避免抗锯齿把众数切碎(同色系会被合成一档) */
|
||||
const key = `${p.r >> 5},${p.g >> 5},${p.b >> 5}`;
|
||||
const e = counts.get(key);
|
||||
if (e) { e.n++; }
|
||||
else { counts.set(key, { n: 1, r: p.r, g: p.g, b: p.b }); }
|
||||
}
|
||||
}
|
||||
if (counts.size < 2) continue;
|
||||
const sorted = [...counts.values()].sort((a, b) => b.n - a.n);
|
||||
const bg = sorted[0];
|
||||
const bgL = lum(bg);
|
||||
/* 离底色最远的那个(按对比度算,而不是按亮度差 —— 亮度差会偏向同向的色) */
|
||||
let ink = null;
|
||||
let best = 0;
|
||||
for (const c of sorted.slice(1)) {
|
||||
const L = lum(c);
|
||||
const r = (Math.max(L, bgL) + 0.05) / (Math.min(L, bgL) + 0.05);
|
||||
if (r > best) { best = r; ink = { L, p: c }; }
|
||||
}
|
||||
/* 没有真实墨迹对比的框跳过(见上面 ③) */
|
||||
if (!lo || !hi || hi.L - lo.L < 0.02) continue;
|
||||
if (!ink || best < 1.2) continue;
|
||||
scanned++;
|
||||
const ratio = (hi.L + 0.05) / (lo.L + 0.05);
|
||||
const ratio = best;
|
||||
const lo = { L: Math.min(ink.L, bgL), p: ink.L < bgL ? ink.p : bg };
|
||||
const hi = { L: Math.max(ink.L, bgL), p: ink.L < bgL ? bg : ink.p };
|
||||
if (ratio < 3) {
|
||||
bad.push(`「${txt}」 对比度 ${ratio.toFixed(2)}:1 ` +
|
||||
`墨 rgb(${lo.p.r},${lo.p.g},${lo.p.b}) / 底 rgb(${hi.p.r},${hi.p.g},${hi.p.b}) ${a.bounds}`);
|
||||
|
||||
@ -48,10 +48,20 @@
|
||||
# ⇒ 这是 `stale`(底本过期),不是 `residue`(变异残留)。
|
||||
# ★ 三个都逐个跑过 `git diff --quiet HEAD -- <f>` 取证,没有一个是"改动忘了还原"。
|
||||
# ★ 这正是这份文件里那条纪律的第 3 次执行:"重算必须是一次有记录的动作"。
|
||||
#
|
||||
# 2026-09-19 20:2x baseline 重算(第 4 次,**仍先核过不是残留**):
|
||||
# ① `pages/AdminUsersPage.ets` / `pages/SettingsPage.ets` / `common/BackgroundPicker.ets`
|
||||
# —— 本次的**有意编辑**:语义色前景接入 `dangerFor()/approveFor()/warnFgFor()`
|
||||
# (深色下红/绿/琥珀要提亮,对齐 WebUI `.dark --c-red/green/amber-700`),
|
||||
# 以及三级文字接入 `textSubtleFor()`(深色下换二级,见 Theme.ets 里那段理由)。
|
||||
# ② 三个都逐个跑过 `git diff --quiet HEAD -- <f>` ⇒ 全部**非空**(工作树有改动)
|
||||
# ⇒ 是"我改的",不是"变异忘了还原"。
|
||||
# ★ 取证命令留在这里,下次照着做:
|
||||
# git diff --quiet HEAD -- <file> && echo STALE || echo INTENTIONAL
|
||||
4f3e0802346ba93740d7a6989fa6a9ef7dce16d1db59ea7402ff554127b07e3e client/harmony/entry/src/main/ets/model/AdminUsers.ts
|
||||
c465b178ec1853ba66ac619e0d5614f48aef66db2ed2fecba25a4ae10e3dd13b client/harmony/entry/src/main/ets/model/ImagePrep.ts
|
||||
efd598149cb52400f1120f1ba4c7fb41ad9706f82005770479f571614a539e73 client/harmony/entry/src/main/ets/pages/AdminUsersPage.ets
|
||||
4681c5f2d57870d64b09a10433f5ea518d777a27f6cb7ece3bbcc29c5f11012a client/harmony/entry/src/main/ets/common/BackgroundPicker.ets
|
||||
3baed1fac54fef5f924c17f658ad05105b8be3ac35b110e2277b1365a84bb15a client/harmony/entry/src/main/ets/pages/SettingsPage.ets
|
||||
ceec4a5aae5e9064d9ab57ef24acd4b7f185506de8582ae2c67e35f634ff8593 client/harmony/entry/src/main/ets/pages/AdminUsersPage.ets
|
||||
73c66c7045f579c3eb8b8e0aa07803e7c9363b7ae8375972b251633d3ce969be client/harmony/entry/src/main/ets/common/BackgroundPicker.ets
|
||||
e773fd556f4454efbb204ff72841b63a0a584f7ca7827c73142884af21328ba5 client/harmony/entry/src/main/ets/pages/SettingsPage.ets
|
||||
f3c7c3de22acaa94c8c3603fdd387547090807ee13e1e9fe35514d2028f5647e client/harmony/entry/src/main/ets/api/ApiClient.ets
|
||||
6550e1892d1ebea82fb75a3d2cf8eaf826186199ff4a0ecf0430b1e3517d8681 client/harmony/entry/src/main/ets/api/AppearanceApi.ets
|
||||
|
||||
Reference in New Issue
Block a user