WebGL Tutorial
and more

颜色混合模式

撰写时间:2024-05-10

修订时间:2024-05-30

Color Blending

source-over

颜色混合color blending)是指当canvas特定区域已有颜色,而若要在canvas上再渲染新颜色时,新旧两种颜色将相互混合而得到另一种全新的颜色。混合模式不一样,所得到的结果也不一样。

我们可以通过设置ctxglobalCompositeOperation属性值来指定不同的混合模式。

ctx.fillStyle = 'red'; ctx.fillRect(0, 0, 100, 100); ctx.fillStyle = 'blue'; ctx.fillRect(180, 0, 100, 100); ctx.globalCompositeOperation = "source-over"; ctx.fillStyle = 'green'; ctx.fillRect(50, 50, 100, 100); ctx.fillStyle = 'yellow'; ctx.fillRect(300, 50, 100, 100);

上面将globalCompositeOperation属性值设定为source-over,则新颜色将直接覆盖旧颜色。这是颜色混合的默认模式。

source-in

再来看另一种混合模式:source-in

ctx.fillStyle = 'red'; ctx.fillRect(0, 0, 100, 100); ctx.fillStyle = 'blue'; ctx.fillRect(180, 0, 100, 100); ctx.globalCompositeOperation = "source-in"; ctx.fillStyle = 'green'; ctx.fillRect(50, 50, 100, 100); ctx.globalCompositeOperation = "source-over"; ctx.strokeStyle = 'gray'; ctx.strokeRect(0, 0, 100, 100); ctx.strokeRect(180, 0, 100, 100); ctx.strokeRect(50, 50, 100, 100);

上面的代码,在颜色混合生效后,再恢复默认的source-over模式,并重新绘制所有正方形的方框,以明示在产生颜色混合之前的填充区域。

可以看出,source-in只在顶层与底层有覆盖的区域,渲染出顶层的颜色。

需注意的是,底层左上角正方形非覆盖部分原为红色,应用source-in模式后,这一部分区域的颜色全都变成透明了。可见,颜色混合并非只在顶层产生效果,还将影响到底层原有图形的颜色

现在,我们准备在顶层最右边,再填充第二个不与底层内容相互覆盖的黄色正方形。

ctx.fillStyle = 'red'; ctx.fillRect(0, 0, 100, 100); ctx.fillStyle = 'blue'; ctx.fillRect(180, 0, 100, 100); ctx.globalCompositeOperation = "source-in"; ctx.fillStyle = 'green'; ctx.fillRect(50, 50, 100, 100); ctx.fillStyle = 'yellow'; ctx.fillRect(300, 50, 100, 100); ctx.globalCompositeOperation = "source-over"; ctx.strokeStyle = 'gray'; ctx.strokeRect(0, 0, 100, 100); ctx.strokeRect(180, 0, 100, 100); ctx.strokeRect(50, 50, 100, 100); ctx.strokeRect(300, 50, 100, 100);

顶层有两个正方形,一个与底层有覆盖,另一个与底层无覆盖,应用颜色混合之后,不管是顶层还是底层,图形内容均全部都变成透明而消失了。

改用两次调用beginPath

ctx.fillStyle = 'red'; ctx.fillRect(0, 0, 100, 100); ctx.fillStyle = 'blue'; ctx.fillRect(180, 0, 100, 100); ctx.globalCompositeOperation = "source-in"; ctx.fillStyle = 'green'; ctx.beginPath(); ctx.moveTo(50, 50); ctx.lineTo(50, 150); ctx.lineTo(150, 150); ctx.lineTo(150, 50); ctx.lineTo(50, 50); ctx.fill(); ctx.fillStyle = 'yellow'; ctx.beginPath(); ctx.moveTo(300, 50); ctx.lineTo(300, 150); ctx.lineTo(400, 150); ctx.lineTo(400, 50); ctx.lineTo(300, 50); ctx.fill(); ctx.globalCompositeOperation = "source-over"; ctx.strokeStyle = 'gray'; ctx.strokeRect(0, 0, 100, 100); ctx.strokeRect(180, 0, 100, 100); ctx.strokeRect(50, 50, 100, 100); ctx.strokeRect(300, 50, 100, 100);

