- A+
作者: zyl910
一、缘由
Html5画布(Canvas)的上下文(Context2D)提供globalCompositeOperation属性,可用于控制图形的绘制时的合成模式。
查了一下文档,发现多达共有26种合成模式。且文字介绍很简略,部分模式看不太懂。
于是我编写了一个功能丰富的演示页面,能够随时调整globalCompositeOperation等绘制参数,且有详细信息文本框能用于分析像素合成的计算算法的等。使用该页面,能够很好的学习这26种合成模式。
本文重点介绍演示页面的功能,及开发过程中的问题处理。下一篇文章将介绍合成模式的计算算法。
二、合成说明与功能设计
2.1 MDN文档说明
下图是MDN的globalCompositeOperation属性的说明文档的截图。可见,对于每一种合成模式,只是用一段话做一下简介而已。
文档上共列出了26种合成模式:
source-over source-in source-out source-atop destination-over destination-in destination-out destination-atop lighter copy xor multiply screen overlay darken lighten color-dodge color-burn hard-light soft-light difference exclusion hue saturation color luminosity
还好每一种模式都配了一张图片范例,让人稍微有一点头绪。
每一种合成模式的附图,由3张子图片所组成,分别是“existing content”(现有内容)、“new content”(新内容)、“[name]”(合成模式的名称,如“source-over”)。即将“子图1(existing content)”的上面绘制“子图2(new content)”时,该合成模式的处理结果是“子图3([name])”。
每个子图的左上角区域,还有一个小范例,演示了 蓝红方块的合成结果。注意是在蓝色方块(子图1:existing content)的上面绘制红色方块(子图2:new content)的,且红色方块向左上角偏移了一点点,这样便于观察非重叠时的合成情况。
该文档的后半部分,提供了一段简单的JavaScript范例代码,演示了xor合成模式。摘录:
const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); ctx.globalCompositeOperation = 'xor'; ctx.fillStyle = 'blue'; ctx.fillRect(10, 10, 100, 100); ctx.fillStyle = 'red'; ctx.fillRect(50, 50, 100, 100);
这段范例代码简洁明了,演示了globalCompositeOperation的用法。但有一个小问题——它绘制的蓝红方块的位置关系为“蓝色在红色的左上侧”,于是先前附图里“红色在蓝色的左上侧”不同,不利于对比分析之前的说明。
于是我感觉有可开发一个新的演示页面,使红蓝方块的位置关系与文档一致,并提供合成模式的下拉框,便于随时切换合成模式,观察合成结果。
2.2 统一术语
MDN文档为了使文章更好懂,尽量减少专业术语,便采用了 “existing content”、“new content”这样浅显的名词。
但这也带来一些麻烦,因为该领域的专业资料是用专业术语的,用专业术语才能使逻辑上更清晰。例如 source-over、destination-over 等合成模式的名称。
常用术语是这3个——
- Source(源):待绘制的内容。一般缩写为“S”。即MDN文档里的 子图1“existing content”(现有内容)。
- Destination(目标):绘制的目标。一般缩写为“D”。即MDN文档里的 子图2“new content”(新内容)。
- Output(输出结果):合成后的结果。一般缩写为“O”。即MDN文档里的 子图3“[name]”(合成模式的名称,如“source-over”)。
使用 Source、Destination等术语,最开始可能会感到比较抽象。但熟悉后,会觉得这些概念很简洁、实用。
在D(Destination)的基础上,绘制S(Source)图像,合成模式的运算用“⊙”运算符代替,合成结果为O(Output)。那么合成处理可以用以下数学式子来简洁的表示:
O = D ⊙ S
在很多时候,输出结果Output与目标Destination(现有内容,existing content)是同一个对象。例如Html5的Canva绘图,目标(Destination)是现有的上下文(Context2D),输出结果(Output)也是该上下文,只是状态不同(合成后的结果)。
故O与D其实是等价的,只是人们为了突出表达状态不同,才用到了O。所以很多时候用“D'”来代替“O”,式子为:
D' = D ⊙ S
为了进一步简写数学式子,可以将运算符(⊙)与等号(=)写在一起,即:
D ⊙= S
这类似编程语言的“复合赋值运算符”——将目标变量D与源值S进行运算,运算结果保存在目标变量D里。
2.3 演示页面功能设计
首先,能显示跟MDN文档一样的红蓝方块,便于对照文档。
提供合成模式(globalCompositeOperation)的下拉框,便于随时切换合成模式,观察合成结果。
红蓝方块能自定义颜色值。即提供文本框能随时修改 Source、Destination 的颜色值。
红蓝方块支持渐变绘制。即“to”复选框右侧的文本框能输入第2颜色进行渐变。勾选“to”复选框时启用渐变,未勾选时不渐变。
红蓝方块支持调整透明度。即“Alpha”复选框右侧的文本框能输入alpha值(值域为 0~1)。
为了解决源渐变绘制时Alpha不同问题,提供“SourceUseImage”复选框。当它复选时,会先在一个临时图片里绘制好Source,再通过globalCompositeOperation进行绘制。默认复选。
这些复选框及文本框能自动生效。具体来说,当焦点离开文本框时,会自动点击“Refresh”按钮,使配置生效。
显示点击信息的坐标及颜色。首先,会在“Current: (0, 0). Destination sample, Source sample.”这一栏显示这些信息,如“Current”是当前点击位置的颜色,其后的括号是点击坐标,“Destination sample”是对应目标像素的颜色,“Source sample”是对应源像素的颜色。
能显示点击像素的详细信息,并尝试给出该合成模式的详细计算过程。见“Current”栏下侧文本框。
除了像MDN文档那样显示 红框在蓝框左上的合成结果(compositeOffset)外,还展示同一位置的合成结果(composite),并显示了合成前的 destination、source 图。若勾选“SourceUseImage”,还会显示半透明处理前的source 图。
在页面背后放上一份颜色名称的表格,便于复制颜色名或rgb值,粘贴到自定义颜色值文本框进行测试。
下面就是演示页面的截图。
2.3.1 详细信息文本框
详细信息文本框的内容范例:
Current : RGBA(0.357, 0.106, 0.427, 0.875), Byte(91, 27, 109, 223), #5b1b6ddf, hsl(287, 0.603, 0.267). Pos(132, 94) Destination: RGBA(0.000, 0.255, 1.000, 0.753), Byte(0, 65, 255, 192), #0041ffc0, hsl(225, 1.000, 0.500). Pos(132, 394) Source : RGBA(0.624, 0.000, 0.000, 0.502), Byte(159, 0, 0, 128), #9f000080, hsl(0, 1.000, 0.312). Pos(469, 431) compositeMode: source-over Fa = 1; Fb = 1 - As; Co = As * Cs + Ab * Cb * (1 - As); Ao = As + Ab * (1 - As) Ro = As * Rs + Ab * Rb * (1 - As) = 0.502 * 0.624 + 0.753 * 0.000 * (1 - 0.502) = 0.313 Go = As * Gs + Ab * Gb * (1 - As) = 0.502 * 0.000 + 0.753 * 0.255 * (1 - 0.502) = 0.096 Bo = As * Bs + Ab * Bb * (1 - As) = 0.502 * 0.000 + 0.753 * 1.000 * (1 - 0.502) = 0.375 Ao = As + Ab * (1 - As) = 0.502 + 0.753 * (1 - 0.502) = 0.877 Premultiplie:RGBA(0.314, 0.094, 0.376, 0.878), Byte(80, 24, 96, 224), #501860e0, hsl(287, 0.600, 0.235) Output : RGBA(0.357, 0.110, 0.427, 0.878), Byte(91, 28, 109, 224), #5b1c6de0, hsl(287, 0.591, 0.269)
说明——
- Current:当前点击位置的颜色信息及坐标信息。格式为“RGBA、Byte、#rrggbbaa、hsl、Pos”,即分别为“归一化的RGBA值、各分量的字节值、十六进制表示的颜色值、hsl格式的颜色值、点击坐标”。
- Destination:当前点击位置对应目标像素的颜色信息及坐标信息。格式为“RGBA、Byte、#rrggbbaa、hsl、Pos”,即分别为“归一化的RGBA值、各分量的字节值、十六进制表示的颜色值、hsl格式的颜色值、点击坐标”。后面公式里用小写的“b”或“d”来表示目标像素。
- Source:当前点击位置对应源像素的颜色信息及坐标信息。格式为“RGBA、Byte、#rrggbbaa、hsl、Pos”,即分别为“归一化的RGBA值、各分量的字节值、十六进制表示的颜色值、hsl格式的颜色值、点击坐标”。后面公式里用小写的“s”来表示源像素。
- compositeMode:合成模式。
- 公式。为了表示在引用公式,且为了便于与其他内容隔开,公式的的前面加了多个空格。
- Ro、Go、Bo、Ao:分别显示 R、G、B、A 通道的计算过程。
- Premultiplie:显示原始计算结果,它是 预乘Alpha(Premultiplie Alpha)模式的颜色值。格式为“RGBA、Byte、#rrggbbaa、hsl”,即分别为“归一化的RGBA值、各分量的字节值、十六进制表示的颜色值、hsl格式的颜色值”。
- Output:显示计算结果,它是 直通Alpha(Straight Alpha)模式的颜色值。格式为“RGBA、Byte、#rrggbbaa、hsl”,即分别为“归一化的RGBA值、各分量的字节值、十六进制表示的颜色值、hsl格式的颜色值”。
注——
- Html5的画布,总是使用直通Alpha(Straight Alpha)模式。因运算公式的中间结果是预乘Alpha(Premultiplie Alpha)的,故最终输出时,需做“预乘Alpha 转 直通Alpha”的转换。
- Output计算结果,理应与Current相同的,而有时会发现字节值会有 12的误差。这是因为Chrome在运算过程中可能用到了低精度整数运算等速度优化手段,而本页面严格按照公式,且使用高精度的浮点运算。对于有256种值的8位色彩通道来说,有字节值12的误差,其实只是 2/256=1/128≈0.781% 的误差,人眼看不出差别,故这些速度优化处理很常见。
- 只是对常用混合模式,编写了了公式与计算过程。有些混合模式尚没有编写内容。
三、问题处理经验
在演示页面的开发过程中,遇到了一些事先没想到的问题。现在分享一下处理经验。
3.1 源渐变绘制时Alpha不同问题
勾选Source的Alpha复选框,并设为“0.5”,若未启用渐变(Source的“to”复选框 未勾选时),可观察到此时绘制的Source区域是正常的,各像素的Alpha为0.5左右。
若启用启用渐变(Source的“to”复选框 被勾选时),可观察到Source区域的Alpha不太对劲,各像素的Alpha为0.75左右。
为了解决源渐变绘制时Alpha不同问题,提供“SourceUseImage”复选框。当它复选时,会先在一个临时图片里绘制好Source,再通过globalCompositeOperation进行绘制。默认复选。
“SourceUseImage”勾选时,可观察到Source区域的Alpha仍是0.5左右,与配置的值相符。
3.2 Image.onload事件是异步触发的
因“SourceUseImage”复选框,故需要先在另一块区域绘制源图,且需要将它转为Image对象,这样能便于使用 drawImage 进行绘图。
使用Image时要注意,它的加载处理是异步的。
若在设置了Image.src后立即进行绘图,会发现大多数时候是空的。
为了解决这一问题,应处理onload事件,该事件触发时才表示已加载完毕,可进行后续的绘图等操作。
代码摘录:
// sourceUseImage let sourceImage = null; if (sourceUseImage) { try{ //let canvasTemp = document.createElement("canvas"); let canvasTemp = document.getElementById('canvasTemp'); canvasTemp.style = "display:block"; canvasTemp.width = blockWidth; canvasTemp.height = blockHeight; //canvas.getContext("2d").drawImage(image, 0, 0); let ctxTemp = canvasTemp.getContext("2d"); ctxTemp.save(); ctxTemp.clearRect(0, 0, blockWidth, blockHeight); //ctxTemp.globalAlpha = alphaS; drawRectS(ctxTemp, 0, 0, blockWidth, blockHeight, sColor0, sColor1); ctxTemp.restore(); // to image. sourceImage = new Image(); sourceImage.onload = function() { doRefresh_draw(sourceImage); } sourceImage.src = ctxTemp.canvas.toDataURL("image/png"); } catch(ex) { sourceImage = null; console.log("Make sourceImage fail! ", ex); } } //console.log("sourceUseImage: ", sourceUseImage, "sourceImage: ", sourceImage); if (null!=sourceImage) return; let canvasTemp = document.getElementById('canvasTemp'); canvasTemp.style = "display:none"; doRefresh_draw(sourceImage);
3.3 部分合成模式会将区域外的颜色均清除为透明的
使用 source-out、destination-out 等合成模式时,不仅影响了Sourcet覆盖的区域,且会将区域外的颜色均清除为透明的。
若画布里只需绘制Sourcet,这种情况还可接收。但若是画布里还有其他内容,这种情况会将区域外的其他内容均清理,变为透明。
例如本演示页面上会绘制 compositeOffset、composite、destination、source 这四类图形。因composite是最后进行合成绘制的,当选择使用 source-out、destination-out 等合成模式时,会将compositeOffset、destination、source 的内容均清除,仅保留composite的。
为了解决这一问题,需要在合成绘制前,设置好剪裁区域。
对于Html5画布来说,剪裁功能是这样使用的:先调用beginPath方法开启路径,随后进行rect等操作添加路径形状,最后调用clip将路径转为剪裁区域。
另外为了在处理后恢复为未剪裁的最初环境,可利用Html5画布的 save/restore 机制。save方法用于在处理前保存环境,restore方法用于在处理后回复环境。
代码摘录:
// Top Left: compositeOffset //ctx.globalCompositeOperation = "source-over"; ctx.save(); ctx.globalAlpha = alphaD; drawRectD(ctx, blockLeft, blockTop, blockWidth, blockHeight, dColor0, dColor1); if (useClip) { ctx.beginPath(); ctx.rect(0, 0, blockWidth*2, blockHeight*2); ctx.clip(); } ctx.globalCompositeOperation = compositeModeLast; ctx.globalAlpha = alphaS; if (null==sourceImage) { drawRectS(ctx, blockLeft-blockOffsetX, blockTop-blockOffsetY, blockWidth, blockHeight, sColor0, sColor1); } else { ctx.drawImage(sourceImage, blockLeft-blockOffsetX, blockTop-blockOffsetY); } ctx.restore();
四、小结
源码地址:
https://github.com/zyl910/zhtml5info/blob/master/src/canvas/CanvasComposite.htm
参考文献
- MDN《CanvasRenderingContext2D.globalCompositeOperation》. https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation
- W3C《Compositing and Blending Level 1》. https://www.w3.org/TR/compositing-1/
- 么贺贵《canvas像素操作、save与restore、合成与变形》. https://blog.csdn.net/ayhg1/article/details/118071037