2012-04-08 83 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控制不透明度。

注:在我進行的繪圖測試中,有一些明顯的方法可以提高性能。特別是,將每個形狀的每個實例繪製到屏幕外緩衝區,然後將該整個緩衝區繪製到可見畫布上會顯着提高性能。但是這會否定這個測試的目標,即比較在可見畫布上繪製各種形狀的相對速度。