JavaScript
画像処理
2D
HTML5,CANVAS

HTML5 Canvas上で低コスト&ローカル動作可能なぼかし(Blur)表現

More than 1 year has passed since last update.

概要

背景

Canvas上でぼかし(Blur)を行えるJSライブラリは数多くありますが、どれもピクセル処理をするため高品質であるものの高負荷なので、スマートフォン向けにアクションゲームを作るような状況だと採用しづらいです。
また、ピクセル処理をする関係で、ローカル動作だとブラウザによってセキュリティ(同一出身ポリシー)に阻まれちゃんと動作しません。

参考:
canvas の getImageDataが少しめんどくさい(特にローカルで動かす場合)
JavaScriptにおけるローカルファイルアクセス権限のポリシー

一方、CSSでCanvas要素をblurでぼかすことはできますが、こちらは内部処理用に作成したCanvasには効きませんし、ぼかし有りCanvasをぼかし無しCanvasに合成するとぼかしが反映されません。

それでもやっぱりぼかし表現をやりたい

多少品質が犠牲になりますが
- ピクセル処理をしない
- CSSを使わない
- 低負荷
- ローカル環境でも動作する

を可能にするぼかし表現をご紹介します。

実装

Canvasにはアンチエイリアス機能が備わっている

Canvasに画像を表示する際、1.0〜0.5倍で縮小表示すれば2x2サンプリングでアンチエイリアスが掛かるようです。
0.5倍を下回るとサンプリングの都合でギザギザします。
参考:
Html5 canvas drawImage: how to apply antialiasing

アンチエイリアスを逆手に取る

小さく描いたものを拡大すればぼかし出力が得られそうです。
また、2倍までの拡大で抑えればそこそこなクオリティを維持できそうです。

小さく描いて拡大する

読み込んだ画像にぼかした文字を付加して表示してみます。

<!DOCTYPE html>
<html>
<body>
<canvas></canvas>
<script>
    var img = new Image();
    img.src = 'sample.jpg';
    img.onload = function() {
        var canvas = document.getElementsByTagName('canvas')[0];
        canvas.width = img.width;
        canvas.height = img.height;
        var text = 'Blur Effect';

        // 0.5倍, 0.25倍の内部キャンバス生成
        var canvasBlur050 = document.createElement("canvas");
        canvasBlur050.width = canvas.width * 0.5;
        canvasBlur050.height = canvas.height * 0.5;
        var canvasBlur025 = document.createElement("canvas");
        canvasBlur025.width = canvas.width * 0.25;
        canvasBlur025.height = canvas.height * 0.25;

        // 普通に画像を表示
        var context = canvas.getContext('2d');
        context.drawImage(img, 0, 0);
        context.fillStyle = '#FFFF00';
        context.strokeStyle = '#FF0000';
        context.font = '64px Arial';
        context.fillText(text, 50, canvas.height * 0.33);
        context.strokeText(text, 50, canvas.height * 0.33);

        // 0.25倍の内部キャンバスに文字描画
        var contextBlur025 = canvasBlur025.getContext('2d');
        contextBlur025.save();
        contextBlur025.setTransform(0.25, 0, 0, 0.25, 0, 0);
        contextBlur025.fillStyle = '#FFFF00';
        contextBlur025.strokeStyle = '#FF0000';
        contextBlur025.font = '64px Arial';
        contextBlur025.fillText(text, 50, canvas.height * 0.66);
        contextBlur025.strokeText(text, 50, canvas.height * 0.66);
        contextBlur025.restore();

        // 0.5倍の内部キャンバスに拡大
        var contextBlur050 = canvasBlur050.getContext('2d');
        contextBlur050.drawImage(canvasBlur025, 0, 0, canvasBlur025.width, canvasBlur025.height, 0, 0, canvasBlur050.width, canvasBlur050.height);

        // 元のキャンバスに拡大して合成
        context.drawImage(canvasBlur050, 0, 0, canvasBlur050.width, canvasBlur050.height, 0, 0, canvas.width, canvas.height);
</script>
</body>
</html>

結果

output.png
ちょっと粗い気もしますが、リアルタイム処理でこのクオリティなら問題ないでしょう。

応用

を使用して被写界深度のような表現
canvas.png
Canvas倍率は遠景0.4、人物1.0、近景0.25です。

  • ぼかし方向が要素の中央からではなく左上からなので重ねる際に注意
  • ぼかし具合を動的に変化させるには手間がかかる(毎回内部Canvasサイズを変えないといけない)
  • ぼかしが粗めなので負荷とのトレードオフの選択肢として