2010年08月27日 03:30 [Edit]
Ajax - データ交換 via PNG
Demo
実際に双方向に変換するデモを作ってみた。
- 復元された文書
ソースはこんな感じ。
/*
* You need base64.js.
* cf: http://blog.livedoor.jp/dankogai/archives/51067688.html
*/
img2txt = function(node) {
var el = document.createElement("canvas"),
ctx = el.getContext("2d"),
w = el.width = el.style.width = node.width,
h = el.height = el.style.height = node.height;
ctx.drawImage(node, 0, 0);
var d = ctx.getImageData(0, 0, w, h).data,
a = [];
for(var i = 0, l = d.length; i < l; i += 4) {
if (d[0]) a.push(String.fromCharCode(d[i]));
}
return Base64.btou(a.join(''));
};
txt2png = function(txt){
if (!txt || !txt.length) return;
var bin = Base64.utob(txt),
el = document.createElement("canvas"),
ctx = el.getContext("2d"),
w = el.width = el.style.width = Math.floor(Math.sqrt(bin.length)),
h = el.height = el.style.height = Math.floor(bin.length / w) + 1,
imd = ctx.createImageData(w, h),
d = imd.data;
for (var i = 0, j = 0, l = bin.length; i < l; i++, j += 4) {
d[j] = bin.charCodeAt(i);
d[j+1] = d[j+2] = d[j+3] = 255;
};
ctx.putImageData(imd, 0, 0);
return el.toDataURL('image/png');
};
PNG生成において、RGBAのうちRを除いた残りのチャンネルは255にしている。PNG解読の際、見ているのはRだけなので。R=G=Bにすると見た目グレイスケールの画像が出来るが、ファイルはより大きくなる。重要なのはAが255でないと駄目だということ。これがそれ以外の値だと失敗する。
これを以下のようにして使っている。
/* Textフィールド */
$('#demo0txt')
.text($('#jssample').text()) /* 適当に長い文字列で初期化 */
.keyup(function(){
var text = this.value,
chars = text.length,
bytes = Base64.utob(text).length
$('#demo0 dt').eq(1).text([chars, 'chars,', bytes, 'bytes'].join(' ') )
}).trigger('keyup');
/* Render ボタン */
$('#demo0 input').click(function(){
var text = $('#demo0txt').val(),
durl = txt2png(text);
$('#demo0url').text(durl);
$('#demo0img').attr('src',durl);
$('#demo0 dt').eq(2)
.text([durl.length, 'chars,',
durl.replace(/.*?,/,'').length * 3/4, 'bytes'].join(' '));
});
/* 画像から文字列を再現 */
$('#demo0img').load(function(){
$('#demo0dst').text(img2txt(this));
});
実験の結果、以下のことがわかった。
ブラウザは小さなPNGを生成できない
PNGのサイズを見てわかるとおり、Firefoxでは圧縮がいくらかかかるがWebKitではほとんどかからない。canvasではRGBAの4チャンネルPNGしか作れず、この場合に最も適当な1チャンネル=グレイスケールPNGができないというのもこれに拍車をかけている。
たとえばブラウザー側でPNGを生成してXHRで送信するというのはやめた方がいいだろう。素直にサーバー側にdeflateで送るので十分なはずだ。
クロスドメイン通信は出来ない
それでもサーバー側であれば圧縮率を高めたグレイスケールなPNGを用意することは容易である。下はミニファイされていないjQuery 1.4.2のソースをPNG化したものだ。クリックしてみて欲しい。
今度は右をクリックしてみよう。こちらはミニファイされた方のjQueryのソース。
上はテキストがalertされるのに、右はDOM Exceptionになる。
具体的にはctx.drawImage()の元画像がクロスドメイン判定された場合、ctx.getImageData()させないという仕様になっている。
これなら素直にCross-Domain XHRをdeflateサポート下で使った方がよいだろう。Apacheであれば
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/text text/html text/plain text/xml text/css application/x-javascript application/javascript
</IfModule>
とするだけでよい。圧縮と伸長は完全に透過的で、これ以上の指定は一切必要ない。静的ファイルのみならずサーバー側で動的生成されたファイルやXHRでも問題ない。主立ったサイトはほぼすべてそうなっている。ここlivedoor blogも例外ではない。
とはいえ、正直言って「裏口通信」のテクニックとしてはJSONPの発見以来の面白いテクニックだと感銘を受けた。似たようなことを私自身も考えていた事は404 Blog Not Found:UTF-PNG (aka Unicolor)からも伺える。jsは本当に面白い。
Dan the Man with Too Much Data to Exchange