ドット絵が好きなので、あらゆるものをドット絵っぽくしたいな〜と思ったので、カメラの映像をリアルタイムにドット絵風に変換する Web アプリを作りました。これで Zoom などのウェブ会議もドット絵エフェクトで参加できますね。

dotmovie2

こんな感じになります。

技術的には、Web カメラを <video> に流して、それを <canvas> に渡して Go WebAssembly でドット絵風のエフェクト処理をかけている感じになります。 今回の記事で紹介するのは画像をドット絵風に変換する処理の部分です。カメラをブラウザで扱う方法はQR コードを連続で読み取れる Web アプリを作ったをみてください。

画像をどうやってドット絵風にするか

そもそも画像をどうやってドット絵風にするかです。とても単純な仕組みです。

d1

ドット絵にしたいピクセルの大き単位で画像を分割します。

d2

分割されました。

d3

それをさらに 1px に分割します。

d4

塗りつぶすために、色の平均値を計算します。

d5

平均値をとったら、そのセルを塗り潰します。

d6

それをひたすら繰り返すだけです。

d7

これが画像を変換してみた例です。なんかドット絵というより解像度が低い画像ですね。

d8

ドット絵というと色数が少ないのが特徴なので、似たような色は一つの色にまとめちゃいましょう。

d9

この色の平均を取るために今回は K means でクラスタリングします。

d10

こんな感じになりました。ちょっと色味が地味に変換されているので RGB での計算じゃなくて Lab の色空間で計算してみます。

d11

なんかそれっぽくなりましたね。

というわけで、こういった処理を Go WebAssembly で書くときに詰まったポイントを紹介します。

Go で K means クラスタリング

探してたら便利なライブラリ muesli/kmeans があったので、それを利用しています。ありがたい。使い方も簡単で、クラスタわけしたい値を float64 で渡してあげて、何個のクラスタにしたいかを選択すれば良いです。今回の場合は、クラスタ数 = 色数になります。それなりに処理が重いのでリアルタイムに計算する場合は 4-8 色くらいが良いと思います。

var d clusters.Observations
for x := 0; x < 1024; x++ {
	d = append(d, clusters.Coordinates{
		rand.Float64(),
		rand.Float64(),
	})
}

km := kmeans.New()
clusters, err := km.Partition(d, 16)

Go で RGB から Lab 変換

RGB から Lab に色変換する式はないので自前で実装しようか悩んだんですが、ちょっとセンスがなくて雑な色味になったのでこれも lucasb-eyer/go-colorful という良い感じのライブラリがあったので、それを使っています。

cc := colorful.Color{r, g, b}
cc.Lab()

これで RGB から Lab に変換ができました。

Go から JavaScript の Uint8Array にアクセスする

canvas の色情報を取得する JavaScript は以下のような感じです。

ctx.getImageData(0, 0, 100, 100).data

この data プロパティに 1pixel あたりの RGBA 4つの情報が入っています。これを Go から読み取りたいのですが、そのまま読み取ると非常にパフォーマンスが悪かったです。

cell := ctx.Call("getImageData", 0, 0, 100, 100)
data := cell.Get("data")
data[0] // 遅い

jsValue にそのままアクセスするのではなく CopyBytesToGo を使って Uint8Array をバイトコードに変換してからアクセスをすると高速に処理できました。

cell := ctx.Call("getImageData", 0, 0, 100, 100)
data := cell.Get("data")
uint8Arr := js.Global().Get("Uint8Array").New(data)
received := make([]byte, data.Get("length").Int())
_ = js.CopyBytesToGo(received, uint8Arr)
received[0] // 速い

以上で、リアルタイムにドット絵風な処理をしたカメラ Web アプリが完成します。やりましたね!