
canvas を苛めていたら気づいたことがあったので書きます。(この記事は2011年5月現在の情報です。あなたがこの記事を読んでいる時点で、より新しくて良い方法が無いか確認して下さい)
canvas には、図形を描画する機能だけでなく、描かれている内容を読み取る機能があります。getImageData を使うと、canvas の内容をピクセル単位で読み取って画像処理をかけたりできます。また、toDataURL を使うと canvas の内容を Data URI として出力でき、サーバに送信したりできます。
しかし、この機能にはリスクがあります。例えば、悪意のあるページを開いただけで、社内SNSにしか公開していないあなたの顔写真を canvas 経由で抜き取られるかもしれません。そういう事が起きないように、他のサイトから読み込んだ画像が canvas に描画されている場合(以下、canvas が汚染されている と表現)は getImageData や toDataURL が使用禁止になり、呼び出すと例外が出ます。
ここまではよく知られている話。
しかし、同じサイトの画像であっても SVG は問答無用で「汚染」扱いになります。スクリプト内で完結している Data URI ですらダメ。 とにかく、canvas に SVG を描画した時点で読み取り禁止(図1)
理由はFrom SVG to Canvas and Backという文書に書いてあり、SVG は PNG や JPEG などと違い、外部のファイルを参照できるので、SVG ファイル自体が同じサイト内にあっても安心できないということ。
これでは、アプリケーションが getImageData や toDataURL を使っている場合は SVG をアセットとして使えないという事になってしまい、デザイナの顰蹙を買う事になります。
余談: Firefox はローカルディスク上で同じディレクトリ内にあるPNG/JPEGファイルも「汚染」扱いするようですが、一方で XmlHttpRequest は許可されており(mala さんの記事参照)、しかも XmlHttpRequest でバイナリファイルを読み込むテクニックが存在するので、canvas を封じても意味が無いように思えます(試してみたところ、XmlHttpRequest → drawImage → toDataURL でPNGが読めました)
先程のFrom SVG to Canvas and Back では、SVGElement にも toDataURL を実装し、さらにこれに外部を参照する要素を取り除いて「クリーンな」SVG をエクスポートする機能を付けて、このクリーンな SVG へのアクセスは許可しよう、と提案していますが、これは将来の話。今すぐ解決したい場合は canvg などの JS+Canvas で SVG をレンダリングするライブラリを使うことを薦めています。
実際に試してみたところ、確かに canvas を汚染せずに SVG をレンダリングできました(以下はIE9)
iPhone でも動きます

みたいなことを先週、ぴろたんと話していてですね。実際どうなるのか試してみました。こうなります。

