document

HTML5 & JavaScript side
twitter: @y_imaya

JavaScript で PSD Parser を作り始めました

はじめに

JavaScript で PSD ファイル (Photoshop Document) をパースする psd.js というのを作り始めました。 以下のURLで公開しています。
https://github.com/imaya/psd.js

psd.js と聞いて、なんか前に聞いた事あるって言う人もいると思いますが、以前話題になった psd.js は Twitpic へ譲渡されたあと GitHub で公開しなくなったようなので、3日ほどでまったく新しく作り始めました。

現在できること

まだざっくりとしか実装していません。

  • 基本的な parse
  • Image Data, Channel Image Data からキャンバス生成

未実装の部分はすごく大雑把にデータを切り出しているだけです。

将来的にやりたいこと

  • ブラウザ上で簡単な PSD Viewer の実装
  • PSD の書き出し実装

Demo

GitHub Pagesで試せます。ぜひお試しください。

psd ファイルを選択すると、自動的に parse されます。parse すると、Image Data は Canvas で描画され、コンソールにはパースされた情報が表示されます。

ライセンス

MIT ライセンスです。

参考資料


その他

うまく parse できない psd ファイルを募集します

実装時に一番苦労しているのがサンプルとなるファイル集めです。
とりあえずざっくりと動けばいいやという感じで作っていたので、まだまだうまく動かないファイルがあると思います。
そういったファイルを見つけた場合、教えていただければ改善(できそうなら)します。

以前あった実装を真似てるんじゃないの?

ありえません。自分の最も嫌いなもののひとつに「〜〜.js」とか「〜〜JS」ってプロジェクト名なのにソースコードが全て .coffee というのがあるのですが、以前存在した psd.js はまさにこれでした。
話題になったときに少し興味があったのでリポジトリを見てみたのですが、.coffee という文字列をみて回れ右しました。

jsPerf, JSPerfView を使った、JavaScript コードのベンチマーク計測とブログなどで計測結果を利用する方法

jsPerf とは

JavaScript のコードスニペットに対してベンチマークを計測するサービスです。

一般的に、コードの速度を計測する際は console.time, console.timeEnd を使う事が多いと思いますが、 実行するたびに結果がブレたり、短い処理では正確な比較ができなかったりします。

jsPerf では何度か同じ処理を実行して最終的に一秒間に何回実行できたかをスコアにするので、実行時間が 1ms より小さい処理でも計測できたり、ブレがあっても大体のスコアが分かったりします。 このスコアを計算する部分は Benchmark.js というライブラリで書かれていますので、サーバサイドの JavaScript コードの速度を計測する際にも利用できます。

どういう時に使うのか

JavaScript では全く同じ事をするのに複数の手段があるということがよくあります。 例えば、Array オブジェクトで数値を先頭から順に書き込んでいくとき、Indexer を使って代入する方法と push メソッドを使う方法があります。

こういった際に、テスト用の HTML + JavaScript を作ってポチポチとリロードして各ブラウザで実行した結果をメモ帳にまとめていっても良いのですが、 jsPerf では比較したい部分のコードを書くだけで簡単に比較することができます。

上記の例のベンチマーク

使用上の注意

コードスニペットの正当性はチェックしてくれないので、各コードスニペットが同じ結果を返すのかなどは予めチェックしておく必要があります。 以前出回った i++, ++i の速度比較など、 不公平な条件での結果をスコアだけみて「こっちの方がはやい!」などと思わないようにしましょう。 あまりに直感と掛け離れた結果は自分でいじってみるなど、疑ったり、理由を考える事も大切です。 ちなみに、上記の例ではベンチマーク対象データの生成を setup でやっておけば問題ありません。

JSPerfViewとは

jsPerf を使いだすと、ベンチマーク結果の共有を行いたい事が多々あります。
JSPerfView は HTML5 Rocks の canvas パフォーマンスチューニングのページで利用されている、jsPerf の計測結果をページに埋め込むためのものです。 上で表示されているグラフは JSPerfView を使って表示されています。 iframe を使って埋め込みます。 jsPerf の出力結果とは異なり、スニペットごとに各ブラウザの結果をまとめて表示します。(jsPerfでは各ブラウザごとにスニペットの結果をまとめて表示する)

簡単な使い方

プロジェクトページから何でも良いのでダウンロードしてきて、そのまま丸ごとどこか見れる場所にアップロードします。
自分は GitHub で fork してそのまま qh-pages ブランチに push して利用しています。

ベンチマーク結果を埋め込みたい場所で、以下のように iframe 要素に embed.html を表示したい jsPerf ベンチマークの Browserscope test ID 付きで指定する。

<iframe src="http://imaya.github.com/jsperfview/embed.html?id=agt1YS1wcm9maWxlcnINCxIEVGVzdBiPnYkODA"></iframe>

