HTML5キャンバスでの双線形フィルタリングによるピクセルの歪み



この投稿では、画像ピクセルに直接アクセスすることにより、特別なライブラリとシェーダーを使用せずに、2D-Canvasで「純粋な」javascriptを使用して画像のピクセルを歪める簡単な手法を説明します。 これが一般的な開発といくつかの問題の解決の両方に興味深く、役に立つことを願っています。




キャンバスとピクセル



Canvasオブジェクトについては完全には説明しませんが、このドキュメントがあります。 必要なものについて説明しましょう。 1つ目は、2Dコンテキストの取得です。

var context = canvas.getContext('2d');
      
      





このコンテキストは、特定の領域のパイに直接アクセスするなど、2次元グラフィックスで多くのことができます。

 var pixels = context.getImageData(x, y, width, height); context.putImageData(pixels, x, y);
      
      





これらは変更する必要があるピクセルです。 32ビット画像のみを考慮します。 このような画像の各ピクセルは、チャネルごとに1バイト(R、G、B、A)の4バイトを表します。 ピクセルは、これらのバイトの1次元配列です。 それらへのアクセスは、データフィールド(x、y-座標、c-チャネル、b-値)を介して行われます。

 pixels.data[(x+y*height)*4+c] = b;
      
      





歪み関数



私たちが検討している画像の歪みは、結果の画像の座標をパラメーターとする関数です(以下ピクセルと呼びます)。結果は元の画像の座標です(実際には元の画像はテクスチャであり、座標はこれらは浮動小数点数です)。 したがって、画像を拡大する関数はおよそ次のとおりです。

 var zoom = function(px, py) { return { 'x': (px+width/2)*0.5, 'y': (py+height/2)*0.5 } }
      
      




他のディストーション用の関数をさらに作成してみましょう。 各アルゴリズムの説明には意味がありません。数学は非常に単純で、それ自体を物語っています。

 var twirl = function(px, py) { var x = px-width/2; var y = py-height/2; var r = Math.sqrt(x*x+y*y); var maxr = width/2; if (r>maxr) return { 'x':px, 'y':py } var a = Math.atan2(y,x); a += 1-r/maxr; var dx = Math.cos(a)*r; var dy = Math.sin(a)*r; return { 'x': dx+width/2, 'y': dy+height/2 } }
      
      




 var reflect = function(px, py) { if (py<height/2) return { 'x': px, 'y': py } var dx = (py-height/2)*(-px+width/2)/width; return { 'x': px+dx, 'y': height-py } }
      
      




 var spherize = function(px,py) { var x = px-width/2; var y = py-height/2; var r = Math.sqrt(x*x+y*y); var maxr = width/2; if (r>maxr) return { 'x':px, 'y':py } var a = Math.atan2(y,x); var k = (r/maxr)*(r/maxr)*0.5+0.5; var dx = Math.cos(a)*r*k; var dy = Math.sin(a)*r*k; return { 'x': dx+width/2, 'y': dy+height/2 } }
      
      




ハッシュテーブル



そこで、各ピクセルにどのテクセルを使用するかを見つける機会を得ました。 しかし、毎回同じ座標を計算しませんか? ストレスが多すぎます。 ハッシュテーブルが助けになります。 したがって、画像サイズごとに変換マップ全体を1回計算し、将来的には各変換に使用します。

 //    .   ,      . var setTranslate = function(translator) { if (typeof translator === 'string') translator = this[translator]; for (var y=0; y<height; y++) { for (var x=0; x<width; x++) { var t = translator(x, y); map[(x+y*height)*2+0] = Math.max(Math.min(tx, width-1), 0); map[(x+y*height)*2+1] = Math.max(Math.min(ty, height-1), 0); } } }
      
      





バイリニアフィルタリング



鋭い境界が気分を歪ませるのを防ぐために、古典的な双線形フィルタリングアルゴリズムを適用します。 ウィキペディアで詳細に説明されます。 アルゴリズムの本質は、4つの最も近いテクセルに応じてピクセルの色を見つけることです。 この場合、アルゴリズムは次のようになります。

 var colorat = function(x, y, channel) { return texture.data[(x+y*height)*4+channel]; } for (var j=0; j<height; j++) { for (var i=0; i<width; i++) { var u = map[(i+j*height)*2]; var v = map[(i+j*height)*2+1]; var x = Math.floor(u); var y = Math.floor(v); var kx = ux; var ky = vy; for (var c=0; c<4; c++) { bitmap.data[(i+j*height)*4+c] = (colorat(x, y , c)*(1-kx) + colorat(x+1, y , c)*kx) * (1-ky) + (colorat(x, y+1, c)*(1-kx) + colorat(x+1, y+1, c)*kx) * (ky); } } }
      
      





おわりに



それだけです。 これを別のオブジェクトでラップし、コードに追加して何が起こるかを確認することは残っています。

JSFiddleでリアルタイムにプレイします。

ChromeとFirefoxで動作します。 他の人はまだ確認できません。うまくいかない場合は、個人で書いてください。

ご清聴ありがとうございました。



All Articles