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);
}