【Ruby】マンデルブロ集合をNArrayで描出してみた

小ネタですよ。

mandel.png

旧NArrayの公式ページにサンプルがあるので、Numo::NArrayで書き直してみました。ほとんどそのままです。

https://masa16.github.io/narray/demo/mandel.en.html

Rubyだし実行に時間がかかるんじゃないの? いいえ、NArrayを使っているので計算時間は一瞬です。

(むしろ画像のサイズが大きくなるとgdkpixbufで画像化するのがもたつくかも)

gem install numo-narray

gem install gtk3

require 'numo/narray'

require 'gdk_pixbuf2'
include Numo

def mandel(w, h, w2, h2, zoom)

z = (SComplex.new(1, w).seq / w - w2) * zoom +
(SComplex.new(h, 1).seq / h - h2) * zoom * Complex(0, 1)

c = z.dup
a = UInt8.zeros(w, h)
idx = Int32.new(w, h).seq

(1..100).each do |i|
z = z**2 + c
idx_t = (z.abs > 2).where
idx_f = (z.abs <= 2).where
a[idx[idx_t]] = i
break if idx_f.empty?

idx = idx[idx_f]
z = z[idx_f]
c = c[idx_f]
end
a
end

data = mandel(600, 600, 0.8, 0.5, 2)

# Grayscale to RGB
data2 = UInt8.zeros(600, 600, 3)
data2[true, true, 1] = (SFloat.cast(data) / 100 * 255)

pixbuf = GdkPixbuf::Pixbuf.new(data: data2.to_string, width: 600, height: 600)

pixbuf.save('mandel.png')

mandel.png

冒頭の画像のようにカラフルにしたい? カラフルにしたいときはこんな感じです。

# frozen_string_literal: true

require 'numo/narray'
require 'gdk_pixbuf2'
include Numo

def mandel(w, h, w2, h2, zoom)
z = (SComplex.new(1, w).seq / w - w2) * zoom +
(SComplex.new(h, 1).seq / h - h2) * zoom * Complex(0, 1)

c = z.dup
a = UInt8.zeros(w, h)
idx = Int32.new(w, h).seq

(1..100).each do |i|
z = z**2 + c
idx_t = (z.abs > 2).where
idx_f = (z.abs <= 2).where
a[idx[idx_t]] = i
break if idx_f.empty?

idx = idx[idx_f]
z = z[idx_f]
c = c[idx_f]
end
a
end

# Cumoの実行速度対策
def uInt8_dstack(ar)
x = UInt8.zeros(*ar[0].shape, 3)
x[true, true, 0] = ar[0]
x[true, true, 1] = ar[1]
x[true, true, 2] = ar[2]
x
end

# HSV を RGB に変換
def hsv2rgb(h)
i = UInt8.cast(h * 6)
f = (h * 6.0) - i
p = UInt8.zeros *h.shape
v = UInt8.new(*h.shape).fill 255
q = (1.0 - f) * 256
t = f * 256
rgb = UInt8.zeros *h.shape, 3
t = UInt8.cast(t)
i = uInt8_dstack([i, i, i])
rgb[i.eq 0] = uInt8_dstack([v, t, p])[i.eq 0]
rgb[i.eq 1] = uInt8_dstack([q, v, p])[i.eq 1]
rgb[i.eq 2] = uInt8_dstack([p, v, t])[i.eq 2]
rgb[i.eq 3] = uInt8_dstack([p, q, v])[i.eq 3]
rgb[i.eq 4] = uInt8_dstack([t, p, v])[i.eq 4]
rgb[i.eq 5] = uInt8_dstack([v, p, q])[i.eq 5]
rgb
end

data = mandel(600, 600, 0.8, 0.5, 2)

# Grayscale to RGB
data2 = hsv2rgb(SFloat.cast(data) / 100)

pixbuf = GdkPixbuf::Pixbuf.new(data: data2.to_string, width: 600, height: 600)

pixbuf.save('mandel.png')

あとは、(実際に試したわけではないですが)Cumoを導入することでGPUで動かすこともできますし、

GdkPixbufで画像データを扱っているので、Gtk3で簡単にデスクトップアプリ化することも可能ですね。

この記事は以上です。


関連資料