2015年5月3日日曜日

nodejsで9-patch画像を自動生成する「FourSide1px9patcher」



追記15.05.05:使いやすいようにnodeモジュール化した。
junkoro/four-sides-1px-9patcher

9-patchツールの現状 〜 開発意図

前から9-patchを作るのは面倒だし問題だと思っていた。そこで改めて現状を調べてみたが、やはり9-patchを作るには

・Android SDKのdraw9patch
・Android Asset Studio - Simple Nine-patch Generator
・Eclipseプラグイン「Draw 9-patch Tool
・Fireworksなど汎用の画像編集ソフトを使う

という感じ。nodejsで、特にgulpで9-patchを自動化できるツールが無いか探したが、これくらい。

gulp-9-patch

このツールはスケーリングしたり非9-patch化する機能しかなく、画像を9-patch仕様に加工することはできない。


開発要件

やはり独自開発するしかないか…もう我慢ならん!こんな ('A`)マンドクセ-ことイチイチやってられるかっての!

ただ、なぜこのような状況なのかは理解できる。9-patchは柔軟性が高く、画像によってパッチすべき場所は変わる。これが自動化ツールが存在しない理由なのだろう。

しかし、画像を作る側が9-patchしやすい条件を作ればどうだろう?たとえばこんな画像で、単純に4辺の周辺1ピクセルを画面にあわせて伸ばすように9-patchすることを考える。

画像はionicのデフォルトスプラッシュスクリーン用の画像

この条件を満たすケースは意外と多いのではないかと。自分的には自動化するためにデザインを犠牲にする覚悟である(^^);

このようなことをしてくれるCLIツールを、後々ionicなどgulpやgruntをビルドシステムとして使っているようなものに組み込むことを考えて、nodejsで開発する。


nodejsでの画像処理をどうする? 〜 技術調査

調べると、みんなよくimagemagickとかを使ってやっているようだが、できればコマンドラインツールに依存するような形にしたくない。

というわけで、前から名前だけは知っていたけれど、よく調べたことがなかったこれを使ってみよう。

EaselJS | A JavaScript library that makes working with the HTML5 Canvas element easy.

EaselJSはHTMLのCanvasをラップしてFlashライクに扱えるようにするライブラリだ。

CreateJSというFlashコンテンツをHTML5に変換するのをサポートするランタイムライブラリのひとつ。当然Adobeが関係してる。node版もある。

CreateJS/EaselJS-NodeJS

これで描画関連は問題無いだろう。画像ファイル化はどうか?

Automattic/node-canvas

node-canvasはtoBuffer()というメソッドを持っていて余裕でPNG化できるらしい。このBufferをfsモジュールでファイルに書き出せば終了だろう。楽ぅ〜(*^_^*)

しかもPDF化とかSVG化もできるらしい。なんかいつの間にか色々やりたい放題な状況になっていたんだね\(^o^)/

そもそもEaselJS-NodeJSがこのnode-canvasに依存しているので、かなり成熟しているライブラリなのだろうと思う。とりあえずこれで。


開発準備

$ mkdir FourSide1px9patcher
$ cd FourSide1px9patcher

node-canvasをインストールするには、結構ネイティブ側の準備が必要らしい。さすがにネイティブと無関係とはいかないか(^^);

Installation OSX · Automattic/node-canvas Wiki

HomebrewとかMacPortとかでインストールした方がいいだろう。自分はbrew派なのでbrewで。

MacOSX - パッケージ管理システム Homebrew - Qiita

$ brew install pkg-config
$ brew install pixman
$ brew install cairo
$ PKG_CONFIG_PATH=/opt/X11/lib/pkgconfig npm install canvas


開発

後はエディターでコーディング。

コーディング中に気づいたのだけど、EaselJSは必要無かった(^^); 描画の部分はCanvasだけで事足りてしまった。

やっていることは、元画像に透明の枠をつけ、左上、左下、右上をこのように加工して9-patchする作業。

左上

左下

右上

//モジュール読み込み
var fs = require('fs');
var Canvas = require('canvas');
var Image = Canvas.Image;


/**
 * ピクセル描画
 */
function drawPixel(context, x, y, color) {
  context.save();
  context.translate(x, y);
  context.fillStyle = color;
  context.fillRect(0, 0, 1, 1);
  context.restore();
}


/**
 * 4辺の周辺1ピクセルを9-patch
 */
function fourSide1px9patcher(src, dst) {

  //イメージ読み込み
  var img = new Image;
  img.src = src;

  //9-patchは元画像の周りに1pxの透明の枠を付ける
  var cv = new Canvas(img.width + 2, img.height + 2);

  //コンテキスト取得
  var ctx = cv.getContext('2d');

  //イメージを座標(1, 1)に描画
  ctx.drawImage(img, 1, 1);

  //9-patchの線は黒
  var color = 'rgba(0, 0, 0, 1.0)';

  //ドット描画 - 左上-TOP
  drawPixel(ctx, 1, 0, color);

  //ドット描画 - 左上-LEFT
  drawPixel(ctx, 0, 1, color);

  //ドット描画 - 左下-LEFT
  drawPixel(ctx, 0, cv.height - 2, color);

  //ドット描画 - 右上-TOP
  drawPixel(ctx, cv.width - 2, 0, color);

  //ファイル書き出し
  fs.writeFile(dst, cv.toBuffer());

}


//実行
fourSide1px9patcher('src.png', 'dst.9.png');


実行

$ node FourSide1px9patcher.js

生成されたdst.9.pngをdraw9patchで開くと、意図通りに9-patchされていることを確認できた。

右側で中央のionicロゴが歪んでいない

draw9patchは画像が正しく9-patch加工されていても、拡張子が「.9.png」になっていないと9-patch画像として認識しないので注意する。

次はionicプロジェクトのリソース構造に合わせて9-patchするラッパーを書こう。

追記15.05.05:書いた。
琴線探査: ionicプロジェクトのsplash.pngを自動でリサイズしながら9-patchする「make-9patched-splash-ionic」