结果仍是一样。但如果两个路径变成一个。

ctx.fillStyle = 'red'; ctx.fillRect(0, 0, 100, 100); ctx.fillStyle = 'blue'; ctx.fillRect(180, 0, 100, 100); ctx.globalCompositeOperation = "source-in"; ctx.fillStyle = 'green'; ctx.beginPath(); ctx.moveTo(50, 50); ctx.lineTo(50, 150); ctx.lineTo(150, 150); ctx.lineTo(150, 50); ctx.lineTo(50, 50); ctx.moveTo(300, 50); ctx.lineTo(300, 150); ctx.lineTo(400, 150); ctx.lineTo(400, 50); ctx.lineTo(300, 50); ctx.fill(); ctx.globalCompositeOperation = "source-over"; ctx.strokeStyle = 'gray'; ctx.strokeRect(0, 0, 100, 100); ctx.strokeRect(180, 0, 100, 100); ctx.strokeRect(50, 50, 100, 100); ctx.strokeRect(300, 50, 100, 100);

现在,顶层中有1条路径,该路径下有2个正方形,其中一个部分覆盖了底层图形,另一个未覆盖。则顶层中覆盖部分的内容被绘制出来了。而未覆盖的部分,无论是顶层,还是底层,其内容均变成透明。

这个例子说明,要应用一次颜色混合,最好只调用一次beginPath,也即只能有一条路径。

可以先后多次进行颜色混合。

ctx.fillStyle = 'red'; ctx.fillRect(0, 0, 100, 100); ctx.fillStyle = 'blue'; ctx.fillRect(180, 0, 100, 100); ctx.globalCompositeOperation = "source-in"; ctx.fillStyle = 'green'; ctx.beginPath(); ctx.moveTo(50, 50); ctx.lineTo(50, 150); ctx.lineTo(150, 150); ctx.lineTo(150, 50); ctx.lineTo(50, 50); ctx.moveTo(300, 50); ctx.lineTo(300, 150); ctx.lineTo(400, 150); ctx.lineTo(400, 50); ctx.lineTo(300, 50); ctx.fill(); ctx.globalCompositeOperation = "source-in"; ctx.fillStyle = 'cyan'; ctx.beginPath(); ctx.arc(120, 120, 50, 0, Math.PI * 2); ctx.fill(); ctx.globalCompositeOperation = "source-over"; ctx.strokeStyle = 'gray'; ctx.strokeRect(0, 0, 100, 100); ctx.strokeRect(180, 0, 100, 100); ctx.strokeRect(50, 50, 100, 100); ctx.strokeRect(300, 50, 100, 100); ctx.beginPath(); ctx.arc(120, 120, 50, 0, Math.PI * 2); ctx.stroke();

上节颜色混合之后的结果成为新的底层,然后再在新的顶层上填充一个青色的圆,部分区域覆盖了新底层内容,从而在新的覆盖区域填充出新图形。

source-out

drawBottom(); ctx.globalCompositeOperation = "source-out"; drawTop(); showRects(); function drawBottom() { ctx.fillStyle = 'red'; ctx.fillRect(0, 0, 100, 100); ctx.fillStyle = 'blue'; ctx.fillRect(180, 0, 100, 100); } function drawTop() { ctx.fillStyle = 'green'; ctx.beginPath(); ctx.moveTo(50, 50); ctx.lineTo(50, 150); ctx.lineTo(150, 150); ctx.lineTo(150, 50); ctx.lineTo(50, 50); ctx.moveTo(300, 50); ctx.lineTo(300, 150); ctx.lineTo(400, 150); ctx.lineTo(400, 50); ctx.lineTo(300, 50); ctx.fill(); } function showRects() { ctx.globalCompositeOperation = "source-over"; ctx.strokeStyle = 'gray'; ctx.strokeRect(0, 0, 100, 100); ctx.strokeRect(180, 0, 100, 100); ctx.strokeRect(50, 50, 100, 100); ctx.strokeRect(300, 50, 100, 100); }

source-out是指:只在底层有图形的区域绘制新图形;canvas的其他区域,全部变为透明

source-atop

