在 html 中用加色法混合颜色
2015-05-28 16:14
543 查看
概要
本文通过解决一个假想的问题介绍了 css screen 混合模式,并介绍了如何用 svg 滤镜、canvas 2d、canvas webgl 实现相同的效果。问题
下面的图片演示三种颜色光叠加的效果,请在 html 中实现这种效果。约定
词语 | 指代 |
---|---|
混合 | blend |
加色 | additive color - 名词 |
特性 | attribute,比如 <a id=1>,说 id 是元素 a 的特性 |
透明度 | α、alpha |
伪输入图像 | pseudo input image |
着色器 | shader |
着色器程序 | shader program |
xml 应用程序 | XML application |
chrome | google chrome 41 |
firefox | firefox developer edition 40 |
ie | internet explorer 11 |
3 个浏览器 | 上面 3 个版本的浏览器 |
分析
当然可以用 photoshop 制作图片,html 用<img>引用该图片,本文不讨论这种方法。
观察重叠部分发现该部分的颜色不仅受自己的影响、还受它下面背景颜色的影响,重叠部分的颜色是自己的颜色和背景颜色混合的结果。换句话说,一个像素绘制出来的颜色等于像素颜色和背景像素颜色的混合,即
C=B(Cb,Cs)C = B(C_b, C_s),其中,
CC 是绘制的颜色
BB 是混合函数
CbC_b 是背景颜色
CsC_s 是前景颜色,即像素的颜色
这里面 CC 的 r、g、b 颜色分量都是 [0, 1] 的小数而不是 [0, 255] 的整数。显然,对同一个像素来说不同的 BB 得到不同的 CC。红绿蓝分别是
rgb(1, 0, 0)、
rgb(0, 1, 0)、
rgb(0, 0, 1),BB 如果满足 B(Cb,Cs)=min(Cs+Cb,1)B(C_b, C_s) = min(C_s + C_b, 1) 就能合成白色。
重点:不同的 BB 得到不同的 CC
不可行的方法
html 中经常用到下面 3 个方法,css opacity 属性
css rgba()/hsla() 颜色
用
<img>引用带 alpha 通道的图像
它们使用相同的混合函数,叫做 α 混合或简单 α 复合,
C=B(Cb,Cs)=Cs×αs+Cb×αb×(1−αs)=Cs×αs+Cb×(1−αs)\begin{align}
C &= B(C_b, C_s) \\
&= C_s \times α_s + C_b \times α_b \times (1 - α_s) \\
&= C_s \times α_s + C_b \times (1 - α_s)
\end{align}
αsα_s 是前景透明度,αbα_b 是背景透明度,上面的式子计算混合后的 r、g、b 颜色,混合后的透明度 αoα_o 由公式 αo=αs+αb×(1−αs)α_o = α_s + α_b \times (1 - α_s) 给出。很多时候背景不透明,即 αbα_b 是 1,上面把 1 代入了 αbα_b。
简单 α 复合 - http://dev.w3.org/fxtf/compositing/#simplealphacompositing
opacity - http://stackoverflow.com/questions/8743482/calculating-opacity-value-mathematically
下面给上面的式子代入几组实际值。设 CsC_s 是不透明红
rgba(1, 0, 0, 1),CbC_b 是不透明蓝
rgb(0, 0, 1),它俩混合的结果不用计算都知道仍然是不透明红,计算过程如下,
r = 1 x 1 + 0 x (1 - 1) = 1 g = 0 x 1 + 0 x (1 - 1) = 0 b = 0 x 1 + 1 x (1 - 1) = 0
红蓝得红,混合失败。另外一组,CsC_s =
rgba(1, 0, 0, 0.5),CbC_b =
rgb(0, 0, 1),有,
r = 1 x 0.5 + 0 x (1 - 0.5) = 0.5 g = 0 x 0.5 + 0 x (1 - 0.5) = 0 b = 0 x 0.5 + 1 x (1 - 0.5) = 0.5
要想让得到的
rgb(0.5, 0, 0.5)和
rgb(0, 1, 0)的绿色混合以得到
rgb(1, 1, 1)的白色,α 需要满足下面的方程组,
{0.5α+0α+0(1−α)=1(1−α)=1={0.5α=11−α=1={α=2α=0
\left\{
\begin{array}{r}
0.5α + & 0(1 -α) = 1\\
0α + & (1 - α) = 1
\end{array}
\right. =
\left\{
\begin{array}{r}
0.5α = 1\\
1 - α = 1
\end{array}
\right. =
\begin{cases}
α = 2\\α = 0
\end{cases}
上面的方程组无解,即无论如何设置 α 都无法通过 BB 混合
rgb(0.5, 0, 0.5)和
rgb(0, 1, 0)得到
rgb(1, 1, 1)。
回过头来观察式子 Cs×αs+Cb×(1−αs)C_s \times α_s + C_b \times (1 - α_s),可以看出结果介于 CsC_s 和 CbC_b 之间。红绿蓝混合时,白色的红色分量只能通过红色得到,这要求红色的 α 是 1,但 α = 1 造成背景颜色蓝或者绿被忽略,而忽略任何一个分量都无法得到白色。因此这个混合函数不合适。
可行的方法
如果可以自己逐一计算像素的颜色,得出要求的效果自然不在话下。除了自己计算外,如果存在正好能够实现要求效果的固定函数,则调用该函数也可以。在 html 中处理颜色有 3 种工具,css、svg、canvas。
css
css 有个模块叫复合与混合,这个模块定义了若干固定函数,其中一个叫 screen,它的 BB 是C=B(Cb,Cs)=1−[(1−Cb)×(1−Cs)]=Cs+Cb−Cs×Cb\begin{align}
C &= B(C_b, C_s) \\
&= 1 - [(1 - C_b) \times (1 - C_s)] \\
&= C_s + C_b - C_s \times C_b
\end{align}
css 复合与混合 - http://dev.w3.org/fxtf/compositing/
假设 add 是 min(Cs+Cb,1)min(C_s + C_b, 1),screen 虽然不是 add 但是也可以把红绿蓝合成白色,实现要求的效果。至于 add、screen 或其它混合函数哪个能更精确地反映光线的混合,我也搞不清楚。
通过指定 html 元素的 css 属性
mix-blend-mode: screen来让元素和其背后的元素以 screen 方式混合。css 目前没办法逐像素计算目标区域的颜色。
html 中的 svg
本文把 svg 写在 html 内 。svg 是 xml 应用程序,遵循 xml 语法,但是放在 html 中又可以采用部分 html 语法。如果大家按照 xml svg 的知识去看本文的代码可能会有疑问,所以在写 svg 之前先说一下 html 中的 svg。html 不支持名字空间,忽略
<svg>里面由特性定义的名字空间,所以本文的 svg 没有
xmlns="http://www.w3.org/2000/svg"或者
xmlns:xlink="http://www.w3.org/1999/xlink",
xlink:href在 html 中是个普通的特性名,冒号和名字空间无关
没有歧义时可以省略特性值周围的引号
xml 中没有内容的元素比如
<circle cx=1 cy=1 r=1></circle>也可以写做
<circle cx=1 cy=1 r=1 />,叫做自闭合;html 不存在自闭合,但内嵌的 svg 元素可以使用自闭合
html 中的 svg 元素可以自闭合 - http://www.w3.org/TR/html-markup/syntax.html#svg-mathml
所有没有内容的 xml 元素都叫 empty 元素,可以自闭合;html 不存在 empty 元素,但是定义了一些 void 元素,void 元素不能有内容,只有开始标记没有结束标记。
所有 void 元素是,area, base, br, col, command, embed, hr, img, input, keygen, link, meta, param, source, track, wbr
http://www.w3.org/TR/html-markup/syntax.html#syntax-elements
所有 html 元素开始标记的 > 前面可以写一个 /,和不写 / 一样。
<br/>解释为
<br>而不是
<br></br>,
<br>是 void 元素,所以没问题;
<div/>解释为
<div>,
<div>不是 void 元素,所以可能会出问题,
<span style="color: green;"><span style="color: red;"/> red = html, green = xhtml </span>
有些元素可以省略结束标记,但不是 void 元素,比如
<li>;有些元素有时候没有内容,但既不是 void 元素也不能省略结束标记,比如
<script src=xxx>;有些元素可以省略开始标记。
http://www.w3.org/TR/html5/syntax.html#optional-tags
浏览器从网站获取的文件 mimetype = text/html 导致调用 html 解析器。
另外,svg 很多要素都没有浏览器支持;当支持的时候,可能各个浏览器有差异。
有了这些知识下面看 svg。
svg
svg 里面的元素也是 dom 元素,也可以应用 css 混合。css 混合在 css 部分讲述svg 有个规范定义了复合,和 css 混合效果差不多,关键字
comp-op。我不知道有哪个浏览器支持该规范
svg 滤镜
<feComposite>和
<feBlend>
svg 滤镜 - http://www.w3.org/tr/svg11/filters.html
svg 复合 - http://www.w3.org/TR/SVGCompositing/
<feComposite>按其
operator特性指出的操作组合两个输入图像 i1i_1、i2i_2。当
operator=arithmetic时需要另外的 4 个特性
k1、
k2、
k3、
k4,默认值是 0,并按如下方式分别计算结果像素的 3 个通道,我不清楚它如何处理 α 通道,
result=k1×i1×i2+k2×i1+k3×i2+k4⋯(svg.1)result = k_1 \times i_1 \times i_2 + k_2 \times i_1 + k_3 \times i_2 + k_4 \cdots (svg.1)
既然知道
mix-blend-mode: screen的混合函数 B=Cs+Cb−Cs×CbB = C_s + C_b - C_s \times C_b,设 CsC_s 是 i1i_1,CbC_b 是 i2i_2,有,
result===k1×i1×i2−1×i1×i2−1×Cs×Cb+++k2×i11×i1Cs+++k3×i21×i2Cb++k40\begin{array}{r}
result &= & k_1 \times i_1 \times i_2 & + & k_2 \times i_1 & + & k_3 \times i_2 & + & k_4 \\
&= & -1 \times i_1 \times i_2 & + & 1 \times i_1 & + & 1 \times i_2 & + & 0 \\
&= & -1 \times C_s \times C_b & + & C_s & + & C_b
\end{array}
所以
<feComposite operator=arithmetic k1=-1 k2=1 k3=1>可以实现效果。
<feBlend>支持 screen 混合模式,
<feBlend mode=screen>,所以应该也能实现效果。
canvas
canvas 分为 2d 和 webgl,它里面的形状都是画上去的,由像素组成,不是 dom 元素,无法应用 css 混合;但是 canvas 2d 有个全局复合操作,和 css 混合是同一个概念在两种不同语言中的实现,支持 css 混合的所有固定函数。当然自己计算像素也行。全局复合操作 - http://dev.w3.org/fxtf/compositing/#canvascompositingandblending
webgl 没有与 css、canvas 2d 完全相同的混合概念,但也有自己的混合函数,解决本文提出的问题不在话下。webgl 有个特点是无论你干什么都需要写着色器代码、写调用编译着色器的函数的代码、写调用连接着色器的函数的代码。
如何运行示例代码
下面是框架代码,后面给出的示例代码需要放在框架代码的<body>里
<!doctype html> <html> <head> <meta charset=utf-8> <style> .sample { display: inline-block; vertical-align: top; width: 200px; } </style> <title>additive color</title> </head> <body> </body> </html>
依次执行下面 3 个步骤,缺一不可,
新建一个空 html 文件
拷贝框架代码,粘贴到空的 html 文件
确保 html 文件编码为 utf8,保存
示例 - css mix-blend-mode
ie 不认识 mix-blend-mode和
isolation
mix-blend-mode - http://dev.w3.org/fxtf/compositing/#mix-blend-mode
isolation - http://dev.w3.org/fxtf/compositing/#isolation
元素的
mix-blend-mode属性是说,我知道你的颜色,但是在显示的时候不要只显示你的颜色,而是要显示你的颜色和你背景颜色混合后的颜色,至于如何混合,我会通过
mix-blend-mode属性的值指出。
因此设计一个容器 div
position: relative,里面有红绿蓝三个方块 div
position: absolute,三个方块之间有重叠部分,通过
mix-blend-mode: screen指出重叠部分颜色的计算方法。
<div class=sample> <style> .s1 { height: 180px; isolation: isolate; position: relative; } .s1 > div { height: 100px; mix-blend-mode: screen; position: absolute; width: 100px; } .s1 > div:nth-of-type(1) { background-color: red; left: 50px; top: 20px; } .s1 > div:nth-of-type(2) { background-color: lime; left: 30px; top: 40px; } .s1 > div:nth-of-type(3) { background-color: blue; left: 70px; top: 60px; } </style> <div class=s1><div></div><div></div><div></div></div> <h4>mix-blend-mode: screen 不是颜色分量相加</h4> </div>
上面的代码实现了刚才的设计,并且额外设置了容器 div 的一个属性
isolation: isolate。元素的
isolation: isolate是说,我的子元素不会和我外面的元素混合。
isolation属性另外一个可能的取值兼默认值是
auto,没有限制、随便混和。
容器 div 放在 html 的
<body>里,
<body>默认的颜色是不透明白,假设没有通过
isolation: isolate限定容器元素的子元素不能与容器外的元素混合,红绿蓝 3 个方块 div 就要和白色以 screen 模式混合。红色
rgb(1, 0, 0)和白色
rgb(1, 1, 1)以 screen 模式 B=Cs+Cb−Cs×CbB = C_s + C_b - C_s \times C_b 混合的结果是
rgb(1, 1, 1)白色,
r = 1 + 1 - 1 x 1 = 1 g = 0 + 1 - 0 x 1 = 1 b = 0 + 1 - 0 x 1 = 1
绿蓝方块和白色混合也得到白色,结果就是一片白,不是要求的效果,所以设置容器的
isolation: isolate。
示例 - svg <feComposite>
和 <feBlend>
svg filter primitive 必须包含在 <filter>元素内
<feComposite>是 svg filter primitive
所以?
<filter x=a y=b width=c height=d>定义一个矩形滤镜区域,默认值是
x=-10%、
y=-10%、
width=120%、
height=120%,
x和
y的值相对于应用滤镜的元素,
x=-10 y=10以应用滤镜的元素为准向左 10 向下 10。
x、
y、
width、
height的数值的解释由另外一个特性 filterUnits 决定,
如果
filterUnits是默认值
objectBoundingBox
x=10是说 x 是应用滤镜的元素的宽度的 10 倍
x=100%是说 x 是应用滤镜的元素的宽度的 1 倍
如果
filterUnits=userSpaceOnUse
x=10是说 x 是 10 个用户单位,用户单位具体是啥要看包含这个元素的 svg 的宽度或高度用的单位,默认 px
x=100%含义不变
filterUnits - http://www.w3.org/TR/SVG11/filters.html#FilterEffectsRegion
这个连接指向 15.5 Filter effects region,因为指向 filterUnits 的连接打开后其内容只是一个指向 FilterEffectsRegion 的连接
如果 svg 宽度单位是 px,高度单位是 cm,用户单位是啥?
这个我也不清楚。难道 x 对应 svg 的 width,y 对应 svg 的 height?
<feComposite in=i1 in2=i2 operator=arithmetic k1=a k2=b k3=c k4=d>接受两个图像
i1和
i2,
<feComposite>逐一扫描
i1和
i2的像素,用 svg.1svg.1 产生新像素,放置到滤镜区域相应的位置。
<feBlend in=i1 in2=i2 mode=screen>对图像
i1和
i2应用 screen 混合模式。
in的默认值是
<filter>中上一个 filter primitive 的结果;如果自己是第一个,则默认
SourceGraphic。
in=SourceGraphic in2=BackgroundImage分别使用当前图片和当前图片的背景图片。
in - http://www.w3.org/TR/SVG11/filters.html#FilterPrimitiveInAttribute
同样是图片,为啥一个叫 source graphic,一个叫 background image?
http://www.w3.org/TR/SVG11/filters.html#AccessingBackgroundImage
简而言之出于性能考虑伪输入图像
BackgroundImage是背景的一个快照,要使用
BackgroundImage作
in或
in2的参数必须指定容器元素的
enable-background=new以要求容器存储背景快照供滤镜使用。
enable-background默认值是
accumulate,不存,也无法使用
BackgroundImage。两个不同的单词可能是要强调输入图像和背景图像的这种差异。当然也可能是我想多了,人家就是喜欢出其不意,如之奈何?
混合 3 个形状需要应用两次滤镜,出于演示的目的正好一个用
<feComposite>一个用
<feBlend>。
创建 3 个分别是红绿蓝的 svg 形状
为了使用
BackgroundImage,把这 3 个形状放到一个组里面,并设置组的
enable-background=new
放置第 1 个形状
把形状 2 放到和形状 1 部分重叠的位置,此时形状 1 可以视为形状 2 的背景,对形状 2 应用滤镜
<feComposite in2=BackgroundImage>,重叠的部分就会经过 svg.1svg.1 计算
形状 3 用
<feBlend in2=BackgroundImage>。
<div class=sample> <svg height=180 width=200> <filter id=s2-composite x=0 y=0 width=1 height=1> <feComposite in2=BackgroundImage operator=arithmetic k1=-1 k2=1 k3=1></feComposite> </filter> <filter id=s2-blend> <feBlend in2=BackgroundImage mode=screen></feBlend> </filter> <g enable-background=new> <rect width=100 height=100 x=50 y=20 fill=red></rect> <rect width=100 height=100 x=30 y=40 fill=lime filter=url(#s2-composite)></rect> <rect width=100 height=100 x=70 y=60 fill=blue filter=url(#s2-blend)></rect> </g> </svg> <h4>svg,仅限 ie 10+</h4> </div>
没有指出
<filter id=s2-blend>的
x、
y、
width、
height所以它们都取默认值。
只有 ie 10+ 支持上面的代码。ie 此刻又迸射出耀眼的光芒,
别的浏览器玩儿蛋去吧!
这是我心里想象的 ie 工作人员心里的想象,请不要以为他们一定是那样想的。
Appendix A: The deprecated enable-background property
http://dev.w3.org/fxtf/filters/#AccessBackgroundImage
svg 最近的风向是不赞成
enable-background了,
enable-background=new要换成
isolation=isolate以“兼容 css 复合与混合”。大家留意一下,
isolation是 css 属性,在样式表里面指定;svg 发明了个
presentation attribute,这种特性也可以在样式表中以 css 属性的形式指定以兼容 css,而 svg 这个
isolation不是所谓的
presentation attribute,不能在样式表里指定。这还兼容个毛?svg 就是这样,当你拿它和 html 比的时候,一眼看上去都差不多,似乎能很容易混用,实际上有很多出其不意的不一样,烦得要死。
无论如何还是要换一下试试。换成
isolation=isolate后连 ie 都没法读取背景图片了,前面的 css 部分说过 ie 不认识样式表中的
isolation,现在看来 ie 也不认识
isolation特性,3 个浏览器没有能运行的。http://dev.w3.org/fxtf/filters/ 里面的示例 Example of feComposite 就是从 http://www.w3.org/TR/SVG11/filters.html 拷贝的同名示例,只是把
enable-background=new换成了
isolation=isolate,但是紧跟其后的连接 View this example as SVG 指向的 svg 文件里面用的仍然是
enable-background=new。
这么看来 ie 是被坑了,但从大的方面看 svg 本身就很坑,废弃
enable-background一点都不亏。svg 很好玩,但是能别用 xml 语法吗?
一般只要 chrome 和 firefox 能用,ie 我常常忽略,前面 css 的示例代码就没管 ie。现在这段代码,由于只有 ie 10+ 支持伪输入图像
BackgroundImage,chrome 和 firefox 上都运行不了,不能说是达到了要求的效果,最好有在 3 个浏览器上都能运行的 svg 例子。所幸对于这个简单的问题,TIMTOWTDI!下面凑一个能在 3 个浏览器上运行的 svg 解法,说凑是因为它没有把 3 个元素两两混合,而是在滤镜里生成了两个方块,和应用滤镜的那个方块元素混合。在滤镜里生成方块指的是把滤镜矩形设置为单一的颜色然后临时保存,这要用到
<feColorMatrix>,
<feColorMatrix type=matrix values=" a00 a01 a02 a03 a04 a10 a11 a12 a13 a14 a20 a21 a22 a23 a24 a30 a31 a32 a33 a34" />
说的是当把图像传递给
<feColorMatrix>时,对图像的每一个像素左乘下面的矩阵以得到新的像素,
a00 a01 a02 a03 a04 a10 a11 a12 a13 a14 a20 a21 a22 a23 a24 a30 a31 a32 a33 a34 0 0 0 0 1 - 最后一行总是它,不写在 values 里
即新像素
(r', g', b', a')等于
<feColorMatrix>给出的矩阵乘以原像素
(r, g, b, a),
⎡⎣⎢⎢⎢⎢⎢⎢R′G′B′A′1⎤⎦⎥⎥⎥⎥⎥⎥=⎡⎣⎢⎢⎢⎢⎢⎢a00a10a20a300a01a11a21a310a02a12a22a320a03a13a23a330a04a14a24a341⎤⎦⎥⎥⎥⎥⎥⎥×⎡⎣⎢⎢⎢⎢⎢⎢RGBA1⎤⎦⎥⎥⎥⎥⎥⎥
\left[
\begin{array}{c}
R'\\G'\\B'\\A'\\1
\end{array}
\right] =
\left[
\begin{array}{c}
a_{00} & a_{01} & a_{02} & a_{03} & a_{04}\\
a_{10} & a_{11} & a_{12} & a_{13} & a_{14}\\
a_{20} & a_{21} & a_{22} & a_{23} & a_{24}\\
a_{30} & a_{31} & a_{32} & a_{33} & a_{34}\\
0 & 0 & 0 & 0 & 1
\end{array}
\right] \times
\left[
\begin{array}{c}
R\\G\\B\\A\\1
\end{array}
\right]
<feColorMatrix type=matrix values=" 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0" />
把
(r, g, b, a, 1)变成
(1, 0, 0, a, 1),这是红色。因此可以写下面的代码,
定义一个蓝方块
<rect fill=blue>
对蓝方块应用滤镜
滤镜从蓝方块生成一个红方块和一个绿方块,偏移,混合
上一步的结果和蓝方块混合,蓝方块在滤镜中通过伪输入图像
SourceGraphic引用
<div class=sample> <svg height=180 width=200> <filter id=s2-2 x=-1 y=-1 width=2 height=2> <feOffset dx=-20 dy=-40></feOffset> <feColorMatrix result=red type=matrix values=" 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0"></feColorMatrix> <feOffset in=SourceGraphic dx=-40 dy=-20></feOffset> <feColorMatrix type=matrix values=" 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0"></feColorMatrix> <feBlend mode=screen in2=red></feBlend> <feBlend mode=screen in2=SourceGraphic></feBlend> </filter> <rect x=70 y=60 width=100 height=100 fill=blue filter=url(#s2-2)></rect> </svg> <h4>svg feBlend</h4> </div>
也可以做红绿蓝 3 张图片,在滤镜里用
<feImage>引用,代码类似下面,记得先做 3 张 100px * 100px 的红绿蓝图片放到 html 的同一目录。
<svg width=200 height=180> <filter id=additive> <feImage x=50 y=20 width=100 height=100 result=layer1 xlink:href=05-red.png /> <feImage x=30 y=40 width=100 height=100 result=layer2 xlink:href=05-lime.png /> <feImage x=70 y=60 width=100 height=100 result=layer3 xlink:href=05-blue.png /> <feBlend in=layer1 in2=layer2 mode=screen result=step-1 /> <feBlend in=step-1 in2=layer3 mode=screen /> </filter> <rect width=200 height=180 filter=url(#additive) /> </svg>
总结
svg 滤镜的思路就是<feComposite>和
<feBlend>,前者自己计算像素,后者调用固定函数。由于 chrome 和 firefox 不支持在滤镜中读取背景图像所以给了两段绕弯的代码,第 2 段代码还依赖 3 张图片。
示例 - canvas 2d
方法 1.CanvasRenderingContext2D.prototype.globalCompositeOperation
globalCompositeOperation 是 HTML Canvas 2D Context 规范定义在接口 CanvasRenderingContext2D 上的一个特性。chrome 里面访问不到 CanvasRenderingContext2D.prototype.globalCompositeOperation,firefox 和 ie 里面存在该 js 属性,定义了 get 和 set 访问函数。
不能直接在代码里使用
CanvasRenderingContext2D.prototype.globalCompositeOperation,因为这句代码会调用
get函数,而
get需要通过
this访问实际的画布上下文对象。当然正常情况下也不会那么写,正常情况是先获取某个画布的 2d 上下文,然后访问上下文的属性,
var t = theCanvas.getContext("2d"); console.log(t.globalCompositeOperation); // 默认 "source-over"
有了上面的
t,可以写
Object.getOwnPropertyDescriptor(CanvasRenderingContext2D.prototype, "globalCompositeOperation").get.call(t);
interface CanvasRenderingContext2D
http://www.w3.org/TR/2dcontext/#canvasrenderingcontext2d
绘制 红 色方块
全局复合模式 = screen
绘制 绿 色方块
绘制 蓝 色方块
<div class=sample> <canvas class=s3-1 height=180 width=200></canvas> <h4>canvas 2d 全局复合</h4> <script> !function () { var canvas = document.querySelector(".s3-1"), cc = canvas.getContext("2d"); cc.fillStyle = "red"; cc.fillRect(50, 20, 100, 100); cc.globalCompositeOperation = "screen"; cc.fillStyle = "lime"; cc.fillRect(30, 40, 100, 100); cc.fillStyle = "blue"; cc.fillRect(70, 60, 100, 100); }(); </script> </div>
方法 2.
CanvasRenderingContext2D.prototype.putImageData
绘制 红 色方块
选定一个部分重叠的方块,使用
CanvasRenderingContext2D.prototype.getImageData把该部分像素读入内存,对每个像素,和 绿 色应用 min(Cs+Cb,1)min(C_s + C_b, 1),然后写入画布
选定一个部分重叠的方块,读取,每个像素和 蓝 色应用 min(Cs+Cb,1)min(C_s + C_b, 1),写入画布
因为红绿蓝两两混合时每个分量必然一个是 0 一个是 1,所以并没有调用 Math.min 而是直接把分量置 1,或者说置 255。
<div class=sample> <canvas class=s3-2 height=180 width=200></canvas> <h4>在 canvas 中把颜色分量置 1,未使用 min(x + y, 1)</h4> <script> !function () { var canvas = document.querySelector(".s3-2"), cc = canvas.getContext("2d"), i, len, idata, arr; cc.fillStyle = "red"; cc.fillRect(50, 20, 100, 100); for (i = 0, idata = cc.getImageData(30, 40, 100, 100), arr = idata.data, len = arr.length; i < len; i += 4) arr[i + 1] = arr[i + 3] = 255; cc.putImageData(idata, 30, 40); for (i = 0, idata = cc.getImageData(70, 60, 100, 100), arr = idata.data, len = arr.length; i < len; i += 4) arr[i + 2] = arr[i + 3] = 255; cc.putImageData(idata, 70, 60); }(); </script> </div>
示例 - canvas webgl
webgl api - https://msdn.microsoft.com/en-us/library/dn621085(v=vs.85).aspxwebgl methods - https://msdn.microsoft.com/en-us/library/dn302341(v=vs.85).aspx
glBlendFunc + glBlendEquation 效果演示 - http://www.andersriggelsen.dk/glblendfunc.php
本文假设你对 webgl 一无所知。
本节的目标是在阅读本节内容之后,对 webgl 一无所知的读者能掌握 webgl 的基本思路、写出基本的 webgl 程序。如果不是这个情况,请跟帖指出,我会修正本节内容直至达到前述目标。
绘制方块
设var gl = theCanvas.getContext("webgl");,webgl 通过下面两个函数之一进行绘制,
gl.drawArrays(mode, first, count);
gl.drawElements(mode, count, type, offset);
这两个函数差不多,先解释
gl.drawArrays。在调用
gl.drawArrays之前
gl必须满足下列条件,
使用
gl.useProgram绑定了 1 个着色器程序
使用
gl.bindBuffer绑定了 1 个数组,数组里面有内容可用
使用
gl.enableVertexAttribArray启用了至少 1 个在顶点着色器里定义的特性
使用
gl.vertexAttribPointer描述了启用的特性
上面 4 个条件就是发起 1 次
gl.drawArrays调用所需的代码 + 数据,代码用
opengl es着色器语言
glsl写成,数据在
javascript代码里面提供,并调用 webgl 的 javascript api 建立
js 数据到
glsl 代码的联系。
1 个 webgl 程序可以有很多着色器程序,每个着色器程序一定有 1 个顶点着色器和 1 个片段着色器。每绘制 1 个点,webgl 都依次调用顶点着色器和片段着色器。顶点着色器的唯一任务是给全局变量
gl_Position赋值,它代表 1 个点的位置;片段着色器的唯一任务是给全局变量
gl_FragColor赋值,它代表刚才那个点的颜色。
gl.drawArrays依次执行下列步骤,
webgl 一次从数组读取由
gl.vertexAttribPointer的
stride参数指出的字节,这些字节视为 1 个顶点
把这么多字节按
gl.vertexAttribPointer指出的方式拆分后分别赋值给顶点着色器里面用
attribute定义的变量,调用了几次
gl.vertexAttribPointer就要给几个变量赋值
进入顶点着色器的
main函数,
main里面一般会使用刚才赋值过的特性
顶点着色器的
main结束,进入片段着色器的
main函数
片段着色器的
main结束,1 个顶点渲染完毕,从数组读取下一个顶点
重复上述过程,直至处理了由
count参数指出的顶点数
webgl 实际上读取的是从 javascript 数组拷贝到显卡上的数组
gl.vertexAttribPointer( index, - 特性在和 gl.ARRAY_BUFFER 绑定的缓冲区中的索引 size, - 1 | 2 | 3 | [4],每个特性有几个分量,比如 vec3 有 3 个分量 type, - gl.BYTE | gl.UNSIGNED_BYTE | gl.SHORT | gl.UNSIGNED_SHORT | [gl.FLOAT] normalized, - true,转化到 [-1.0, 1.0] stride, - [0, 255],默认 0,单位字节,必须是 type 的整数倍 offset - 默认 0,单位字节,必须是 type 的整数倍 )
读作:为了给顶点着色器里面定义的第
index号特性赋值,从数组中取
stride个字节作为一个顶点,从这个顶点的第
offset个字节开始取
size个
type,每个
type依次对应特性的一个分量。
假设在顶点着色器里定义了 2 个特性
attribute vec3 position; attribute vec2 resolution; void main() { gl_Position = ???; }
每次进入顶点着色器的时候都希望这俩变量被赋值,以便在顶点着色器的
main里面使用它们。在 javascript 里面用一个
Float32Array保存顶点,数组形如
[x0, y0, z0, w0, h0, x1, y1, z1, w1, h1, ...]
下面的调用
gl.vertexAttribPointer(idPosition, 3, gl.FLOAT, false, 5 * 4, 0); gl.vertexAttribPointer(idResolution, 2, gl.FLOAT, false, 5 * 4, 3 * 4); // 4 是 Float32Array 数组的元素 Float32 的字节数,对应 gl.FLOAT // 5 是说一个顶点有 5 个 Float32,5 * 4 是这个顶点的字节数 // 第 2 个调用里面的 3 是说 resolution 从每个顶点的第 3 个 Float32 开始 // // idPosition 和 idColor 是 gl.getAttribLocation 返回的一个整数, // 代表顶点着色器里面的特性 position 和 resolution。position 和 resolution // 是在顶点着色器里面定义的变量,不能直接在 javascript 里面用,需要通过 // gl.getAttribLocation 建立一个对应关系 // // 如果给 resolution 的 offset 参数传 0 则 resolution 和 position 重叠, // 这没有问题但是数值可能没有意义
让 webgl 这样取值
| 数组中每个顶点的长度是 5 * 4 = 20(stride)个字节 | | x0, y0, z0, w0, h0, x1, y1, z1, w1, h1, ... | | | | | | | 从数组 arr 的第 3 * 4 = 12(offset)个字节开始取 2(size)个 | | | gl.FLOAT(type)组成一个 vec2(arr[3], arr[4]),把这个 vec2 | | | 赋值给顶点着色器特性 resolution | | | 从数组 arr 的第 0(offset)个字节开始取 3(size)个 gl.FLOAT(type)组成 | 一个 vec3(arr[0], arr[1], arr[2]),把这个 vec3 赋值给顶点着色器特性 position
每个顶点的画布分辨率
resolution都一样,所以一般不这么传递,放在这里只是为了举例。
gl.drawArrays(mode, first, count);的
mode参数从 webgl 定义的枚举里面取值,分 3 种类型
gl.POINTS,点。数组中每个顶点代表一个点
gl.LINES、
gl.LINE_STRIP、
gl.LINE_LOOP,直线段。数组中每个顶点代表直线的一个端点或者说顶点,顶点之间的点由 webgl 以线性插值的方式计算出来
gl.TRIANGLES、
gl.TRIANGLE_STRIP、
gl.TRIANGLE_FAN,平面三角形。数组中每个顶点代表三角形的一个顶点,顶点之间的点由 webgl 以线性插值的方式计算出来
为了绘制一个方块,调用
gl.drawArrays(gl.TRIANGLE_FAN, first, 4);,意思是从当前绑定的数组的第
first个顶点开始用连续的 4 个顶点组成 1 个三角扇,这 4 个点的位置是事先规划好的,排列如下
0 3 1 2
webgl.1webgl.1
4 个点的三角扇包含 2 个三角形,分别是
0 - 1 - 2和
0 - 2 - 3,三角扇绘制的三角形的第 1 个顶点总是
first处的那个顶点
这 2 个三角形共享 1 条边
0 - 2,两条边的方向相反,第 1 个是
2 -> 0,第 2 个是
0 -> 2,说这样的 2 个三角具有相同的朝向。三角扇两个相邻三角形的朝向一定相同
如果改变了顶点的顺序,得到的三角扇可能就不是一个方块
有了这些知识下面写一个绘制黑色方块的程序,里面出现了顶点着色器和片段着色器代码,
ie 只支持
experimental-webgl
这里面调用的函数
glProgram在正式示例中定义,如果要运行需拷贝
glProgram函数
上面是注意事项
<canvas class=s4-rect width=200 height=180></canvas> <script> !function () { var canvas = document.querySelector(".s4-rect"), cc = canvas.getContext("webgl"), // 片段着色器源代码 // 片段着色器必须用 precision mediump float 指出 float 的默认精度。 // 不像顶点着色器,片段着色器里面的 float 没有默认精度,不指定 float 默认 // 精度的话编译片段着色器就会失败。这是个比较荒唐的事实 // http://stackoverflow.com/questions/28540290/why-it-is-necessary-to-set-precision-for-the-fragment-shader // // 这个片段着色器代码就一句话,把所有顶点的颜色设置成不透明黑。由于是 4 个 // 顶点组成的方块,方块上除了 4 个顶点之外的点的颜色都由 webgl 通过线性插值 // 得出,不会进入片段着色器的 main,插值的结果还是不透明黑 sfs = "precision mediump float; void main() { gl_FragColor = vec4(0, 0, 0, 1); }", // 顶点着色器源代码 // vec2 是两个 float。每处理一个顶点,从数组中取得的两个 float 都会赋值给 // 这个 attribute vec2 posxy,posxy 把数组传进来的内容原封不动地作为 // gl_Position 的 x 和 y // // gl_Position 的 4 个坐标 x, y, z, w 都是 [-1, 1] 的小数 // // 绘制了 4 个顶点,所以顶点着色器的 main 总共进入 4 次。方块上其它点的坐标 // 由线性插值生成 svs = "attribute vec2 posxy; void main() { gl_Position = vec4(posxy, 0, 1); }", // glProgram 的定义在下面的示例代码中给出 program = glProgram(cc, sfs, svs), // 顶点数组。一会要通过 gl.bufferData 给顶点数组写入内容 arr = cc.createBuffer(), i; // gl.drawArrays 先决条件:gl.useProgram cc.useProgram(program); // gl.drawArrays 先决条件:gl.bindBuffer cc.bindBuffer(cc.ARRAY_BUFFER, arr); // 通过 gl.bufferData 往 arr 写入内容,作为顶点位置 // 参数里面并没有出现 arr,之所以能写进去是因为前面用 bindBuffer 指出了 arr 是 // 当前的 gl.ARRAY_BUFFER // 最后一个参数是 gl.STATIC_DRAW,不用考虑 // 对照图 webgl.1 cc.bufferData(cc.ARRAY_BUFFER, new Float32Array([ -0.3, +0.3, // 0 - 左上 -0.3, -0.7, // 1 - 左下 +0.7, -0.7, // 2 - 右下 +0.7, +0.3 // 3 - 右上 ]), cc.STATIC_DRAW); // gl.drawArrays 先决条件:gl.enableVertexAttribArray // 用 gl.getAttribLocation(program, "posxy") 获取顶点着色器里面定义的特性 // posxy 对应的整数索引,保存这个整数索引供 gl.enableVertexAttribArray 使用 i = cc.getAttribLocation(program, "posxy"); cc.enableVertexAttribArray(i); // gl.drawArrays 先决条件:gl.vertexAttribPointer // 一次从数组里取出 stride 个字节。如果只调用了一次 // gl.vertexAttribPointer,stride 也可以填 0,会自动计算 stride cc.vertexAttribPointer(i, 2, cc.FLOAT, false, 2 * 4, 0); // gl.drawArrays cc.drawArrays(cc.TRIANGLE_FAN, 0, 4); }(); </script>
上面是
gl.drawArrays,它从用
gl.bindBuffer(gl.ARRAY_BUFFER, arr)绑定的
arr中依次读取每个顶点。
gl.drawElements需要用
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ids)额外绑定一个数组作为
gl.ARRAY_BUFFER的索引,这样一来它使用两个数组,
arr保存顶点,
ids保存遍历
arr的顺序。设
// vertex 0, vertex 1, vertex 2, vertex 3, ... arr = [ x0, y0, x1, y1, x2, y2, x3, y3, ...] ids = [0, 3, 1, 2]
并且
已经调用了两次
gl.bindBuffer让
gl.ARRAY_BUFFER和
gl.ELEMENT_ARRAY_BUFFER分别对应
arr和
ids
gl.vertexAttribPointer指出每个顶点是 2 个
type
索引数组
ids的元素类型是
Uint16或者说
gl.UNSIGNED_SHORT
则
gl.drawArrays(gl.TRIANGLE_FAN, 0, 4)绘制 4 次顶点,依次是
0 - 1 - 2 - 3
gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, 0)绘制 4 次顶点,它依次读取
ids的每个元素,以元素的值作为
arr的索引去获取顶点,依次绘制
arr的
0 - 3 - 1 - 2
我假设读者通过前面的阅读和练习已经理解了
gl.drawArrays和
gl.drawElements,下面简要介绍设置颜色。
前面在片段着色器里硬编码了个颜色,不透明黑,要画 3 个颜色的方块就需要 3 个片段着色器。如果能让片段着色器接受一个 javascript 传入的变量,有点像顶点着色器里面的特性
attribute,从 javascript 指定颜色,那就可以只写一个片段着色器。出于两个原因,不使用
attribute
只有顶点着色器可以定义
attribute,片段着色器不可以
方块是单色的,不需要像
attribute那样每个顶点都传一个值
着色器程序总共可以定义 3 种变量:
attribute、
uniform、
varying
这里使用
uniform。
varying用来从顶点着色器往片段着色器传值,当然也可以实现效果。
uniform的意思是,每次调用
gl.drawArrays绘制一系列的顶点之前,先设置一个在绘制过程中保持不变的值,绘制这些点的过程中,着色器程序可以读取但不能修改该定值。对比
attribute,
attribute对
gl.drawArrays绘制的每 1 个顶点都赋值 1 次;
uniform只在
gl.drawArrays开始前赋值一次。
因为
uniform在 1 次绘制中只赋值 1 次,所以它不从数组里面取值,
gl.uniformXxx用于设置
uniform的值。
所以下面的示例中,顶点着色器定义 1 个
attribute以接受顶点,片段着色器定义 1 个
uniform以接受颜色,调用 3 次
gl.drawArrays以绘制 3 个方块。
混合颜色
这里需要把 C=B(Cb,Cs)C = B(C_b, C_s) 换个形式以反映 webgl 的混合方法,换成 C=e(f(Cs),g(Cb))C = e(f(C_s), g(C_b))。看上去更复杂了,但马上就会发现,它很简单。ee 对应
gl.blendEquation(mode),
mode是 3 个枚举值之一
mode | ee |
---|---|
gl.FUNC_ADD- 默认值 | e(x,y)=clamp(x+y)e(x, y) = clamp(x + y) |
gl.FUNC_SUBTRACT | e(x,y)=clamp(x−y)e(x, y) = clamp(x - y) |
gl.FUNC_REVERSE_SUBTRACT | e(x,y)=clamp(y−x)e(x, y) = clamp(y - x) |
ff 和 gg 分别对应
gl.blendFunc(sfactor, dfactor)中的
sfactor和
dfactor,均从下列枚举中取值
factor | ff |
---|---|
gl.ZERO- dfactor默认值 | f(x)=x×0f(x) = x \times 0 |
gl.ONE- sfactor默认值 | f(x)=x×1f(x) = x \times 1 |
gl.SRC_COLOR | f(x)=x×Csf(x) = x \times C_s |
gl.ONE_MINUS_SRC_COLOR | f(x)=x×(1−Cs)f(x) = x \times (1 - C_s) |
gl.DST_COLOR | f(x)=x×Cbf(x) = x \times C_b |
gl.ONE_MINUS_DST_COLOR | f(x)=x×(1−Cb)f(x) = x \times (1 - C_b) |
gl.SRC_ALPHA | f(x)=x×αsf(x) = x \times α_s |
gl.ONE_MINUS_SRC_ALPHA | f(x)=x×(1−αs)f(x) = x \times (1 - α_s) |
gl.DST_ALPHA | f(x)=x×αbf(x) = x \times α_b |
gl.ONE_MINUS_DST_ALPHA | f(x)=x×(1−αb)f(x) = x \times (1 - α_b) |
gl.SRC_ALPHA_SATURATE | f(x)=x×min(αs,αb)f(x) = x \times min(α_s, α_b) |
dfactor = gl.ONE、
sfactor取默认值
gl.ONE、
mode取默认值
gl.FUNC_ADD时有
C=e(f(Cs),g(Cb))=clamp(Cs×1+Cb×1)=clamp(Cs+Cb)\begin{align}
C &= e(f(C_s), g(C_b)) \\
&= clamp(C_s \times 1 + C_b \times 1) \\
&= clamp(C_s + C_b)
\end{align}
当 CsC_s 和 CbC_b 都是正数时 clamp(Cs+Cb)=min(Cs+Cb,1)clamp(C_s + C_b) = min(C_s + C_b, 1),就是前面用过的 add 混合模式。相应的 js 代码是
gl.blendFunc(gl.ONE, gl.ONE); gl.enable(gl.BLEND); // 必须明显的启用混合
而如果让
sfactor = gl.ONE_MINUS_DST_COLOR、
dfactor = gl.ONE、
mode取默认值
gl.FUNC_ADD,有
C=e(f(Cs),g(Cb))=clamp(Cs×(1−Cb)+Cb)=clamp(Cs+Cb−Cs×Cb)\begin{align}
C &= e(f(C_s), g(C_b)) \\
&= clamp(C_s \times (1 - C_b) + C_b) \\
&= clamp(C_s + C_b - C_s \times C_b)
\end{align}
当 CsC_s 和 CbC_b 都是正数时 clamp(Cs+Cb−Cs×Cb)=Cs+Cb−Cs×Cbclamp(C_s + C_b - C_s \times C_b) = C_s + C_b - C_s \times C_b,就是前面用过的 screen 混合模式。相应的 js 代码是
gl.blendFunc(gl.ONE_MINUS_DST_COLOR, gl.ONE); gl.enable(gl.BLEND); // 必须明显的启用混合
代码
现在的情况是会用
gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);绘制方块
知道将要用
uniform给片段着色器传递一个代表颜色的变量
知道如何设置混合
下面看具体的代码
<div class=sample> <canvas class=s4 height=180 width=200></canvas> <h4>webgl 混合</h4> <script> !function () { var canvas = document.querySelector(".s4"), ch = canvas.height, cw = canvas.width, cc = canvas.getContext("webgl") || canvas.getContext("experimental-webgl"), svs = "attribute vec2 position;" + "uniform vec2 resolution;" + "void main() {" + " vec2 p = position / resolution * 2.0 - 1.0;" + " gl_Position = vec4(p * vec2(1, -1), 0, 1);" + "}", sfs = "precision mediump float;" + "uniform vec3 color;" + "void main() { gl_FragColor = vec4(color, 1); }", rects = [ 50, 20, 50, 120, 150, 120, 150, 20, // red 30, 40, 30, 140, 130, 140, 130, 40, // lime 70, 60, 70, 160, 170, 160, 170, 60 // blue ], buffer = cc.createBuffer(), attrs = { position: 0, }, unifs = { color: 0, resolution: 0 }, program = glProgram(cc, sfs, svs, attrs, unifs); cc.useProgram(program); cc.uniform2f(unifs.resolution, cw, ch); cc.bindBuffer(cc.ARRAY_BUFFER, buffer); cc.vertexAttribPointer(attrs.position, 2, cc.UNSIGNED_BYTE, false, 0, 0); cc.bufferData(cc.ARRAY_BUFFER, new Uint8Array(rects), cc.STATIC_DRAW); //cc.blendFunc(cc.ONE, cc.ONE); cc.blendFunc(cc.ONE_MINUS_DST_COLOR, cc.ONE); cc.enable(cc.BLEND); draw(cc, 0, [1, 0, 0]); draw(cc, 4, [0, 1, 0]); draw(cc, 8, [0, 0, 1]); function draw(gl, offset, color) { gl.uniform3fv(unifs.color, color); gl.drawArrays(gl.TRIANGLE_FAN, offset, 4); } }(); function glProgram(gl, sfs, svs, attrs, unifs) { var prop, i, program = glLink(gl, glCompile(gl, sfs, gl.FRAGMENT_SHADER), glCompile(gl, svs, gl.VERTEX_SHADER)); for (prop in attrs) { i = gl.getAttribLocation(program, prop); attrs[prop] = i; gl.enableVertexAttribArray(i); } for (prop in unifs) unifs[prop] = gl.getUniformLocation(program, prop); return program; function glCompile(gl, source, type) { var shader = gl.createShader(type); gl.shaderSource(shader, source); gl.compileShader(shader); return shader; } function glLink(gl, fs, vs) { var program = gl.createProgram(); gl.attachShader(program, fs); gl.attachShader(program, vs); gl.linkProgram(program); return program; } } </script> </div>
要点
uniform不像
attribute那样要调用
gl.enableVertexAttribArray,使用
gl.getUniformLocation获取
uniform在 javascript 中的索引后就能用了
js 中的顶点数组使用了大于 1 的整数坐标,这个整数坐标传入顶点着色器后,顶点着色器要根据当前的画布尺寸换算出相应的小数,然后才给
gl_Position赋值
代码包含 add 和 screen 混合模式,就一句代码,add 被注释掉了
全部代码
<!doctype html>
<html>
<head>
<meta charset=utf-8>
<style>
.sample { display: inline-block; vertical-align: top; width: 200px; }
</style>
<title>additive color</title>
</head>
<body>
<div class=sample>
<style>
.s1 { height: 180px; isolation: isolate; position: relative; }
.s1 > div { height: 100px; mix-blend-mode: screen; position: absolute; width: 100px; }
.s1 > div:nth-of-type(1) { background-color: red; left: 50px; top: 20px; }
.s1 > div:nth-of-type(2) { background-color: lime; left: 30px; top: 40px; }
.s1 > div:nth-of-type(3) { background-color: blue; left: 70px; top: 60px; }
</style>
<div class=s1><div></div><div></div><div></div></div>
<h4>mix-blend-mode: screen 不是颜色分量相加</h4>
</div>
<div class=sample> <svg height=180 width=200> <filter id=s2-composite x=0 y=0 width=1 height=1> <feComposite in2=BackgroundImage operator=arithmetic k1=-1 k2=1 k3=1></feComposite> </filter> <filter id=s2-blend> <feBlend in2=BackgroundImage mode=screen></feBlend> </filter> <g enable-background=new> <rect width=100 height=100 x=50 y=20 fill=red></rect> <rect width=100 height=100 x=30 y=40 fill=lime filter=url(#s2-composite)></rect> <rect width=100 height=100 x=70 y=60 fill=blue filter=url(#s2-blend)></rect> </g> </svg> <h4>svg,仅限 ie 10+</h4> </div>
<div class=sample> <svg height=180 width=200> <filter id=s2-2 x=-1 y=-1 width=2 height=2> <feOffset dx=-20 dy=-40></feOffset> <feColorMatrix result=red type=matrix values=" 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0"></feColorMatrix> <feOffset in=SourceGraphic dx=-40 dy=-20></feOffset> <feColorMatrix type=matrix values=" 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0"></feColorMatrix> <feBlend mode=screen in2=red></feBlend> <feBlend mode=screen in2=SourceGraphic></feBlend> </filter> <rect x=70 y=60 width=100 height=100 fill=blue filter=url(#s2-2)></rect> </svg> <h4>svg feBlend</h4> </div>
<div class=sample> <canvas class=s3-1 height=180 width=200></canvas> <h4>canvas 2d 全局复合</h4> <script> !function () { var canvas = document.querySelector(".s3-1"), cc = canvas.getContext("2d"); cc.fillStyle = "red"; cc.fillRect(50, 20, 100, 100); cc.globalCompositeOperation = "screen"; cc.fillStyle = "lime"; cc.fillRect(30, 40, 100, 100); cc.fillStyle = "blue"; cc.fillRect(70, 60, 100, 100); }(); </script> </div>
<div class=sample> <canvas class=s3-2 height=180 width=200></canvas> <h4>在 canvas 中把颜色分量置 1,未使用 min(x + y, 1)</h4> <script> !function () { var canvas = document.querySelector(".s3-2"), cc = canvas.getContext("2d"), i, len, idata, arr; cc.fillStyle = "red"; cc.fillRect(50, 20, 100, 100); for (i = 0, idata = cc.getImageData(30, 40, 100, 100), arr = idata.data, len = arr.length; i < len; i += 4) arr[i + 1] = arr[i + 3] = 255; cc.putImageData(idata, 30, 40); for (i = 0, idata = cc.getImageData(70, 60, 100, 100), arr = idata.data, len = arr.length; i < len; i += 4) arr[i + 2] = arr[i + 3] = 255; cc.putImageData(idata, 70, 60); }(); </script> </div>
<div class=sample> <canvas class=s4 height=180 width=200></canvas> <h4>webgl 混合</h4> <script> !function () { var canvas = document.querySelector(".s4"), ch = canvas.height, cw = canvas.width, cc = canvas.getContext("webgl") || canvas.getContext("experimental-webgl"), svs = "attribute vec2 position;" + "uniform vec2 resolution;" + "void main() {" + " vec2 p = position / resolution * 2.0 - 1.0;" + " gl_Position = vec4(p * vec2(1, -1), 0, 1);" + "}", sfs = "precision mediump float;" + "uniform vec3 color;" + "void main() { gl_FragColor = vec4(color, 1); }", rects = [ 50, 20, 50, 120, 150, 120, 150, 20, // red 30, 40, 30, 140, 130, 140, 130, 40, // lime 70, 60, 70, 160, 170, 160, 170, 60 // blue ], buffer = cc.createBuffer(), attrs = { position: 0, }, unifs = { color: 0, resolution: 0 }, program = glProgram(cc, sfs, svs, attrs, unifs); cc.useProgram(program); cc.uniform2f(unifs.resolution, cw, ch); cc.bindBuffer(cc.ARRAY_BUFFER, buffer); cc.vertexAttribPointer(attrs.position, 2, cc.UNSIGNED_BYTE, false, 0, 0); cc.bufferData(cc.ARRAY_BUFFER, new Uint8Array(rects), cc.STATIC_DRAW); //cc.blendFunc(cc.ONE, cc.ONE); cc.blendFunc(cc.ONE_MINUS_DST_COLOR, cc.ONE); cc.enable(cc.BLEND); draw(cc, 0, [1, 0, 0]); draw(cc, 4, [0, 1, 0]); draw(cc, 8, [0, 0, 1]); function draw(gl, offset, color) { gl.uniform3fv(unifs.color, color); gl.drawArrays(gl.TRIANGLE_FAN, offset, 4); } }(); function glProgram(gl, sfs, svs, attrs, unifs) { var prop, i, program = glLink(gl, glCompile(gl, sfs, gl.FRAGMENT_SHADER), glCompile(gl, svs, gl.VERTEX_SHADER)); for (prop in attrs) { i = gl.getAttribLocation(program, prop); attrs[prop] = i; gl.enableVertexAttribArray(i); } for (prop in unifs) unifs[prop] = gl.getUniformLocation(program, prop); return program; function glCompile(gl, source, type) { var shader = gl.createShader(type); gl.shaderSource(shader, source); gl.compileShader(shader); return shader; } function glLink(gl, fs, vs) { var program = gl.createProgram(); gl.attachShader(program, fs); gl.attachShader(program, vs); gl.linkProgram(program); return program; } } </script> </div>
</body>
</html>
参考
复合与混合级别 1http://www.w3.org/TR/compositing/
http://dev.w3.org/fxtf/compositing/
http://dev.w3.org/fxtf/compositing-1/
svg 1.1 滤镜
http://www.w3.org/TR/SVG11/filters.html
http://www.w3.org/TR/SVG/filters.html
滤镜效果模块级别 1
http://www.w3.org/TR/filter-effects-1/
http://dev.w3.org/fxtf/filters/
svg 复合
http://www.w3.org/TR/SVGCompositing/
http://dev.w3.org/SVG/modules/compositing/master/SVGCompositingPrimer.html
混合模式
http://en.wikipedia.org/wiki/Blend_modes
相关文章推荐
- jquery中用$.ajax实现注册(html、jquery、php、接口文档)、ajax验证用户提交数据
- cocos2dx中的颜色混合
- 改变panel 中html的背景颜色
- 颜色混合BlendFunc用法实例总结
- QLabel html颜色
- CSS颜色混合模式
- HTML颜色代码表
- 一步一步学Silverlight 2系列(23):Silverlight与HTML混合之无窗口模式
- 16进制颜色(html颜色值)字符串转为UIColor
- HTML颜色代码表
- 训练8-HTML 更换文本的颜色
- [HTML] 琐琐碎碎(五)——颜色
- android 设置html字体背景颜色
- html中用js调用ASP文件 实现静态页面动态显示
- 我对【纹理颜色混合】与【ALPHA混合】的理解
- HTML 颜色
- iOS开发中16进制颜色(html颜色值)字符串转为UIColor
- HTML基础 body标签text属性将普通文本颜色改为白色,实现有趣的密文效果
- HTML CSS 鼠标鼠标选中的更改颜色
- 网络编辑必读手册5:HTML颜色参考