14

私は現在heatmap.js<canvas>の修正に取り組んでおり、の 2D レンダリング コンテキストで次の効果​​を達成できるかどうかを誰かが知っているかどうか疑問に思っていました。

  • 黒 (アルファ 0.5) から透明な 40 ピクセルの半径までの放射状グラデーションがあります。放射状グラデーションの中心は x=50、y=50 です
  • 黒 (アルファ 0.5) から透明な半径 40 ピクセルまでの別の放射状グラデーションがあります。放射状グラデーションの中心は x=80、y=50 です。

2 つのグラデーションが重なっています。私の問題は次のとおりです。重なり合う領域が合計され、放射状グラデーションの中心よりも高いアルファ値が得られ、誤ったデータが表示されます(たとえば、グラデーション間の追加により、ヒートマップのよりホットな領域が表示されます)。

次の要点を見てください。コンソールで実行すると、問題を確認できます。

予想される動作は次のとおりです。最も暗い領域はグラデーションの中心であり、2 つのグラデーションの重なり合う領域はマージされますが、加算されません。

globalCompositeOperations のいずれも期待どおりの動作にならなかったことを確認した後、これらの操作の組み合わせを試しました。私がそれが可能かもしれないと思った方法は次のとおりでした:

  • 最初のグラデーションを描く
  • 複合操作の「宛先アウト」を使用してください
  • 2 番目のグラデーションを描画 -> 最初のグラデーションから重複領域を減算します
  • compositeOperation 'source-over' を使用する
  • 2 番目のグラデーションを再度描画する

残念ながら、うまくいく組み合わせは見つかりませんでした。フィードバックをお待ちしております。よろしくお願いします。

PS: ピクセルを手動で操作することでこれを実行できることはわかっていますが、そのためのより簡単でエレガントで高速なソリューションがあるかどうか疑問に思っていました。

4

4 に答える 4

24

これは非常に奇抜ですが、imageData を使用せずに必要な処理を実行します。

頭に浮かんだのは、パスをストロークしたときにキャンバス上でパス自体が持つ正確な機能が必要だということでした。仕様を引用するには:

パスをトレースするアルゴリズムがどのように定義されているかの結果として、1 回のストローク操作でパスの重なり合う部分は、それらの結合がペイントされたものであるかのように扱われます。

詳細については、こちらをご覧ください。

とにかく、基本的には、一度ストロークできる円弧だけのぼやけたパスが必要であり、探していた効果を完全に得ることができます。

唯一の問題は、キャンバスにぼやけたパスを作成する方法がないことです。というかほぼ無理。

パス自体を使用する代わりに、パスと同じルールに従うぼやけた円をシミュレートするために、パスの影を使用できます。

The only problem there, then, is that you don't want to see the actual path, you just want to see the shadow of it. Making the stroke transparent won't work: A shadow will only draw will not draw at a higher opacity than the thing it is shadowing.

But shadows do have the properties shadowOffsetX and shadowOffsetY, which are typically used to shift the shadow by a pixel or two to make the illusion of a light source.

But what if we draw the shadows so far away that you can't see them? Or rather, what if we draw the paths so far away that you can't see them, all you can see are the shadows?

Well that happens to do the trick. Here is a quick result, your original code is on the top and the shadows are the second canvas:

ここに画像の説明を入力

グラデーションとサイズの点で以前とまったく同じではありませんが、非常に近く、値をいじることでさらに近づけることができると確信しています。いくつかの console.log は、124 (255 のうち) を超えないアルファが、古い方法で 143 と 134 だった場所で正しく発生していることを確認しています。

コードの動作を確認するフィドル: http://jsfiddle.net/g54Mz/

それで、あなたはそれを持っています。2 つの放射状グラデーションの結合の効果を得るには、imageDataシャドウを使用し、実際のパスをオフセットして画面からはみ出さなくても可能です。

于 2012-04-15T23:12:16.973 に答える
1

このフィドルhttp://jsfiddle.net/2qQLz/は、解決策を提供する試みです。必要なものに近い場合は、さらに開発することができます。グラデーションの塗りつぶしを、「円」の交線が片側にある境界長方形に制限します。水平線に沿って配置された同じ半径の 2 つの「円」の場合、「円」の交点の x 値を見つけて、各「円」のグラデーション塗りつぶしの境界長方形を描くのは簡単です。

2 つの任意の「円」の場合はより困難になりますが、それでも交線を見つけることができ、各「円」に対して外接する四角形を描くことができます。より多くの「円」が追加されるにつれて、それは次第に複雑になります。

于 2012-04-16T09:52:44.630 に答える
0

複合モードは、あなたが見つけたとおりです。私が知る限り、手動でpixel を設定しない限り、これよりも優れた合成はできません。ピクセルをブレンドする方法の明示的な説明で質問を更新すると、それに応じて回答が更新されます。

では、ピクセルごとのソリューションとは何ですか?

この問題を解決し始める 2 つの主要なピクセルベースのアプローチがあります。

  1. 非表示のコンテキストに描画し、手動関数とブレンドする

  2. 勾配を手動で計算し、カスタム ブレンド関数を適用する関数を記述します。

最初のオプションは、通常のキャンバス メソッドを使用して好きなように描画し、このキャンバスを表示されているキャンバスにブレンドできるという意味で、2 番目のオプションよりも用途が広いです。あるコンテキストを別のコンテキストにブレンドする方法の良いアイデアについては、@Phrogzコンテキスト ブレンダープロジェクトを参照してください。

2 番目のオプションは、キャンバスが既定では容易に描画できない方法で描画する必要がある場合に必要です。

取り組むべき最大の問題は、放射状グラデーションの背後にコンテンツが存在する可能性があるため、アルファ透明度です。背景画像の上に描画すると、その背景のコピーを保持しない限り、その上に描画する前の状態を確認することはほぼ不可能です。ピクセル単位でも問題があります。別の画像の上に画像をブレンドすることはできません

基本的に、これは、一般的な合成関数の選択に関係なく、表示されているキャンバスの上に複数の半透明グラデーションの画像を合成しても、そのキャンバスに透明な背景がない限り機能しないことを意味します。

オプション 1 の精神で、空白のコンテキストを作成し、複数のグラデーションをレンダリングできます。次に、これを上にレンダリングします。

また、オプション 2 の精神に則り、レンダリング前にすべてのポイントを定義できれば、1 回のパスでそれらのポイントからイメージとブレンドを計算できます。

ワンパス レンダリング手法をバックグラウンド コンテキストと組み合わせると、手動でピクセルを読み取ることなく、ピクセルの書き込みだけで、表示されているキャンバスの上に輪郭を描画できます。

私が知っているほどエレガントではありませんが、2D キャンバスでアルファ ブレンドされた輪郭効果と私が呼ぶものを実現する唯一の現実的な方法かもしれません。


必要なピクセルごとのブレンド関数は、ソースと宛先からアルファ値が最大のピクセルを選択すると思います。

if (src.a <= dst.a) {
    result = dst;
} else {
    result = src;
}
于 2012-04-12T12:46:30.607 に答える