こうこく
作 ▸
改 ▸

Uint8Array, Uint16Array, Uint32Array をバイトの並び順を保持して相互変換

普通にUint○○Array (TypedArray) 同士をキャストすると、入りきらないデータは切り落とされます。

const uint16Array = Uint16Array.from([0xff01, 0xfe02, 0xfd03, 0xfc04]);
const uint8Array = new Uint8Array(uint16Array);
console.log(uint8Array);
// -> Uint8Array(4) [1, 2, 3, 4] = 0x01, 0x02, 0x03, 0x04

これを [255, 1, 254, 2, 253, 3, 252, 4] = 0xff, 0x01, 0xfe, 0x02, 0xfd, 0x03, 0xfc, 0x04 に変換できないかと思って、やってみました。

もくじ

[2019-11-23 追記] 以下の記事を参照してください。この記事より役に立つと思います。

JavaScript Blob, TypedArray, 文字列, Canvas, Imageとか周りの変換まとめ

どれ同士でも相互変換できる関数

Uint8Array Uint16Array Uint32Array どれ同士でも変換できる関数です。なんとなく Uint8ClampedArray も受け付けてみました。

いったん Uint8Array に変換した後、他のビット長の Uint○○Array に変換してます。

でもこの関数、これだけ見ると、自分で書いたのに意味が分かりません。なので以降の項には、バラバラで読める状態のものを載せておきます。

/**
 * UintArrayを別のビット長のUintArrayに変換
 * @param  {Uint8Array|Uint8ClampedArray|Uint16Array|Uint32Array} uintArray - 変換元のUintArray
 * @param  {Number} toBitLength - 変換先のビット長 (8, 16, 32)
 * @return {Uint8Array|Uint16Array|Uint32Array} 変換後のUintArray
 */
const convertUintArray = (uintArray, toBitLength) => {
	// 型から変換元のビット長を判定
	let fromBitLength;
	if (uintArray instanceof Uint8Array || uintArray instanceof Uint8ClampedArray) {
		fromBitLength = 8;
	} else if (uintArray instanceof Uint16Array) {
		fromBitLength = 16;
	} else if (uintArray instanceof Uint32Array) {
		fromBitLength = 32;
	} else {
		throw new TypeError('引数がUintArrayではありません。');
	}
	// 変換元と変換先のビット長が同じならそのまま返却
	if (fromBitLength == toBitLength) {
		return uintArray;
	}
	
	// 変換元のビット長が8でなければ8に変換
	let uint8Array;
	if (fromBitLength == 8) {
		uint8Array = uintArray;
	} else {
		const divisor = fromBitLength / 8;
		const len = uintArray.length;
		const shiftMax = fromBitLength - 8;
		uint8Array = new Uint8Array(len * divisor);
		for (let i = 0; i < len; ++i) {
			for (let j = 0; j < divisor; ++j) {
				uint8Array[i * divisor + j] = (uintArray[i] & (0xff << (8 * (divisor - 1 - j)))) >> (shiftMax - j * 8);
			}
		}
	}
	// 変換先のビット長が8ならここで返却
	if (toBitLength == 8) {
		return uint8Array;
	}
	
	// 8から変換先のビット長に変換
	let type;
	if (toBitLength == 16) {
		type = Uint16Array;
	} else if (toBitLength == 32) {
		type = Uint32Array;
	} else {
		throw new RangeError('変換元がUint8Arrayの場合、変換先のバイト長は16か32を指定してください。');
	}
	const len = uint8Array.length;
	const divisor = toBitLength / 8;
	if (len % divisor > 0) {
		throw new RangeError('Uint8Arrayの長さが'+ divisor +'の倍数ではありません。');
	}
	const newUintArray = new type(len / divisor);
	const shiftMax = toBitLength - 8;
	for (let i = 0, j = 0; i < len; ++i) {
		const mod = i % divisor;
		newUintArray[j] += uint8Array[i] << (shiftMax - mod * 8);
		if (mod == divisor - 1) {
			++j;
		}
	}
	return newUintArray;
};

使ってみるとこんな感じです。

