📝
JavaScript での バイナリ → base64 変換
やりたいこと
// ArrayBuffer を base64 に変換したい
const arrayBuffer = // なんらかのバイナリ(画像・テキストなどなど)
const encodedData = convBase64(arrayBuffer)
しかし、convBase64(arrayBuffer)
のような便利な関数をJSは提供してくれないため自分である程度考える必要がある。
色々調べてみる
ヒントになりそうな関数にbtoa関数
がある
Base64 でエンコードされた ASCII 文字列をバイナリ文字列から生成します。
なるほど。ただ、このバイナリ文字列
とはなんだろう?
バイナリー文字列とは、この 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
解決へのプロセスが見えてきた。整理する
- バイナリをバイナリ文字列化する
- バイナリ文字列を 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 の操作については以下が参考になる
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