まだプログラマーですが何か?

プログラマーネタとアスリートネタ中心。たまに作成したウェブサービス関連の話も http://twitter.com/dotnsf

タグ:canvas

先日、HTML5 Canvas のサーバーサイド版である Node-Canvas を紹介しました:
Node.js から使えるサーバーサイド Canvas : node-canvas


HTML5 Canvas と互換性のある API を使って動的に Canvas 上に描画をします。実際には単に描画をするだけでなく、描画した内容を画像として取り出したりすることで、サーバー側の JavaScript で動的に画像ファイルを生成することができるようになり、とても便利なライブラリです。

実際に使っていて、1点問題気になることがありました。開発中は(日本語環境バッチリな)自分のマシンを使って動作テストなどを行うので気が付きにくいのですが、実際の本番サーバー上で動かした際にキャンバス上に描画した日本語文字列が化けてしまうケースを発見しました。

例えばこのようなコードを実行して、サーバーサイドで画像を動的に生成したとします:
  :
var fs = require( 'fs' );
var Canvas = require( 'canvas' ),
    Image = Canvas.Image;

  :

var canvas = new Canvas( 300, 300 );
var ctx = canvas.getContext( '2d' );

//. 赤い直線を1本描画する
ctx.beginPath();
ctx.moveTo( 100, 100 );
ctx.lineTo( 200, 200 );
ctx.strokeStyle = 'red';
ctx.stroke();

//. 文字列を表示する
ctx.fillText( 'ハローワールド!', 10, 30 );

//. 画像データをファイルにして保存する
var b64 = canvas.toDataURL().split( ',' )[1];
var buf = new Buffer( b64, 'base64' );
fs.writeFileSync( __dirname + '/images/aaa.png', buf );

  :


このコードを Node.js で実行すると、./images/aaa.png というファイル名で動的に画像ファイルが生成されて保存されます。が、その時に生成される画像で「ハローワールド!」と明示されている文字列はサーバー実行環境によって正しく表示されたり、されなかったりします:

(正しく表示される場合)
2017052901

(文字化けを起こす場合)
2017052902


これはサーバー OS 側に日本語フォントが導入されているかどうかで決まります。特にクラウド環境においては OS は(デフォルトでは)英語版が用意されることが多く、そのまま利用しても日本語フォントが用意されていないため、「ハローワールド」という文字列を正しく描画できず、結果として文字化けを起こしてしまうことになります。

ということは、仮に文字化けを起こしても、OS に日本語フォントを導入した上でアプリケーションサーバーを再起動すれば回避できる、ということになります。

が、ここで問題が発生します。IBM Bluemix のような PaaS 環境の場合、OS 環境設定の自由度が低く、用意されたプラットフォームをカスタマイズして使うハードルが高くなります。現に SSH でログインすることはできますが apt-get などは使えません。またフォントファイルを後から無理やりコピーしてもアプリケーションサーバーを再起動するとそのファイルは消えてしまいます。このような自由度があまり高くないプラットフォーム上で日本語を正しく表示させることが結構難しかったりします。

そしてなんと更に、Node-Canvas の Issue ボードによると、この部分にはどうもバグがあるらしく、(2017/May/29 現在)ステータスが Open になっているので、まだ解決できていない模様でした:
https://github.com/Automattic/node-canvas/issues/783


・・・と、とても困った状況なのですが、結論から言うとなんとかアプリケーション側の工夫で回避することができました。まずは日本語フォントが必要になるので、フリーの日本語フォントを適当にダウンロードしてきます。例えば eFont プロジェクトから配布されているさざなみフォントの最新版を使うことにします。アーカイブファイル(*.tar.bz2)をダウンロード&展開し、sazanami-***.ttf というファイルをアプリケーション内の fonts/ ディレクトリに格納しておきます:
2017052903

(以下のコードではゴシックフォントしか使いませんが、とりあえず明朝フォントと合わせて展開しておきます)


そしてコードの該当部分を以下のように変更します:
  :
var fs = require( 'fs' );
var Canvas = require( 'canvas' ),
    Image = Canvas.Image;

  :

var canvas = new Canvas( 300, 300 );
var ctx = canvas.getContext( '2d' );

//. 赤い直線を1本描画する
ctx.beginPath();
ctx.moveTo( 100, 100 );
ctx.lineTo( 200, 200 );
ctx.strokeStyle = 'red';
ctx.stroke();

//. フォントを強制指定する
var Font = Canvas.Font;
var myFont = new Font( 'myFont', 'fonts/sazanami-gothic.ttf' );
ctx.addFont( myFont );
ctx.font = '20px myFont';

//. 文字列を表示する
ctx.fillText( 'ハローワールド!', 10, 30 );

//. 画像データをファイルにして保存する
var b64 = canvas.toDataURL().split( ',' )[1];
var buf = new Buffer( b64, 'base64' );
fs.writeFileSync( __dirname + '/images/aaa.png', buf );

  :


これで実行すると、フォントが指定のものに強制指定され、日本語が化けずに表示できるようになります(下図は IBM Bluemix 環境下で実行した結果です):
2017052904


