web-dev-qa-db-ja.com

Javascript型付き配列とエンディアンネス

WebGLを使用して、バイナリエンコードされたメッシュファイルをレンダリングしています。バイナリファイルはビッグエンディアン形式で書き込まれます(16進エディターでファイルを開くか、フィドラーを使用してネットワークトラフィックを表示することでこれを確認できます)。 Float32ArrayまたはInt32Arrayを使用してバイナリ応答を読み取ろうとすると、バイナリがリトルエンディアンとして解釈され、値が間違っています。

// Interpret first 32bits in buffer as an int
var wrongValue = new Int32Array(binaryArrayBuffer)[0];

http://www.khronos.org/registry/typedarray/specs/latest/ に型付き配列のデフォルトのエンディアンへの参照が見つからないので、どうすればいいのでしょうか?型付き配列を使用して読み取る場合、すべてのバイナリデータはリトルエンディアンであると想定する必要がありますか?

問題を回避するには、DataViewオブジェクト(前のリンクで説明)を使用して、次のように呼び出します。

// Interpret first 32bits in buffer as an int
var correctValue = new DataView(binaryArrayBuffer).getInt32(0);

「getInt32」などのDataView関数は、デフォルトでビッグエンディアン値を読み取ります。

(注:Google Chrome 15とFirefox 8を使用してテストしましたが、どちらも同じように動作します)

55
Bob

参考までに、次のjavascript関数を使用してマシンのエンディアンを判別できます。その後、適切にフォーマットされたファイルをクライアントに渡すことができます(ビッグエンディアンとリトルエンディアンの2つのバージョンのファイルをサーバーに保存できます)。

function checkEndian() {
    var arrayBuffer = new ArrayBuffer(2);
    var uint8Array = new Uint8Array(arrayBuffer);
    var uint16array = new Uint16Array(arrayBuffer);
    uint8Array[0] = 0xAA; // set first byte
    uint8Array[1] = 0xBB; // set second byte
    if(uint16array[0] === 0xBBAA) return "little endian";
    if(uint16array[0] === 0xAABB) return "big endian";
    else throw new Error("Something crazy just happened");
}

あなたの場合、おそらくファイルをリトルエンディアンで再作成するか、データ構造全体を実行してリトルエンディアンにする必要があります。上記の方法のひねりを使用して、エンディアンをオンザフライで交換できます(実際にはお勧めできません。構造全体が同じ密集した型である場合にのみ意味があります。実際には、必要に応じてバイトを交換するスタブ関数を作成できます)。

function swapBytes(buf, size) {
    var bytes = new Uint8Array(buf);
    var len = bytes.length;
    var holder;

    if (size == 'Word') {
        // 16 bit
        for (var i = 0; i<len; i+=2) {
            holder = bytes[i];
            bytes[i] = bytes[i+1];
            bytes[i+1] = holder;
        }
    } else if (size == 'DWORD') {
        // 32 bit
        for (var i = 0; i<len; i+=4) {
            holder = bytes[i];
            bytes[i] = bytes[i+3];
            bytes[i+3] = holder;
            holder = bytes[i+1];
            bytes[i+1] = bytes[i+2];
            bytes[i+2] = holder;
        }
    }
}
28
Ryan

ここから http://www.khronos.org/registry/typedarray/specs/latest/ (その仕様が完全に実装されている場合)を使用できます:

new DataView(binaryArrayBuffer).getInt32(0, true) // For little endian
new DataView(binaryArrayBuffer).getInt32(0, false) // For big endian

ただし、実装されていないためにこれらの方法を使用できない場合は、ヘッダーでファイルのマジック値(ほぼすべての形式にマジック値が含まれています)を常に確認して、エンディアンに従って反転する必要があるかどうかを確認できます。

また、エンディアン固有のファイルをサーバーに保存し、検出されたホストエンディアンに応じて使用できます。

27
Chiguireitor

他の回答は私には少し時代遅れに思えますので、ここに最新の仕様へのリンクがあります:

http://www.khronos.org/registry/typedarray/specs/latest/#2.1

特に:

型付き配列ビュータイプは、ホストコンピューターのエンディアン方式で動作します。

DataView型は、指定されたエンディアン(ビッグエンディアンまたはリトルエンディアン)のデータを操作します。

したがって、ビッグエンディアン(ネットワークバイト順)でデータの読み取り/書き込みを行う場合は、以下を参照してください: http://www.khronos.org/registry/typedarray/specs/latest/#DATAVIEW

// For multi-byte values, the optional littleEndian argument
// indicates whether a big-endian or little-endian value should be
// read. If false or undefined, a big-endian value is read.
15
user1338062

エンディアンをチェックする簡単な方法

/** @returns {Boolean} true if system is big endian */
function isBigEndian() {
    const array = new Uint8Array(4);
    const view = new Uint32Array(array.buffer);
    return !((view[0] = 1) & array[0]);
}

使い方:

  • 4バイトの配列が作成されます。
  • 32ビットビューはその配列をラップします。
  • view[0] = 1は、32ビット値1を保持するように配列を設定します。
  • ここで重要な部分があります。システムがビッグエンディアンの場合、その1は右端のバイトによって保持されます(最後に来ることはほとんどありません)。リトルエンディアンの場合、それを格納するのは左端のバイトです(少し先に来ます)。そのため、マシンがビッグエンディアンの場合、左端のバイトでビット単位のANDを実行するとfalseが返されます。
  • この関数は、!演算子の結果を&演算の結果に適用することにより、最終的にブール値に変換します。また、ビッグエンディアンに対してtrueを返すように反転します。
5
Lucio Paiva