Blob, ArrayBuffer, Uint8Array, DataURI の変換
- カテゴリ:
- JavaScript
- コメント数:
- Comments: 4
◆ Blob からは FileReader をつかって非同期で変換処理
◆ ArrayBuffer, DataURI, text, BinaryString
◆ ArrayBuffer や BinaryString, Uint 系配列 から Blob は Blob のコンストラクタでできる
◆ Uint 系配列の buffer プロパティが ArrayBuffer
◆ ArrayBuffer を Uint 系のコンストラクタに入れるとその TypedArray に変換できる
◆ ArrayBuffer, DataURI, text, BinaryString
◆ ArrayBuffer や BinaryString, Uint 系配列 から Blob は Blob のコンストラクタでできる
◆ Uint 系配列の buffer プロパティが ArrayBuffer
◆ ArrayBuffer を Uint 系のコンストラクタに入れるとその TypedArray に変換できる
Blob とか ArrayBuffer とか JavaScript でバイナリデータを扱うものがあります
色々あって変換するときにどうすればいいんだっけと思うのでまとめ
今回の対象はこれ
UintXXArray は Uint8Array とかのシリーズです
おおまかに分けると
と
と
になります
ArrayBuffer のグループと BinaryString は近めの関係です
簡単な変換する方法の図
いろいろ見づらいので詳しく書いていきます
Blob 型はバイナリデータを保持していますが 中身の場所を指定して値を取り出すことができないものです
Blob.prototype.slice や Blob.prototype.size というプロパティがあるのでサイズの確認や切り出しはできます
実際に中身を見るには Uint8Array などの型にする必要があります
File は Blob を継承しています
Blob にファイル名や最終更新日の情報が追加されてます
基本は Blob と同じように扱って他の型に変換します
UintXXArray は ArrayBuffer を決まったバイト数で配列に格納してプログラムで扱えるようにしたものです
Uint8Array だと 8bit なので 1バイト分ずつ ArrayBuffer から取り出して数値型として配列に入っています
それぞれの要素は 0 から 255 の範囲です
Uint16Array だと 16bit なので 2 バイトずつになります
0 ~ 65535 の範囲です
Uint32Array は 32bit で 4 バイト 0 ~ 4294967295 までです
UintXXArray から ArrayBuffer を取得するときは buffer プロパティにアクセスするだけです
逆に ArrayBuffer を UintXXArray にしたいときは 変換したい種類のコンストラクタに ArrayBuffer を入れるだけです
16 と 32 もやってみます
32 の方ではエラーになりました
32 は 8 の 4 倍で 32bit (4byte) でまとまりになります
元が Uint8Array で長さ 6 の配列なので Uint32Array に変換するにはサイズが合わないのでエラーです
Uint8Array の長さが 4 の倍数になってる必要があります
16 の方も 8 の 2 倍なので Uint8Array のサイズが奇数だと
同じくエラーになります
Uint8Array で長さ 4 のデータにすると
とちゃんと変換できます
このときの 2 バイト 4 バイトのデータですが
という 2 進数のデータです
8 文字ずつに分けると
00000010 00000001
2 と 1 になっています
元が [1, 2] なので逆です
もうひとつも
00000100 00000011
4 と 3 です
リトルエンディアンなので 2 バイトで取り出すと 逆向きの 00000100 と 00000011 の順番になってるんだと思います
32 バイトの場合も
8 つずつにすると
00000100 00000011 00000010 00000001
4 3 2 1 となっていて全体が逆になってます
単純に String.fromCharCode で 0 ~ 255 の値を文字にしてつなげたもの
制御文字になったりもするので表示するには向かないです
内部的な処理で配列より文字列でもっておいた方が効率がいいときに使ったり base64 変換するときの途中で作られたりします
DataURI は URL にデータを書けるようにする DataURI Scheme の文字列です
こういうの
正式な書き方は
data: って書いてファイルの種類を mimetype で書きます
charset="utf-8" みたいなパラメータもかけるみたい
その後に base64 なら ;base64 って書いて , で区切ってその後に好きなデータを書けるという構文
最小サイズの gif
この DataURI で base64 を使う場合に BinaryString が関係してきます
base64 はバイナリデータを 64 文字の英数字とちょっと記号で表したものです
制御文字とか入らないようにしてるので同じデータを表すにも必要なデータ量がちょっと増えます
この base64 は JavaScript だと BinaryString から作ります
普通の文字列 abc を使ってますが方法はこうなります
btoa 関数に BinaryString を渡すと base64 文字列で返って来ます
btoa は binary to ascii の略らしいです
逆の atob (ascii to binary) もあって base64 文字列を BinaryString に変換できます
上で出力された UWJj を BinaryString に変換してみます
ちゃんと戻ってます
第一引数には 配列で ArrayBuffer を渡します
第二引数では type に mimetype を指定します
blob のサイズで Uint8Array のサイズ 16 が表示されています
注意は Uint8Array って配列だからといってそのまま第一引数に渡してしまうとダメということ
サイズは一緒ですが 1,2,3,4,5 が文字列として扱われてしまって この Blob から取り出した Uint8Array は [49, 50, 51, 52, 53] になっています
49 は "1" の char code です
↑の最後の行の read 関数などは自作関数で Uint8Array にしてるだけです
のようにします
Blob からの変換は絶対に非同期になるので注意が必要です
readAsArrayBuffer のところに readAsText や readAsDataURL もありどれを実行するかで結果 (result プロパティ) が変わります
readAsBinaryString を使って BinaryString にも変換できますが 一応このメソッドは 4 年前の時点で廃止になってるようです
あまり積極的に使わない方が良さそうです
そもそも BinaryString を作るときはたいてい base64 が欲しいときなので readAsDataURL を使えば必要になることはそうないはずです
BinaryString 自体が欲しい時に自分で変換するなら
ArrayBuffer と取り出して pstr2 の then の中にあるような処理をすればいいです
関数にするまでもないのもありますが 方法をまとめて書くためということで
Blob のタイプは application/octet-stream にしてます
色々あって変換するときにどうすればいいんだっけと思うのでまとめ
今回の対象はこれ
- Blob
- ArrayBuffer
- UintXXArray
- File
- BinaryString
- DataURI
UintXXArray は Uint8Array とかのシリーズです
おおまかに分けると
- Blob
- File
と
- ArrayBuffer
- UintXXArray
と
- BinaryString
- DataURI
になります
ArrayBuffer のグループと BinaryString は近めの関係です
簡単な変換する方法の図
いろいろ見づらいので詳しく書いていきます
Blob と File
まずは Blob と File についてBlob 型はバイナリデータを保持していますが 中身の場所を指定して値を取り出すことができないものです
Blob.prototype.slice や Blob.prototype.size というプロパティがあるのでサイズの確認や切り出しはできます
実際に中身を見るには Uint8Array などの型にする必要があります
File は Blob を継承しています
Blob にファイル名や最終更新日の情報が追加されてます
基本は Blob と同じように扱って他の型に変換します
ArrayBuffer と UintXXArray
ArrayBuffer はバイナリデータでデータがずらーっと並んでるようなものですUintXXArray は ArrayBuffer を決まったバイト数で配列に格納してプログラムで扱えるようにしたものです
Uint8Array だと 8bit なので 1バイト分ずつ ArrayBuffer から取り出して数値型として配列に入っています
それぞれの要素は 0 から 255 の範囲です
Uint16Array だと 16bit なので 2 バイトずつになります
0 ~ 65535 の範囲です
Uint32Array は 32bit で 4 バイト 0 ~ 4294967295 までです
UintXXArray から ArrayBuffer を取得するときは buffer プロパティにアクセスするだけです
逆に ArrayBuffer を UintXXArray にしたいときは 変換したい種類のコンストラクタに ArrayBuffer を入れるだけです
var ua = Uint8Array.of(1,2,3,4,5,6)
var buf = ua.buffer
ua
// [1, 2, 3, 4, 5, 6]
buf
// ArrayBuffer {}
new Uint8Array(buf)
// [1, 2, 3, 4, 5, 6]
var buf = ua.buffer
ua
// [1, 2, 3, 4, 5, 6]
buf
// ArrayBuffer {}
new Uint8Array(buf)
// [1, 2, 3, 4, 5, 6]
16 と 32 もやってみます
new Uint16Array(buf)
// [513, 1027, 1541]
new Uint32Array(buf)
Uncaught RangeError: byte length of Uint32Array should be a multiple of 4(…)
// [513, 1027, 1541]
new Uint32Array(buf)
Uncaught RangeError: byte length of Uint32Array should be a multiple of 4(…)
32 の方ではエラーになりました
32 は 8 の 4 倍で 32bit (4byte) でまとまりになります
元が Uint8Array で長さ 6 の配列なので Uint32Array に変換するにはサイズが合わないのでエラーです
Uint8Array の長さが 4 の倍数になってる必要があります
16 の方も 8 の 2 倍なので Uint8Array のサイズが奇数だと
var ua = Uint8Array.of(1,2,3,4,5,6,7)
var buf = ua.buffer
new Uint16Array(buf)
Uncaught RangeError: byte length of Uint16Array should be a multiple of 2(…)
var buf = ua.buffer
new Uint16Array(buf)
Uncaught RangeError: byte length of Uint16Array should be a multiple of 2(…)
同じくエラーになります
Uint8Array で長さ 4 のデータにすると
var u8 = Uint8Array.of(1,2,3,4)
var buf = u8.buffer
var u16 = new Uint16Array(buf)
var u32 = new Uint32Array(buf)
u8
// [1, 2, 3, 4]
u16
// [513, 1027]
u32
// [67305985]
var buf = u8.buffer
var u16 = new Uint16Array(buf)
var u32 = new Uint32Array(buf)
u8
// [1, 2, 3, 4]
u16
// [513, 1027]
u32
// [67305985]
とちゃんと変換できます
このときの 2 バイト 4 バイトのデータですが
("0000000000000000" + 513..toString(2)).substr(-16)
// "0000001000000001"
// "0000001000000001"
という 2 進数のデータです
8 文字ずつに分けると
00000010 00000001
2 と 1 になっています
元が [1, 2] なので逆です
もうひとつも
("0000000000000000" + 1027..toString(2)).substr(-16)
// "0000010000000011"
// "0000010000000011"
00000100 00000011
4 と 3 です
リトルエンディアンなので 2 バイトで取り出すと 逆向きの 00000100 と 00000011 の順番になってるんだと思います
32 バイトの場合も
("00000000000000000000000000000000" + 67305985..toString(2)).substr(-32)
// "00000100000000110000001000000001"
// "00000100000000110000001000000001"
8 つずつにすると
00000100 00000011 00000010 00000001
4 3 2 1 となっていて全体が逆になってます
BinaryString と DataURI
BinaryString はバイナリデータを文字列にしたもの単純に String.fromCharCode で 0 ~ 255 の値を文字にしてつなげたもの
制御文字になったりもするので表示するには向かないです
内部的な処理で配列より文字列でもっておいた方が効率がいいときに使ったり base64 変換するときの途中で作られたりします
DataURI は URL にデータを書けるようにする DataURI Scheme の文字列です
data:text/html, <html contenteditable>
こういうの
正式な書き方は
dataurl := "data:" [ mediatype ] [ ";base64" ] "," data
mediatype := [ type "/" subtype ] *( ";" parameter )
data := *urlchar
parameter := attribute "=" value
data: って書いてファイルの種類を mimetype で書きます
charset="utf-8" みたいなパラメータもかけるみたい
その後に base64 なら ;base64 って書いて , で区切ってその後に好きなデータを書けるという構文
最小サイズの gif
data:image/gif;base64,R0lGODlhAQABAPAAAP///wAAACwAAAAAAQABAAACAkQBADs=
この DataURI で base64 を使う場合に BinaryString が関係してきます
base64 はバイナリデータを 64 文字の英数字とちょっと記号で表したものです
制御文字とか入らないようにしてるので同じデータを表すにも必要なデータ量がちょっと増えます
この base64 は JavaScript だと BinaryString から作ります
普通の文字列 abc を使ってますが方法はこうなります
var b_str = "abc"
btoa(b_str)
// YWJj
btoa(b_str)
// YWJj
btoa 関数に BinaryString を渡すと base64 文字列で返って来ます
btoa は binary to ascii の略らしいです
逆の atob (ascii to binary) もあって base64 文字列を BinaryString に変換できます
上で出力された UWJj を BinaryString に変換してみます
var a_str = "YWJj"
atob(a_str)
// abc
atob(a_str)
// abc
ちゃんと戻ってます
ArrayBuffer → Blob
ArrayBuffer を Blob にするときは Blob のコンストラクタを使いますvar u8 = new Uint8Array(16).map((e,i) => i*16)
var blob = new Blob([u8.buffer], {type: "application/octet-binary"})
console.log(blob.size)
// 16
var blob = new Blob([u8.buffer], {type: "application/octet-binary"})
console.log(blob.size)
// 16
第一引数には 配列で ArrayBuffer を渡します
第二引数では type に mimetype を指定します
blob のサイズで Uint8Array のサイズ 16 が表示されています
UintXXArray → Blob
UintXXArray からでも Blob コンストラクタを使いますvar u8 = Uint8Array.of(1,2,3,4,5)
var blob = new Blob([u8], {type: "application/octet-binary"})
console.log(blob.size)
// 5
var blob = new Blob([u8], {type: "application/octet-binary"})
console.log(blob.size)
// 5
注意は Uint8Array って配列だからといってそのまま第一引数に渡してしまうとダメということ
var u8 = Uint8Array.of(1,2,3,4,5)
var blob = new Blob(u8, {type: "application/octet-binary"})
console.log(blob.size)
// 5
read(blob).arrayBuffer().then(e => {console.log(new Uint8Array(e))})
// [49, 50, 51, 52, 53]
var blob = new Blob(u8, {type: "application/octet-binary"})
console.log(blob.size)
// 5
read(blob).arrayBuffer().then(e => {console.log(new Uint8Array(e))})
// [49, 50, 51, 52, 53]
サイズは一緒ですが 1,2,3,4,5 が文字列として扱われてしまって この Blob から取り出した Uint8Array は [49, 50, 51, 52, 53] になっています
49 は "1" の char code です
↑の最後の行の read 関数などは自作関数で Uint8Array にしてるだけです
BinaryString → Blob
文字列の場合も同じで Blob コンストラクタにいれるだけvar blob = new Blob(["abcd"], {type: "application/octet-binary"})
console.log(blob.size)
// 4
console.log(blob.size)
// 4
Blob →
Blob から変換するには FileReader を使う必要がありますvar fr = new FileReader()
fr.onload = eve => {
console.log(fr.result)
}
fr.onerror = eve => {
console.error(fr.error)
}
fr.readAsArrayBuffer()
fr.onload = eve => {
console.log(fr.result)
}
fr.onerror = eve => {
console.error(fr.error)
}
fr.readAsArrayBuffer()
のようにします
Blob からの変換は絶対に非同期になるので注意が必要です
readAsArrayBuffer のところに readAsText や readAsDataURL もありどれを実行するかで結果 (result プロパティ) が変わります
readAsBinaryString を使って BinaryString にも変換できますが 一応このメソッドは 4 年前の時点で廃止になってるようです
あまり積極的に使わない方が良さそうです
そもそも BinaryString を作るときはたいてい base64 が欲しいときなので readAsDataURL を使えば必要になることはそうないはずです
BinaryString 自体が欲しい時に自分で変換するなら
var pstr1 = read(blob).binaryString()
var pstr2 = read(blob).arrayBuffer().then(e => Array.from(new Uint8Array(e), e => String.fromCharCode(e)).join(""))
Promise.all([pstr1, pstr2]).then(e => {
console.log(e[0] === e[1])
})
// true
var pstr2 = read(blob).arrayBuffer().then(e => Array.from(new Uint8Array(e), e => String.fromCharCode(e)).join(""))
Promise.all([pstr1, pstr2]).then(e => {
console.log(e[0] === e[1])
})
// true
ArrayBuffer と取り出して pstr2 の then の中にあるような処理をすればいいです
<追記>
readAsBinaryString は一度仕様から削除されましたが後方互換性のために再導入されています
readAsArrayBuffer が推奨されていますが 経緯的に削除される予定はなさそうなので 頑張って避けるほどでもなさそうです
Blob に text や arrayBuffer メソッドができたので FileReader を使わないで済むことが多くなりました
この記事も参照
readAsBinaryString は一度仕様から削除されましたが後方互換性のために再導入されています
readAsArrayBuffer が推奨されていますが 経緯的に削除される予定はなさそうなので 頑張って避けるほどでもなさそうです
Blob に text や arrayBuffer メソッドができたので FileReader を使わないで済むことが多くなりました
この記事も参照
Uint8Array → BinaryString
それぞれの要素の数値を char code として String.fromCharCode で文字列化してつなげるだけですvar u8 = Uint8Array.of(97,98)
Array.from(u8, e => String.fromCharCode(e)).join("")
// "ab"
Array.from(u8, e => String.fromCharCode(e)).join("")
// "ab"
BinaryString → Uint8Array
一文字一文字に charCodeAt して取り出した数値を配列にしますvar str = "ab"
Uint8Array.from(str.split(""), e => e.charCodeAt(0))
// [97, 98]
Uint8Array.from(str.split(""), e => e.charCodeAt(0))
// [97, 98]
まとめた関数
関数にするまでもないのもありますが 方法をまとめて書くためということで
Blob のタイプは application/octet-stream にしてます
// BinaryString -> Uint8Array
Uint8Array.fromBinaryString = function(str){
return Uint8Array.from(str.split(""), e => e.charCodeAt(0))
}
// Uint8Array -> BinaryString
Uint8Array.prototype.binaryString = function(){
return Array.from(this, e => String.fromCharCode(e)).join("")
}
// BinaryString -> DataURI
function bStr2dataURI(b_str){
return "data:application/octet-stream;base64," + btoa(b_str)
}
// DataURI -> BinaryString
function dataURI2bStr(data){
return atob(data.split(",")[1])
}
// UintXXArray -> ArrayBuffer
function toArrayBuffer(ua){
return ua.buffer
}
// ArrayBuffer -> Uint8Array
ArrayBuffer.prototype.asUint8Array = function(){
return new Uint8Array(this)
}
// ArrayBuffer -> Uint16Array
ArrayBuffer.prototype.asUint8Array = function(){
return new Uint16Array(this)
}
// ArrayBuffer -> Uint32Array
ArrayBuffer.prototype.asUint8Array = function(){
return new Uint32Array(this)
}
// BinaryString, UintXXArray, ArrayBuffer -> Blob
function toBlob(val){
return new Blob([val], {type: "application/octet-stream"})
}
// Blob -> ArrayBuffer, BinaryString, DataURL, text
function read(blob){
var fr = new FileReader()
var pr = new Promise((resolve, reject) => {
fr.onload = eve => {
resolve(fr.result)
}
fr.onerror = eve => {
reject(fr.error)
}
})
return {
arrayBuffer(){
fr.readAsArrayBuffer(blob)
return pr
},
binaryString(){
fr.readAsBinaryString(blob)
return pr
},
dataURL(){
fr.readAsDataURL(blob)
return pr
},
text(){
fr.readAsText(blob)
return pr
},
}
}
Uint8Array.fromBinaryString = function(str){
return Uint8Array.from(str.split(""), e => e.charCodeAt(0))
}
// Uint8Array -> BinaryString
Uint8Array.prototype.binaryString = function(){
return Array.from(this, e => String.fromCharCode(e)).join("")
}
// BinaryString -> DataURI
function bStr2dataURI(b_str){
return "data:application/octet-stream;base64," + btoa(b_str)
}
// DataURI -> BinaryString
function dataURI2bStr(data){
return atob(data.split(",")[1])
}
// UintXXArray -> ArrayBuffer
function toArrayBuffer(ua){
return ua.buffer
}
// ArrayBuffer -> Uint8Array
ArrayBuffer.prototype.asUint8Array = function(){
return new Uint8Array(this)
}
// ArrayBuffer -> Uint16Array
ArrayBuffer.prototype.asUint8Array = function(){
return new Uint16Array(this)
}
// ArrayBuffer -> Uint32Array
ArrayBuffer.prototype.asUint8Array = function(){
return new Uint32Array(this)
}
// BinaryString, UintXXArray, ArrayBuffer -> Blob
function toBlob(val){
return new Blob([val], {type: "application/octet-stream"})
}
// Blob -> ArrayBuffer, BinaryString, DataURL, text
function read(blob){
var fr = new FileReader()
var pr = new Promise((resolve, reject) => {
fr.onload = eve => {
resolve(fr.result)
}
fr.onerror = eve => {
reject(fr.error)
}
})
return {
arrayBuffer(){
fr.readAsArrayBuffer(blob)
return pr
},
binaryString(){
fr.readAsBinaryString(blob)
return pr
},
dataURL(){
fr.readAsDataURL(blob)
return pr
},
text(){
fr.readAsText(blob)
return pr
},
}
}