// TypedArrayを16進表記に変換する関数
const toHexStr = (typedArray) => typedArray.reduce((a, c) => a +(a === '' ? '' : ' ')+ c.toString(16), '');

// Uint○○Array作成
const uint8Array = Uint8Array.from([0xff, 0x01, 0xfe, 0x02, 0xfd, 0x03, 0xfc, 0x04]);
const uint16Array = Uint16Array.from([0xff01, 0xfe02, 0xfd03, 0xfc04])
const uint32Array = Uint32Array.from([0xff01fe02, 0xfd03fc04]);

// 確認
console.log('16 ->  8', toHexStr(convertUintArray(uint16Array, 8)));  // -> ff 1 fe 2 fd 3 fc 4
console.log('32 ->  8', toHexStr(convertUintArray(uint32Array, 8)));  // -> ff 1 fe 2 fd 3 fc 4
console.log(' 8 -> 16', toHexStr(convertUintArray(uint8Array, 16)));  // -> ff01 fe02 fd03 fc04
console.log('32 -> 16', toHexStr(convertUintArray(uint32Array, 16))); // -> ff01 fe02 fd03 fc04
console.log(' 8 -> 32', toHexStr(convertUintArray(uint8Array, 32)));  // -> ff01fe02 fd03fc04
console.log('16 -> 32', toHexStr(convertUintArray(uint16Array, 32))); // -> ff01fe02 fd03fc04

Uint8Array → Uint16Array

const uint8ArrayToUint16Array = (uint8Array) => {
	const len = uint8Array.length;
	if (len % 2 > 0) {
		throw new RangeError('Uint8Arrayの長さが2の倍数ではありません。');
	}
	const uint16Array = new Uint16Array(len / 2);
	for (let i = 0, j = 0; i < len; ++i) {
		const mod = i % 2;
		if (mod == 0) {
			uint16Array[j] += uint8Array[i] << 8;
		} else {
			uint16Array[j] += uint8Array[i];
			++j;
		}
	}
	return uint16Array;
};

Uint8Array → Uint32Array

見るからに冗長なコードですが、これじゃないと私が読めません。すみません。

const uint8ArrayToUint32Array = (uint8Array) => {
	const len = uint8Array.length;
	if (len % 4 > 0) {
		throw new RangeError('Uint8Arrayの長さが4の倍数ではありません。');
	}
	const uint32Array = new Uint32Array(len / 4);
	for (let i = 0, j = 0; i < len; ++i) {
		const mod = i % 4;
		if (mod == 0) {
			uint32Array[j] += uint8Array[i] << 24;
		} else if (mod == 1) {
			uint32Array[j] += uint8Array[i] << 16;
		} else if (mod == 2) {
			uint32Array[j] += uint8Array[i] << 8;
		} else {
			uint32Array[j] += uint8Array[i];
			++j;
		}
	}
	return uint32Array;
};

Uint16Array → Uint8Array

const uint16ArrayToUint8Array = (uint16Array) => {
	const len = uint16Array.length;
	const uint8Array = new Uint8Array(len * 2);
	for (let i = 0, j = 0; i < len; ++i, j += 2) {
		uint8Array[j]     = (uint16Array[i] & 0xff00) >> 8;
		uint8Array[j + 1] =  uint16Array[i] & 0x00ff;
	}
	return uint8Array;
};

Uint32Array → Uint8Array

見るからに冗長なコードですg

const uint32ArrayToUint8Array = (uint32Array) => {
	const len = uint32Array.length;
	const uint8Array = new Uint8Array(len * 4);
	for (let i = 0, j = 0; i < len; ++i, j += 4) {
		uint8Array[j]     = (uint32Array[i] & 0xff000000) >> 24;
		uint8Array[j + 1] = (uint32Array[i] & 0x00ff0000) >> 16;
		uint8Array[j + 2] = (uint32Array[i] & 0x0000ff00) >> 8;
		uint8Array[j + 3] =  uint32Array[i] & 0x000000ff;
	}
	return uint8Array;
};
この記事に何かあればこちらまで (非公開)