こうこく
作 ▸
改 ▸

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

FileReaderreadAsArrayBuffer() を使います。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 の内容をコピーする必要があります。

Uint8Array
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;
}
Uint16Array
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;
}
Uint32Array
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
/**
 * 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
/**
 * 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
/**
 * 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
/**
 * 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文字列

FileReaderreadAsDataURL() で生成した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

StringcharCodeAt() を使って、文字列を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);
}
この記事に何かあればこちらまで (非公開)