Hatena::ブログ(Diary)

最速チュパカブラ研究会

 

2011年5月10日 SVGヤバイ

canvas を苛めていたら気づいたことがあったので書きます。(この記事は2011年5月現在情報です。あなたがこの記事を読んでいる時点で、より新しくて良い方法が無いか確認して下さい)

基本的な話

canvas には、図形を描画する機能だけでなく、描かれている内容を読み取る機能があります。getImageData を使うと、canvas の内容をピクセル単位で読み取って画像処理をかけたりできます。また、toDataURL を使うと canvas の内容を Data URI として出力でき、サーバに送信したりできます。

しかし、この機能にはリスクがあります。例えば、悪意のあるページを開いただけで、社内SNSにしか公開していないあなたの顔写真を canvas 経由で抜き取られるかもしれません。そういう事が起きないように、他のサイトから読み込んだ画像が canvas に描画されている場合(以下、canvas が汚染されている と表現)は getImageData や toDataURL が使用禁止になり、呼び出すと例外が出ます。

ここまではよく知られている話。

SVG問答無用でダメ

しかし、同じサイトの画像であっても SVG は問答無用で「汚染」扱いになります。スクリプト内で完結している Data URI ですらダメ。 とにかく、canvas に SVG を描画した時点で読み取り禁止(図1)

f:id:gyuque:20110510152428p:image

↑図1: toDataURL が使えるかどうかのテスト(左3列はインターネット上、右3列はローカルディスク上)

理由はFrom SVG to Canvas and Backという文書に書いてあり、SVG は PNGJPEG などと違い、外部のファイルを参照できるので、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

f:id:gyuque:20110510155307p:image

iPhone でも動きます

f:id:gyuque:20110510160053p:image

関連記事等

特に関連しない記事

2011年2月19日 HTML要素の上にWebGLの描画を合成したらどうなるの

HTML要素の上にWebGLの描画を合成したらどうなるの

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

GL on HTML

赤い点線の枠が WebGL を有効にした canvas で、中の青い三角形は WebGL で描画されています。後ろの写真と「GL Overlay Test」という文字は通常の HTML です。

一見ちゃんと描画されているように見えますが、左側の半透明の三角形が、加算合成したような描画結果になっており、少し変です。本来であればもう少し暗い色で描画されている筈です。これは Chrome/Firefox あるいは Mac/Windows を問わず同じなので、今のところWebGL の描画結果を半透明で合成すると確実におかしくなるので、やめたほうがいいと言えます。が、逆に言えば、完全に不透明か完全に透明な部分は正しく合成されるので、限定的には使えるとも言えます。(描画結果にアンチエイリアスがかかっている場合はエッジがおかしくなるので、そこを許容できるのであれば)

WebOS Goodies さんの記事 - WebGL の描画結果を HTML に正しく合成する方法 - WebOS Goodies で、解説されているレンダーステートの設定をすると、半透明の合成を正しく行えます。

というわけで、こんなデモを作ってみました。

webgl album demo

http://gyu.que.jp/wglalbum/

img で写真を並べただけのページですが、WebGL が有効な状態で写真をクリックすると、iPhoneマップアプリみたいに、ぺろんと捲れて詳細情報が出てきます。Flash と違ってブラウザ標準の右クリックメニューテキスト選択が生きている点が HTML ベースならではの特徴でしょうか。ただ、そこを考えると写真のタイトルを裏側のテクスチャに焼き込んでしまったのはあまり良くないですが。(テクスチャに焼き込む代わりに、CSS Transform を使って合成して意地でも選択できるようにする、という手はあります)

ユーザーインターフェースアニメーションは単なる賑やかしではない、ということは iPhone が証明してくれましたし、ブラウザの上で OpenGL が使えることの意味はこういう方向じゃないかなー、と思いました。 ゲームのデモとかもあるけど、ゲームはゲーム機でやればいいんだし……

hokousyahokousya 2011/02/23 05:32 はじめまして、WebOS Goodiesというブログを書いている者です。
この問題、私も以前ハマりまして、そのときの解決方法をブログに書いてみました。よろしければご参照ください。

