Lambdaカクテル

京都在住Webエンジニアの日記です

Invite link for Scalaわいわいランド

K-means法でドット絵を作る記事が面白かったのでScala.jsで真似てみた

こういう記事を見た。

zenn.dev

これは非常に面白かった。特に、寿司の画像が面白かった。そこで、自分も同じように寿司の画像をドット絵にしてみたいと思い、真似することにした。

ただ真似するだけではつまらないので、Scalaで関数型っぽくスマートに実装できないかと思った。ありがたいことに、ScalaはScala.jsという技術により、どういうわけかJavaScriptにトランスパイルできるので、全部Scala.jsで書いてしまうことにした。

github.com

できた。機能は先駆者様と同じなので、サービスとしての公開はあまり考えていない。

UIはめちゃくちゃ雑に実装したので、先駆者様のような先進的な雰囲気は皆無で、田舎の図書館のパソコンみたいな雰囲気になってしまっている。

いちおう、UIとK-means法を適用する箇所は分けて実装してあるけれど、K-means法のアルゴリズムに画像固有のドメイン知識が混入してしまっていて、なぜかK-means法のアルゴリズムが色のことを知っているみたいな状態になってしまった。

trait KMeans {
  val alpha = 10
  def kmeans(x: Seq[Color], nk: Int): Seq[Color] = {
    var oldK: Seq[Color] = Seq()
    var initialK = Seq.fill(nk)(Util.choosePixel(x))
    var diff = 99999
    var belongMap: Seq[Color] = Seq()

    while (diff > alpha) {
      belongMap = x.map { pix =>
        initialK.minBy(k => Util.colorDelta(k, pix))
      }
      val zipped = x zip belongMap
      oldK = initialK
      initialK = initialK map { k =>
        val belongingToK = zipped.filter(_._2 == k)
        Util.seq2Color(
          belongingToK
            .map(pair => Seq(pair._1.r, pair._1.g, pair._1.b, pair._1.a))
            .transpose
            .map(_.sum / x.size)
        )
      }
      initialK = initialK.map(k => x.minBy(Util.colorDelta(_, k)))

      diff = (oldK zip initialK).map { case (c, d) =>
        Util.colorDelta(c, d).toInt
      }.sum / x.size

      if (initialK.distinct.size < initialK.size) { diff = 0 }
    }

    belongMap
  }

}

関数型テクノロジーを活用したかったけれど、途中で買い物に行く用事が発生してしまったので、やっつけ関数型プログラミングを行ってしまった。もっとちゃんとした生き方をしたほうがいいのだろう。ちゃんとした生き方をしないと、関数型プログラミングを貫徹できない気がする。

まあ、きちんと動くものを1日で作れたので良いか、と思った。そういえばこれは発見だったのだけれど、手持ちの2次元イラストをこのツールにかけると、めちゃくちゃ味のあるイラストが出力されて非常に嬉しかった。著作権的なやつで貼れないのだけれど。

★記事をRTしてもらえると喜びます
Webアプリケーション開発関連の記事を投稿しています.読者になってみませんか?