2012-04-08 82 views
13

我目前正在研究heatmap.js修复,我想知道是否有人知道是否有可能通过<canvas>的2D渲染上下文实现以下效果。HTML5 Canvas globalCompositeOperation用于叠加渐变而不增加更高的亮度?

  • 我有从黑色(alpha 0.5)到透明40pixel半径的径向渐变。径向渐变的中心位于x = 50,y = 50我还有另一个从黑色(alpha 0.5)到透明的40 pixel半径的径向渐变。径向梯度的中心位于x = 80,y = 50

两个梯度重叠。我现在的问题是:重叠区域被累加在一起,导致比径向梯度中心更高的α值,从而显示错误的数据(例如,由于梯度之间的加法,热图中较热的区域)

看一看在下面的gist中,通过在控制台中执行它可以看到问题。

预期行为是: 最暗的区域是渐变中心,两个渐变的重叠区域合并但不合并。

在看到没有任何globalCompositeOperations导致预期的行为后,我尝试了这些操作的组合。 我认为这也许是可能的一种方式是执行以下操作:

  • 绘制第一梯度
  • 使用compositeOperation“目的地出”
  • 绘制第二梯度 - > substracts从第一梯度重叠区域
  • 使用compositeOperation '源上方'
  • 绘制第二梯度再次

但是UNFO幸运的是,我没有找到有效的组合。我很乐意听取您的反馈意见,提前致谢! PS:我知道这可以通过手动操作像素来完成,但我想知道是否有更简单,更优雅和更快的解决方案。

+2

两个渐变的重叠区域合并但不合并。 - 你能不能逐像素地描述合并? – 2012-04-12 09:11:46

回答

20

Ohhhhhh。我在这里有一些东西给你。这真的很古怪,但它没有得到imageData就做了你想要的。

想到的事情是,你需要确切的功能,当你抚摸它们时,路径本身在画布上。引用规范:

由于如何定义跟踪路径的算法,一个笔画操作中路径的重叠部分被视为它们的联合是被绘制的。

你可以阅读更多有关here.

总之,你想要什么,基本上是没什么一个模糊的路径,但弧线,你可以抚摸一次,你会得到完美你的效果寻找。

唯一的问题是,没有办法在画布上做出模糊的路径。或者几乎没有办法。

为了模拟遵循与路径相同的规则的模糊圆圈,我们可以使用路径的阴影代替路径本身。

存在的唯一问题,那么,就是你不希望看到实际的路径,你只是想看到它的影子。使笔画变得透明是行不通的:只有绘制的阴影不会以比阴影更高的不透明度绘制。

但是阴影确实具有shadowOffsetXshadowOffsetY的属性,它们通常用于将阴影移动一个或两个像素以产生光源假象。

但是,如果我们绘制阴影那么远,你不能看到他们?或者说,如果我们画出路径如此远以至于看不到它们,那么只能看到阴影?

那么碰巧做的伎俩。这里是一个快速的结果,你原来的代码是在顶部和阴影是第二画布:

enter image description here

这不正是你在渐变的条款和大小,但它非常接近之前有什么,我我确信通过摆弄这些值你可以更接近它。有几个console.log确认我们想要的东西,一个不超过124(超出255)的alpha会正确地发生在它曾经是143的地方,134以旧的方式发生。

小提琴看到的代码在行动:http://jsfiddle.net/g54Mz/

所以你有它。如果使用阴影并偏移实际路径以至于离开屏幕,则获得两个径向渐变的联合的效果是可能的,没有imageData

+3

谢谢你的回答,这是一个非常整洁的技术。我很快黑了一个实施,它似乎工作正常。 :) – 2012-04-16 20:11:29

0

composite modes就像你找到它们一样。据我所知,你不能复合任何比这更好的东西,而不需要手动setting pixels。如果您更新了您的问题并明确说明了如何混合像素,我会相应地更新我的答案。

那么,每像素解决方案是什么?

有2个主要基于像素的方法我可以看到,将开始解决这个问题

  1. 绘制到一个隐藏的情况下,用手动功能

  2. 混合编写一个函数,计算手动渐变,并应用自定义混合功能。

你的第一个选项是比第二位,你可以画任何你喜欢用普通的帆布方法,然后融入这个帆布到你的画布上可见的意义上更灵活。请参阅@Phrogz context-blender项目,了解如何将一个上下文融合到另一个上下文

