JavaScriptパターン本の5章で紹介されているサンドボックスパターンがおもしろかったので試してみました。
これは広く使われている名前空間を使ったモジュールパターンとは違うアプローチになっています。モジュールパターンというのは以下のような書き方で、即時関数が返すオブジェクトをモジュールとして利用するものです。
* Module Pattern
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
// 名前空間を定義 MYAPP = { utilities: { } }; // ... MYAPP.utilities.array = (function() { // 依存関係 var uobj = MYAPP.utilities.object, ulang = MYAPP.utilities.lang, // プライベートプロパティ array_string = "[object Array]", ops = Object.prototype.toString(); // パブリックAPI return { inArray: function(needle, haystack) { // ... }, isArray: function(a) { // ... } }; }()); // 使い方 MYAPP.utilities.array.inArray(['a', 'b', 'c'], 'c'); // => 2 MYAPP.utilities.array.isArray([1, 2, 3]); // => true |
サンドボックスパターンはその名前の通り、モジュールが他のモジュールとそのサンドボックスに影響を与えることなく動作できるような環境を提供します。このパターンは例えば YUI 3.x 以降で利用されていて、以下のように書くことができます。
1 2 3 4 5 |
// node, anim モジュールはコールバック関数の中でのみ利用可能でグローバルを汚さない YUI().use('node', 'anim', function(Y) { Y.one ('div.status').setContent ('ready!'); // node モジュールを利用 new Y.Anim({ node: '#demo', to: {opacity: 0} }); // anim モジュールを利用 }); |
YUIはバージョン3からなかなか便利になってるなぁという印象で、なによりYQLとの連携が大変強力になっているので、そっち方面に興味がある人は使ってみるといいんじゃないでしょうか。
今回はCanvasを使った簡単な画像処理ライブラリのベースをこのサンドボックスパターンを使って書いてみます。画像I/Oを core モジュール、畳み込みフィルタを filter モジュールに実装することにします。使い方としては以下のようにフィルタ適用の処理をコールバック関数内に書くことになります。new
は外に出てきません。
* Sandbox Pattern
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
var context = document.getElementById('Canvas').getContext('2d'); var kernel = [-1, -1, -1, -1, 8, -1, -1, -1, -1]; // 畳み込み用のフィルタカーネル var divisor = 1; // 変換中に使用する除数 var bias = 0; // 加算するバイアス値 // CV: サンドボックス // core, filter の2つのモジュールを利用 CV(['core', 'filter'], function(self) { self.load('./images/sample.jpg', context); // 画像を読み込む var result = self.convolution(kernel, divisor, bias); // 畳み込みフィルタをかける self.draw(result, context); // 結果をCanvasに描く }); // モジュールを個々のパラメータとして指定してもOK CV('core', 'filter', function(self) { // ... }); // ワイルドカード '*' を指定すると利用できる全てのモジュールを読み込む CV('*', function(self) { // ... }); // モジュール指定を省略した場合も同様 CV(function(self) { // ... }); |
CV コンストラクタの中身を見る前に、まずはモジュールの追加方法を見てみます。各モジュールを実装する関数は現在のインスタンスである self
を引数として受け取ります(名前はなんでもいい、YUI 3だとY
という名前に統一されている)。この self
をいちいち付けるのは面倒かもしれませんが、Pythonにおける self
と同じような感じで扱えばいいかと。モジュール内で浮気者の this
を相手にしなくていいというメリットは大きいです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
CV.modules = {}; /*--- core モジュール ---*/ CV.modules.core = function(self) { var p = self.constructor.prototype; // 必要なら prototype にアクセス可能 self.load = function(source, context) { // 画像を読み込む }; self.draw = function(data, context) { // Canvasに描く }; }; /*--- filter モジュール ---*/ CV.modules.filter = function(self) { self.convolution = function(kernel, divisor, bias) { // 畳み込みフィルタをかける }; }; |
最後に CV コンストラクタの中身を見てみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
function CV() { var args = Array.prototype.slice.call(arguments), // 引数を配列に callback = args.pop(), // 最後の引数はコールバック関数 modules = (args[0] && typeof args[0] === "string") ? args : args[0], // モジュール i; // this が CV のインスタンスでなければコンストラクタとして呼ぶ if(!(this instanceof CV)) { return new CV(modules, callback); } // モジュールの指定がない、または "*" で指定された場合はすべてのモジュールを組み込む if(!modules || modules[0] === "*") { modules = []; for(i in CV.modules) { if(CV.modules.hasOwnProperty(i)) { modules.push(i); } } } // 必要なモジュールの初期化 for(i=0; i<modules.length; i++) { CV.modules[modules[i]](this); } // 最後にコールバック関数を呼ぶ callback(this); } |
サンドボックスパターンで書いた画像処理のデモです。Firefox3.6, Safari5.0, Chrome10.0 以降で動作確認しています。Operaだと画素値の境界チェックが不十分らしく結果が壊れてしまいます。ロジックで判定を増やせば直るとは思いますが、相当遅くなりそう。。
HTML5 Image Processing (Convolution Filter)
jsdo.it への投稿: Convolution Filter – jsdo.it – Share JavaScript, HTML5 and CSS
フィルタカーネルは3*3のサイズであれば WebWorkers を使う必要はないですが、カーネルサイズあるいは対象画像が大きい場合は使った方が良さそうです。周波数解析や特徴量抽出などを行う時は必須になると思います。
実はこの畳み込みフィルタのデモですが、2009年にCanvasの勉強のために書いたものをサンドボックスパターンで書き直したものになります (参照: HTML5でConvolutionFilter) 。当時はActionScript3.0に近いインタフェースになるように実装することを心掛けていましたが(クラスの真似事)、今はそういう考え方をしなくなりました。同じECMAScriptを親に持つ2つの言語ですが、今では文化がかなり異なってきているので、これからは”JavaScriptらしさ”を大切にしたいと思います。
また、2, 3年前にDouglas Crockford氏がウチのオフィスに来てJavaSciprtのレクチャーをしてくださったことがあります。セキュリティなども含めてJavaScriptの再教育が必要だ、と。確かにjQueryなどのライブラリを使ってAPIをただペタペタ貼ってるだけだと知識は深まらないですね。僕もJavaScriptを丁寧に勉強しなおす必要があると感じました。
* 関連記事
HTML5でSymmetric Nearest Neighbor