赤い点線の枠が WebGL を有効にした canvas で、中の青い三角形は WebGL で描画されています。後ろの写真と「GL Overlay Test」という文字は通常の HTML です。
一見ちゃんと描画されているように見えますが、左側の半透明の三角形が、加算合成したような描画結果になっており、少し変です。本来であればもう少し暗い色で描画されている筈です。これは Chrome/Firefox あるいは Mac/Windows を問わず同じなので、今のところWebGL の描画結果を半透明で合成すると確実におかしくなるので、やめたほうがいいと言えます。が、逆に言えば、完全に不透明か完全に透明な部分は正しく合成されるので、限定的には使えるとも言えます。(描画結果にアンチエイリアスがかかっている場合はエッジがおかしくなるので、そこを許容できるのであれば)
→ WebOS Goodies さんの記事 - WebGL の描画結果を HTML に正しく合成する方法 - WebOS Goodies で、解説されているレンダーステートの設定をすると、半透明の合成を正しく行えます。
というわけで、こんなデモを作ってみました。
img で写真を並べただけのページですが、WebGL が有効な状態で写真をクリックすると、iPhone のマップアプリみたいに、ぺろんと捲れて詳細情報が出てきます。Flash と違ってブラウザ標準の右クリックメニューやテキスト選択が生きている点が HTML ベースならではの特徴でしょうか。ただ、そこを考えると写真のタイトルを裏側のテクスチャに焼き込んでしまったのはあまり良くないですが。(テクスチャに焼き込む代わりに、CSS Transform を使って合成して意地でも選択できるようにする、という手はあります)
ユーザーインターフェースのアニメーションは単なる賑やかしではない、ということは iPhone が証明してくれましたし、ブラウザの上で OpenGL が使えることの意味はこういう方向じゃないかなー、と思いました。 ゲームのデモとかもあるけど、ゲームはゲーム機でやればいいんだし……
最近、Webページをクローリングして画像を引っこ抜いたりしているのですが、そこで困るのが、一見すると一枚だけど複数のファイルに分割されている画像。
↓こういうやつ
多くの人が遅いアナログ回線で Web を見ていた頃は、体感速度を上げる効果があった、らしい。しかし、機械的に画像を収集するような向きにはえらい迷惑です。
そこで、クローラがブラウザ上でのレンダリング結果を見て、これらの画像は隣接して表示されているぞ、といった判断をできないかなーとぴろたんに尋ねてみたところ
piro_or> っ [MozRepl]
と、5秒で答えが返ってきました。MozRepl というのを使うといいらしいです。というわけで、MozRepl を使って Ruby のプログラムから Firefox のレンダリング情報を取得してみました。
mala さんによる MozRepl の紹介( http://la.ma.la/blog/diary_200609280045.htm )を見ながら、以下のような方針でスクリプトを書く
コードは以下
ページのロード完了を検出する方法を思いつかなかったので、とりあえず5秒待っています。ちょっとダサいですが、とにかく実行すると、以下のような JSON で画像の位置と大きさが出力されます。
[ {"tagname": "img", "url": "http://www.jewelpet.jp/common/images/logo_jewelpet.jpg", "x":158, "y":0, "w":239, "h":121}, {"tagname": "img", "url": "http://www.jewelpet.jp/images/title_top.jpg", "x":363, "y":121, "w":745, "h":293}, {"tagname": "img", "url": "http://www.jewelpet.jp/images/txt_top.jpg", "x":363, "y":414, "w":745, "h":241}, {"tagname": "img", "url": "http://www.jewelpet.jp/common/images/nav_top_o.jpg", "x":173, "y":121, "w":190, "h":82}, {"tagname": "img", "url": "http://www.jewelpet.jp/common/images/nav_about_o.jpg", "x":173, "y":203, "w":190, "h":79}, {"tagname": "img", "url": "http://www.jewelpet.jp/common/images/nav_characters_o.jpg", "x":173, "y":282, "w":190, "h":79}, {"tagname": "img", "url": "http://www.jewelpet.jp/common/images/nav_products_o.jpg", "x":173, "y":361, "w":190, "h":79}, {"tagname": "img", "url": "http://www.jewelpet.jp/common/images/nav_contest_o.jpg", "x":173, "y":440, "w":190, "h":82}, {"tagname": "img", "url": "http://www.jewelpet.jp/images/btn_jlol_o.jpg", "x":173, "y":522, "w":190, "h":128}, {"tagname": "img", "url": "http://www.jewelpet.jp/images/top_movie.jpg", "x":158, "y":655, "w":324, "h":62}, {"tagname": "img", "url": "http://www.jewelpet.jp/images/movie_about_o.jpg", "x":287, "y":717, "w":121, "h":58}, {"tagname": "img", "url": "http://www.jewelpet.jp/images/top_whats-new.jpg", "x":482, "y":655, "w":626, "h":62}, {"tagname": "img", "url": "http://www.jewelpet.jp/images/bnr_jewelpet-sega.jpg", "x":188, "y":866, "w":173, "h":50}, {"tagname": "img", "url": "http://www.jewelpet.jp/images/bnr_segaprize.gif", "x":365, "y":876, "w":115, "h":40}, {"tagname": "img", "url": "http://www.jewelpet.jp/common/images/logo_segatoys.gif", "x":328, "y":1021, "w":82, "h":44}, {"tagname": "img", "url": "http://www.jewelpet.jp/common/images/logo_sanrio.gif", "x":415, "y":1021, "w":92, "h":26} ]
図にプロットするとこんな感じ(図を作るスクリプトはこちら)
ブラウザのキャプチャと重ねてみると、各画像のレンダリング位置を正確に取れていることが分かります
これでどの画像が隣接しているか分かるので、それらを画像処理にかけて境界部分のピクセルを比較したりすれば、見た目上繋がっているか正確に判断できるでしょう。
視覚に頼ったWebページを作るな、という理想はともかく、現実問題として、レンダリング結果があってこそ出来る事というのはいろいろあると思います。本文を抽出したり、ユーザーインターフェイスの研究に使ったり、あとは……クローラ除けされているメールアドレスを引っこ抜いたりもできますが、そんな悪い人はここを見ていないと信じています。
おわり。
余談ですが、Ruby 1.9.1 の telnet モジュールは派手にバグっています。どれくらい派手かというと、モジュールが丸ごと動かなくなっています。MozRepl を Ruby から叩こうとして変なエラーが出た場合は、Ruby をアップデートしてみて下さい。
今までさんざん canvas やら SVG やらで遊んでおいてなんですが、そういった華やかな部分だけではなく、AJAX の要である通信の技術も忘れてはいけません。
まだ先の話ですが、Cross-Site XMLHttpRequest や、Web Sockets がメジャーなブラウザに実装されれば、JSONP や Comet 等の小手先のハックで無理矢理実現しつつも根本的には Google Suggest の頃から進歩していなかった、という状況が変わりつつあります。
しかしながら、上に挙げた方法は、ネットワークに接続されているマシン同士でしか通信できないという欠点があります。ネットワークに繋がっていない機器と Javascript で通信したい、という需要が年に一度ぐらいはあるかもしれません。
そこで今日は、WiFi や Ethernet が無くても通信を行える方法をご紹介します。
はい、出オチでした……
ネタ元は↑これです。個人でもクレジットカードでの支払いを受けることができる Square というサービスがあるのですが、カードの読み取り機と iPhone 等との通信をフォーンジャック経由で行っているわけですね。原理は、昔懐かしい8ビット機のデータレコーダとか、アナログモデムのピーガガ音と(多分)同じです。これを Javascript で真似しようと。
非常に雑な言い方をすると「Javascript で実装されたモデム」ですが、JS側は送るだけで受信してないので、正確に言うとモデムの「モ」だけ実装したということになります。尚、受信側のiPhoneアプリはネイティブコードです。
最初は Flash で実装してましたが、せっかくなので pure Javascript で…… ということで悪ノリしてこれができました。WAV ファイルを生成する部分は id:moriyoshi さんの Adobe MAX のデモをベースにしています。
↓受信用のプログラムが無いと面白くないですが、一応ここで聞けます。
http://gyu.que.jp/private/jsfsk/ (Firefox 3.5, Opera 10.51, Safari 4.0.4 専用)
QRコードを使った方が速くて確実です。
情報・通信におけるディジタル信号処理 (ディジタル信号処理シリーズ)
昨年稼動開始した Twitter bot 「ねる」をバージョンアップし、ソースコードを公開しました。 基本的に、表から見える機能はほとんど変えず、内部の改良をしました。
BASIC 認証は既に非推奨になっており、廃止の噂も出ています(まあ、6月に廃止の噂はガセだと思いますが…)ので、OAuth 認証の API に切り替えました。
開発者向けの話。bot の基本的な動作(Twitter とのやりとり)の部分を分離しました。これを再利用して別の bot を簡単に作れる……かどうかは試してないのでまだ分かりません。
完全に裏側の話。昔は App Engine から直接 Twitter に post できなかった(…ハズ)ので、他のサーバを経由して Reply するという、App Engine を使っている意味が無いような仕様になっていました。現在はどうやらその制限はなくなったようなので、直接 post するように変更しました。
以前は、経由サーバが落ちているせいで Reply が飛ばなくなるといったことがありましたが、今後そのような事は起こり得ません。
http://twneru.appspot.com/dashboard
表から見てわかる変化はこれ。いままでトップページに「最近の"ねる"」というリストを出していましたが、これに「起きた」のリストや、システムの状態なども追加して独立したページを作りました。
例えば、トップページでIDを指定せずにボタンを押したときに、無愛想なエラーメッセージを返すだけじゃなくて、使い方を表示する… など。多少マシになりました。
わかる人にはわかるロゴです
Google Code でホスティングしています: http://code.google.com/p/twneru/
AGPL です。(但し、一部のモジュールは MIT ライセンスです。コードの先頭のコメントを見て確認してください)
Google Code に AGPL の選択肢が無かったので GPL と出ていますが、正しくは AGPL です。
バージョンアップした当初、「おはよう」「おやすみ」の Reply の先頭にピリオドをつけるよう仕様変更しました。が、これはかなり不評ですぐに再変更しました。
ピリオドをつけるようにした理由としては、
というところですが、一方で
という反応もあったので、今のところ
という仕様にしています。
Following が100ぐらいあると bot の発言が流れてきても気にならないのですが、10 ぐらいの人だと、 bot に占領されて困るということも確かにありえます。
この問題、私も以前ハマりまして、そのときの解決方法をブログに書いてみました。よろしければご参照ください。
http://webos-goodies.jp/archives/overlaying_webgl_on_html.html
写真がめくれる感じのデモは素晴らしいです。こういうキモチいいUIがこれからのトレンドになりそうですね。参考にさせていただきます。
真面目にAPIを覚えてないせいで、知らない設定がいろいろあります……
可能であればぜひ使わせていただきたいと考えています。