第二个选项是必要的,当您需要以画布不方便默认情况下绘制的方式进行绘制时。

要解决的最大困难是alpha透明度,因为您可能在径向渐变背后有内容。一旦你绘制了背景图像,除非你保留该背景的副本,否则几乎不可能看出它在绘制顶部图像之前的内容。即使在每个像素的基础上,你也有困难:将图像混合到另一个图像的顶部将不起作用。基本上,这意味着在可见画布上合成多个半透明渐变的图像,无论您选择哪种通用合成函数,都不会起作用,除非该画布具有透明背景。

本着选择1的精神,您可以创建一个空白上下文并在其上呈现多个渐变。然后将其渲染在上面。

本着选择2的精神,如果您能够定义全部的点之前渲染,您可以计算图像和混合从这些点在一次。

将单程渲染技术与背景上下文相结合,可以让您在可见画布上绘制轮廓,而无需单个手动像素读取,只需像素写入。

远不像我所知道的那样优雅,但它可能是实现我在2D画布上称为alpha混合轮廓效果的唯一真正方法。


我认为你需要的每像素混合函数将从源和目标中选择具有最大alpha值的像素。

if (src.a <= dst.a) { 
    result = dst; 
} else { 
    result = src; 
} 
1

这拨弄http://jsfiddle.net/2qQLz/是试图提供解决方案。如果它接近你所需要的,可以进一步发展。它将渐变填充限制为边界矩形,其中一边是“圆圈”的交点。对于同一半径的两个“圆”沿水平线放置,很容易找到“圆”的交点的x值,并绘制每个“圆”的渐变填充的边界矩形。

这将是两个任意“圆圈”,但相交线仍然可以发现,边界矩形可能仍然是绘制为每个“圆圈”更加困难。随着更多“圈子”的增加,这将变得越来越复杂。

2

我正在研究一个基于HTML5的游戏,我希望在网格中混合绘制成数百个方格的单元格的不同颜色的半圆形区域。效果就像热图。经过一番研究,我发现了Simon Sarris上面记录的“阴影”技术。

实现这种技术实现了我想要的外观。我喜欢这很容易理解。然而,在实践中,我发现即使是少数(〜150)的阴影渲染,也比我之前用于绘制数千个填充矩形的技术(不过没有吸引力)慢得多。

