W3C File APIを使ってJavaScriptでファイル加工

現在W3Cで仕様策定が進められているFile APIを使うと、JavaScriptからローカルファイルの情報や内容にアクセス出来るようになります。

Firefoxでは3.0時代から似たような機能が実装されていたようですが*1、今回やや仕様を変更した上で標準化されます。

まだワーキングドラフトの段階ですが、Firefox 3.6 RC1*2に既にほとんどのAPIが実装されていますので、今すぐに使ってみることができます。

そこで、試しにこんなサンプルを作ってみました。Firefox 3.6で以下のページにアクセスしてみてください。

ファイル選択欄でビットマップファイル(.bmp)を選択、あるいはブラウザにドラッグアンドドロップすると、その画像をネガポジ反転(階調の反転)して表示します。複数選択も可です。*3 *4

↓実行例

ファイル加工の際にサーバと一切通信せず、JavaScriptだけで処理を行っていることがポイントです。JavaScript/CSSを全てHTMLインラインで記述していますので、上のページをローカルに保存しておけば、LANケーブルを引っこ抜いても動作させられます。

今回はごく単純なサンプルですが、オープンソースの画像処理ライブラリ等をJavaScriptに移植すれば、もっと本格的な画像編集アプリケーションも作れそうです。

…もっとも、正直なところ、この類の処理をJavaScriptで実装するメリットはあまり無いのも事実なのですがw

強いて言えば、

  • プラグイン無しのブラウザだけで動作可能なアプリケーションで、
  • かつWeb上にデータを送信する必要が無い

あたりがメリットに成り得るでしょうか。

実際はCSVXMLなどのテキストデータを読み込ませてちょっとした処理を行うとか、ファイルをアップロードする前になんらかのバリデーションやフィルタをかけるとか、そういう使い方がメインになる…のかな?

サンプル解説

先程のサンプルのコード説明を少しだけ。抜粋になりますので、全コードは上のページでソース表示して見てください。File APIだけでなく、HTML5の各種機能も色々ごっちゃ混ぜになった説明になっていますが、あしからず。また、各仕様のアップデートにより、将来的にこのページの説明が間違ったものとなる可能性もありますので、ご注意ください。

最初に、ヘッダやフッタを除いたHTML本体部分*5のコードを載せておきます。

fileタイプのinput要素にmultiple属性をつけてやると、ファイルが複数選択できるようになります。

  // 203〜208行目
  <p> 
    ビットマップファイルを選択、またはここへドロップしてください。
    (複数選択可)
  </p>
  <input id="file_select" type="file" multiple="multiple" />
  <div id="result_container"></div>
選択されたファイルを取得する方法

fileタイプのinput要素で選択されたファイルはfiles属性から取得することができます。

  // 30〜33行目
  var file_select = document.getElementById('file_select');
  file_select.addEventListener('change', function() {
    update(file_select.files);
  }, false);
ドロップされたファイルを取得する方法

本題ではありませんが、ドラッグアンドドロップされたファイルの情報の取り方も。

ドラッグアンドドロップAPIの詳細は省きますが、DataTransferオブジェクトのfiles属性から取得することができます。

  // 36〜42行目
  document.documentElement.addEventListener('dragover', function(event) {
    event.preventDefault();
  }, false);
  document.documentElement.addEventListener('drop', function(event) {
    event.preventDefault();
    update(event.dataTransfer.files);
  }, false);
取得したファイルを処理する

いずれの方法で取得したファイルもFileList型になっていますので、添字を使って各ファイルにアクセスします。ファイルを一つしか選択しなかった場合でも必ずFileList型になります。

  // 53〜55行目
  for (var i = 0; i < files.length; i++) {
    result_container.appendChild(convert(files[i]));
  }
ファイルの情報を参照する

各ファイルの情報はFile型として格納されています。

