JavaScript を PNG に圧縮するツールを作りました。JS_Packer
demoscene は最近 WebGL を使ったものも多くなってきています。
demoecene は基本的に
という文化です。そして JS ファイルを圧縮する手法の一つに、JS を PNG 画像にして、それをデコードする、という手法が存在します。
JS のコードは基本的にアスキー文字の集まりです。アスキーコードは、小文字/大文字のアルファベット、数字、スペースといった 128 種類しか存在しません。
8 ビット PNG は 256 種類の色をパレットに持っています。 PNG は可逆圧縮(ロスレス)形式の画像です。圧縮しても失われるデータはありません。
JS のコードは 128 種類の文字で構成されており、PNG8 は 256 色で構成されています。文字 (アスキーコード) を色に変換しても余裕があります。
コードから画像を作るには、JS の 1 文字目の文字を画像の 1 ピクセル目の色、 2 文字目を 2 ピクセル目...とするだけです。
2D canvas の imagedata
の機能を使えば、「n ピクセル目の色」を作ることができます。例えば次のようなコードでそれができます。
var JScode = 'alert( 1 );'
var pixels = JScode.length;
for ( i = 0, l = pixels; i < l; i ++ ) {
imagedata.data[ i * 4 ] = JScode.charCodeAt( i );
}
上記の仕組みで、コードを色にすると例えば次のような画像を作ることができます。
画像の大きさは、元 JS のコード量に応じて大きくなります。圧縮が聞いているのでファイル容量は生の JS ファイルよりも小さくなります。
作った画像をデコードするのも簡単です。「色 = 文字」なので、色コードをアスキーコードにも戻すだけです。
以下のは、HTML Image を渡すと色からコードに戻す例です。
var decode = function ( $img ) {
var i, l;
var ctx = document.createElement( 'canvas' ).getContext( '2d' );
var data, code = [];
var pixels = $img.width * $img.height;
ctx.drawImage( $img, 0, 0 );
data = ctx.getImageData( 0, 0, $img.width, $img.height ).data;
for ( i = 0; i < pixels; i ++ ) {
code.push( String.fromCharCode( data[ i * 4 ] ) );
}
eval( code.join( '' ) );
}
PNG ファイル自身に自己解凍の仕組みをつけることができます。
PNG ファイルの任意チャンクとして
'<canvas id=c><img onload=for(w=c.width=' + width + ',h=c.height=' + height + ',a=c.getContext(\'2d\'),a.drawImage(this,p=0,0),e=\'\',d=a.getImageData(0,0,w,h).data;t=d[p+=4];)e+=String.fromCharCode(t);(1,eval)(e) src=#>'
を PNG のバイナリーデータ内に差し込みます。そして、その PNG 画像をHTMLとしてブラウザーで開けば、
という流れで自己解凍、実行ができます。
できるだけファイルの先頭に近い部分に、開始タグだけの canvas を配置すれば、バイナリーデータがテキストとして表示されてしまうことを防ぐことができます。
なので、IHDR の次のチャンクになるように、33 バイト目にこの任意チャンクを差し込みます。
自己解凍後は、問題なく JS が動きますので、removeChild
や createElement
で自由に DOM を操作できます。
大体の場合、gzip のほうが圧縮率が高くなります。demoscene のような特別な縛りがなければ gzip を使ったほうがいいでしょう。