Node.jsでCanvas(ImageData)を使った簡単な画像処理

# 背景
画像を扱うときにHTML5のCanvasのImageDataがフォーマットとかを意識せずに画素単位でアクセスできて簡単!
Nodeだけでできないかなぁって思ってたらnode-canvasを使うことでできました。

# 追記
Nodeで画像いじるようなことをしている人はマゾしかいません。普通の人はOpenCVとか使いましょう。

# ImageDataとは?
HTML5のCanvasをJavaScriptで扱うときの画像の画素値の配列みたいなものです。
以下のカヤックの人が書いた記事がわかりやすいのでご参照ください。
[canvasをバイト単位で修正する方法（ImageDataの使い方）](http://tech.kayac.com/archive/javascript-canvas-imagedata.html)

# 環境
Windows10上でVagrantを使った仮想マシンを作りました。
仮想マシンのOSは[ubuntu/trusty64](https://atlas.hashicorp.com/ubuntu/boxes/trusty64)を使いました。
Node.jsをそのままいれるとコマンドが"node"ではなく"nodejs"になってしまうので、シンボリックリンクを作成しています。

```sh:Ubuntuにインストールする場合
$ sudo apt-get install nodejs -y
$ sudo ln -s /usr/bin/nodejs /usr/bin/node
$ sudo apt-get install npm -y
```

# 事前準備
[node-canvas](https://github.com/Automattic/node-canvas)を使います。
carioを使うので先にインストールしてから、npmで任意の場所にnode-canvasをいれます。

```sh:Ubuntuにインストールする場合
$ sudo apt-get install libcairo2-dev
$ npm install canvas
```

# カラー画像を擬似モノクロにしてみる
実際に画像([プロ生ちゃん](http://pronama.azurewebsites.net/pronama/))を読み込み、画像処理を行いファイルとして出力してみます。```node canvas_test.js```を実行すると同じディレクトリ内の"pronama.png"から擬似モノクロ画像"monochrome.png"を出力します。

こんな感じになります。
処理前 (pronama.png)
![pronama.png](https://qiita-image-store.s3.amazonaws.com/0/91558/a207b610-1252-3143-47df-f858373775a3.png)

処理後 (monochrome.png)
![monochrome.png](https://qiita-image-store.s3.amazonaws.com/0/91558/88eb425f-94e9-0b4f-4fea-9e1e11ae39e5.png)

### コード

```js:canvas_test.js
// Node.js標準装備のファイルの読み書きするやつ
var fs = require('fs');

// 別途用意した画像を保存してくれるやつ
var canvas_saver = require('./canvas_saver.js');

// node-canvas
var Canvas = require('canvas'),
	Image = Canvas.Image;

fs.readFile(__dirname + '/pronama.png', function(err, data){
	if (err) throw err;

	// データをcanvasのcontextに設定
	var img = new Image;
	img.src = data;
	var canvas = new Canvas(img.width, img.height);
	var ctx = canvas.getContext('2d');
	ctx.drawImage(img, 0, 0, img.width, img.height);

	// RGBの画素値の配列を取得
	var imagedata = ctx.getImageData(0, 0, img.width, img.height);

	// 画像加工(擬似モノクロ化)
	for(var y=0; y<imagedata.height; y++){
		for(var x=0; x<imagedata.width; x++){
			var index = (y*imagedata.width+x)*4;
			// imagedata.data[index] = imagedata.data[index]; // R
			imagedata.data[index+1] = imagedata.data[index]; // G
			imagedata.data[index+2] = imagedata.data[index]; // B
			// imagedata.data[index+3]; // alpha
		}
	}

	// 加工したデータをセット
	ctx.putImageData(imagedata, 0, 0);

	// データを保存
	canvas_saver.save(canvas, "monochrome.png", function(){
		console.log("画像保存完了したよ!!");
	});

});
```

```js:canvas_saver.js
// node-canvasのcanvasを画像データとして保存する

module.exports = (function(){
	"use strict";

	var fs = require('fs');

	var canvas_to_base64 = function(canvas){
		return canvas.toDataURL().split(',')[1];
	}

	// 追記: もっと簡単にできる方法がたしかあります(その方法は忘れました)
	var decode_and_copy = function(string, filename, callback) {
		var buffer = new Buffer(string, 'base64');
		fs.writeFile(filename, buffer, callback);
	}

	return {
		save: function(canvas, name, callback){
			decode_and_copy(
				canvas_to_base64(canvas),
				name,
				callback
			);
		}
	}

})();
```

# その他の例
ImageDataを取得することによって、画像のヘッダやフォーマットを意識せずに画像の画素値をいじれます。
例えば、擬似モノクロ化の部分を以下のように書き換えれば簡単にネガポジ反転の処理が行えます。

```js:ネガポジ反転
imagedata.data[index] = 255 - imagedata.data[index]; // R
imagedata.data[index+1] = 255 - imagedata.data[index+1]; // G
imagedata.data[index+2] = 255 - imagedata.data[index+2]; // B
// imagedata.data[index+3]; // alpha
```

# まとめ
node-canvasを利用して、Web上のCanvasの扱いのように画像処理が行うことができた。

# 参考文献など
[node-canvas](https://github.com/Automattic/node-canvas)
[canvasをバイト単位で修正する方法（ImageDataの使い方）](http://tech.kayac.com/archive/javascript-canvas-imagedata.html)
[サーバサイドでCanvasを利用する(node.js)](http://blog.asial.co.jp/789)
[プロ生ちゃん](http://pronama.azurewebsites.net/pronama/)
[ImageData仕様書](https://html.spec.whatwg.org/multipage/scripting.html#imagedata)

# その他
間違いや意見等ありましたら、ぜひコメントください。

# 宣伝
フォローお願いします:custard: [@redshoga](https://twitter.com/redshoga)