JavaScriptでfloat32のバイナリファイルを読み込む

はじめに

JavaScriptでローカルにある単精度浮動小数点数型データ(float32)のバイナリデータを読みこむサンプル。

バイナリデータの作成

まず、C/C++で言うところのfloat型をバイナリで保存する。

#include <cstdio>
#include <cstdlib>
int
main(void){
  const int N=10;
  float a[N];
  for(int i=0;i<N;i++){
    a[i] = 1.1*(i+1);
    printf("%f\n",a[i]);
  }
  FILE *fp = fopen("test.dat","w");
  fwrite((char*)a,sizeof(float),N,fp);
}

コンパイル、実行すると、test.datが生成されるので中身を見てみる。

$ g++ test.cpp
$ ./a.out
$ od -tfF test.dat 
0000000     1.100000e+00    2.200000e+00    3.300000e+00    4.400000e+00
0000020     5.500000e+00    6.600000e+00    7.700000e+00    8.800000e+00
0000040     9.900000e+00    1.100000e+01                                
0000050

1.1から11までの数字がバイナリで保存されている。

JavaScriptでの読み込み

こんなHTMLを書く。

<!DOCTYPE html>
<html>
<head>
  <title>Float32 Sample</title>
</head>
<body>
<input type="file" id="file" accept=".dat">
<ul id="data"></ul>
<script>
  function onFileSelect(e) {
    var f = e.target.files;
    var reader = new FileReader();
    reader.onload = function(filename){
      a = new Float32Array(reader.result);
      list = $('data');
      var s = "";
      a.forEach(function(v){
        s = s + "<li>" + v.toFixed(6);
      })
      list.innerHTML=s;
    }
    reader.readAsArrayBuffer(f[0]);
  }
  $('file').addEventListener('change', onFileSelect, false);

  function $(id){
    return document.getElementById(id);
  }
</script>
</body>
</html>

処理はこんな感じ。

  • ファイルを読み込むinputタグのchangeイベントに対して、FileReaderオブジェクトreaderreadAsArrayBufferでロードする。
  • readAsArrayBufferは非同期なので、予めreader.onloadにロードが完了した時の処理を書いておく。
  • 読み込まれたデータはreader.resultに貼っているので、それをFloat32Arrayを使ってfloat32の型付き配列として読みこむ。
  • Float32ArrayforEachはコールバック関数を取る。コールバック関数の引数に配列の中身が順番に入ってくるので、ここではそれをリストに追加している。

実行結果

先程のtest.htmlを開くとこんな画面になる。

image0.png

参照ボタンを押して、先程作成したtest.datを開くと、中身が表示される。

image1.png

ちゃんと読めているようですね。

ハマりポイント

いつくかハマったところなど。

  • ローカルファイルはwindow.onloadなどでは読み込めず、ユーザが明示的に指定する必要がある(セキュリティのため)。
  • Float32Array(reader.result)で読み込んだ配列にたいして、for(var v in a)みたいなfor文を書くと、vString型になってしまい、かつ整数に丸められたものになってしまう。for(var i=0;i<a.length;i++)みたいにしてa[i]でアクセスするのはOK。1
  1. 素直に配列で回すのとコールバック関数使うのとどちらが良いのかは知りません。