原文地址:https://segmentfault.com/a/1190000021493586
作者:Fw恶龙
本文首发于:思否
一、 Alpha合成/阿尔法合成/透明合成
在计算机图形学领域,Alpha合成(alpha compositing)是一种将图像与背景结合的过程,结合后可以产生部分透明或全透明的视觉效果。
二维图像里记录着每个像素的颜色信息,额外的信息以0和1之间的值表示,记录在Alpha通道里。0 表示该像素没有覆盖信息,是透明的,即图中的几何体没有覆盖到本像素;而1则表示像素不透明,几何体完全覆盖了此像素。
图像中使用的Alpha通道通常有两种表示形式:
平直Alpha/非预乘Alpha(straight alpha/non-premultiplied):图像中的RGB分量仅表示像素的颜色,与是否透明无关。
用平直的RGBA元组表达像素颜色,像素值(0, 0.7, 0, 0.5)表示有70%绿色亮度,50%不透明度。同样透明度条件下的纯绿色是(0, 1, 0, 0.5)。
预乘Alpha(premultiplied alpha):图像中的RGB分量也表示像素的颜色,但事先已经和不透明度做了乘法。某些使用场景下,这样的做法可以在后续合成时节省一次乘法。不过预乘Alpha的最显著优势在于使用简单、准确而非性能。
用预乘Alpha,此处的RGB值(0, 0.7, 0)需要都乘以 0.5,表达为(0, 0.35, 0, 0.5)。虽然此处G通道的值是0.35,但它表示的还是最大亮度的70%(其中包含了 50% 的不透明度)。此时的纯绿色表达为(0, 0.5, 0, 0.5)。
二、关于iOS下css渐变插值不正确的bug
具体表现如下图(iOS 12.1.2),当在css中使用渐变时,若是从透明过渡到一个颜色或者一个颜色过渡到透明,会明显的看到有多余的颜色出现,而该多余的颜色取决于你所使用的透明色,比如rgba(255,0,0,0),这个是红色透明,查阅相关资料得知,是 iOS的图形框架Core Graphics的渲染方式问题 。
20221024更新:iOS 15.4 已修复该问题
产生这个问题的具体原因没有深入了解,但可以参考以下栗子。
三、举个栗子
以线性插值为例,一个宽2px高1px的图片,左边是50%透明蓝色rgba(0,0,255,0.5),右边是黑色rgba(0,0,0,1),把这个图片缩放到1x1的大小,那么缩放后1像素的颜色就是左右两个像素线性插值的结果,也就是把两个像素各个通道加起来除以2。
使用非预乘Alpha进行插值,结果是:
((0,0,255,0.5)+(0,0,0,1))⋅0.5=(0,0,127,0.75)
使用预乘Alpha进行插值,结果是:
((0,0,255*0.5,0.5)+(0,0,0,1))⋅0.5=(0,0,63,0.75)
对比上下两个插值结果,上面显得更蓝,下面显得更黑,因为上面蓝色通道没有乘以透明度,所以在线性插值的时候占了过大的权重。
所以预乘Alpha最重要的意义是使得带透明度图片纹理可以正常的进行线性插值。这样旋转、缩放或者非整数的纹理坐标才能正常显示,否则就会像上面的例子一样,在透明像素边缘附近产生奇怪的颜色。
同理计算从透明红色过渡到黑色的插值:
使用非预乘Alpha进行插值,结果是:
((255,0,0,0)+(0,0,0,1))⋅0.5=(127,0,0,0.5)
使用预乘Alpha进行插值,结果是:
((255*0,0,0,0)+(0,0,0,1))⋅0.5=(0,0,0,0.5)
四、修复iOS下css渐变插值不正确的bug
根据以上栗子,除了等待Apple修复它的图形框架外,我们可以用同一颜色的透明色来手动修复该bug.
例如:从透明过渡到红色,我们可以使用红色透明过渡到红色
使用非预乘Alpha进行插值,结果是:
((0,0,255,0)+(0,0,255,1))⋅0.5=(0,0,255,0.5)
使用预乘Alpha进行插值,结果是:
((0,0,255*0,0)+(0,0,255,1))⋅0.5=(0,0,255,0.5)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。