drawBottom(); ctx.globalCompositeOperation = "source-atop"; drawTop(); showRects(); function drawBottom() { ctx.fillStyle = 'red'; ctx.fillRect(0, 0, 100, 100); ctx.fillStyle = 'blue'; ctx.fillRect(180, 0, 100, 100); } function drawTop() { ctx.fillStyle = 'green'; ctx.beginPath(); ctx.moveTo(50, 50); ctx.lineTo(50, 150); ctx.lineTo(150, 150); ctx.lineTo(150, 50); ctx.lineTo(50, 50); ctx.moveTo(300, 50); ctx.lineTo(300, 150); ctx.lineTo(400, 150); ctx.lineTo(400, 50); ctx.lineTo(300, 50); ctx.fill(); } function showRects() { ctx.globalCompositeOperation = "source-over"; ctx.strokeStyle = 'gray'; ctx.strokeRect(0, 0, 100, 100); ctx.strokeRect(180, 0, 100, 100); ctx.strokeRect(50, 50, 100, 100); ctx.strokeRect(300, 50, 100, 100); }

source-atop是指:只在底层有图形的区域制新图形;底层原有图形予以保留

destination-over

drawBottom(); ctx.globalCompositeOperation = "destination-over"; drawTop(); showRects(); function drawBottom() { ctx.fillStyle = 'red'; ctx.fillRect(0, 0, 100, 100); ctx.fillStyle = 'blue'; ctx.fillRect(180, 0, 100, 100); } function drawTop() { ctx.fillStyle = 'green'; ctx.beginPath(); ctx.moveTo(50, 50); ctx.lineTo(50, 150); ctx.lineTo(150, 150); ctx.lineTo(150, 50); ctx.lineTo(50, 50); ctx.moveTo(300, 50); ctx.lineTo(300, 150); ctx.lineTo(400, 150); ctx.lineTo(400, 50); ctx.lineTo(300, 50); ctx.fill(); } function showRects() { ctx.globalCompositeOperation = "source-over"; ctx.strokeStyle = 'gray'; ctx.strokeRect(0, 0, 100, 100); ctx.strokeRect(180, 0, 100, 100); ctx.strokeRect(50, 50, 100, 100); ctx.strokeRect(300, 50, 100, 100); }

destination-over是指:在底层有图形的区域之后面绘制新图形;底层原有图形予以保留

destination-in

drawBottom(); ctx.globalCompositeOperation = "destination-in"; drawTop(); showRects(); function drawBottom() { ctx.fillStyle = 'red'; ctx.fillRect(0, 0, 100, 100); ctx.fillStyle = 'blue'; ctx.fillRect(180, 0, 100, 100); } function drawTop() { ctx.fillStyle = 'green'; ctx.beginPath(); ctx.moveTo(50, 50); ctx.lineTo(50, 150); ctx.lineTo(150, 150); ctx.lineTo(150, 50); ctx.lineTo(50, 50); ctx.moveTo(300, 50); ctx.lineTo(300, 150); ctx.lineTo(400, 150); ctx.lineTo(400, 50); ctx.lineTo(300, 50); ctx.fill(); } function showRects() { ctx.globalCompositeOperation = "source-over"; ctx.strokeStyle = 'gray'; ctx.strokeRect(0, 0, 100, 100); ctx.strokeRect(180, 0, 100, 100); ctx.strokeRect(50, 50, 100, 100); ctx.strokeRect(300, 50, 100, 100); }

destination-in是指:只在新图形的区域绘制图形;canvas的其他区域,全部变为透明。

destination-out

drawBottom(); ctx.globalCompositeOperation = "destination-out"; drawTop(); showRects(); function drawBottom() { ctx.fillStyle = 'red'; ctx.fillRect(0, 0, 100, 100); ctx.fillStyle = 'blue'; ctx.fillRect(180, 0, 100, 100); } function drawTop() { ctx.fillStyle = 'green'; ctx.beginPath(); ctx.moveTo(50, 50); ctx.lineTo(50, 150); ctx.lineTo(150, 150); ctx.lineTo(150, 50); ctx.lineTo(50, 50); ctx.moveTo(300, 50); ctx.lineTo(300, 150); ctx.lineTo(400, 150); ctx.lineTo(400, 50); ctx.lineTo(300, 50); ctx.fill(); } function showRects() { ctx.globalCompositeOperation = "source-over"; ctx.strokeStyle = 'gray'; ctx.strokeRect(0, 0, 100, 100); ctx.strokeRect(180, 0, 100, 100); ctx.strokeRect(50, 50, 100, 100); ctx.strokeRect(300, 50, 100, 100); }