http://webos-goodies.jp/archives/overlaying_webgl_on_html.html

写真がめくれる感じのデモは素晴らしいです。こういうキモチいいUIがこれからのトレンドになりそうですね。参考にさせていただきます。

gyuquegyuque 2011/02/23 13:40 ありがとうございます。本文を修正いたしました。

真面目にAPIを覚えてないせいで、知らない設定がいろいろあります……

ruda01ruda01 2011/02/28 21:32 この記事とは関係ないのですが、以前書かれていた記事のjsplashのライセンスがどのようになっているのか教えていただけませんでしょうか?
可能であればぜひ使わせていただきたいと考えています。

2010年10月15日 MozRepl で画像のレンダリング位置を調べる

最近Webページをクローリングして画像を引っこ抜いたりしているのですが、そこで困るのが、一見すると一枚だけど複数のファイルに分割されている画像。

↓こういうやつ

f:id:gyuque:20101015171024j:image

(http://www.jewelpet.jp/)

多くの人が遅いアナログ回線で Web を見ていた頃は、体感速度を上げる効果があった、らしい。しかし、機械的に画像を収集するような向きにはえらい迷惑です。

そこで、クローラブラウザ上でのレンダリング結果を見て、これらの画像は隣接して表示されているぞ、といった判断をできないかなーとぴろたんに尋ねてみたところ

piro_or> っ [MozRepl]

と、5秒で答えが返ってきました。MozRepl というのを使うといいらしいです。というわけで、MozRepl を使って Rubyプログラムから Firefox のレンダリング情報を取得してみました。


mala さんによる MozRepl の紹介( http://la.ma.la/blog/diary_200609280045.htm )を見ながら、以下のような方針でスクリプトを書く

  1. Ruby スクリプトから MozRepl を通じて Firefox に接続する
  2. ターゲットのページを Firefox で読み込む
  3. 読み込みが完了したら、Javascript コードを注入して実行する。この Javascript コードは、ドキュメント中の IMG 要素を拾い、offsetLeft 等のプロパティを参照して視覚的な情報を取得する。
  4. 取得した情報を JSON として Ruby 側に渡す

コードは以下

gist 627852

ページのロード完了を検出する方法を思いつかなかったので、とりあえず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}
]

