📝

JavaScript での バイナリ → base64 変換

2022/06/15に公開

やりたいこと

// ArrayBuffer を base64 に変換したい
const arrayBuffer = // なんらかのバイナリ(画像・テキストなどなど)
const encodedData = convBase64(arrayBuffer)

しかし、convBase64(arrayBuffer)のような便利な関数をJSは提供してくれないため自分である程度考える必要がある。

色々調べてみる

ヒントになりそうな関数にbtoa関数がある
https://developer.mozilla.org/ja/docs/Web/API/btoa

Base64 でエンコードされた ASCII 文字列をバイナリ文字列から生成します。

なるほど。ただ、このバイナリ文字列とはなんだろう?
https://developer.mozilla.org/ja/docs/Web/API/DOMString/Binary

バイナリー文字列とは、この ASCII 部分集合と似た概念ですが、コードポイントを 127 までではなく、255 まで許可するものです

つまり、 バイナリ文字は1バイトで表現できる文字 であり
バイナリ文字列とは、バイナリ文字が連なったものということになる

予備知識

JSの文字列はUTF-16によりエンコードされている。
UTF-16は16bit(2byte)を1文字として扱う方法だ。
以下のコードを見た方がわかりやすいだろう。

// サロゲートペアを用いた例だ
const a = "A"
console.log(a.length) // => 1
const cat = "🐱"
console.log(cat.length) // => 2
// "🐱"は4byteを使って表現されるということを意味する
// JSはUTF-16により、内部的に文字列を処理するので、"🐱"の長さは2ということになる

// ちなみに"🐱"の内部表現は以下のようになっている
console.log(cat[0].charCodeAt().toString(16)) // 1byte目 => d83d
console.log(cat[1].charCodeAt().toString(16)) // 2byte目 => dc31

解決へのプロセスが見えてきた。整理する

  1. バイナリをバイナリ文字列化する
  2. バイナリ文字列を btoa 関数に与えて、エンコード結果を得る

1. バイナリをバイナリ文字列化する

MDNからの引用だが、勉強になったコード
(string から バイナリ文字列に変換するコード)

function toBinary(string) {
  // JSの文字列はUTF-16からなるので、16bitの箱を用意する
  const codeUnits = new Uint16Array(string.length);
  for (let i = 0; i < codeUnits.length; i++) {
    // 1文字ずつコードポイントを出力して、入れていく
    codeUnits[i] = string.charCodeAt(i);
  }
  // (...) 内がわかりにくいが、16bit 毎だったバイナリを 8bit 毎にわけて、それぞれを文字列に変換している
  // Uint8Array の 要素の範囲は 0..255 であるため変換後の文字が`バイナリ文字`であることが保証できる
  return String.fromCharCode(...new Uint8Array(codeUnits.buffer));
}

今回はバイナリ→バイナリ文字列なので以下のようなコードになる

function arrayBufferToBinaryString(arrayBuffer) {
  let binaryString = "";
  const bytes = new Uint8Array(arrayBuffer);
  const len = bytes.byteLength;
  for (let i = 0; i < len; i++) {
    binaryString += String.fromCharCode(bytes[i]);
  }
  return binaryString
}

ArrayBuffer をいじるには view オブジェクトを使う必要があったりと
少し取り回しがプリミティブな配列とは異なる。
ArrayBuffer の操作については以下が参考になる

https://ja.javascript.info/arraybuffer-binary-arrays

2. バイナリ文字列を btoa 関数に与えて、エンコード結果を得る

あとは簡単です。

function arrayBufferToBinaryString(arrayBuffer) {
  let binaryString = "";
  const bytes = new Uint8Array(arrayBuffer);
  const len = bytes.byteLength;
  for (let i = 0; i < len; i++) {
    binaryString += String.fromCharCode(bytes[i]);
  }
  return binaryString
}

const arrayBuffer = // なんらかのバイナリ(画像・テキストなどなど)
const binaryString = arrayBufferToBinaryString(arrayBuffer)
const encodedData = btoa(binaryString) // base64 にエンコードできた!

// 意外と、バイナリ → base64 変換も大変だな・・・。

所感

基礎的なところなんだけど、結構おざなりに仕事しちゃってるなぁ...
という自戒から記事にしてみました

Discussion