2014年2月11日火曜日

Node.jsってなんじゃ?(gm:GraphicsMagickで画像合成)

今回はnode.jsを利用して、画像合成をしてみたいと思います。
この手の画像生成にはImageMagickがよく使われますが、ImageMagickから派生したGraphicMagickがImageMagickよりもパフォーマンスが優れているのでこちらの方を利用します。

node.js内からは、GraphicMagick/ImageMagickを使用できるgmというモジュールを利用します。

今回node.jsは久しぶりなので、nodeのインストールから始めてみたいと思います。
他のLL言語などでもそうですが、nvmやenv系などインストール環境管理ツールが乱立しているようです。
今回はnodebrewが便利そうなので、nodebrewを使ってみました。


GraphickMagickのインストール


yumでインストールします。
# yum install gcc-c++ GraphicsMagick -y



nodebrewを使ったnode.jsとモジュールのインストール


nodebrewでは、インストール実行した場所だけにファイルが作られるようで環境を汚さず、rootでなくともインストールが可能なことが特徴のようです。
インストールを行い、PATHを通すだけで完了なので、とても簡単です。
# cat /etc/profile.d/nodebrew.sh
#!/bin/bash

export NODE_PATH=$HOME/.nodebrew/current/node_modules
export PATH=$HOME/.nodebrew/current/bin:$PATH

$ source /etc/profile
$ cd ~/
$ curl https://raw.github.com/hokaccha/nodebrew/master/nodebrew | perl - setup

$ nodebrew install latest
$ nodebrew use latest

$ node -v
v0.11.11

$ npm install gm
$ npm install argv



実装



image.js

画像合成のメイン処理です。引数の画像URLをダウンロードして画像合成します。

//モジュール読込
var http = require('http');
var url = require('url');
var gm = require('gm');
var gmcomposite = require('./gmcomposite');
var fs = require('fs');
var argv = require('argv');
//コマンドオプション定義
argv.option([{
name: 'facebook_id',
short: 'f',
type : 'string',
description :'your facebook_id',
example: "'script --facebook_id=value' or 'script -f value'"
},
{
name: 'url',
short: 'u',
type: 'string',
description: 'your picture url',
example: "'script --url=value or 'script -u value''"
}]);
var config = argv.run();
//画像パス定義
var base_path = './assets/frame.png';
var mask_path = './assets/frame.png';
var down_path = './rslt/download/'+config.options.facebook_id+".jpg";
var convert_path = './rslt/convert/'+config.options.facebook_id+".jpg";
var composite_path = './rslt/composite/'+config.options.facebook_id+'.jpg';
var target_url = url.parse(config.options.url);
target_url.protocol = 'http:';
target_url.path = target_url.pathname;
config.base_path = base_path;
config.mask_path = mask_path;
config.down_path = down_path;
config.convert_path = convert_path;
config.composite_path = composite_path;
/**
* URLの画像を指定ディレクトリにダウンロード
* @param {string} target_url:ダウンロード対象のURL
* @param {string} save_path:保存先
* @param {string} fn:コールバック関数
*/
function get(target_url, save_path, fn) {
var callee = arguments.callee;
var opts = url.parse(target_url);
var req = (opts.protocol.match(/https/) ? https : http).request({
host: opts.hostname,
port: opts.port,
path: opts.pathname + (opts.search || ''),
method: 'GET',
});
req.on('response', function(res){
if(res.statusCode == 301 ||res.statusCode == 302) {
callee(res.headers.location, destPath, fn);
} else if(res.statusCode == 200) {
var writableStream = fs.createWriteStream(save_path);
writableStream.on('error', fn);
writableStream.on('close', fn);
res.on('data', function(chunk){
writableStream.write(chunk, 'binary');
});
res.on('end', function(){
writableStream.end();
});
} else {
fn(new Error('statusCode is ' + res.statusCode));
}
});
req.on('error', fn);
req.end();
}
/**
* ダウンロードした画像をマスク合成
*/
get(target_url, down_path, function(err){
//エラーなら出力して終了
if(err){console.log(err);return;}
//画像処理
gm(down_path)
.extent(800,800)//リサイズ
.quality(100)//画質
.write(convert_path, (function(data){ return function(err){//写真1次加工
//エラーなら終了
if(err){console.log(err);return;}
//合成処理
gmcomposite(data.base_path, data.convert_path, data.mask_path, data.composite_path, (function(data){ return function(err){
if(err){console.log(err);return;}
console.log("create complete !");
}})(data))
}})(config))
});
view raw image.js hosted with ❤ by GitHub


gmcomposite.js

合成を子プロセスで実行する箇所をモジュール化したものです。

//合成処理のモジュール化
var spawn = require('child_process').spawn
var fs = require('fs');
//子プロセスで処理
var gmcomposite = function(change_path, base_path, mask_path, output_path, callback) {
var c = spawn('gm', ['composite', '-quality', 100, '-geometry', '+0+0', change_path, base_path, output_path]);
c.stderr.on('data', function(data) {
console.log('stderr: ' + data);
})
c.on('exit', function(code) {
if (code !== 0) callback(code, null)
else callback(null, null)
})
};
//モジュール出力
module.exports = gmcomposite;
view raw gmcomposite.js hosted with ❤ by GitHub


画像部品の配置



今回の合成ではfacebookのプロフィール写真がどうにも無愛想なので、かわいくしてみたいと思います。 合成素材用のディレクトリ、合成後の出力先のディレクトリを作成します。
$ mkdir -p assets rslt/convert rslt/composite rslt/download
$ tree ~/
/home/memorycraft/
|-- assets
|   `-- frame.png //合成素材(マスク+ベース兼用)
|-- gmcomposite.js 
|-- image.js
|-- node_modules
|   |-- argv
|   `-- gm
|-- rslt
|   |-- composite
|   |-- convert
|   `-- download
`-- tmp

また、httpdサーバのドキュメントルートから出力先ディレクトリにリンクします。
# chmod 755 /home/memorycraft/
# cd /var/www/html
# ln -s /home/memorycraft/rslt rslt


assetsディレクトリにマスク素材を配置します。

frame.png



実行



それでは実行します。 オプション引数にfacebookの画像URLと、facebook ID(画像名につかうだけなので何でも良い)を渡します。
$ node image.js -u http://fbcdn-sphotos-b-a.akamaihd.net/hphotos-ak-prn1/t1/1525482_10202990719234672_615625235_n.jpg -f memocra
create complete !
無事出力できたようです。



確認


それでは出力された画像をブラウザで確認してみます。


。。。。ちょっと思った感じと違いますが、とりあえず合成できたのでよしとします。
今回は以上です。