図にプロットするとこんな感じ(図を作るスクリプトはこちら

f:id:gyuque:20101015171751p:image

ブラウザのキャプチャと重ねてみると、各画像のレンダリング位置を正確に取れていることが分かります

f:id:gyuque:20101015030135j:image

これでどの画像が隣接しているか分かるので、それらを画像処理にかけて境界部分のピクセル比較したりすれば、見た目上繋がっているか正確に判断できるでしょう。


視覚に頼ったWebページを作るな、という理想はともかく、現実問題として、レンダリング結果があってこそ出来る事というのはいろいろあると思います。本文を抽出したり、ユーザーインターフェイス研究に使ったり、あとは……クローラ除けされているメールアドレスを引っこ抜いたりもできますが、そんな悪い人はここを見ていないと信じています。

おわり。


余談ですが、Ruby 1.9.1 の telnet モジュールは派手にバグっています。どれくらい派手かというと、モジュールが丸ごと動かなくなっています。MozRepl を Ruby から叩こうとして変なエラーが出た場合は、Ruby をアップデートしてみて下さい。

トラックバック - http://d.hatena.ne.jp/gyuque/20101015

2010年4月1日 Javascript で通信を行う方法

今までさんざん canvas やら SVG やらで遊んでおいてなんですが、そういった華やかな部分だけではなく、AJAX の要である通信の技術も忘れてはいけません。

まだ先の話ですが、Cross-Site XMLHttpRequest や、Web Socketsメジャーブラウザに実装されれば、JSONPComet 等の小手先のハックで無理矢理実現しつつも根本的には Google Suggest の頃から進歩していなかった、という状況が変わりつつあります。

しかしながら、上に挙げた方法は、ネットワーク接続されているマシン同士でしか通信できないという欠点があります。ネットワークに繋がっていない機器Javascript で通信したい、という需要が年に一度ぐらいはあるかもしれません。

そこで今日は、WiFiEthernet が無くても通信を行える方法をご紹介します。

物理層が無ければ自分で作ればいいじゃない

D

はい、出オチでした……

Square for iPhone

ネタ元は↑これです。個人でもクレジットカードでの支払いを受けることができる 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コードを使った方が速くて確実です。

参考書籍

C言語によるディジタル信号処理入門

C言語によるディジタル信号処理入門

2010年1月17日 Twitter bot「ねる」をバージョンアップ&ソースコード公開

昨年稼動開始した Twitter bot 「ねる」バージョンアップし、ソースコード公開しました。 基本的に、表から見える機能はほとんど変えず、内部の改良をしました。

機能変更などについて

OAuth 対応

BASIC 認証は既に非推奨になっており、廃止の噂も出ています(まあ、6月に廃止の噂はガセだと思いますが…)ので、OAuth 認証の API に切り替えました。

bot コアの分離

開発者向けの話。bot の基本的な動作(Twitter とのやりとり)の部分を分離しました。これを再利用して別の bot を簡単に作れる……かどうかは試してないのでまだ分かりません。

通信方法の変更

完全に裏側の話。昔は App Engine から直接 Twitter に post できなかった(…ハズ)ので、他のサーバを経由して Reply するという、App Engine を使っている意味が無いような仕様になっていました。現在はどうやらその制限はなくなったようなので、直接 post するように変更しました。

以前は、経由サーバが落ちているせいで Reply が飛ばなくなるといったことがありましたが、今後そのような事は起こり得ません。

Dashboard 追加

http://twneru.appspot.com/dashboard

表から見てわかる変化はこれ。いままでトップページに「最近の"ねる"」というリストを出していましたが、これに「起きた」のリストや、システムの状態なども追加して独立したページを作りました。

細かい部分の作りこみ

例えば、トップページでIDを指定せずにボタンを押したときに、無愛想なエラーメッセージを返すだけじゃなくて、使い方を表示する… など。多少マシになりました。

ロゴ変更

わかる人にはわかるロゴです

ソースコード公開について

Google Code でホスティングしています: http://code.google.com/p/twneru/

ライセンスについて

AGPL です。(但し、一部のモジュールMIT ライセンスです。コードの先頭のコメントを見て確認してください)

Google Code に AGPL の選択肢が無かったので GPL と出ていますが、正しくは AGPL です。

その他

Reply の仕様変更について

バージョンアップした当初、「おはよう」「おやすみ」の Reply の先頭にピリオドをつけるよう仕様変更しました。が、これはかなり不評ですぐに再変更しました。

ピリオドをつけるようにした理由としては、

  • 先頭の @ が見知らぬ人(フォローしていない人)の場合、Home 上で bot からの返事が見えない(Twitter の仕様) ので、確実に全員に見えるようにする
  • 他の人の生活が Home 上に流れてきたほうが面白いと思った

というところですが、一方で

  • Home が bot に占領されて困る
  • 知らない人の寝起きの時間なんて知りたくない

という反応もあったので、今のところ

  • Reply の相手が2人以上の場合だけピリオドをつける

という仕様にしています。

Following が100ぐらいあると bot の発言が流れてきても気にならないのですが、10 ぐらいの人だと、 bot に占領されて困るということも確かにありえます。

謝辞

ねる子(仮称)を描いていただいたアサヌさん、コードの助言をしていただいたとぴあさん、ありがとう!

ozeozeozeoze 2012/10/19 11:07 neruが存続の危機です。
このまま終わってしまうのでしょうか?
一利用者でしかない私はお願いする事しか出来ません。

ozeozeozeoze 2014/01/27 09:31 neruがまたまた存続の危機です。
不安な日々を過ごしています。
復活を祈ってます。

トラックバック - http://d.hatena.ne.jp/gyuque/20100117