ACPA算法详解

之前我们说到通过web worker的方式优化了图片取色,同时也将根据根据背景色选择对应的文本色也放到了worker中进行优化,那么这段逻辑是怎么实现的呢?主要是通过ACPA算法实现,本文将对其进行详细解释他的实现思路。

文本取色逻辑

  1. 计算黑色和白色文本与背景的对比度 (APCA)
  2. 选择对比度更高的颜色

说到APCA,我们可以先来了解一下他的背景,为什么选择用APCA?

在APCA之前,对于对比度的标准主要采用的是WCAG2的对比度标准,WCAG 是利用色彩的相对亮度值(亮度)进行计算,即一个色彩本身的亮度。

然后再将文本与背景色做比值处理:

L1 是前景色的亮度,L2 是背景色的亮度

AA级:对比度(最小):

  • 文本视觉呈现和文本图像至少要有4.5:1的对比度
  • 大文本: 大号文本以及大文本图像至少有3:1的对比度;

AAA级: 对比度(加强):

  • 文本视觉呈现以及文本图像至少要有7:1的对比度
  • 大文本: 大号文本以及大文本图像至少有4.5:1的对比度;

WCAG 利用色彩本身的相对亮度值作为计算依据,就不符合人眼自适应的特性 ,相对亮度值是固定的,但人眼适应性是动态的,怎么会适应呢?

举一个我看到很喜欢的一个例子:

假设一个全黑的房间,点了 100 根蜡烛,会变亮吗?会。

有了 100 根蜡烛之后,再加上 100 根蜡烛,会更亮吗?会。

但是,是从 0 到 100 感觉比较亮,还是 100 到 200?

从感知的角度來看,应该是 0 到 100 的感觉会更明亮。

但奇怪的是明明都是差距 100 根蜡烛的亮度,怎么感觉就不一样了?

答案就是人眼的自适应性

WCAG2.X 的错误之处

按照上面的公式计算,当我们的文本与背景颜色都偏向亮色系时,就会导致因为色彩本身的相对亮度值高,而套用公式计算之后数值偏低。

相反的,在色彩本身的相对亮度值偏低的情况下,套用公式后,计算结果数值则偏高。

APCA的实现逻辑

AA/AAA 准则相比,APCA 更加与上下文相关。

对比度的计算基于以下特征:

  • 空间属性(字体粗细和文本大小)
  • 文本颜色(文本和背景之间的感知亮度差异)
  • 上下文(环境光、周围环境和文本的预期目的)

第一点和第三点主要是影响对比度值该达到多少才算合格,而第二点在公式中有体现

我们可以详细看一下APCA的实现逻辑:

获取线性亮度

scss 复制代码
sRGB(0~255)
   ⬇️
除以255 → 归一化(0~1)
   ⬇️
Gamma 解码(^2.4)
   ⬇️
线性 RGB
   ⬇️
Y = 0.2126 * R + 0.7152 * G + 0.0722 * B //这个就是上面提到的不同通道对应不同的权重啦
   ⬇️
线性亮度(Y 值,0~1)//这是物理光强度。它不是人眼感知亮度,但为感知建模打基础。

Gamma 解码 why?

因为sRGB 是"伽马编码"的空间,sRGB 图像中的 RGB 值 不是线性的,而是经过「伽马压缩」的

举个🌰:

  • R = 128(中间值) ≠ 发光强度的一半
  • 实际光能量只大约是 22% 左右
ini 复制代码
//解压出来的亮度
linear = (srgb / 255) ** 2.2
R = 128 / 255 ≈ 0.502
线性亮度 = 0.502 ^ 2.2 ≈ 0.2182

这是因为:

人眼对亮度变化的感知是对数型的,对暗部更敏感,对亮部更不敏感。

所以图像系统(显示器、图像编码)有意地把暗部展开,让更多比特分配在暗区,从而视觉上更平衡。

📦 sRGB 就用了一个近似 gamma=2.2 的非线性曲线来"压暗亮部,提亮暗部" 而这个曲线的逆运算就是:Gamma 解码(指数 2.4)转换回真实的线性光值

对比度计算

得到亮度值后我们需要进行对比度计算

主要的对比逻辑就是这段代码

  • 黑字白底(BoW):正常极性
  • 白字黑底(WoB):反极性,结果为负数
ini 复制代码
if (bgY > txtY) {
        SAPC = (Math.pow(bgY, SA98G.normBG) - Math.pow(txtY, SA98G.normTXT)) * SA98G.scaleBoW;
        outputContrast = SAPC < SA98G.loClip ? 0.0 : SAPC - SA98G.loBoWoffset;
} else {
        SAPC = (Math.pow(bgY, SA98G.revBG) - Math.pow(txtY, SA98G.revTXT)) * SA98G.scaleWoB;
        outputContrast = SAPC > -SA98G.loClip ? 0.0 : SAPC + SA98G.loWoBoffset;
}

抽象一下,其实主要计算对比度的就是这个公式

为了模拟人眼"对亮度差异的感知",对 Y 加指数处理:

人眼对于亮度的感知不是线性的。我们对黑暗区域的小亮度变化非常敏感,而在亮的区域,小的变化不容易察觉。

所以要在算法中模拟人眼真实的感知,就不能直接相减,而是要加个幂函数。

  • 分别给 亮背景/暗背景 用不同的指数(expBGexpTXT
  • 再乘一个缩放因子 scale,得到统一单位。调出来让结果值大致落在合理的范围内(如 ±100)
makefile 复制代码
normBG: 0.56,
normTXT: 0.57,
revTXT: 0.62,
revBG: 0.65,

为什么会有正负之分,之前的wcag只有正数 why??

因为人眼在不同背景亮度下,对亮度差的感知强度不同

情况 感知效果(人眼) 模型中的表现
黑字 on 白底 非常清晰,分离感强 指数更小(更敏感)
白字 on 黑底 模糊、发光感,难以聚焦 指数更大(降低过敏反应)

因为我们对亮背景下的暗文字特别敏感,能够感受到非常微小的亮度差异。所以要让机器也"更敏感",就用更小的指数来处理亮度。

APCA算法大致思路就是这样,具体实现会包含很多补充逻辑

🔚ending....

相关推荐
小小小小宇1 分钟前
TypeScript 中 infer 关键字
前端
__不想说话__15 分钟前
面试官问我React状态管理,我召唤了武林群侠传…
前端·react.js·面试
Cutey91616 分钟前
前端SEO优化方案
前端·javascript
webxin66617 分钟前
带鱼屏页面该怎么适配?看我的
前端
axinawang18 分钟前
SpringBoot整合Java Web三大件
java·前端·spring boot
小old弟21 分钟前
🎨如何动态主题切换 —— css变量🖌️
前端
JiangJiang24 分钟前
🎯 Vue 人看 useReducer:比 useState 更强的状态管理利器!
前端·react.js·面试
iOS阿玮1 小时前
待业的两个月,让我觉得独立开发者才是职场的归宿。
前端·app
八了个戒1 小时前
「数据可视化 D3系列」入门第六章:比例尺的使用
前端·javascript·信息可视化·数据可视化·canvas