name属性でパスを含まないファイル名、type属性でMIMEタイプ、size属性でバイト単位のサイズを取得できます。

  // 63〜69行目
  var info = document.createElement('div');
  var name = document.createElement('a');
  name.appendChild(document.createTextNode(file.name));
  info.appendChild(name);
  var text = ' (' + file.size + 'バイト) [' + file.type + ']';
  info.appendChild(document.createTextNode(text));
  result.appendChild(info);
ファイルの内容をバイナリで取得する

ファイルの内容を読み込むにはFileReader型を使用します。

readAsBinaryStringメソッドで、ファイルの内容をそのままバイナリで取得できます。

非同期で読み込まれますので、onloadイベントハンドラをセットしておき、その中でresult属性からデータを参照する形になります。*6

  // 80〜108行目
  var byte_reader = new FileReader();
  byte_reader.onload = function() {
    try {
      var bitmap = new BitMapImage(byte_reader.result);
      /* 中略 */    
    }
  };
  byte_reader.readAsBinaryString(file);
ファイルの内容をDataスキームURLで取得する

サンプルでは処理が重くなるのでコメントアウトしてありますが、readAsDataURLメソッドを使うと、ファイルの内容をDataスキームURLで取得できます。

これをそのままimgタグのsrc属性に設定すれば画像を表示できますし、aタグのhref属性に設定すればクリックでダウンロードさせることもできます。*7

  // 73〜77行目
  var url_reader = new FileReader();
  url_reader.onload = function() {
    name.setAttribute('href', url_reader.result);
  };
  url_reader.readAsDataURL(file);
ファイルの内容をテキストで取得する

サンプルでは使っていませんが、readAsTextメソッドを使うと、エンコードの変換を行った上でテキストを読み込むことができます。テキストファイル類はこちらを使用するのが良いでしょう。

バイナリデータを加工する

File APIには直接関係ありませんが、JavaScriptでバイナリデータを扱うには少し工夫が要ります。

中身はバイナリですが、あくまでもString型のデータとして返ってきますので、バイナリコードを取り出すには都度String#charCodeAtをかける必要があります。

サンプルでは、加工したデータを一旦配列にためています。

  // 177行目
  reversed_data[reversed_data.length] = 255 - this.data.charCodeAt(i);

この段階ではただの整数の配列ですので、再度これをバイナリデータに変換してやる必要があります。これはString.fromCharCodeで行えます。

  // 187〜188行目
  this.data = this.data.substr(0, this.off_bits) +
              String.fromCharCode.apply(null, reversed_data);

これでデータの加工は完了です。

その他

今回は加工したデータをimgタグで表示しただけですが、例えば取得・加工したデータをXMLHttpRequestで送信したり、aタグのhref属性にくっつけてダウンロード出来るようにしたり*8 *9、あるいはWeb Storageに保存してみたり、色々使い方はあるかと思います。

加工処理に時間がかかる場合はWeb Workersを使い、別スレッドで処理するようにしても良いかもしれません。

今までも非標準のブラウザ独自実装を使えば色々凝ったこともできましたが、これからは標準でこういう機能が使えるようになるということで、ちょっとワクワクしますね。

現在のところFile APIが使えるのはFirefox 3.6だけのようですが、他のHTML5関連の仕様も含め、早く多くのブラウザで使えるようになって欲しいなぁ、と思います。…そう遠いことではなさそうな気もしますけど。

*1:参考:Taken SPC : Firefox 3 における <input type="file"> で指定されたファイルへのアクセス

*2:2010年1月11日現在

*3:Windows形式、24bitカラー、圧縮なしのビットマップファイル以外には対応していません。

*4:あまり大きな、あるいは多数のファイルを選択すると、処理が非常に重くなります。

*5:articleタグ内

*6:同期読み込み用のインターフェースもあります。

*7:MIMEタイプによってはそのままブラウザのウィンドウに表示されます。

*8:MIMEタイプをapplication/octet-streamにすれば、常にダウンロードダイアログを出させることができます。

*9:ただし常にファイル名の入力が必要になります。本当はもっとベターなダウンロードの方法があれば良いのですが…。