Yo! Chrome App
なんとなく検索してたらYeomanにChromeアプリ用のジェネレーターがあったので、試しに作成中のIRCクライアントを乗っけてみたらなかなかいい感じです。
特にソースコードを編集したらChromeアプリが自動リロードされるのがいい。これまではいちいち chrome://extensions で該当のアプリをリロードする必要があって、面倒くさくて仕方なかったので。
まぁYeoman使ってディレクトリ構成を生成した後は、既存のコードを持ってきてコピペしただけなので、ジェネレーターとしてはまともに使ってないけど、それもまたよし。
https://github.com/yeoman/generator-chromeapp
正直近頃のJavaScript開発に全然ついていってなくて、Yeoman使うの自体始めてだったわけだけど、Yeomanにはbowerだのgruntだのmochaだのいろいろ付いて来て勉強なるなる。フルスタックのフレームワークはこうやって最新のツールやら環境を強制してくれるのがなんだかんだ言ってありがたいよなと、久しぶりにRails使いはじめの頃を思い出したりしましたとさ。
Chromeウェブアプリで作ったIRCクライアントを公開してみた
以前、ChromeウェブアプリでIRCクライアント作ってみた後、当時はChromeアプリはウェブストアに公開できなかったのでそのまま放置してたんですが、気が付くと公開できるようになってたのと、いろいろ立て込んでたのが落ち着いたので、ちょっと修正してウェブストア上で公開してみました。
https://chrome.google.com/webstore/detail/chrocha-irc-client-for-ch/eoajgjnmcikichkdpkjkpgoejkglinbd
この流れ?なのでAngular製か、はたまたDart製か、とか思う人もいるかもしれませんが
「質問だ・・・AngularJSで作るか?Dartで作るか?あててみな」 「ひ・・・ひと思いにAngularJSで・・・やってくれ」 「NO!NO!NO!NO!NO!」 「D・・・Dart?」 「NO!NO!NO!NO!NO!」 「り・・・りょうほーですかあああ〜」 「NO!NO!NO!NO!NO!」 「もしかしてPure JavaScriptですかーッ!?」 「YES!YES!YES!"OH MY GOD"」
まぁそんなわけで今のところChromeアプリであること以外とりたてて特徴もなし。でも作り始めた時点ではAngularJSはCSPに引っかかって使えなかったし、Dartも1.0前で仕様がブレブレで選択肢になかったんスよ。
多分まだまだいろいろ不具合もあるんだろうけど、とりあえず普通に使えてるので、よろしければみなさんもどうぞ。
AngularJS開発者のためのAngularDart
AngularDartはすごい勢いで進化していて、今のAngularDartはこの記事とはずいぶん違っています。元記事の内容は最新版に合わせて書き換えられているので取り急ぎそちらを見たほうがいいです。時間が取れたら訳文も直します。(2014-06-06追記)
みんな大好きangular.jsのdart版がangularチームによって絶賛開発中なんですが、これが単なるJS版の移植ではなくて、ちゃんとdart用に新たに練りなおしてる感じなわけです。
そんなAngularDartをAngularJSと比較した紹介記事がよかったのでちょっと訳してみました。個人的にはDart版の方が黒魔術分が減ってていい感じなんじゃないかと。
記事に書いてあるけど、AngularDartの機能の幾つかはAngularJSに逆輸入されるらしいので、Dartなんか知らんわって人も読んでおいて損はなさそう。
http://victorsavkin.com/post/72452331552/angulardart-for-angularjs-developers-introduction-to
AngularJS開発者のためのAngularDart - 最高のAngularの紹介
AngularDartは高い評価を受けているフレームワークのDartプラットフォームへの移植版で、Angularコアチームによって開発されています。この記事ではこのフレームワークのDart版とJS版を比較します。中でも特に依存性の注入、ディレクティブ、digestingについて詳しく述べます。
依存性の注入
名前に基づいた注入と型に基づいた注入
AngularDartはDartのオプショナルな型システムを興味深い形で利用しています。つまり、型情報をインジェクタを設定するために利用します。言い換えると、注入は名前ではなく型に基づいて実行されます。
//JS: // ここで、名前は重要です。本番環境でminifyされるので // このように配列を使用しなければいけません。 m.factory("usersRepository", ["$http", function($http){ return { all: function(){/* ... */} } }]);
//DART: class UsersRepository { // ここでは型だけが重要です。変数名はDIと無関係です。 UsersRepository(Http h){/*...*/} all(){/* ... */} }
注入可能なオブジェクトの登録
AngularJSでは注入可能なオブジェクトは、filter, directive, controller, value, constant, service, factory, providerなどのメソッドを使用することでAngular DIシステムに登録できます。
メソッド | 目的 |
---|---|
filter | フィルターの登録 |
directive | ディレクティブの登録 |
controller | コントローラーの登録 |
value, constant | 設定オブジェクトの登録 |
service, factory, provider | サービスの登録 |
見て分かる通り、注入可能なオブジェクトを登録する方法がいくつもあることが開発者の混乱の種になってきました。ひとつには、filter、directive、controller関数はすべて型の異なるオブジェクトのために利用されるため、交換可能にはできなかったということがあります。しかし一方で、service、factory、provider関数は(providerが最も汎用的ですが)すべてサービスオブジェクトを登録するものです。
AngularDartはこれとは全く異なるアプローチを取りました。オブジェクトの型とDIシステムにどのように登録されるかを分離したのです。
value、type、factoryを使用すると任意のオブジェクトを登録できます。
メソッド | 目的 |
---|---|
value, type, factory | あらゆるオブジェクトの登録 |
これは次のように利用されます。
//DART: // 渡されたオブジェクトは注入に利用できます。 value(UsersRepositoryConfig, new UsersRepositoryConfig()); // AngularDartはすべての依存性を解決して // UsersRepositoryをインスタンス化します。 type(UsersRepository); // AngularDartはfactory関数を呼び出します。 // 渡されたインジェクタを使用して依存性を解決する必要があり、 // その後、UsersRepositoryをインスタンス化します。 factory(UsersRepository, (Injector inj) => new UsersRepository(inj.get(Http)));
これらの関数を使用するとどのようなオブジェクトでも登録できるため、APIが非常に単純になっています。
任意のクラスをサービスとして利用できます。単にAugular DIシステムを利用して登録するだけで構いません。必要になった時にAngularがそのクラスをインスタンス化して、コンストラクタ引数を通してすべての依存性を注入します。
しかし、他の型のオブジェクトについては追加の情報を与える必要があります。それにはアノテーションを使用します。
//DART: @NgController( selector: '[users-ctrl]', publishAs: 'ctrl' ) class UsersCtrl { UsersCtrl(UsersRepository repo); }
同様に、filterやcomponent、directiveを定義するための特別なアノテーションがあります。
AngularDartでは注入可能オブジェクトの型と、どのようにそれらがDIシステムに登録されるかということは、直交した概念です。
モジュールの作成とアプリケーションの開始
以下はAngularJSでアプリケーションを作成する標準的な方法です。
//JS: var m = angular.module("users", ['common.errors']); m.service("usersRepository", UsersRepository); angular.bootstrap(document, ["users"]);
これはAngularDartには次のように対応付けられます。
//DART: final users = new Module() ..type(UsersRepository) ..install(new CommonErrors()); ngBootstrap(module: users);
同様のことをModuleを継承することによっても実現できます。
//DART: class Users extends Module { Users(){ type(UsersRepository); install(new CommonErrors()) } } ngBootstrap(module: new Users());
実行中のプラットフォームやアプリケーションに応じて異なるコンポーネントと紐付けたいような場合には後者の方法がより適切でしょう。
注入可能オブジェクトの設定
AngularJSは注入可能オブジェクトを設定するためのオプションをいくつか提供しています。もっとも単純な方法はvalueを使用して設定用のオブジェクトを注入することです。
//JS: m.value("usersRepositoryConfig", {login: 'jim', password: 'password'}); m.service("usersRepository", function (usersRepositoryConfig){ //... });
同様なことはDartでも可能です。
//DART: class UsersRepositoryConfig { String login; String password; } class UsersRepository { UsersRepository(UsersRepositoryConfig config){/* ... */} } type(UsersRepository); value(UsersRepositoryConfig, new UsersRepositoryConfig()..login="Jim"..password="password");
ではここでUsersRepositoryはhashではなく引数を2つ取り、それは変更できないものとしましょう。この場合は、factoryを使用することもできます。
//JS: m.value("usersRepositoryConfig", {login: 'jim', password: 'password'}); m.factory("usersRepository", function (usersRepositoryConfig){ return new UsersRepository(usersRepositoryConfig.login, usersRepositoryConfig.password); });
AngularDart版でも、再び非常によく似たコードになります。
//DART: value(UsersRepositoryConfig, new UsersRepositoryConfig()..login="Jim"..password="password"); factory(UsersRepository, (Injector inj){ final c = inj.get(UsersRepositoryConfig); return new UsersRepository(c.login, c.password); });
このような目的ではproviderを定義することを好む人もいるでしょう。
//JS: m.provider("usersRepository", function(){ var configuration; return { setConfiguration: function(config){ configuration = config; }, $get: function($modal){ return function(){ return new UsersRepository(configuration); } } }; });
setConfigurationメソッドはアプリケーションの設定フェーズ中に呼び出されなければいけません。
//JS: m.config( function(usersRepositoryProvider){ usersRepositoryProvider.setConfiguration({login: 'Jim', password: 'password'}); } );
AngularDartにはproviderも明示的な設定フェーズもないため、上記の例を直接Dartに置き換えることはできません。思いつく最も近い例は次のようなものです。
//DART: final users = new Module()..type(UsersRepositoryConfig) ..type(UsersRepository); Injector inj = ngBootstrap(module: users); inj.get(UsersRepositoryConfig)..login = "jim" ..password = "password";
directive、controller、component
それでは話題を変えて、フレームワークのもう一つの柱、 directiveについて話しましょう。
AngularJSのdirectiveは非常に強力で基本的には使いやすいものですが、新しいdirectiveを定義することが混乱の元になる場合があります。思うに、Augularチームもこれは認識していて、それがこのフレームワークのDart版ではAPIが大きく異る理由なのでしょう。
AngularJSにはUIのインタラクションをまとめるためのオブジェクトが2つあります:
- directiveはDOMとのすべてのやりとりをカプセル化します。これは宣言的で、htmlを拡張する手段とみなすことができます。
- controllerは命令的です。DOMについては意識せず、アプリケーションのロジックを扱うことができます。
AngularJSではこれら2種類のオブジェクトが区別されています。それらを登録するために異なるヘルパーが利用され、全く異なるAPIを使用して定義されます。
AngularDartに導入された1つ目の重大な変更は、これら2つのオブジェクトがより似通ったものになったということです。controllerは基本的に要素に新しいスコープを作成するdirectiveです。
2つ目の変更は新しいオブジェクト型、componentです。AngularDartでは、directiveはほとんどの場合で既存のDOM要素を拡張するために利用されます。新しい独自要素を定義するには、componentを使用します。
それでは例をいくつか見てみましょう。
directive
vs-match directiveはinput要素に適用できます。要素の変更を監視して、値が特定のパターンに一致した時にdirectiveが要素にmatchクラスを追加します。
これは次のように使用します:
<input type="text" vs-match="^\d\d$">
説明したdirectiveの非常に簡単なAngularJSでの実装は次のようになります:
//JS: directive("vsMatch", function(){ return { restrict: 'A', scope: {pattern: '@vsMatch'}, link: function(scope, element){ var exp = new RegExp(scope.pattern); element.on("keyup", function(){ exp.test(element.val()) ? element.addClass('match') : element.removeClass('match'); }); } }; });
ではこれをAngularDart版と比較してみましょう。
//DART: @NgDirective(selector: '[vs-match]') class Match implements NgAttachAware{ @NgAttr("vs-match") String pattern; Element el; Match(this.el); attach(){ final exp = new RegExp(pattern); el.onKeyUp.listen((_) => exp.hasMatch(el.value) ? el.classes.add("match") : el.classes.remove("match")); } }
順に説明します:
- NgDirectiveはAngularにこのクラスがdirectiveであると伝えます。
- selector属性はこのdirectiveがいつ活性化されるかを定義します。今回の例では、それは要素がvs-match属性を持つ時です。
- 任意のserviceをdirectiveに注入できるだけでなく、directiveが適用される要素も注入できます。これがMatch(this.el)の行っていることです。
- bindingはAngularJSと同様にマップを渡すことで設定できます。しかしこれはアノテーションを使用しても実現でき、個人的にはそちらの方がずっと読みやすく理解もしやすいと感じます。
- directiveのコンストラクタが実行された時には、パターンの値はまだ設定されていません。これはNgAttachAwareインターフェースを実装することで解決します。NgAttachAwareインターフェースにはattachメソッドが宣言されていて、新しいdigestが発生した時に実行されます。この時点ではすべての属性のマッピングが完了しているので、安全に正規表現を構築できます。
- 最終的に、リンクされる関数もコンパイルされる関数もありません。
component
次に見ていくことになるcomponentは、コンテンツの表示非表示を切り替えます。これは次のようにして使用します:
<toggle button="Toggle"> <p>Inside</p> </toggle>
このcomponentのAngularJSでの実装は次のようになります:
//JS: directive("toggle", function(){ return { restrict: 'E', replace: true, transclude: true, scope: {button: '@'}, template: "<div><button ng-click='toggle()'>{{button}}</button><div ng-transclude ng-if='showContent'/></div>", controller: function($scope){ $scope.showContent = false; $scope.toggle = function(){ $scope.showContent = !$scope.showContent ; }; } } })
ではこれをDart版と比較してみましょう:
//DART: @NgComponent( selector: "toggle", publishAs: 't', template: "<button ng-click='t.toggle()'>{{t.button}}</button><content ng-if='t.showContent'/>" ) class Toggle { @NgAttr("button") String button; bool showContent = false; toggle() => showContent = !showContent; }
- NgComponentはAngularにこのクラスがcomponentであることを伝えます。
- publishAsはテンプレートの中でトグルするオブジェクトにアクセスするために利用できる名前です。この名前は挿入されるコンテンツではなく、コンポーネントのテンプレート内でだけ利用できるということに注意してください。
- templateは当然この独自要素をどのように描画するかを定義します。
JS版とDart版は同じように見えますが、その内側には重大な違いがあります。
AngularDartのcomponentはテンプレートを描画するためにshadow DOMを使用します。
AngularJS:
AngularDart:
Shadow DOMを使用するとDOMとCSSをカプセル化でき、再利用可能なコンポーネントを構築するのに非常に役立ちます。APIについてもWeb componentsの仕様に沿うよう変更が加えられています(例えばng-transcludeはcontentと置き換えられました)。
AngularDartのcomponentはそのテンプレートを保持するためにtemplate要素を使用します。
これによりng-srcのようなハックが不要になりました。
まとめると、directiveはDOM要素を拡張するために使用されます。componentはWeb Componentsの軽量版で、独自要素を作成するために利用されます。
controller
以下の例はAngularJSで実装された非常に単純なcontrollerです。
//JS: <div ng-controller="CompareCtrl as ctrl"> First <input type="text" ng-model="ctrl.firstValue"> Second <input type="text" ng-model="ctrl.secondValue"> {{ctrl.valuesAreEqual()}} </div>
controller("CompareCtrl", function(){ this.firstValue = ""; this.secondValue = ""; this.valuesAreEqual = function(){ return this.firstValue == this.secondValue; }; });
Dart版はこれと大きく異なります。
//DART: <div compare-ctrl> First <input type="text" ng-model="ctrl.firstValue"> Second <input type="text" ng-model="ctrl.secondValue"> {{ctrl.valuesAreEqual}} </div>
@NgController( selector: "[compare-ctrl]", publishAs: 'ctrl' ) class CompareCtlr { String firstValue = ""; String secondValue = ""; get valuesAreEqual => firstValue == secondValue; }
先に述べたように、contollerは基本的にはdirectiveで、要素上に新しいスコープを作成します。新しいdirectiveを定義するときに利用できるオプションは全てcontrollerを定義するときにも利用できます。とはいえ、フレームワークによって規定されているわけではありませんが、controller内であらゆるDOM操作ロジックを書くことを避けるのはいい考えです。
filter
最後に、どのようにしてfilterを定義できるか見て行きましょう。
//JS: filter("isBlank", function(){ return function(value){ return value.length == 0; }; });
Dart版では:
//DART: @NgFilter(name: 'isBlank') class IsBlank { call(value) => value.isEmpty; }
zoneと$scope.$apply
熟練したAngular開発者は次の機能をありがたく思うでしょう。この記事で紹介する内容の中で、最も衝撃的だと感じる人もいるかもしれません:
サードパーティ製のcomponentと結合するときに$scope.$applyを呼ぶ必要はありません。
次の例を使用して上記についてを説明しましょう。
<div ng-controller="CountCtrl as ctrl"> {{ctrl.count}} </div>
CountCtrlは単にcount変数をインクリメントするだけのcontrollerです。
//JS: controller("CountCtrl", function(){ var c = this; this.count = 1; setInterval(function(){ c.count ++; }, 1000); })
熟練したAngularJS開発者であればこのコードは壊れているとすぐに気づくでしょう。このままではAngularはコールバック内でcount変数が変更されたことを知ることができません。この問題を修正するには、次のように$scope.$applyで囲まなければいけません。
//JS: controller("CountCtrl", function($scope){ var c = this; this.count = 1; setInterval(function(){ $scope.$apply(function(){ c.count ++; }); }, 1000); })
これはAngularJSの原理的な制限です。あなた自身がAngularに変更をチェックするよう明示的に示さなければいけません。フレームワークはfutureライブラリをバンドルしたり、$interval serviceを提供したりして、これの作業が必要な場所を最小化しようと努力しています。しかし、他のfutureライブラリを使い始めたり、サードパーティ製の非同期componentと結合し始めると、やはり$scope.$applyを使わなければいけません。
それでは、これをDart版と比べてみましょう。
//DART: <div count-ctrl> {{ctrl.count}} </div>
@NgController( selector: "[count-ctrl]", publishAs: 'ctrl' ) class CountCtrl { num count = 0; CountCtrl(){ new Timer.periodic(new Duration(seconds: 1), (_) => count++); } }
Dart版には$applyはありませんがちゃんと動作します。TimerはAngularについては何も知りません。すばらしいではないですか!これがどのように動作しているか理解するためには、Zoneという概念について学ぶ必要があります。
Dartドキュメント: Zoneは動的な範囲の非同期版を表しています。非同期コールバックはそれがキューに入れられたzoneの中で実行されます。例えば、future.thenのコールバックはthenが実行されたのと同じzoneで実行されます。
Zoneはイベントベースの環境でのスレッドローカル変数のようなものだと考えられるかもしれません。環境は常にcurrent zoneを持っています。そして全ての非同期操作の全てのコールバックはcurrent zoneで動作します。これによってAngularは変更をチェックすべき場所が分かります。
さらに、このメカニズムを使うことで、フレームワークはあなたのプログラムの実行についての情報を集め、例えば、長いスタックトレースを生成することができます。そうすれば例外が発生したときに、multiple VM turnsに渡ってスタックトレースを見ることができます。言うまでもなく、これは開発環境を大幅に改善するでしょう。
まとめ
- AngularDart APIはクラスベースです。
- このフレームワークは名前による注入ではなく型による注入を使用します。
- オブジェクトの型はDIシステムにどのように登録されるかとは無関係です。
- NgControllerやNgDirectiveのようなアノテーションが注入可能なオブジェクトを設定するために使用されます。
- directive、filter、component、controller、services、全てをvalue、type、factoryを使用して登録できます。
- directiveはDOM要素を拡張するために使用されます。
- componentはWeb Componentsの軽量版で、独自要素を作成するために使用されます。
- componentはテンプレートを描画するためにshadow DOMを使用します。
- controllerはそれが適用される要素で新しいスコープを作成するdirectiveです。
- scopeはDartのzoneを使用して自動的に組み込まれます。$scope.$applyは不要になりました。
なにがJSに移植されるのでしょう
MiskoとIgorがDevoxxで話した内容によると、これらの変更の多く、特に以下のものがAngularJSに移植される予定のようです。
- 型に基づいた注入
- アノテーションを使用したオブジェクトの定義
- Shadow DOMの利用
- Zone
より深く学ぶには
この記事があなたの入門の役に立つことを望んでいます。より深く知りたければ以下をチェックしてください:
pry-parsecom作りました
最近お仕事でParseを使っています。parse.comはいわゆるBaaSで、多言語のライブラリやブラウザから使える管理ツールも揃ってて手っ取り早く動くものを公開したいときにはとても便利なんですが*1、運用でデータストアをどうこうしたいときは基本ブラウザ上でデータブラウザを使うしかありません。もしくは、APIを利用して独自のツールを作るか。
まぁデータブラウザもものすごく悪いという感じでもないですが、可能であればコマンドラインで操作したい。rails consoleみたいに。もしくはpry-rails。
ということで作りました。
http://github.com/technohippy/pry-parsecom
$ pry-parsecom [1] pry(main)> login-parse Email for parse.com: andyjpn@gmail.com Password for parse.com: logged in [2] pry(main)> show-applications Email for parse.com: andyjpn@gmail.com Password for parse.com: Name | Using ================ FakeApp | FakeApp2 | (cached at: 2013-11-18 13:59:48 +0900)
起動してログインするとアプリの一覧が見られます。
[3] pry(main)> use-application FakeApp The current app is FakeApp. [4] pry(main)> show-classes Name | Class ===================== Comment | Comment Post | Post _User | Parse::User (cached at: 2013-11-18 13:59:48 +0900) [5] pry(main)> show-schema Post Name | Type ============================ author | pointer<_User> body | string comments | relation<Comment> (cached at: 2013-11-18 13:59:48 +0900)
使用するアプリを選択すればクラスの一覧や、クラスのスキーマも確認できます。
[6] pry(main)> posts = Post.find :all => [--- __type: Post author: __type: Pointer className: _User objectId: ZybBXQEIjI body: Hello comments: <Ralations> ...snip... [7] pry(main)> posts[0].body = 'World' => "World" [8] pry(main)> posts[0].save
use-applicationコマンド実行時にParseのクラスに対応するRubyクラスを定義するので、手間いらずで検索や新規作成・更新・削除いろいろできます。Parseオブジェクトの操作は、合わせて自作したparsecomライブラリを使ってます。
http://github.com/technohippy/parsecom
game_score = GameScore.find_by_id 'Ed1nuqPvcm' game_score.score = 73453 game_score.save
ParseのREST APIを使ってできることは、特殊なのを除いてだいたいできるはず。
game_scores = GameScore.find :where => proc { subquery = subquery_for :Team subquery.where {column(:winPct).gt(0.5)} subquery.key = 'city' column(:hometown).select(subquery) }
使いやすいかどうかは別にして、サブクエリも発行できるライブラリは他にあんまりなさそう。
seans_score = GameScore.new 'score' => 1337, 'playerName' => 'Sean Plott' zerocools_score = GameScore.new 'score' => 1338, 'playerName' => 'ZeroCool' Parse.batch do seans_score.save zerocools_score.save end
バッチリクエストも割と簡単。
[9] pry(main)> logout-parse logged out [10] pry(main)> exit
APIキーをローカルに保存するので、気になる人は終了前にログアウトしておいた方がいいかもしれません。
$ gem install pry-parsecom
で使えるようになります。軽い用途にはわりと便利に使えると思うのでよろしければどうぞ。
http://github.com/technohippy/pry-parsecom
*1:実際にプロダクションで使うととてもとても面倒くさいですが、それはまた別の話
「開発者のためのChromeガイドブック」という本を書きました(共著)
開発者のためのChromeガイドブック (Google Expert Series)
- 作者: 吉川徹,あんどうやすし,田中洋一郎,小松健作
- 出版社/メーカー: インプレス
- 発売日: 2013/07/05
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (10件) を見る
内容としては、
ということで、まさに「開発者のための」Chrome本です。
特にDeveloper Toolsの章に関しては、ウェブ系の開発者であれば手元に置いておいて損はないんじゃないでしょうか。その辺、ちゃんと紙にまとまっってる情報はあまりないはずなので。
前著は大変不幸な結果に終わってしまったので今回はそれなりに息の長い本になってくれるといいなぁ・・・。
レンズレスカメラ
twitter見てたらこんな記事が流れてきまして。
http://www.gizmodo.jp/2013/06/post_12461.html
「ベル研究所、レンズのない1画素カメラ実現。ピンぼけもない」
ランダムなフィルタを通して一画素カメラでたくさんデータを取って、最後に合わせてやるとちゃんとした画像ができ上がるというお話らしい。
なんとなく感覚的には合点が行くけど、実際に試してみるとどんなもんなんじゃろと思ったのでやってみました。これでいいのかどうかよく分からないけど。
# ...前略... shot_image = Square.new real_image = Image.new # 10万回試行 100000.times do # ランダムフィルタ生成 filter = Filter.new # valはフィルタがオープンになっている部分の明度の平均値 # 元記事の言うところの「一画素カメラ」の値 val = filter.filter real_image filter.filtered_each do |x, y| # フィルタがオープンになっていた部分の値をvalだけ増やす # 本物はこの辺できっといろいろ工夫してる shot_image[x, y] += val end end # 各画素を8ビット長に正規化 shot_image.normalize! 0..255 # 画像生成 shot_image.to_png 'shot.png'
で、結果。
ほほう。なかなかうまくいくもんですね。ちなみに試行回数を変えるとこんな感じ。今回の単に積算するだけのやり方だとたかが10×10の画像でも10万回くらいは繰り返さないとダメみたい。
しかし、結局何度も撮るなら一画素カメラで例えば左上から右下に順に走査して行った方が楽な気がするけど、そういう話でもないんですかねー。
ソースコード全文
require 'chunky_png' class Square attr_reader :width, :height def initialize(x=10, y=10) @width = x @height = y @values = Array.new y do Array.new x, 0 end end def min @values.flatten.sort.first end def max @values.flatten.sort.last end def normalize_pixel(x, y, my_min, my_max, min_value, max_value) ((self[x, y] - my_min) / (my_max - my_min) * max_value).floor + min_value end def normalize! range my_min = min my_max = max min_value = range.begin max_value = range.end each do |x, y| self[x, y] = normalize_pixel x, y, my_min, my_max, min_value, max_value end self end def [](x, y) @values[y][x] end def []=(x, y, v) @values[y][x] = v end def each(&block) @height.times do |y| @width.times do |x| block.call x, y end end end def to_png(filename='filename.png') png = ChunkyPNG::Image.new(@width, @height, ChunkyPNG::Color::TRANSPARENT) each do |x, y| color = self[x, y] png[x, y] = ChunkyPNG::Color.rgba(color, color, color, 255) end png.save(filename, :interlace => true) end def to_s @values.to_s end end class Image < Square def initialize(x=10, y=10) super each do |x, y| self[x, y] = x < y ? 255 : 0 end end end class Filter < Square def initialize(x=10, y=10) super each do |x, y| self[x, y] = Random.rand(2) end end def filter(image) count = 0 sum = 0 filtered_each do |x, y| count += 1 sum += image[x, y] end count == 0 ? 0 : sum.to_f / count end def filtered_each(&block) each do |x, y| block.call(x, y) unless self[x, y] == 0 end end end shot_image = Square.new real_image = Image.new 100000.times do filter = Filter.new val = filter.filter real_image filter.filtered_each do |x, y| shot_image[x, y] += val end end shot_image.normalize! 0..255 shot_image.to_png 'shot.png'