blog icon indicating copy to clipboard operation
blog copied to clipboard

有趣的 CSS Alpha 值

Open yisibl opened this issue 9 years ago • 11 comments

在 Chrome 中输入 「background-color: rgba(0, 0, 0, 0.5)」

最终渲染出来你会发现变成了 「background-color: rgba(0, 0, 0, 0.498039)」。

Gif demo

阿西!这是什么鬼?( ・᷄ὢ・᷅ ) ,别急下面慢慢揭开它的神秘面纱。

一、 故事背景

在与 @Justineo 聊到 CSS Alpha 百分比表示方式的时候,偶然发现 Chrome 里他的这个项目kolor 的背景色实际计算出来的 Alpha 值和指定的不一样。

这引起了我们的兴趣,一番探讨搜索后,做个简要总结,备忘。

二、概念

CSS Color 中阿尔法通道(α Channel 或 Alpha Channel)是指颜色的不透明度,而不是透明度。也就是说:

  • 不透明度=0 的时候是全透明。
  • 不透明度=1 的时候就是完全不透明。

其中 32 位的颜色被分解成四个8位整数:

  • R: 8位
  • G: 8位
  • B: 8位
  • A: 8位

所以 Alpha 值在内部表示为介于0和255之间的一个整数(即8位无符号整数)。为了更加方便开发者使用,书写的时候我们采用 0-1 之间的浮点数来表示 Alpha 值。

三、 探索

那么我们看看 Chrome 中是如何计算出 0.5 的 Alpha 值的呢?

我们用input来表示输入 CSS 值中解析出来的字符串,A代表最终的 Alpha 值,alphaString来表示 CSS Color 中 Alpha 值的序列化结果:

1 把 0.5 转换为8位无符号整数

 A = atof(input) * 255; // atof() 相当于 JS 函数 parseFloat()

 => A = 0.5 * 255 = 127.5 => 127 // 舍去非整数部分

2 alphaString 通过以下定义来计算

const char* alphaString = numberToFixedPrecisionString(alpha() / 255.0f, 6, buffer, true);

注意:早期版本的 WebKit 采用 16 位来存储 Alpha 值,这会导致和现在版本最终计算出来存在差异,参见:http://www.zhihu.com/question/29781299/answer/45617540

这里alpha()的结果就是 A

alpha() / 255 = 127 / 255 = 0.498039216

然后 CSS Object Model (CSSOM) 规范中的 <number> 值规定最多取 6 位小数,最终反向序列化成字符串,得到你看到的 0.498039

<number> A base-ten number using digits 0-9 (U+0030 to U+0039) in the shortest form possible, using "." to separate decimals (if any), rounding the value if necessary to not produce more than 6 decimals, preceded by "-" (U+002D) if it is negative.

这里 WebKit 偷懒了啊:

CSSOM 规范里的算法是这样说的:

<alphavalue>  If the value is internally represented as an integer between 0 and 255 inclusive (i.e. 8-bit unsigned integer), follow these steps:

  1. Let alpha be the given integer.
  2. If there exists an integer between 0 and 100 inclusive that, when multiplied with 2.55 and rounded to the closest integer (rounding up if two values are equally close), equalsalpha, let rounded be that integer divided by 100.
  3. Otherwise, let rounded be alpha divided by 0.255 and rounded to the closest integer (rounding up if two values are equally close), divided by 1000.
  4. Return the result of serializing rounded as a <number>. Otherwise, return the result of serializing the given value as a <number>.

所以有人提交了 Patch 来修复这个问题,使其反向序列号字符串的时候可以得到同样的值:

if (colorHasAlpha) {
  result.appendLiteral(", ");

  double floatAlpha = 0;
  // See  section in CSS Object Model (CSSOM)
  // Why ceil, not round here is that we ommit decimals in CSSPropertyParser::parseColorParameters.
  for (int i = static_cast < int > (ceil(alpha() / 2.56)); i <= 100; i) {
    int computedIntAlpha = static_cast < int > (i * nextafter(2.56, 0.0));
    if (computedIntAlpha == alpha()) {
      floatAlpha = i * 0.01;
      break;
    }
    if (computedIntAlpha > alpha())
      break;
  }

  if (!floatAlpha)
    floatAlpha = ceil(alpha() / nextafter(0.256, 0.0)) * 0.001;

  NumberToStringBuffer buffer;
  const char * alphaString = numberToFixedPrecisionString(alpha() / 255.0 f, 6, buffer, true);
  //const char * alphaString = numberToFixedPrecisionString(floatAlpha, 6, buffer, true);
  result.append(alphaString, strlen(alphaString));
}

更多

  • Firefox 比较忠实的还原了用户的输入,这里是相关代码:http://hg.mozilla.org/mozilla-central/file/9b6b80222e66/layout/style/nsStyleUtil.cpp#l567
  • 更多讨论参阅:https://lists.w3.org/Archives/Public/www-style/2015Jan/0596.html

特别鸣谢 @kyriosli 对 C++ 代码的帮助٩(๑ᵒ̴̶̷͈᷄ᗨᵒ̴̶̷͈᷅)و 。

更多讨论请移步知乎:http://www.zhihu.com/question/26248345/answer/45773454

yisibl avatar Apr 24 '15 09:04 yisibl

٩(๑ᵒ̴̶̷͈᷄ᗨᵒ̴̶̷͈᷅)و

antife-yinyue avatar Apr 24 '15 09:04 antife-yinyue

好高深,膜拜一下

zhy1stgg avatar Apr 24 '15 10:04 zhy1stgg

NB

kirk-ye avatar Apr 24 '15 10:04 kirk-ye

:+1:

wangjeaf avatar Apr 24 '15 10:04 wangjeaf

研究问题就是要这么深刻才行~

maxming2333 avatar Apr 24 '15 10:04 maxming2333

还可以研究得这么深入,学习一下!

yreenchan avatar Apr 24 '15 10:04 yreenchan

还是姐姐看到远啊

xiaoqiang730730 avatar Apr 24 '15 12:04 xiaoqiang730730

一丝姐姐人气好高

leoxoocanada avatar Apr 24 '15 13:04 leoxoocanada

ysjj人气好高

kyriosli avatar Apr 27 '15 11:04 kyriosli

真够深入的~~

MurphyL avatar May 15 '15 01:05 MurphyL

牛逼带闪电,佩服!

iPonyo avatar May 15 '15 01:05 iPonyo