JavaScriptだけで画像加工

折原レオナルド賢氏:よろしくお願いします。今日は@potato4dさんの発表が予定していたので@potato4dさんの発表を聞きに来た方には本当に申し訳ないんですが、代打でUITに所属しています折原レオナルド賢が発表させていただきます。よろしくお願いします!

(会場拍手)

今日は「Web Assemblyと画像・動画」という話をしようとしています。みなさん「急にC言語の話とか始まるの?」と不安の人もいるかもしれませんが、そんな話は一切しません。ライブラリを使って簡単にできるという話をメインにしたいと思います。

僭越ながら自己紹介をさせていただくと、折原レオナルド賢と申します。フロントエンドエンジニアで、UXデザイナーをやっていたこともあります。@potato4dさんと同じLINEのUIT室で働いています。

まずはwasm-imagemagickというライブラリについて紹介したいと思います。これはちょっと古いのですが、今UITの中でも熱いです。JavaScriptだけで画像加工しようということですが「なんで?」と思うじゃないですか。

「あらゆる画像編集ソフトがあるのになんでJavaScriptで?」という話はあると思いますが、最近PWAが増えてきているので、TwitterもWebページからインストールできるようになりましたよね? 今度はPhotoshopやGimpのようなアプリケーションがWebで使えるようになる日が来るんじゃないかと思っています。

先ほどmiyaokaさんの発表でもありましたが、ホームページビルダーもWebに来ています。なので今後はもっとじゃんじゃん出てくるのではないかと思っています。

では、JavaScriptでやってみましょう! サンプルコードを書いたのですが、わかりづらいですよね。何かと言いますと、大事なのはここだけです。まずはimagemagickを使うために必要なUnit8Arrayに変換する処理が入っていて、ここでimagemagickに対してコマンドを送信します。

それを叩くことでファイルが返ってきて、ファイルをイメージURLにしてドキュメントのイメージタグに差し込むということをしています。ここは何をしているかと言うと、convert、srcFile.png、-rotate、90。画像を入力したら90度クルッとするだけです。

もしimagemagickをターミナルで使ったことがある方がいたらわかるかもしれませんが、ただそのままコマンドを配列にしているだけです。使い方としては何も違いはなくて、本当にimagemagickがそのまま動いています。なぜこういったことが起きているかと言うと、emscriptenでコンパイルされた実際のCのコードのimagemagickがWeb Assemblyに来ているからです。

これで何が起きるかというと、この画像がこうなります。「これだけなの?」みたいなところもあると思いますが、画像の加工ができます。

別途デモもあるので、デモを見せたいと思います。

今ここで見せたいのは、前に作った画像を管理するアプリをWebでできないかなということで遊んでいたものです。そこに画像を投げるとサムネを作成してくれたりするというデモを見せたいと思います。

これはWebで動いていますが、こんな画像ビューアになっています。右下に+があって、ここで画像が入れられるようになっているので、画像を読み込んでみます。適当に選択すると、画像がアップロードされます。そうすると裏でサムネイルが生成されて後ろに出てきているのがわかると思います。

ファイルをinput type="file"で回収してそれをimagemagickにかけて、サムネイルを作ってここに出しています。なのでこの画像も、よく見てみるとそのまま出てきたばかりなのでURLではなくてデータイメージになっています。

一旦ここでデモは終わって続きにいきます。

他に何ができるかですが、imagemagickにできることならだいたいできます。例えば映像をgifにしてサムネイルに書き出したり、画像比較やdiffが取れたりするので何か面白いことができそうですよね。ちょっと思ったのは、Webでもカメラが使えるので、間違い探しをカメラで撮ると差分がimagemagickで赤く出るみたいな。

「何が楽しいんだ」と思うと思うんですけど。

(会場笑)

そういうこともできるよねという話をしたいと思います。注意なんですが、このwasm-imagemagickは、ご存知の方もいるかもしれませんが、全然メンテナンスされていません。最後の更新が3年前だったりするので、基本的にはCにあるimagemagickをそのままポンと出しているだけで使い勝手はあまり変わりません。

ただ、npm版がエラーを出していて、これはビルドの環境によって起きるエラーで、みんなに起こるエラーでないですが、こういうissueが上がっています。その場合はモジュール版として使うと回避できます。

JavaScriptで動画をエンコード

今度はJavaScriptで動画をエンコードしてみます。ここではffmpeg.jsというものを使います。なのでWeb Assemblyとか言っていますがゴリゴリのCの話ではなく、この辺のライブラリを使って「今こんなことできます!」という話をしていきたいと思います。