Browserscope test ID の確認方法

対象となる jsPerf のページを開き、計測結果のグラフの上の方にある "Browserscope" というリンクがあるので、これの URL の末尾が Browserscope test ID です。 例えば、以下の URL の強調した部分です。

http://www.browserscope.org/user/tests/table/agt1YS1wcm9maWxlcnINCxIEVGVzdBiPnYkODA
jsperf-browserscope-ss

バグとか表示方法の変更とか

グラフの表示がおかしくなる

現在、重い処理や貧弱なスマートフォンなどで実行して Ops/sec が 1 未満の結果を含む際にグラフがずれるようです。 この現象への対処は以下の変更を行う事で可能です。

diff --git a/js/chart.js b/js/chart.js
index e778b45..0281d4f 100644
--- a/js/chart.js
+++ b/js/chart.js
@@ -60,7 +60,7 @@ function parseChart_(response) {
       }
       // Compute series data
       var result = parseInt(platformResults[testName].result, 10);
-      if (result && !isNaN(result)) {
+      if (typeof result === 'number' && !isNaN(result)) {
         data.push(result);
       }
     }

全てのブラウザでの結果を表示したい

デフォルトのままでは、jsPerf でテストを行ったブラウザでも結果が表示されないものがあります。 これは、Browserscope の API でデータを取得する際にパラメータを省略すると "Top Browsers" のしか結果を返さないようになっているためです。 詳しくは Browserscope の API ページを参照してください。 全ての結果を表示するには以下のように変更します。

diff --git a/js/chart.js b/js/chart.js
index 2aa93f6..f682877 100644
--- a/js/chart.js
+++ b/js/chart.js
@@ -12,7 +12,7 @@ function Chart(testId) {
   this.chart = null;
 }
 
-Chart.URL_FORMAT = 'http://www.browserscope.org/user/tests/table/{ID}?o=json&callback=?';
+Chart.URL_FORMAT = 'http://www.browserscope.org/user/tests/table/{ID}?v=3&o=json&callback=?';
 Chart.MODERN = /Firefox ([4-9]|[1-2][0-9])|Chrome [1-2][0-9]|IE 9|IE 1[0-9]|Safari ([5-9]|1[0-9])/;
 Chart.MOBILE = /iPhone|iPad|Android/
 

グラフのサイズを変更したい

グラフが大きすぎる場合などサイズを変更したいことがあります。そういう場合は以下のように Highchart のオプションを指定してやれば変更できます。
ちなみに、iframe の height はこのグラフの大きさ + 40px くらいで指定するとちょうど良くなる事が多いようです。

