誰かもつぶやいていましたが、ブログの順番が回ってくるのがあまりにも早いような感じがします。

前回まで、node.jsとOpenCVを使ったチャットを作ろうと奮闘しておりましたが、
OpenCV(node-opencv)だけで画像合成に手間取っており、
他の手段で画像合成を行うことを検討しています。

今回は、その調査結果を記事にしていきたいと思います。

ざっと調べてみたところ、node.jsで画像合成を行うモジュールは
有名どころでは、下の2つがあるみたいです。

  • gm

    • http://aheckmann.github.io/gm/
    • node.jsからGraphicsMagick(ImageMagickの派生ライブラリ)を扱うためのmodule
  • node-canvas

    • https://github.com/Automattic/node-canvas
    • node.js上でHTML5のCanvasを扱うライブラリ

今回は、”node-canvas”を使ってnode.jsで画像の合成を行ってみようと思います。

node-canvasを使って実装してみる

node-canvasについて

node-canvasはnode.jsでHTML5のCanvas要素を扱えるモジュールです。
Canvasの操作自体はクライアントサイドと変わらないみたいなので、
学習コストが低そうなのがメリットでしょうか。
半面、速度はあまり出ないかもしれません
(完全にイメージで発言しました。。でも、Canvasの処理は重い印象があります。)

ハマったこと

Canvasの操作以外で少しハマったことがありましたので、
先にご紹介したいと思います。

ハマったのは、ブラウザからPOSTしたファイル情報を受け取る処理です。
今回、サンプルコードを書くのにExpressを使ってみました。

クライアントからtype=”file”なinputタグでPOSTして、
console.logで、requestの中身を覗いてみたのですが、
ファイルの情報が見当たりません。

なんとなく、フォームのenctypeの設定漏れかなと推測し、
formタグの属性をチェックしてもenctype="multipart/form-data"になっており、
問題なさそうに見えました。

怪訝に思いGoogle先生に問いかけてみると、
Express(node.jsで?)でPOSTデータ中のファイル情報を受け取るには、
ひと工夫必要らしいということを教えてくださいました。
※ このあたり、あまりよく理解できておりません。ご指摘いただければ幸いです。

今回はExpress4を使っていたので、multerというモジュールを使って
POSTデータ内のファイル情報を取得してみることにしました。

multerの使い方

まずはインストールです。無心になってnpmコマンドをたたきましょう
特に問題なくインストールが完了すると思います。

$ npm install multer

Expressからmulterを呼び出してみる

Express4を利用したサンプルコードは下記のようになりました。

サーバサイドコード

var express  = require('express');
var app      = express();
var multer   = require('multer'); // multerをrequire
var fs       = require('fs');
var upload   = multer({dest: 'uploads'}); // プロジェクトルートの'uploads'ディレクトリにファイルを受け取るようにしている

app.set('view engine', 'jade');
app.set('views', __dirname + '/views');

app.get('/', function(req, res) {
  res.render('index', {title: 'test'});
});

// コントローラの第2引数に受け取る画像(ファイル)データの設定を渡す。
// 具体的にはmulter().single(), multer().fields, multer.any() などの実行結果を渡す
// 今回は複数のファイルを受け取るため、multer().fields()を設定してみた
app.post('/merge', upload.fields([ {name: 'base_img', maxCount: 1}, {name: 'over_img', maxCount: 1} ]), function(req, res) {
  console.log(req.files); // 
});

app.listen(8080);

クライアントHTML(テンプレートエンジンのレンダリング結果)

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title>test</title>
  </head>
  <body>
    <h1>node.js画像合成テスト(canvas)</h1>
    <form method="POST" action="/merge" enctype="multipart/form-data" target="_blank">
      <div>
        <h2>ベースの画像</h2>
        <input type="file" name="base_img">
      </div>
      <div>
        <h2>重ねる画像</h2>
        <input type="file" name="over_img">
      </div>
      <div style="margin-top:10px;">
        <input type="submit">
      </div>
    </form>
  </body>
</html>

上記のクライアントHTMLに画像をセットしてフォームを送信したところ、、
consoleに下記のように出力されました。無事、ファイル情報を取得することができたようです。

{ base_img:
   [ { fieldname: 'base_img',
       originalname: 'penguin.jpg',
       encoding: '7bit',
       mimetype: 'image/jpeg',
       destination: 'uploads',
       filename: '344e3b7bdebc94aae3d6e2781ff10fac',
       path: 'uploads/344e3b7bdebc94aae3d6e2781ff10fac',
       size: 28484 } ],
  over_img:
   [ { fieldname: 'over_img',
       originalname: 'logo.png',
       encoding: '7bit',
       mimetype: 'image/png',
       destination: 'uploads',
       filename: '20f835abad18fa3e540073245d107f1d',
       path: 'uploads/20f835abad18fa3e540073245d107f1d',
       size: 1949 } ] }

node-canvasで画像を重ねてみる

無事、POSTデータから画像情報を取得することができましたので、
今度はnode-canvasを使って画像を重ねてみましょう。

クライアントサイドのコードを、下記に変更します

var express  = require('express');
var app      = express();
var multer   = require('multer');
var fs       = require('fs');
var Canvas   = require('canvas'); // node-canvasをrequireする
var upload   = multer({dest: 'uploads'});

app.set('view engine', 'jade');
app.set('views', __dirname + '/views');

app.get('/', function(req, res) {
  res.render('index', {title: 'test'});
});

app.post('/merge', upload.fields([ {name: 'base_img', maxCount: 1}, {name: 'over_img', maxCount: 1} ]), function(req, res) {
  // Canvas.Image() メソッドでImg要素を作り、
  // srcに受け取ったファイルのパスをセットする
  var baseImg = new Canvas.Image();
  baseImg.src = fs.readFileSync(req.files.base_img[0].path);
  var overImg = new Canvas.Image();
  overImg.src = fs.readFileSync(req.files.over_img[0].path);

  // Canvasを初期化する
  var canvas = new Canvas(baseImg.width,baseImg.height);
  ctx = canvas.getContext('2d');

  // Canvas上に2つの画像を描画する
  ctx.drawImage(baseImg,0,0);
  ctx.drawImage(overImg,0,0);

  // viewにCanvasをdataURL化して渡す。
  res.render('merge', {resultImg: canvas.toDataURL()});
});

app.listen(8080);

上記のコードを実行しフォームを送信したところ、
無事に画像の合成ができました。

最後に、動作結果のキャプチャを張っておきます。

2015-10-26 20h10_59

感想

node-canvasを触ってみて、クライアントサイドでもサーバサイドでも、
ほとんど同じコードでCanvasを扱えるというのは、魅力に感じました。