Webブラウザで画像にファイルのバイナリデータを入れる。

感想

うん、Twitter経由で画像を使ったファイル交換(暗号化、伝達用QRコード付き)が出来た。出来たが思ったのと違った。

togetter.com

github.com

HTML+js+cssのみで鯖は要らないので、デモページか、zipを解凍してブラウザでHTML開けば使えます。

動機

むしゃむしゃしてやった、草なら何でも良かった。ではなくー

このツール自体はGoogleAppleWebサービスを無条件に信頼してしまっている大半の人には無用だと思う。 とは言え、いつかその牧歌的な世界が終わる可能性が有ることをyoutube-dlは教えてくれたので作ってみた。

www.itmedia.co.jp

ファイルが画像になると嬉しいこと。

  • 画像ならアップロードできるサービスが多い。
  • 画像だよ?ファイルじゃないよ?と迂回できる。

実装上の注意

さて実装を進めていたのだが、問題にぶち当たった。

アルファチャンネルは使えない

まず、画像はビットマップ形式で存在する。ビットマップだと1ピクセルの画像表現ならRGBA、 Red、Green、Blue、Alphaの各8bit=1byteの計4バイト、32bitでいけるやん!と皮算用していたのだが、 いざ、32bitで、Alphaチャンネルを使うと画像に入れたデータと画像から取り出した数字が違う。

理由は簡単で、アルファチャンネルはRGBに対して最終的に計算して表示する値で、 表示用のRGBデータがCanvas上に存在する。そのCanvas上のデータを引っ張り出すと、ブラウザが「こんな値だったら表示のデータになるぜ?」等と 入れた値とは異なる値を出してくるのである。まあ、そりゃ画像としてはそうだよね。うん。何も間違ってない。

似たような質問はじゃんじゃか上がってる。その一例。 stackoverflow.com

じゃあどうするか。

単純にRGBだけ使えばいい。 32bit使える!と皮算用していたが、利用範囲は24bitにしなければイケない。アルファチャンネルは255=透明度0に設定する。なお、透明度Maxにすると全部データが0、白飛びするのであった。

他にも必要なこと。

Twitterで900x900だとPNGJpegにならない。なので900x900の画像サイズにするのだが、問題はあって、 900x900のサイズにピッタリ合わない場合はどうしたらいいの?と言う問題。 今ならそこだけアルファチャンネル0にしたらいいよと言えるんだけど、日和った私はBASE64にすることにした。

BASE64にするとデータ格納量は本来256通り、8bitを使えるところが64通り6bitに縮退する。 一方で、DataSchemeで取り出せばアレヤコレヤは必要ないので便利でもある。なので足りない部分は","でPaddingにしている。 また区切り文字を使えるので、ついでにDataSchemeに加えファイル名もBASE64でカンマ区切りで格納した。 あと、カンマでのPaddingだが、そのままくっつけるとPNGの圧縮最適化に飲み込まれてカンマが上手く出てこない。マジかよ。 仕方ないので100byteほど、データ部とパディング部のカンマの直後にランダムなデータをくっつけることで上手くいった。

結局どれぐらいデータ入るの?

画像の総ピクセル数x3 byte のBase64(3/4倍)になった。圧縮率が3/4倍ぐらいなので、実質圧縮されない。うーむ。 本当はデータをBase64に変換する前にdefrateを使えばよかったんだろうが、そこまで手間をかける意味を見いだせなかった。多分圧縮ほとんどされないと思われる。

それよりもアルファチャンネルを区切り文字に使ったほうが有益だったかかもしれない。 900x900だと、論理値は900x900x4byteで画像四枚なので13MB弱。現実はまあ7.3MBぐらいかな。パスワードで暗号化をかけてもあまり差はない。

なお、Twitter前提なのでこの数字だが、900x900限定ではなく、1200x1200とかフォームから設定できるので もっと太っ腹なサービス経由ならこれも有りだと思う。

QRコードについて。

ここに超便利なjsが有るのだが、ES3レベルの記述なので、ESLintで真っ赤になってどうしようもない。これWebpackに入れづらいやろ・・ ※一応varをletにして!=と==を!==と===に直せば大丈夫。

github.com

ES2017レベルにリファクタリングした。

github.com

ESLintでも赤くならなし、classで記述してるから使いやすいよ。 Webpackで取り込んでしゅって使えるはず。MITライセンス万歳!