これも「なんで?」という感じですが、動画なんていくらでもサーバでできるのですが、あえてブラウザでやる意味は、スマートフォンの処理性能もだいぶ上がってきていますしTikTokなんかを見てもらうとわかると思いますが、リアルタイムにエフェクトを掛けることがアプリでできているので、今後WebがPWAとかでアプリになってくるのであれば、そういったこともできたほうがいいよねということがあります。

なので、ローカルでエンコードしたり、サムネイルを書き出したりといったことができるようになっています。では、さっそくやってみましょう。

今回はWeb Workerを使っています。「Web Assemblyだったら何でも早くなるでしょ?」と思われるかもしれませんが、あながちそうでもないです。もちろん早くはなるんですけど、実際早いのをPromiseとかawaitで待ってしまったりすると、JavaScriptのメインスレッドを止めてしまうので、こういった本当に重い処理をする場合はWeb Workerを使うことが多く、優しいことにこのffmpeg.jsを作った方もWeb Worker用のモノを用意してくれていました。

なので、まずはワーカーを読み込んで、先ほどと同じように動画を読み込んでUint8Arrayしなければいけないので変換しています。次に、Workerとクライアントのプログラムはメッセージでやりとりをすることになるので、.postMessageで「こういう情報を送るよ」と言って、今度はWeb Workerで待機しているffmpeg.jsがこういう処理をしてくれます。

先ほどのimagemagickと同じように、ここでもコマンドを配列にしているだけなのでけっこうわかりやすい……ffmpegのコード自体がわかりやすくないと思うんですけど。

(会場笑)

ffmpegを使ったことがあればわかりやすいかなと思います。これはpostMessageを送ってるだけですよね。Web Workerは処理が終わったら「終わったよ」と返してくれるんですが、それはonmessageで監視する必要があります。先ほどのWorkerのonmessageに関数を用意しておいてデータを受け取ります。

いろいろ割愛してしまいますが、もし正常に終わった場合はメッセージタイプがdoneで返ってきます。そうするとMEMFSというファイルシステムの1つにデータを格納してくれて、それを取ってUnit8Arrayで来るので、それもまたデコードして人間が実際に見ている文字にしてやるという感じにしています。

あ、そもそも何をしているかというと、今回は動画をサムネイルに切り出しました。動画をエンコードしたのではなくて、MP4の動画を上げるとサムネイルを作ってくれて、それがローカルで実行できます。なのでサーバを介さずにサムネイルを生成することができます。

では、これも先ほどのデモを使って見ていきたいと思います。

今回も先ほどと同じアプリなんですが、ここの+アイコンで動画を選択します。例えばこの動画なんですが、これはMP4の動画です。1,280×720のちょっと大きめの動画で12メガバイトぐらいの短いものです。これを選んでみましょう。

今できましたね。一番左のところに動画のサムネイルが作られています。この場で証明することはできませんが、サーバを一切介さずにローカルで全部やっていますよということを信じていただきたい。

(会場笑)

これをもう1回見てみるとURLではなくてデータのイメージで、今ここで作ったものだよという感じになっています。これでデモは終わりです。

締めに入りたいと思います。ffmpeg.jsはおもしろいと思いますが、これも同じくメンテナンスがぜんぜんされていません。最後の更新は4年前です。僕は開発者でもないんですけど先ほどと同じ理由でフォローすると、もともとffmpegをemscriptenでコンパイルしてJavaScriptなどで使えるように持ってきてくれているので、別にメンテナンスをしなくても本当にffmpeg自体は動きます。

ですが、コーデック不足なのでH.265や最近のAndroidで動画を撮ったり、iPhoneで写真を撮ると作られるHEICは対応していません。最近のスマートフォンはjpegとかMP4で書き出される時代でなくなっているのですね。

実はこっそりごまかしていましたが、本来ならここでカメラを起動してバッと撮ってやればよかったんですが、それはコーデック不足のためできません。

でも、実際にffmpegが動くということ自体に偽りはないので、自分でちゃんと動作させたいという場合にはffmpegのソースコードを落としてきて、それをemscriptenでemmakeすると実際にJavaScriptで動かせるようになります。

ただ動かすことはできるのですがちょっとややこしくて、先ほどWeb Workerを使っていたと思いますが、これはffmpeg.jsがWeb Workerで動かせるように提供してくれていたためです。なので、Workerにする部分は自分で作らなければいけません。ハードルは高いですが、事実可能なので「できますよ」ということだけ心にとめていただければと思います。

最新の動画エンコードが必要なかったら今あるffmpeg.jsで十分なので、ぜひやってみてください。

これで終わりになります。ありがとうございました。

(会場拍手)