destination-out是指:只在顶层有图形的区域之外绘制底层图形;canvas的其他区域,全部变为透明

destination-atop

drawBottom(); ctx.globalCompositeOperation = "destination-atop"; drawTop(); showRects(); function drawBottom() { ctx.fillStyle = 'red'; ctx.fillRect(0, 0, 100, 100); ctx.fillStyle = 'blue'; ctx.fillRect(180, 0, 100, 100); } function drawTop() { ctx.fillStyle = 'green'; ctx.beginPath(); ctx.moveTo(50, 50); ctx.lineTo(50, 150); ctx.lineTo(150, 150); ctx.lineTo(150, 50); ctx.lineTo(50, 50); ctx.moveTo(300, 50); ctx.lineTo(300, 150); ctx.lineTo(400, 150); ctx.lineTo(400, 50); ctx.lineTo(300, 50); ctx.fill(); } function showRects() { ctx.globalCompositeOperation = "source-over"; ctx.strokeStyle = 'gray'; ctx.strokeRect(0, 0, 100, 100); ctx.strokeRect(180, 0, 100, 100); ctx.strokeRect(50, 50, 100, 100); ctx.strokeRect(300, 50, 100, 100); }

destination-atop是指:绘制新图形;覆盖区域绘制义底层图形;底层图形非覆盖区域的图形变成透明

lighter

ctx.fillStyle = 'red'; ctx.fillRect(0, 0, 100, 100); let aa = CanvasUtils.GetClientPixelInfo(51, 51, ctx); console.log(aa.r, aa.g, aa.b, aa.a); ctx.fillStyle = 'blue'; ctx.fillRect(180, 0, 100, 100); ctx.globalCompositeOperation = "lighter"; ctx.fillStyle = 'green'; ctx.fillRect(50, 50, 100, 100); ctx.globalCompositeOperation = "source-over"; ctx.strokeStyle = 'red'; ctx.strokeRect(0, 0, 100, 100); ctx.strokeStyle = 'blue'; ctx.strokeRect(180, 0, 100, 100); ctx.strokeStyle = 'green'; ctx.strokeRect(50, 50, 100, 100);

lighter是指:绘制新图形;新图形与原图形重叠部分的颜色值取自两者的颜色值之和;原图形其他部分保持不变。

总结列表

source, destination

混合模式作用覆盖区域颜色值未覆盖区域颜色值混合模式作用覆盖区域颜色值未覆盖区域颜色值
source-over直接绘制新图形src顶层srcdestination-over在底层有内容的区域后面绘制新图形dst顶层src
底层dst底层dst
source-in在覆盖区域绘制新图形src顶层透明destination-in在覆盖区域绘制底层图形dst顶层透明
底层透明底层透明
source-atop在覆盖区域绘制新图形src顶层透明destination-atop绘制新图形,但覆盖区域绘制底层图形dst顶层src
底层dst底层透明
source-out在非覆盖区域绘制新图形透明顶层srcdestination-out在非覆盖区域绘制底层图形透明顶层透明
底层透明底层dst

混合模式名称中的source, destination是指需绘制哪层图形;over, in, atop将在覆盖区域内绘制该层图形,out将在覆盖区域外绘制该层图形。

over不会改变任一图层的透明度;in覆盖区域外全部变成透明,out覆盖区域内变成透明,atop在非覆盖区域该层图形将变成透明。

source, destination系列不会导致混色,而只是简单地选择使用顶层或底层的颜色。

参考资源

  1. HTML5 Canvas Element
  2. W3C: Compositing and Blending Level 1
  3. W3C: Compositing and Blending Level 2
  4. Polygon Clipping (Part 1)
  5. 深入canvas/svg的布尔运算(Martinez法)