Canvasで不気味で怖い？ブラウン管テレビのようなエフェクトを書いてみた

# はじめに
Canvasを利用して、不気味で怖いブラウン管テレビのようなエフェクトを書いてみました。
CSS3のfilterプロパティを使っても似たようなことはできるかもしれませんが、ザラザラしたアニメーションをさせたかったためCanvasを使いました（もしくはfilterプロパティと組み合わせてより雰囲気を出すこともできそうです）。

画像にCanvas要素を重ねているだけなので何でも応用できるかと思います。何かのお役に立てば幸いです。

なお実際の動作は以下で確認することができます。
http://codepen.io/nekoneko-wanwan/pen/xZegVE


**[動作イメージ]**
![def.gif](https://qiita-image-store.s3.amazonaws.com/0/58436/04892590-e1db-ce84-9cdb-df3234f42f1e.gif)
......おや！？ネコのようすが......！


![anim.gif](https://qiita-image-store.s3.amazonaws.com/0/58436/182b7c33-9dc5-d6ea-ffd6-b7fb7384760d.gif)
おめでとう！ネコはホラーなネコにしんかした！
（もしくはノスタルジー？w）

# 特長・仕様

- Canvasサイズに依存しない
- ノイズの色は自由に変更可能
- ノイズの濃さは自由に変更可能
- Canvasのピクセルデータを直接操作することで、高速にアニメーションをさせる

> Canvasのピクセルサイズと動作環境によっては重たいかもしれません


# ソースコード

```index.html
<canvas id="canvas" width="300" height="200"></canvas>
```

```style.css
canvas {
  background-image: url('ネコ画像');
  background-position: 0 0;
  background-size: cover; 
}
```

```canvas.js
var canvas = document.getElementById('canvas'),
    ctx = canvas.getContext('2d'),
    w = ctx.canvas.width,
    h = ctx.canvas.height,

    // noise関係
    rgb = {
      // ノイズの点に使う色を設定（↓はセピア色）
      r: 107,
      g: 74,
      b: 43
    },
    idata = ctx.createImageData(w, h),
    data  = idata.data,
    dotAlpha = 100, // max256（数値が大きいほどノイズが強くなる=点の不透明度が下がる）

    // 円形グラデーション関係
    x = w / 2,
    y = h / 2,
    radius = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)) | 0, // canvasの外接円の半径
    grad   =  ctx.createRadialGradient(x,y,0,x,y,radius),
    blend  = 'screen';  // ブレンドモードを定義

var draw = {
  // canvas全体にnoise（1pxごとにランダムな不透明度を持つ点）を作成
  noise: function() {
    for(var i = 0, l = data.length; i < l; i += 4) {
      data[i + 0] = rgb.r;
      data[i + 1] = rgb.g;
      data[i + 2] = rgb.b;
      data[i + 3] = Math.random() * dotAlpha | 0;
    }
    ctx.putImageData(idata, 0, 0);
  },
  // Gradation領域の作成
  grad: function() {
    grad = ctx.createRadialGradient(x,y,0,x,y,radius);
    grad.addColorStop(0,'rgba(0,0,0,0)');
    grad.addColorStop(1,'rgba(0,0,0,1)');
  },
  // ループさせたい描画
  render: function() {
    ctx.clearRect(0,0,w,h);
    this.noise();

    // 円形グラデーションの描画
    ctx.beginPath();
    ctx.fillStyle = grad;
    ctx.rect(0,0, w,h);
    ctx.fill();
    requestAnimationFrame(this.render.bind(this));
  },
  initialize: function() {
    ctx.globalCompositeOperation = blend;  // canvasのブレンドモードを定義
    this.grad();
    this.render();
  }
}

draw.initialize();
```

# 作り方・考え方

大きく分けて2つ、Canvas全体への**ノイズ**と**円形グラデーション**を作成します。

## ノイズの作り方

Canvasにはピクセルデータを直接操作できる機能があります。数値の入った配列を直接操作するので、ピクセル単位の描画などを高速に処理することができます。
ここではImageDataオブジェクトを利用し、Canvasのピクセルごとに点（データ）を格納していきノイズを表現します。


**ImageDataオブジェクトとは**
簡単にいうとCanvasのバイト・ピクセルデータが操作できるオブジェクトです。
dataプロパティには指定したピクセル範囲における、rbgaデータが順番に格納された1次元配列が入っています。

```dataプロパティの値イメージ
0: r  // ここから
1: g
2: b
3: a  // ここまでで1pxのrgba=1色を表す
4: r
5: g
6: b
.
.
.

- rgbaはそれぞれ0-255の範囲で数値を格納することができます
- alpha成分（透明度）も0-255の範囲で表現します
- 初期だとすべて0（透明な黒）が格納されています
```

そしてこれが指定したピクセル分、つまりwidth:100px, height:100pxのCanvas全体では`100 * 100 * 4(r+g+b+a) = 40,000`の長さを持つ配列を操作することになります。

**具体的な手順**

- createImageDataメソッドでCanvas全体を対象としたImageDataオブジェクトを生成します
- ImageData.dataプロパティの配列に点の色データを格納していきます
- ここでは点の色を統一するため決め打ちにしています（Math.random()にすればカラフルなノイズができます）
- CanvasにImageDataオブジェクトを描画させるために、putImageDataでデータを書き戻します

**[ノイズを適用したイメージ]**
![noize-only.gif](https://qiita-image-store.s3.amazonaws.com/0/58436/074d8111-83f1-580f-4edb-81beb68315ce.gif)



## 円形グラデーション

怖そうな雰囲気（もしくはノスタルジー）を出すために、透明→黒の円形グラデーションを使ってトンネルエフェクトを表現します

> トンネルエフェクトについては以下に詳しい説明があります
> [トイカメラ百科](http://toycam.imaimax.com/modules/howto/index.php?content_id=20)

- Canvasの**四隅**を暗くするために、Canvasとなる四角形の外接円の半径を算出し、中心からその半径の範囲で円形グラデーションを作成します
- 円形グラデーションにはcreateRadialGradientメソッドを使用します（基本メソッドなので省略）
- ブレンドモードを指定することで、ノイズの色によって微妙に色合いが変化します
- また半径を増やしたり、グラデーションの透明度やベース色を調整することで暗さの調整が容易にできます（アニメーションさせても面白いかも）。


**[トンネルエフェクトを適用したイメージ]**
![grad-only.gif](https://qiita-image-store.s3.amazonaws.com/0/58436/56d78a1e-da1b-122f-66d9-459b99c152cc.gif)





# 終わりに

今回は何となく怖そうな雰囲気を目指してみましたが、アイデアしだいで色々な表現が実現できそうですね。

> [参考サイト]
> [canvasをバイト単位で修正する方法（ImageDataの使い方）](http://tech.kayac.com/archive/javascript-canvas-imagedata.html)
> [putImageDataによるCanvasの画像データ設定](
http://www.programmingmat.jp/webhtml_lab/canvas_putdata.html)

**今まで投稿したCanvasエフェクトシリーズ**
> [Canvasでﾃｨｳﾝﾃｨｳﾝﾃｨｳﾝ...](http://qiita.com/nekoneko-wanwan/items/12db2ad52b664ca96149)
> [Canvasでビックリマンなシールを書いてみた（光るゾ！）](http://qiita.com/nekoneko-wanwan/items/a6db5995029328f03720)
> [Canvasで斬！っと画面が斬れるようなエフェクトを書いてみた](http://qiita.com/nekoneko-wanwan/items/bdbd0d3cf294fc54f6dd)
> [Canvasで漫画にあるような吹き出しを書いてみた](http://qiita.com/nekoneko-wanwan/items/df5d8eaf33fa477fe7c9)
> [Canvasで漫画にあるような "ざわ・・・" を書いてみた](http://qiita.com/nekoneko-wanwan/items/4b196365183580831758)
> [Canvasで漫画にあるような集中線を書いてみた](http://qiita.com/nekoneko-wanwan/items/0911a59bf835d5b9e35a)