この手の画像生成には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をダウンロードして画像合成します。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//モジュール読込 | |
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)) | |
}); |
gmcomposite.js
合成を子プロセスで実行する箇所をモジュール化したものです。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//合成処理のモジュール化 | |
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; |
画像部品の配置
今回の合成では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 !無事出力できたようです。
確認
それでは出力された画像をブラウザで確認してみます。
。。。。ちょっと思った感じと違いますが、とりあえず合成できたのでよしとします。
今回は以上です。