JavaScript Blob, TypedArray, 文字列, Canvas, Imageとか周りの変換まとめ
- Blob (≒ File)
- ArrayBuffer
- Buffer
- TypedArray (Uint8Array, Uint16Array, Uint32Array)
- Canvas (HTMLCanvasElement)
- Image (HTMLImageElement)
- 文字列 (普通の文字列, Base64文字列, バイナリ文字列)
JavaScriptの、ここらへん同士の変換方法をむやみやたらにまとめてます。
[2022-02-27 追記] 全体の見直しを行いました。また、コメントを参考に一部の関数 (uint8ArrayToUint16Array, uint8ArrayToUint32Array) を修正しました。ご指摘くださりありがとうございました。
TypedArray → Blob
Blob コンストラクタを使います。
なお、次項の方法で ArrayBuffer にしてみると分かりますが、この時バイトオーダーはリトルエンディアンになります。
function typedArrayToBlob(typedArray, mimeType = 'application/octet-binary') {
return new Blob([typedArray], { type: mimeType });
}Blob → ArrayBuffer
FileReader の readAsArrayBuffer() を使います。onload イベントを待つので非同期です。
function blobToArrayBuffer(blob, callback) {
const reader = new FileReader();
reader.addEventListener('load', function (event) {
const arrayBuffer = event.target.result;
callback(arrayBuffer);
});
reader.readAsArrayBuffer(blob);
}ArrayBuffer → TypedArray
いずれかの TypedArray コンストラクタを使います。ここでは例として Uint8Array を使いました。
function arrayBufferToUint8Array(arrayBuffer) {
return new Uint8Array(arrayBuffer);
}TypedArray → ArrayBuffer
ArrayBuffer を参照するビューを作成して、その中に Buffer の内容をコピーする必要があります。
function uint8ArrayToArrayBuffer(uint8Array) {
const arrayBuffer = new ArrayBuffer(uint8Array.length);
const view = new Uint8Array(arrayBuffer); // ArrayBufferを参照するTypedArray (ビュー) を作成
for (let i = 0, len = uint8Array.length; i < len; ++i) {
view[i] = uint8Array[i]; // ビューに元のTypedArrayの中身をコピー
}
return arrayBuffer;
}function uint16ArrayToArrayBuffer(uint16Array) {
const arrayBuffer = new ArrayBuffer(uint16Array.length * 2);
const view = new Uint16Array(arrayBuffer);
for (let i = 0, len = uint16Array.length; i < len; ++i) {
view[i] = uint16Array[i];
}
return arrayBuffer;
}function uint32ArrayToArrayBuffer(uint32Array) {
const arrayBuffer = new ArrayBuffer(uint32Array.length * 4);
const view = new Uint32Array(arrayBuffer);
for (let i = 0, len = uint32Array.length; i < len; ++i) {
view[i] = uint32Array[i];
}
return arrayBuffer;
}Uint8Array → Uint16Array|Uint32Array
手動で変換できますが、バイトオーダーを気にする必要があります。
例えば Uint8Array([0x11, 0x22, 0xAA, 0xBB]) を Uint16Array に変換する場合、リトルエンディアンとして扱えば Uint16Array([0x2211, 0xBBAA]) に、ビッグエンディアンとして扱えば Uint16Array([0x1122, 0xAABB]) になります。
同様に Uint8Array([0x11, 0x22, 0xAA, 0xBB]) を Uint32Array に変換する場合、リトルエンディアンとして扱えば Uint16Array([0xBBAA2211]) に、ビッグエンディアンとして扱えば Uint16Array([0x1122AABB]) になります。
/**
* Uint8Array → Uint16Array
* @param {*} uint8Array
* @param {*} byteOrder BE|LE
*/
function uint8ArrayToUint16Array(uint8Array, byteOrder = 'LE') {
const len = uint8Array.length;
if (len % 2 > 0) {
throw new Error('Uint8Arrayの長さが2の倍数ではありません。');
}
const uint16Array = new Uint16Array(len / 2);
if (byteOrder.toUpperCase() == 'LE') {
for (let i = 0, j = 0; i < len; i += 2, j += 1) {
uint16Array[j] = uint8Array[i] + (uint8Array[i + 1] << 8);
}
} else {
for (let i = 0, j = 0; i < len; i += 2, j += 1) {
uint16Array[j] = (uint8Array[i] << 8) + uint8Array[i + 1];
}
}
return uint16Array;
}/**
* Uint8Array → Uint32Array
* @param {*} uint8Array
* @param {*} byteOrder BE|LE
*/
function uint8ArrayToUint32Array(uint8Array, byteOrder = 'LE') {
const len = uint8Array.length;
if (len % 4 > 0) {
throw new Error('Uint8Arrayの長さが4の倍数ではありません。');
}
const uint32Array = new Uint32Array(len / 4);
if (byteOrder.toUpperCase() == 'LE') {
for (let i = 0, j = 0; i < len; i += 4, ++j) {
uint32Array[j] = uint8Array[i] + (uint8Array[i + 1] << 8) + (uint8Array[i + 2] << 16) + (uint8Array[i + 3] << 24);
}
} else {
for (let i = 0, j = 0; i < len; i += 4, ++j) {
uint32Array[j] = (uint8Array[i] << 24) + (uint8Array[i + 1] << 16) + (uint8Array[i + 2] << 8) + uint8Array[i + 3];
}
}
return uint32Array;
}Uint16Array|Uint32Array → Uint8Array
ほぼ同上です。これも手動で変換できますが、バイトオーダーを気にする必要があります。
例えば Uint16Array([0x1122, 0xAABB]) を変換する場合、リトルエンディアンとして扱えば Uint8Array([0x22, 0x11, 0xBB, 0xAA]) に、ビッグエンディアンとして扱えば Uint8Array([0x11, 0x22, 0xAA, 0xBB]) になります。
同様に Uint32Array([0x1122AABB]) を変換する場合、リトルエンディアンとして扱えば Uint8Array([0xBB, 0xAA, 0x22, 0x11]) に、ビッグエンディアンとして扱えば Uint8Array([0x11, 0x22, 0xAA, 0xBB]) になります。
/**
* Uint16Array → Uint8Array
* @param {*} uint16Array
* @param {*} byteOrder BE|LE
*/
function uint16ArrayToUint8Array(uint16Array, byteOrder = 'LE') {
const len = uint16Array.length;
const uint8Array = new Uint8Array(len * 2);
if (byteOrder.toUpperCase() == 'LE') {
for (let i = 0, j = 0; i < len; ++i, j += 2) {
uint8Array[j] = uint16Array[i] & 0x00ff;
uint8Array[j + 1] = (uint16Array[i] & 0xff00) >> 8;
}
} else {
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
* @param {*} uint32Array
* @param {*} byteOrder BE|LE
*/
function uint32ArrayToUint8Array(uint32Array, byteOrder = 'LE') {
const len = uint32Array.length;
const uint8Array = new Uint8Array(len * 4);
if (byteOrder.toUpperCase() == 'LE') {
for (let i = 0, j = 0; i < len; ++i, j += 4) {
uint8Array[j] = uint32Array[i] & 0x000000ff;
uint8Array[j + 1] = (uint32Array[i] & 0x0000ff00) >> 8;
uint8Array[j + 2] = (uint32Array[i] & 0x00ff0000) >> 16;
uint8Array[j + 3] = (uint32Array[i] & 0xff000000) >> 24;
}
} else {
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;
}ArrayBuffer → Buffer
普通に Buffer.from() を使います。
function arrayBufferToBuffer(arrayBuffer) {
return Buffer.from(arrayBuffer);
}Buffer → ArrayBuffer
ArrayBuffer を参照するビューを作成して、その中に Buffer の内容をコピーする必要があります。
function bufferToArrayBuffer(buffer) {
const arrayBuffer = new ArrayBuffer(buffer.length);
const uint8Array = new Uint8Array(arrayBuffer); // ArrayBufferを参照するTypedArray (ビュー) を作成
for (let i = 0, len = buffer.length; i < len; ++i) {
uint8Array[i] = buffer[i]; // ビューにBufferの中身をコピー
}
return arrayBuffer;
}Buffer → TypedArray
いずれかの TypedArray コンストラクタを使います。ここでは例として Uint8Array を使いました。
function bufferToUint8Array(buffer) {
return new Uint8Array(buffer);
}TypedArray → Buffer
普通に Buffer.from() を使います。
function typedArrayToBuffer(typedArray) {
return Buffer.from(typedArray);
}文字列 → Blob (UTF-8)
Blob コンストラクタを使います。
上述の方法で ArrayBuffer にしてみると分かりますが、この時 Blob が保持するバイト列はUTF-8になってます。なのでJavaScriptの内部文字コードはUTF-16ですが、この方法で生成した Blob をダウンロードすると文字コードはUTF-8です。
function stringToBlob(str) {
return new Blob(binStr, { type: 'application/octet-binary' });
}文字列 → ArrayBuffer (UTF-8)
文字列を Blob に変換するとUTF-8になるのを利用します。
function stringToArrayBufferUTF8(str, callback) {
const blob = new Blob([str], { type: 'text/plain' });
const reader = new FileReader();
reader.addEventListener('load', function (event) {
const arrayBuffer = event.target.result;
callback(arrayBuffer);
});
reader.readAsArrayBuffer(blob);
}ArrayBuffer (UTF-8) → 文字列
JavaScriptの内部文字コードはUTF-16ですが、Ajaxの戻りなどでUTF-8のバイト列が手元にある場合にデコードする方法です。以下の記事を参考にさせていただきました。
javascript - Conversion between UTF-8 ArrayBuffer and String - Stack Overflow
UTF-8のバイナリ文字列を escape() でURLエンコードした後、decodeURIComponent() で戻すと行けるみたいです。なお、escape() は標準機能ではなくなったそうなので、ここでは自前で移植したものを使っています。
function arrayBufferUTF8ToString(arrayBuffer) {
const myescape = (str) => {
// escape() の代わり
return str.replace(/[^a-zA-Z0-9@*_+\-./]/g, function (m) {
const code = m.charCodeAt(0);
if (code <= 0xff) {
return '%' + ('00' + code.toString(16)).slice(-2).toUpperCase();
} else {
return '%u' + ('0000' + code.toString(16)).slice(-4).toUpperCase();
}
});
};
const uint8Array = new Uint8Array(arrayBuffer);
const binStr = String.fromCharCode(...uint8Array);
return decodeURIComponent(myescape(binStr));
}文字列 → Buffer
Buffer.from() を使います。デフォルトのエンコーディングはUTF-8です。
function stringToBuffer(str) {
return Buffer.from(str);
}Blob → Base64文字列
FileReader の readAsDataURL() で生成したDataURLから、Base64文字列の部分 (最初のカンマ以降) を切り出して使います。onload イベントを待つので非同期です。
function blobToBase64String(blob, callback) {
const reader = new FileReader();
reader.addEventListener('load', (event) => {
const dataUrl = event.target.result;
callback(dataUrl.substr(dataUrl.indexOf(',') + 1));
});
reader.readAsDataURL(blob);
}Base64文字列 → バイナリ文字列
atob() を使います。
function base64StringToBinaryString(base64Str) {
return atob(base64Str);
}バイナリ文字列 → Base64文字列
btoa() を使います。
function binaryStringToBase64String(binStr) {
return btoa(binStr);
}バイナリ文字列 → Blob
Blob コンストラクタを使います。
function binaryStringToBlob(binStr) {
return new Blob(binStr, { type: 'application/octet-binary' });
}バイナリ文字列 → Uint8Array
String の charCodeAt() を使って、文字列を1文字ずつコード値に変換します。
バイナリ文字列なので Uint8Array を使ってます。JavaScript内で普通に扱う文字列の場合は文字列 → Uint16Arrayを使ってください。
function binaryStringToUint8Array(str) {
const newArr = [];
for (let i = 0, len = str.length; i < len; ++i) {
const code = str.charCodeAt(i);
if (code < 0 || 255 < code) {
throw new RangeError('文字コードが 0 ~ 255 の範囲外の文字が含まれています。');
}
newArr[i] = code;
}
return Uint8Array.from(newArr);
}Uint8Array → バイナリ文字列
全部いっぺんに String.fromCharCode() に渡して変換できます。
function uint8ArrayToBinaryString(uint8Array) {
return String.fromCharCode(...uint8Array);
}文字列 → Uint16Array
JavaScriptの内部文字コードはUTF-16 (コード値 0 ~ 65535) なので、Uint16Array なら格納できます。
function stringToUint16Array(str) {
const newArr = [];
for (let i = 0, len = str.length; i < len; ++i) {
newArr[i] = str.charCodeAt(i); // 0 ~ 65535;
}
return Uint16Array.from(newArr);
}Uint16Array → 文字列
全部いっぺんに String.fromCharCode() に渡して変換します。いっぺんに渡さないと、サロゲートペア使用文字がちゃんと戻りません。
function uint16ArrayToString(uint16Array) {
return String.fromCharCode(...uint16Array);
}Blob → Image
Blob の画像データを <img> の src に使えます。オブジェクトURLを使う方法とデータURLを使う方法があります。どちらも onload イベントを待つので非同期です。
オブジェクトURLの場合は、URL.createObjectURL() を使います。
function blobToImage(blob, callback) {
const img = new Image();
img.src = URL.createObjectURL(blob);
img.addEventListener('load', () => {
callback(img);
});
}データURLを使う場合は、下記のコードです。
function blobToImage(blob, callback) {
const reader = new FileReader();
reader.addEventListener('load', (event) => {
const img = new Image();
img.src = event.target.result;
img.addEventListener('load', () => {
callback(img);
});
});
reader.readAsDataURL(blob);
}Image → Canvas
変換というかですが、Canvasにそのまま描画してしまいます。コンテキストの drawImage() を使います。
function imageToCanvas(img) {
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
return canvas;
}Canvas → Image
Canvasの toDataURL() を使います。onload イベントを待つので非同期です。
function canvasToImage(img, type, callback) {
const img = new Image();
img.src = canvas.toDataURL(type ? type : 'image/png');
img.addEventListener('load', () => {
callback(img);
});
}Canvas → Blob
Canvasの toBlob() を使えば一発です。toBlob() は非同期の関数で、コールバックに Blob が渡されます。
function canvasToBlob(canvas, callback) {
canvas.toBlob(callback, type ? type : 'image/png');
}Canvas → Base64文字列
Canvasの toDataURL() で生成したDataURLから、Base64文字列の部分を切り出して使います。
function canvasToBase64String(canvas, type = 'image/png') {
const dataUrl = canvas.toDataURL(type);
return dataUrl.substr(dataUrl.indexOf(',') + 1);
}
キリウ君が読まないノート