やっつけな日常(7) スマホでGo! fmt.Printで吉例マンデルブロ集合を描く

Joseph Halfmoon

前回、スマホ上のGo言語でフルカラー画像出力ができるようになったので、今回はその応用?としてマンデルブロ集合を描いてみたいと思います。昔、カオスとかフラクタルとか流行った?時期にはよく見たですが、正直この頃は影が薄い?Go言語のプログラムの下敷きにしたのは10年以上前にEXCELのVBAで自作したコードです。

※「やっつけな日常」投稿順 indexはこちら

※Androidスマホ上のLinux環境、Termux上にインストールした golang でソースをビルドしています。実験に使用したGoのソース全文は末尾に。

今回は古いVBAのソースをコピーしたものに前回作ったGoのPPM出力関数をマージして、エディタでVBAをGoに一行一行書き直すというスタイルで「移植」を行いました。意外と簡単。

Go言語の「学び」としては制御構造ですかね。

    • Goのループは for のみである。
    • Goには switch がある。Cのswitchより強力、VBA的なコードもOK

for一つで、whileも do until も書かねばならないですが、記法はフレキシブルで応用が効きます。今回はVBAの do untilループが mandelbrot()関数の中にあったのですが、まったく問題なかったです。

またPythonのように switchが無く、if 文の連なりに書くのも悪くはないと思うのですが、switchある方が見やすいです。何といっても良いのが、break要らないこと。Cで書いていてbreak 書き忘れてツボにハマったことも何度もあり、慌て者としては嬉しいです。またC同様に変数値で分岐もできれば、VBA風に条件かけるところも便利。

スマホ上で編集しているところが以下に。

 

実機上での実行

今回描くのは、縦横200ドットサイズの小さなマンデルブロ集合ですが、まあ計算量は多めということでコンパイルして実行しました。

$ go build mandelbrot.go
$ ./mandelbrot > mb.ppm
$ convert mb.ppm mb.png

ビルドしてしまえば実行時間は気にならなかったです。200ドットサイズなど一瞬。できた mb.ppm は、例によって ImageMagick(要インストール)のconvert コマンドで、表のAndroidアプリが表示できる PNG形式に変換しております。

表示された画像が以下に。

Screenshot_20220415-202355

久しぶりにマンデルブロ集合見たな~ フラクタルな感じ。どんな感じだ?

カラー画像が作れるといろいろ描けて嬉しいです。

やっつけな日常(6) スマホでGo! fmt.Printで今度はフルカラー画像出力 へ戻る

やっつけな日常(8) どこでもマンデルブロ? へ進む

実験に使用したGoのソース
package main
import "fmt"

type ppmImage struct {
  width int
  height int
  cmax int
  BUF []int
}

func newPpmImage(w int, h int, cx int) *ppmImage {
  p := ppmImage{width: w, height: h, cmax: cx}
  p.BUF = make([]int, w*h*3)
  return &p
}

func dotXY(img *ppmImage, x int, y int, r int, g int, b int) bool {
  if x > (img.width - 1) {
    return false
  } else if y > (img.height - 1) {
    return false
  } else {
    img.BUF[3*(y*img.width + x)]     = r
    img.BUF[3*(y*img.width + x) + 1] = g
    img.BUF[3*(y*img.width + x) + 2] = b
  }
  return true
}

func writePPM(img *ppmImage) bool {
  fmt.Println("P3")
  fmt.Println(img.width, " ", img.height)
  fmt.Println(img.cmax)
  for y := 0; y < img.height; y++ {
    for x := 0; x < img.width; x++ {
      fmt.Print(  img.BUF[3*(y*img.width + x)], " ")
      fmt.Print(  img.BUF[3*(y*img.width + x) + 1], " ")
      fmt.Println(img.BUF[3*(y*img.width + x) + 2])
    }
  }
  return true
}

func mandelbrot(a float64, b float64) int {
  var xn, yn float64
  var x float64 = 0.0
  var y float64 = 0.0
  var r float64 = 0.0
  var cnt int = 0
  for {
    xn = x * x - y * y + a
    yn = 2.0 * x * y + b
    r = xn * xn + yn * yn
    x = xn
    y = yn
    cnt = cnt + 1
    if (cnt > 999) || (r > 100.0) {
      break
    }
  }
  return cnt
}

func drawMandelbrot(img *ppmImage) bool {
  var pa, pb float64
  var mdlev int
  for i := 1; i < 200; i++ {
    pa = -1.5 + float64(i) / 100.0
    for j := 1; j < 200; j++ {
      pb = 1.0 - float64(j) / 100.0
      mdlev = mandelbrot(pa, pb)
      switch {
        case mdlev > 999:
          dotXY(img, i, j, 0, 0, 0)
        case mdlev > 20:
          dotXY(img, i, j, 255, 255, 255)
        case mdlev > 15:
          dotXY(img, i, j, 200, 200, 120)
        case mdlev > 10:
          dotXY(img, i, j, 150, 150, 120)
        case mdlev > 5:
          dotXY(img, i, j, 100, 100, 120)
        case mdlev > 3:
          dotXY(img, i, j, 60, 60, 120)
        case mdlev > 2:
          dotXY(img, i, j, 20, 20, 120)
        default:
          dotXY(img, i, j, 10, 10, 80)
      }
    }
  }
  return true
}

func main() {
  XMAX := 200
  YMAX := 200
  img := newPpmImage(XMAX, YMAX, 256)
  drawMandelbrot(img)
  writePPM(img)
}