Flutter WebでWidgetをgif出力したかった
これはやろうとしたけどなんか長い道のりになりそうなのでメモ。ネイティブではできたのでその記録。 メモなので ですます調ではない。
想定ルート
Flutterで任意のWidgetを画像として取得する → それを1フレームと捉え、必要フレーム分取得する → ffmpegみたいなツールでgifにする
任意のWidgetを画像として取得
これは超簡単。RepaintBoundaryを使って、RenderRepaintBoundaryのtoImageメソッドを呼ぶだけ。
ただし型はFuture<Image>で、一枚の画像にしか対応しない(それはそう)。
必要フレーム分取得する
ここは恐らく自前で書かなければならないところ。
とはいえ一枚分が簡単に取れるというのはわかったため、上記をAnimationControllerのリスナーに乗せてやれば良さそう。
出力をStreamにしておいて、受け取り用の管理クラスでList<Image>として保持、必要数受け取ったら終了という流れで行けそう。
gifにする
そこで取れる選択肢は
Web APIに処理を丸投げする
あるいはそういうAPIを自作する
JSのffmpegライブラリを呼び出す
となる。まずWeb APIだが、その手のAPIは見当たらなかった。まぁ大量の画像を投げることになるからないか…
これを使って、連番画像→gif出力をすれば目的達成できそう。
デモ:
DartからJSの関数を呼び出す
package:jsからできそう。
dart:jsでも一応できるようだが、こちらが推奨になっている記述が見受けられる。
課題
現状ファイルI/Oをはさみそうなのでなんとかなくしたい
Dart側ではバイト列で確保できるので、それをそのままJSに渡してやる設計が必要
しかしffmpegで受け取れるのか不明
あるいはJS側に渡すのではなく、Dart側から直接呼び出す発想か
それでもffmpegで受け取れ(ry
仮にgifにできたとしてどうやって保存あるいは共有するのか
BREAKING NEWS
これでできそう.いやできる.勝ったな風呂入ってくる.
しかし依然として,保存or共有の手段が整わない.
とりあえずキャプチャ→エンコードの流れを整理
キャプチャ:1フレームずつ dart:ui の Image クラスとしてキャプチャ
toByteData メソッドにより様々な形式に変換可能
エンコード:GifEncoder.addFrame()に1フレームずつ足していく
image パッケージの Image クラスを使用するが,これは dart:ui のものとは違う.
前者はピクセル情報を非符号32ビットの整数で保存するようなので,それに合わせる.
code:dart
for (final original in images) {
final imageBytes = await original.toByteData();
final translatedImage = image.Image.fromBytes(
original.width,
original.height,
imageBytes.buffer.asUint32List().toList(),
);
encoder.addFrame(translatedImage);
}
後はGifEncoder.finish() するだけで,エンコード済みのバイト列が List<int> として取得できる.
オチ
https://gyazo.com/21f18498fd9b1b7fc4e387d74369252d
泣いた.
とりあえずAndroidで…
https://gyazo.com/fa3de87117b471023af7ca7220c116e4
やってみたが,試しにWidgetの Image.memory() で表示してみようと思ったらこれ.
うまく行っていないように見えるが,よくよく見たら謎の三角形が4つ描画されている.
つまり表示はできたものの,エンコードがうまく行っていなさそう.
また原因を探る.
ちょっと改善
https://gyazo.com/5dc8472735ab93d53158054fba6e98a7
エンコードの際,フレーム直接追加ではなくAnimationクラスを噛ませることで多少マシになった.
できた
https://gyazo.com/9b954494720e1c2388b05745abe9824b
imageパッケージのImageクラスが
Pixels are stored in 32-bit unsigned integers in # AARRGGBB format.
なんて書いてあるもんだからずっとImageをUint32Listとして扱ってきたところ,試しにUint8Listにしてみたらいい感じに.
三角形が分裂してたのはその分情報量が増えた扱いになってたということだった.
そして色がおかしい問題は,CustomPainterで描画するときに背景色を足しただけで解決.
RGBA→ARGBに変換するわけだからそのへんが怪しいと思ってわざわざfor文で配列いじりに行く必要なんてなかったんや……
画像として出力してみるとフレームレートおかしいけど,とりあえず一旦できたということで.