JavaScriptでバイナリデータを扱う時の動作を理解する
2011/01/07(金) 28:56 Javascript親記事へこのエントリーをはてなブックマークに追加

資料的な文章なので実用的な話という感じではないです
追記:ついでに、この文章が書かれたのは昔なので、BlobやArrayBufferといったもっとバイナリ向けものが提供されてきていることにも注意
この文章ではJavaScriptにおいて文字列としてバイナリを扱うにはどうするかという観点で書いたものです
JavaScriptで画像などのバイナリデータを読み込むという目的に沿って追っていく。
XHRを使用し取得した画像データはバイナリコンテンツである。その際にJavaScriptはデフォルトで受信したデータをユニコードとして扱うため、バイナリデータに制御文字*1を表すバイト列が含まれていた際に予期せぬ動作を引き起こすことがある。そのため、overrideMimeTypeメソッド*2を使いcharsetをx-user-definedとして受信データを特別なものとして扱うように設定する。
// overrideMimeTypeを設定するプログラム
  var req = new XMLHttpRequest();// XHRオブジェクト
  req.open('GET', URL, false); // 読み込むURLと方式
  req.overrideMimeType('text/plain; charset=x-user-defined');// 受信データはバイナリを扱う
  req.send();// 通信開始
charset=x-user-defined*3を設定することにより、取得したバイト列に対して次のような変換が行われる。
  • ASCII文字のバイト値 (0x00 - 0x7F)はそのままユニコード(U+0000 - U+007F)に割り当てる
  • それ以外のバイト値(0x80 - 0xFF)はユニコードの私用領域(U+E000 - U+F8FF)に割り当てる
ユニコードの私用領域(Private Use Area)*4は私的利用のために定義されている領域で、ユニコード標準ではこの領域には何も定義されていないため変換の割り当てのために利用することができる。この変換により全てのビットパターンを問題なく扱えるようになる。*5
変換が行われたデータからそれぞれを元々のバイト値にするには上位bitを削る必要があるため、全てのバイト値に対して0xFFとの論理積を取る事でU+0000 - U+007Fは0x00 - 0x7Fとなり、U+E000 - U+F8FFは上位の0xF7が消え0x80 - 0xFFとなり元々のバイト値になる。
// 正しいバイト値への変換したものを配列に
var bytearray = [];// 元々の正しいバイト列を入れる配列
var res = req.responseText;// XHRで取得したデータ
for (var i = 0, l = res.length; i < l; i++) {
    bytearray[i] = res.charCodeAt(i) & 0xff;// それぞれのバイト値と論理積を取ったものを配列へ
}
// bytearray.join("");すれば元々のバイト列と同じモノになる
しかし、バイナリデータが画像などであった場合そのままのバイト列では表示させる事ができないため表示できる形へ変換する必要がある。
JavaScriptでバイナリコンテンツを扱うにはBase64でエンコードしたデータスキーム URIに変換することが挙げられる。データスキームURIへの変換はbtoaメソッド又は独自にBase64変換を実装することで行える。
これらの手順を全てまとめ図 9のようなプログラムを書くことでJavaScriptにおいてバイナリデータを扱う事ができる。
// XHRで取得したバイナリをデータスキームURIへ変換
var req = new XMLHttpRequest();
req.open('GET', URL, true);
req.overrideMimeType('text/plain; charset=x-user-defined')
req.onload = function() {
    var bytearray = [];
    var res = req.responseText;
    for (var i = 0, l = res.length; i < l; i++) {
        bytearray[i] = res.charCodeAt(i) & 0xff;
    }
    // バイナリをデータスキームURIに変換したもの
    var dataScheme = "data:" + req.getResponseHeader("Content-Type") // データのコンテンツタイプの設定
                    + ";base64," // データのエンコードはBase64
                    + btoa(bytearray.join(""));// バイト列を結合したものをbtoa関数でBase64変換
}
req.send();
参考

1: ぉぉっゃ 2011年11月07日(月) 午後5時32分

JavaScriptでバイナリを扱うプログラムを書く際に、とても役に立ちました。ありがとうございます。
一点ご報告です。当方の環境(Mac OS X 10.7, Safari 5.1.1, Chrome 16)では、以下のいずれかの方法で書き換えないと動きませんでした。

・方法1
(変更前) bytearray[i] = res.charCodeAt(i) & 0xff;
(変更後) bytearray[i] = String.fromCharCode(res.charCodeAt(i) & 0xff);

・方法2
(変更前) + btoa(bytearray.join(""));
(変更後) + btoa(String.fromCharCode.apply(null, bytearray));


名前:  非公開コメント   

  • TB-URL  http://efcl.info/adiary/092/tb/