なお、上記の方法を使う場合、Ubuntu はこのままでも動くのですが、Mac OS X 環境においては Pango ライブラリを無効にした上で実行する必要があります。具体的には Node-Canvas を npm install した後で、node_modules/canvas/binding.gyp ファイルを編集し、15行目の値を 'false' にする(8行目と同じにする)作業を行い、再度 npm rebuild した上で node コマンドを実行する必要がありました。ご注意ください。


サーバーサイドで動的に画像を作りたい、という要求を実現する方法はいくつかありますが、今回は Node.js から使えるライブラリ "node-canvas" を紹介します:
https://github.com/Automattic/node-canvas/


まず、このライブラリを使う上ではいくつかのネイティブライブラリが必要です。詳しくは上記オフィシャルページを参照いただきたいのですが、例えば Ubuntu 環境であれば以下のコマンドを最初に実行して、必要なネイティブライブラリをあらかじめ用意しておきます:
$ sudo apt-get install libcairo2-dev libjpeg8-dev libpango1.0-dev libgif-dev build-essential g++

ネイティブライブラリの導入後に、以下のコマンドで node-canvas をインストールします:
$ npm install canvas

ちなみに、Bluemix の SDK for Node.js ランタイムを使う場合には上記のネイティブライブラリが導入されたビルドパックを利用するので、実行環境でのネイティブライブラリの有無に関しては意識する必要はありません。


さて、node-canvas ではサーバーサイドで HTML5 の Canvas を操作するイメージで動的に画像を作ったり、変更したりすることができます。

ブラウザ上でも Canvas は JavaScript で操作することが多いと思いますが、以下のようにほぼ同じような操作で扱うことができます:
var fs = require( 'fs' );

var Canvas = require( 'canvas' ),  //. ここでライブラリ読み込み
    Image = Canvas.Image;          //. 画像生成用オブジェクト

  :

//. public という空ディレクトリをあらかじめ用意しておく(そこに画像を作る)
app.use( express.static( __dirname + '/public' ) );

app.get( '/xxx', function( req, res ){
  //. /xxx に GET アクセスがあったら、その場で画像ファイル xxx.png を生成して、画像にリダイレクトする
  var img = new Image;
  var canvas = new Canvas( 300, 300 );
  var ctx = canvas.getContext( '2d' );

  //. 斜めに赤い線が1本引いてあるだけの画像を作る
  ctx.beginPath();
  ctx.moveTo( 100, 100 );
  ctx.lineTo( 200, 200 );
  ctx.strokeStyle = 'red';
  ctx.stroke();

  //. 画像を Base64 エンコードで取り出して、デコードして、xxx.png という名前で保存する
  var b64 = canvas.toDataURL().split( ',' )[1];
  var buf = new Buffer( b64, 'base64' );
  fs.writeFile( __dirname + '/public/xxx.png', buf, function(){
    res.redirect( '/xxx.png' ); //. 作った画像にリダイレクト
  });
});

上記は僕が作ったサンプルコードの一部を多少変更したものです。Node.js で実行して、/xxx というパスに GET アクセスがあると動的に /xxx.png という画像ファイルを作って(そこにリダイレクトして)表示する、というものです。実際に画像を描いたり作ったりしているのは赤字の部分なのですが、ほぼそのままブラウザ内の JavaScript でも動く記法になっています。

普段から HTML5 の Canvas を使っている人であれば、そのスキルをそのままサーバーサイドで動的に画像を作る技術に応用できると思います。とても便利。

 

IBM InterConnect 2017 の期間中に IBM Bluemix Platform にドイツリージョン(eu-de)が追加されました。記念に早速ランタイムアプリを1つデプロイして公開してみました:
http://spen.eu-de.mybluemix.net/


このアプリはスマホ用(正確にはジャイロセンサーを搭載したスマホ用)なので、試しに使ってみる場合は上記 URL にスマホのブラウザでアクセスしてみてください。以下のような画面が表示されます:
IMG_0430


画面の下半分あたりを指でタップし、離さずにそのまま空中でスマホを振って、文字等を一筆書で書いてみてください。書き終わったらタップした指を離してください:
IMG_0430


指でタップしていた間にスマホの先端が動いた軌跡が画面内に表示されます。また画面上部の表にはタップしていた間のスマホの左右(LR)や前後(FB)の動きが数値として表示されます(ちなみに DIR は向いている方向角で、今回の軌跡描画では使っていません):
IMG_0431


これだけのアプリですが、全て1つの HTML だけで実現しています。スマホの動きは JavaScript でジャイロセンサーにアクセスしてその値を取得し、軌跡の描画には HTML5 の Canvas を利用しています。また HTML 自体では Canvas は非表示としており、画面に表示されているのはこの Canvas を描き終えたタイミングで動的に画像データを生成して、その画像を <img> タグで動的に表示しています。

スマホのジャイロデータを JavaScript だけで取得するとか、Canvas データから画像データを生成するとか、それなりに凝った処理を行っているこの HTML を Github で公開しました:
https://github.com/dotnsf/SPen/


MIT ライセンスで公開しています。勉強目的なり、取得データを使って別の処理を行うよう改造するなり、いろんな応用はできると思うので、よかったら遊んでみてください。

このページのトップヘ