HTML5 Canvas globalCompositeOperation用于覆盖不增加更高强度的渐变?

时间:2012-04-08 03:49:09

标签: javascript canvas html5-canvas heatmap

我目前正在进行heatmap.js修复,我想知道是否有人知道是否可以使用<canvas>的二维渲染上下文来实现以下效果。

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

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

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

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

在看到没有globalCompositeOperations导致预期的行为后,我尝试了这些操作的组合。 我认为可能的方式可能如下:

  • 绘制第一个渐变
  • 使用compositeOperation'destination-out'
  • 绘制第二个渐变 - &gt;从第一个梯度中减去重叠区域
  • 使用compositeOperation'source-over'
  • 再次绘制第二个渐变

但不幸的是,我没有找到一个有效的组合。我很想听听您的反馈,提前谢谢!

PS:我知道这可以通过手动操作像素来完成,但我想知道是否有更简单,更优雅和更快的解决方案。

4 个答案:

答案 0 :(得分:20)

这真的很古怪,但它可以做到你想要的而不会涉及到图像数据。

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

  

由于定义了跟踪路径的算法,一次描边操作中路径的重叠部分被视为它们的联合是被绘制的。

您可以详细了解here.

无论如何,你想要的东西,实际上是一条模糊的路径,除了弧线,你可以抚摸一次,你就能完美地得到你想要的效果。

唯一的问题是无法在画布中制作模糊路径。或者几乎没有办法。

我们不是使用路径本身,而是使用路径的阴影来模拟符合路径相同规则的模糊圆圈。

那里唯一的问题是,你不想看到实际的路径,你只是想看到它的影子。使笔划透明不起作用:阴影只会绘制不会以比阴影更高的不透明度绘制。

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

但是如果我们把阴影画得那么遥远而你却看不到呢?或者更确切地说,如果我们将路径绘制得如此遥远以至于您无法看到它们,您可以看到的只是阴影?

那恰好就是这个伎俩。这是一个快速的结果,您的原始代码位于顶部,阴影是第二个画布:

enter image description here

这不是你以前在渐变和尺寸方面所拥有的,但它非常接近,我确信通过摆弄这些值,你可以更接近它。一些console.log确认我们想要的东西,一个不超过124(在255中)的alpha正确地发生在以前曾经是143和134这样做的地方。

查看代码的小提琴:http://jsfiddle.net/g54Mz/

所以你有它。如果使用阴影并偏移实际路径以至于它们不在屏幕上,则可以在没有imageData的情况下获得两个径向渐变的并集效​​果。

答案 1 :(得分:2)

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

实施这项技术带来了我想要的外观。而且我喜欢它很容易理解。然而,在实践中我发现渲染甚至几个(~150)阴影的速度要慢得多,因为我之前的技术(无论多么缺乏吸引力)吸引成千上万的填充物。

所以我决定做一些分析。我写了一些基本的JavaScript(可以在https://jsfiddle.net/Flatfingers/4vd22rgg/看到的一个修改版本),将五种不同形状类型的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个形状时,Chrome和Opera会崩溃,其中shadowOffsetX或shadowOffsetY被赋予超出物理屏幕分辨率的值。
  8. IE 11和Edge绘制弧形和渐变的速度比其他桌面浏览器慢。
  9. drawImage()在移动版Safari上很慢。绘制多个渐变和阴影弧实际上比使用drawImage()多次绘制一个副本更快。
  10. 在Firefox中绘图对以前的操作很敏感:绘制阴影和渐变会使绘制弧线变慢。显示最快的时间。
  11. 在Mobile Safari中绘图对以前的操作很敏感:阴影会使渐变变慢;渐变和弧线使drawImage()比平常更慢。显示最快的时间。
  12. <强>分析

    虽然shadowOffset功能是一种简单且视觉上有效的混合形状的方法,但它比所有其他技术慢得多。这限制了它对仅需要绘制几个阴影的应用程序的有用性,并且不需要快速且重复地绘制许多阴影。此外,当使用drawImage()加速时,给shadowOffsetX或shadowOffsetY一个大于约3000的值会导致Chrome 48和Opera 34挂起近一分钟,消耗CPU周期,然后崩溃我的nVidia显示驱动程序,即使在更新后也是如此到最新版本。 (Google搜索发现,当同时使用大型shadowOffset和drawImage()时,Chromium没有错误报告描述此错误。)

    对于需要混合模糊不清的形状的应用程序,最直观的阴影方法是将globalCompositeOperation设置为&#34; light&#34;并使用具有globalAlpha值的drawImage()重复绘制预涂漆的径向渐变,或者如果它们需要不同的颜色,则绘制单个渐变。这不是重叠阴影的完美匹配,但它接近并避免进行每像素计算。 (但请注意,在移动Safari中,直接绘制阴影填充弧实际上比渐变和drawImage()更快。)将globalCompositeOperation设置为&#34;更轻&#34;导致IE 11和Edge在绘制弧线时速度减慢约10倍,使用径向渐变仍然比在所有主要桌面浏览器中使用阴影填充弧更快,并且仅比移动Safari中阴影填充弧的速度慢两倍。

    <强>结论

    如果您唯一的目标平台是iPad / iPhone,那么漂亮的混合形状的最快方法是阴影填充弧。否则,迄今为止我发现的可用于所有主要桌面浏览器的具有可比外观的最快方法是绘制径向渐变,其中globalCompositeOperation设置为&#34;更轻&#34;用globalAlpha控制不透明度。

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

答案 2 :(得分:1)

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

对于两个任意“圆圈”来说会更加困难,但仍然可以找到交叉线,并且仍然可以为每个“圆圈”绘制一个边界矩形。随着更多“圈子”的加入,它将变得越来越复杂。

答案 3 :(得分:0)

composite modes正如您所发现的那样。据我所知,没有手动setting pixels,你无法比这更好地合成。如果您使用关于如何混合像素的明确说明更新您的问题,我将相应地更新我的答案。

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

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

  1. 绘制隐藏的上下文并与手动功能混合

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

  3. 你的第一个选项比第二个选项更通用,你可以使用普通的画布方法绘制你喜欢的任何东西,然后将这个画布混合到你的可见画布上。请参阅@Phrogz context-blender项目,了解如何将一个上下文混合到另一个上下文

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

    要应对的最大困难是alpha透明度,因为您可能拥有径向渐变背后的内容。一旦你在背景图像上绘制,除非你保留该背景的副本,否则几乎不可能在你绘制它之前看到它是什么。即使在每个像素的基础上,您也有困难:将图像混合在另一个图像的顶部不起作用

    基本上,这意味着无论选择哪种通用合成功能,在可见画布上合成多个半透明渐变的图像都是不可行的,除非该画布具有透明背景。

    根据选项1的精神,您可以创建一个空白上下文并在其上呈现多个渐变。然后将其渲染到最顶层。

    根据选项2的精神,如果您能够在渲染之前定义所有点,则可以在一次传递中计算图像并从这些点进行混合。

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

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


    我认为您需要的每像素混合功能会从源和目标中选择具有最大alpha值的像素。

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