diff --git a/js/chart.js b/js/chart.js
index b967797..6859bb8 100644
--- a/js/chart.js
+++ b/js/chart.js
@@ -87,7 +87,8 @@ function parseChart_(response) {
 function renderChart_(chartInfo, id, type) {
   chartInfo.chart = {
     renderTo: id,
-    type: type
+    type: type,
+    height: 300
   };
   chartInfo.yAxis = {
     title: {

JavaScript で書かれた ZLIB の伸張速度比較

はじめに

最近、Inflate 実装のチューニングを行うことが多かったので、現状でどの程度の速度が出ているか把握するため、他の実装と比較してみました。

比較に使用した ZLIB ライブラリ

今回の比較では、以下のライブラリの存在を確認しています。 uncompress.js に関しては、今回入手できなかったため比較対象からはずしています。

名前 Input Output 名前空間 ライセンス ファイルサイズ
pdf.js Uint8Array,
Array,
ArrayBuffer(*)
Uint8Array FlateStream,
Stream,
DecodeStream,
etc...
MIT stream.js: 80,349
zlib-js String String ZLIB zlib zlib-inflate.js: 86,884
zlib.js: 4,893
zlib.js Uint8Array,
Array
Uint8Array,
Array
Zlib MIT inflate.min.js: 13,222
uncompress.js Uint8Array,
Array
Uint8Array,
Array
zlib zlib uncompress.min.js: 11,843
jsziptools Uint8Array,
Array,
String,
ArrayBuffer
ArrayBuffer jz MIT jsziptools.min.js: 10,536(*)
dataview.min.js: 3,189
zpipe String String zpipe zlib? zpipe.min.js: 206,267

各ライブラリの簡単な特徴

pdf.js Typed Array のサポートが必要。
zlib-js Typed Array 未サポートの環境でも使用可能。
zlib の移植。
zlib.js Typed Array 未サポートの環境でも使用可能。
Stream 版(逐次展開)実装あり。
strict mode 対応。
Node.js 版あり。
Closure Library 対応。
出力バッファサイズの指定可能。
uncompress.js Typed Array 未サポートの環境でも使用可能。
zlib 1.2.5 の移植。
strict mode 対応。
出力バッファ指定可能。
現在サイトにつながらない?
jsziptools PKZIP 対応。
Typed Array のサポートが必要。
Inflate 実装は pdf.js の実装を使用。
ffDataView が必要。
zpipe zlib ライブラリに添付されている zpipe.c を emscripten で移植したもの。
Typed Array のサポートが必要。
Node.js 版あり。

分類について

見た感じ、大きく分けて3種類に分類することが出来ます。

  • zlib 移植系
    • zlib-js
    • uncompress.js
    • zpipe
  • pdf.js 系
    • pdf.js
    • jsziptools
  • 独自実装系
    • zlib.js

FlateStream について

FlateStream の入力は同じ stream.js 内で定義されている Stream のオブジェクトです。
new FlateStream(new Stream(array)) のような形で使います。array は Uint8Array のコンストラクタに渡せるものならば何でも OK です。
FlateStream を利用しているライブラリでは、この Stream を直接使うのではなく、getBytes メソッドを呼ぶ部分のコードを削って Uint8Array をそのまま渡すようにしていることが多いようです。

ビルドについて

ビルドスクリプトなどがついていて、Inflate の比較においてサイズ削減が可能なものに関してはすべて最低限で行っています。

jsziptools

jsziptools はビルドスクリプトが添付されていて、必要な実装のみでビルドすることが可能です。
今回は以下のようにして、zlib 伸張のみでビルドしました。

$ python build.py -m zlib.decompress

zlib.js

zlib.js では全ての実装を分離してビルドすることが出来、リポジトリの bin ディレクトリにそれぞれのファイルが生成されています。
今回は ZLIB の伸張なので inflate.min.js と inflate_stream.min.js をそのまま利用しています。

比較条件

速度比較に利用したデータ

http://www.compression.ca/act/act-files.html で使用しているデータの中からいくつかを Node.js の Zlib でデフォルト設定のまま圧縮したものです。
また、画像に関しては HTML5 Logo (PNG) から IDAT チャンクを抜き出したものを使用しています。

それぞれのライブラリで入力データの形式が違うので、データは予め String, Array, Uint8Array それぞれの形式に変換し、最も速度の出るものを利用しています。(純粋に伸張処理にかかった時間で比較しています。)
実際にライブラリで使用する際には、対応していない入力形式だった場合は変換コストも考慮して選ぶと良いかもしれません。

Adler-32 Checksum について

ほとんどの実装では Adler-32 によるチェックは行われないか、あるいはオプショナルであるため、今回の比較では行わないようにしています。

その他

比較結果について

Inflate は実装毎にバッファの管理方法が異なったりするため、展開するデータによって性能が上下することがあります。ここでの結果はあくまでも参考として利用した方が良いです。

zlib.js について

zlib.js では最新のコードは develop ブランチになっています。今回の比較では develop ブランチの実装を用いています。

ストリーム版 (inflate_stream.js) の結果も添付していますが、一度に全てのデータを流し込んでいるのであまり参考になりません。これは本来、断片的にデータが取得可能な場合に威力を発揮する実装です。

比較時の名前について

zlib-js と zlib.js は名前が似ているため zlib-js は iz-zlib と表記させていただきます。
すみません…。

比較結果

テキストデータ

The Three Musketeers

実行可能バイナリ

PINE

画像データ

HTML5 Logo (IDAT)

補足 (2012/08/15 - 23:22)

グラフの表示には jsperfview というものを使っているのですが、それで利用している Browserscope の API では最近のブラウザは表示されないようです。できるだけリンクをクリックして jsperf の方のグラフを見ることをおすすめします。

補足の補足 (2012/08/16 - 12:30)

jsperfview で利用している Browserscope の API パラメータを変更し、全てのブラウザの結果を表示するようにしました。

Inflate 実装の修正

はじめに

前回までは他の実装に速度面で負けていた zlib.js の Inflate 実装ですが、最近の修正により他の実装と渡り合えるくらいまで速度向上したので、どんな修正をしたのかまとめておこうと思います。

TypedArray#set の使用

前回までは for-loop でバッファのコピーを行なっていましたが、Uint8Array などには set メソッドという値をまとめてセットするものがあるので(利用できる場合は)これを利用するようにしました。 このメソッドを使用することにより、従来の形式でもかなりの速度向上ができました。
実はこのメソッドの存在を完璧に失念していたのですが、@haxe さんと @polygon_planet さんのお陰で使うことができました。ありがとうございました!

新しいバッファ確保方法の実装

zlib.js のバッファ確保はブロック単位の連結リスト方式のような形式でしたが、新しいバッファの保持方法を実装しました。現在はデフォルトで新しい方を利用するようになっていますが、従来の方式も有利な点があるため選択可能になっています。

伸張後のサイズを予測して拡張

新しい方式は、基本的には倍々で増やす方法と同じく、常に連続したデータを保持します。 違うのは拡張する際の挙動で、倍々方式は2倍ずつ固定で増やしますが、こちらの戦略は伸長後のサイズをなんとなく予測して一気に拡張します。

伸長後のサイズ予測というと、難しそうな感じがするかもしれませんが、初期ブロックが埋まった際に、入力データのサイズと現在の読み込み位置から比率を計算して、初期ブロックのサイズに掛けるだけです。(本当はもっと正確な計算ができるのかもしれませんが、現時点ではアイデアがありません。)

20120520203515

この方式だと、運が良ければ1回の拡張で終わるのでとても速いですが、初期部分とそれ以降の部分で全然圧縮率が違う場合は拡張後のサイズが小さすぎるか、あるいは大幅に余ってしまいます。

そこで今回の実装では、小さすぎる場合の対処として最小の拡張倍率を2倍にして最低でも倍々方式と同じくらいの速度が出るようにしています。最大のサイズが大きすぎる場合は特に上限を設けていません。ただし、大きく取った後、出力する際に新しいバッファに切り詰める設定というのは用意しておきました。

今回の実装では対象データによって大きく結果がかわりますので、比較用のデータはなしです。ただし、最小でも2倍で拡張するのと、サイズを計算する部分はほとんどコストがかかっていないため、大体の場合で速度ははやくなっていると思います。

Inflate 実装を作って PDF.js の凄さを思い知った話 (後編)

はじめに

このエントリーは前回の続きです。 PDF.js の方式については今回はほとんど触れていませんので、そちらに興味がある方はごめんなさい。

安定したメモリ使用量にしたかった

前回のおさらいになりますが、まずは PDF.js の実装と自作ライブラリのバッファ使用量の違いを見てみます。

bufsize

自作ライブラリでは元データのサイズに比例して安定した増加をしているのに対し、PDF.js では2倍ずつ増加しているため元のサイズが大きくなるにつれてバッファを確保してしまいすぎるといった事が見られます。自作ライブラリでは「概ね元データのサイズに比例してバッファを使用したい」と思っていたので、速度を犠牲にしてこのような形になっています。

自作ライブラリでは、ブロックサイズを指定してその大きさずつ増えているのでこのような増加になっています。 ただ、ブロック単位でバッファを増やしているとはいっても、高速化の過程でただのブロックとは異なった形になってしまっています。

自作ライブラリのバッファ管理

というわけで、自作ライブラリではどのような構造で持っているのか説明しようと思います。

簡単に言うと

連結リスト方式です。

初期状態

20120426042552

上記のように、処理用のバッファの前後に固定のブロックがついた作りになっています。 なぜこのような部分が付いているかというと、LZ77処理の高速化のためです。 Deflate で使用されている LZ77 は最大 32,768 Byte 戻ってそこから最大 258 Byte コピーするという仕様のため、このコピーの途中でバッファの断絶が起こると速度面で影響が出るのでこのように長さ距離符号が先頭や末尾に出現してもバッファの断絶を意識しないで良いという構造になっています。

バッファの拡張

20120426043909

バッファがブロックサイズを超えた際はバッファの拡張を行います。 この際、元のバッファはそのままで、新しくブロック分(+はみ出ている場合はその分も)サイズのバッファを作成してそれにコピーし、リストに投げ込みます。 ここで出力サイズの2倍+32,768Byte のコピーが発生していることがわかります。あまり効率的ではないです。 そして、コピーが終わった後は出力用のポインタを初期位置にもどします。あとは初期状態と同じです。

バッファの連結

出力時にはシーケンシャルなデータとなっていないといけないため、これらを連結する処理が走ります。 バッファリストからブロック単位で最初から最後までコピーするため、最後に今までのバッファサイズの合計分のコピーが必要となります。バッファ拡張時の処理と合わせると、概ねサイズの 3 倍のコピーが必要となってしまいます。 PDF.js との一番大きな違いはこの部分で、PDF.js の倍々方式ではすでにシーケンシャルなデータとなっているため、この処理は必要ありません。ビューを切り出す subarray をうまく使っているとおもいます。 また、ピーク時のバッファサイズであまり有利にならないのもこの部分が問題となっています。

おわりに

安定したバッファ量にしようと思って今回の方式をとったのですが、PDF.js の subarray で切り出す方式がかなり有効なため、思ったよりも有利になりませんでした。 今後、PDF.js と同じように拡張時にシーケンシャルなデータに寄せることも考えていますが、同じように倍々方式にはならないと思います。 有効な手段ができたらまたエントリーを書いて報告したいと思います。

記事検索
最新コメント
最新トラックバック
カテゴリ別アーカイブ
タグクラウド
QRコード
QRコード
  • ライブドアブログ