所以我决定做一些分析。我写了一些基本的JavaScript(可在https://jsfiddle.net/Flatfingers/4vd22rgg/上看到一个修改版本),将5种不同形状类型的2000个副本绘制到1250x600画布的非重叠部分上,最后记录这五个操作中每一个的运行时间五种主流桌面浏览器以及移动Safari的版本。 (对不起,桌面Safari,我也没有Android的方便测试。)然后我尝试了不同的效果组合,并记录了经过的时间。

下面是如何我绘制两个梯度具有相似的外观,以阴影填充弧一个简化的例子:

var gradient1 = context.createRadialGradient(75,100,2,75,100,80); 
gradient1.addColorStop(0,"yellow"); 
gradient1.addColorStop(1,"black"); 

var gradient2 = context.createRadialGradient(125,100,2,125,100,80); 
gradient2.addColorStop(0,"blue"); 
gradient2.addColorStop(1,"black"); 

context.beginPath(); 
context.globalCompositeOperation = "lighter"; 
context.globalAlpha = 0.5; 
context.fillStyle = gradient1; 
context.fillRect(0,0,200,200); 
context.fillStyle = gradient2; 
context.fillRect(0,0,200,200); 
context.globalAlpha = 1.0; 
context.closePath(); 

的时间设置

(2000不相重叠的形状,集globalAlpha的,的drawImage()被用于梯度而不是阴影)

IE 11 (64-bit Windows 10) 
Rects  = 4 ms 
Arcs  = 35 ms 
Gradients = 57 ms 
Images = 8 ms 
Shadows = 160 ms 

Edge (64-bit Windows 10) 
Rects  = 3 ms 
Arcs  = 47 ms 
Gradients = 52 ms 
Images = 7 ms 
Shadows = 171 ms 

Chrome 48 (64-bit Windows 10) 
Rects  = 4 ms 
Arcs  = 10 ms 
Gradients = 8 ms 
Images = 8 ms 
Shadows = 203 ms 

Firefox 44 (64-bit Windows 10) 
Rects  = 4 ms 
Arcs  = 21 ms 
Gradients = 7 ms 
Images = 8 ms 
Shadows = 468 ms 

Opera 34 (64-bit Windows 10) 
Rects  = 4 ms 
Arcs  = 9 ms 
Gradients = 8 ms 
Images = 8 ms 
Shadows = 202 ms 

Mobile Safari (iPhone5, iOS 9) 
Rects  = 12 ms 
Arcs  = 31 ms 
Gradients = 67 ms 
Images = 82 ms 
Shadows = 32 ms 

观测

  1. 其中填充图案,充满rects一贯在所有测试的浏览器和环境的最快运行。
  2. 在IE 11和Edge中填满的圆弧(圆圈)比填充的矩形慢10倍,而其他主流浏览器中的慢了大约3.5倍。
  3. 渐变的速度比IE 11,Chrome 48和Opera 34中的反应速度慢大约3倍,但是在Firefox 44中速度要慢100倍(请参阅Bugzilla report 728453)。
  4. 通过drawImage()处理的图像大约是所有桌面浏览器中填充矩形的1.5倍。
  5. 带阴影的实心圆弧是最慢的,范围从IE,Edge,Chrome和Opera中的实际直径的50倍慢到Firefox的100倍。
  6. Chrome 48和Opera 34在除阴影填充圆弧之外的每种形状都非常迅速,但它们并没有比其他浏览器差。
  7. 当drawImage()绘制1000个形状,其中shadowOffsetX或shadowOffsetY被赋予一个超出物理屏幕分辨率的值时,Chrome和Opera崩溃。
  8. IE 11和Edge比其他桌面浏览器绘制弧线和渐变要慢。
  9. drawImage()在移动Safari上很慢。绘制多个渐变和阴影弧比用drawImage()多次绘制一个副本更快。
  10. Firefox中的绘制对以前的操作非常敏感:绘制阴影和渐变使绘制弧线变慢。显示最快的时间。
  11. Mobile Safari中的绘图对以前的操作非常敏感:阴影会使渐变变慢;渐变和弧使drawImage()甚至比通常慢。显示最快的时间。

分析

虽然shadowOffset特征是混合形状的简单和直观有效的方式,它是比所有其他技术显著慢。这限制了其仅适用于绘制一些阴影的应用程序,并且不需要快速反复绘制多个阴影。此外,当使用drawImage()加快速度时,给shadowOffsetX或shadowOffsetY一个大于3000的值会导致Chrome 48和Opera 34挂起将近一分钟,消耗CPU周期,然后崩溃我的nVidia显示驱动程序,即使在更新之后到最新版本。 (谷歌搜索没有发现有关Chromium的错误报告,当大型shadowOffset和drawImage()一起使用时,它会描述此错误。)

对于需要混合模糊形状的应用程序,最直观的方法是将globalCompositeOperation “更轻”,并使用带有globalAlpha值的drawImage()重复绘制预制的径向渐变,或者在需要使用不同颜色时绘制单独的渐变。这不是重叠阴影的完美匹配,但它很接近,避免了按像素计算。 (但是,请注意,在移动Safari中,直接绘制阴影填充弧实际上比渐变和drawImage()更快)。将globalCompositeOperation设置为“更浅”会导致IE 11和Edge在绘制弧中速度减慢大约10倍,并使用径向渐变仍然比在所有主要桌面浏览器中使用阴影填充弧更快,并且只比移动Safari中的阴影填充弧要慢两倍。

结论

如果你唯一的目标平台是iPad/iPhone的,对于好看的混合形状的阴影充满弧线的最快方法。否则,迄今为止我发现的所有主要桌面浏览器都具有可比较外观的最快方法是绘制放射状渐变,并将globalCompositeOperation设置为“更轻”,并使用globalAlpha控制不透明度。

注:在我进行的绘图测试中,有一些明显的方法可以提高性能。特别是,将每个形状的每个实例绘制到屏幕外缓冲区,然后将该整个缓冲区绘制到可见画布上会显着提高性能。但是这会否定这个测试的目标,即比较在可见画布上绘制各种形状的相对速度。