Pharoといっしょ(6) PPM出力で描く、吉例mandelbrot集合

Joseph Halfmoon

前回、ビットマップグラフィクスをPPMファイル出力できるようになったので、今回は「吉例マンデルブロ集合」を描きたいと思います。過去の別シリーズでも何度となく描いてきた目出度い?フラクタル図形であります。本来は複素数で記述すべきところ、フロート2個で誤魔化してます。ファイル出力さえできてりゃ、後は簡単だ、と。ホントか?

※Pharo関係記事の投稿順Indexは こちら

※Smalltalkの法灯を継ぐモダンな処理系Pharo様を練習してます。今回の動作確認には、Windows11上のWSL2の上のUbuntu24.04LTS上のPharo 10.0.0を使用しています。

※今回のソース全文は末尾に

過去回でのマンデルブロ様

なんどとなくやっている気がするのですが、忘却力の年寄に記憶の残っている過去回は以下です。

RustにいればRustに従え(6) 吉例mandelbrot集合を描く、mut無、for2

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

改めて上記2つを見直したらば、上のRust版は再帰で計算しているのに対して、下のGo版はループで計算してました。どーでもよい違いだけれども。

今回のPharo版は下のGo言語版をほぼほぼ踏襲しているのでループです。

先ずは実行結果

前回作成クラスにいくつかメソッドを追加後、以下のコードをPlaygroundで走らせてます。

| mandelbrot |
mandelbrot := MyBitMap new.
mandelbrot drawMandelbrot.
mandelbrot writePPM: 'mandelbrot.ppm'

上記により出力された mandelbrot.ppm を外部のグラフィック表示プログラムで表示したところが以下に。PharoMandelbrot

マンデルブロ集合、描けておるようです。拡大と再計算などはできないけど、あしからず。

今回追加のメソッド

MyBitMapクラスに以下の drawMandelbrotメソッドを追加してます。画面が256×256サイズである前提で定数をキメウチしてます。画面サイズの変更に対応できない手抜きなメソッドっす。

MyBitMap >> drawMandelbrot
    
    | pa pb |
            
    1 to: 256 do: [ :i |
        pa := -1.5 + (i / 128.0).
        1 to:  256 do: [ :j |
            pb := 1.0 - (j / 128.0).
            self dotX: i dotY: j color: (MyBitMapColor new setMandelbrotColorPIXa: pa b: pb)
        ]
    ]

上記では、MyBitMapColorクラスのインスタンスに setMandelbrotColorPIXa:b:なるメソッドを送って各ピクセルの色を指定しています。

setMandelbrotColorPIXa:b:の実体が以下に。

MyBitMapColor >> setMandelbrotColorPIXa: afloatA b: afloatB
            
    | mdlev |
    mdlev := self class mandelbrotPA: afloatA pb: afloatB.
    mdlev > 999 ifTrue: [ self red:   0 green:   0 blue:   0. ^ self ].
    mdlev >  20 ifTrue: [ self red: 255 green: 255 blue: 255. ^ self ].
    mdlev >  15 ifTrue: [ self red: 200 green: 200 blue: 120. ^ self ].
    mdlev >  10 ifTrue: [ self red: 150 green: 150 blue: 120. ^ self ].
    mdlev >   5 ifTrue: [ self red: 100 green: 100 blue: 120. ^ self ].
    mdlev >   3 ifTrue: [ self red:  60 green:  60 blue: 120. ^ self ].
    mdlev >   2 ifTrue: [ self red:  20 green:  20 blue: 120. ^ self ].
    self red: 10 green: 10 blue: 80.
    ^ self

上記のメソッドは、「長すぎるメソッドだ」とPharo様からお叱り受けてます。普通の言語だとこんなくらい「大丈夫だ~」というところですが、簡潔を旨とするSmalltalkの法灯なのでいたしかたありませぬ。ただ、ま、ここは「人間的には明瞭」ではないかということで、ここのメソッドだけ「目をつぶっていただく」ことにいたしました。

さて上記の中で呼び出している mandelbrotPA: pb: こそがマンデルブロの中核の計算であります。これは苦し紛れにMyBitMapColorクラスのクラスメソッドにしてしまいました。どうなんだろ~。

MyBitMapColor class >> mandelbrotPA: afloatA pb: afloatB
            
    | xn yn x y r cnt |
    x := 0.0.
    y := 0.0.
    r := 0.0.
    cnt := 0.
    1 to: 1000 do: [:i | 
        xn := (x * x) - (y * y) + afloatA.
        yn := 2.0 * x * y + afloatB.
        r := (xn * xn) + (yn * yn).
        x := xn.
        y := yn.
        cnt := i.
        r > 100.0 ifTrue: [ ^ cnt ]
    ].
    ^ cnt

元にしたGo言語コードがループで書いてあったので、上記もループです。個人的にはRust版の再帰定義の方がそれっぽいですが。

ビットマップグラフィクスで何やら描けるようになったので、いろいろ書きながらPharo様のスタイルに慣れていきたいと。そういえば全然TDDになってないじゃん。

Pharoといっしょ(5) PPM形式カラービットマップグラフィック(テキストファイル)出力 へ戻る

Pharoといっしょ(7) 工夫の無い数値積分でπ(パイ)を計算してみる へ進む

My-BitMapのソース全文
Object subclass: #MyBitMap
    instanceVariableNames: 'width height cmax buf'
    classVariableNames: ''
    package: 'My-BitMap'!
!MyBitMap commentStamp: 'JosephHalfmoon 7/9/2024 14:09' prior: 0!
To generate a PPM format bit map graphics file.

- writePPM 'filename.ppm' 
- dotX: x dotY: y color: c  
- generateSamplePattern
- To create instances.

   MyBitMap new.
!


!MyBitMap methodsFor: 'accessing' stamp: 'JosephHalfmoon 7/8/2024 14:11'!
width
    ^ width! !

!MyBitMap methodsFor: 'accessing' stamp: 'JosephHalfmoon 7/8/2024 14:11'!
height
    ^ height! !

!MyBitMap methodsFor: 'accessing' stamp: 'JosephHalfmoon 7/8/2024 17:22'!
dotX: w dotY: h color: rgb 

    w > width ifTrue: [ ^ false ].
    h > height ifTrue: [ ^ false ].
    buf at: ((h - 1) * width + w ) put: rgb.
    ^ true	
    
    ! !

!MyBitMap methodsFor: 'accessing' stamp: 'JosephHalfmoon 7/8/2024 14:12'!
cmax
    ^ cmax! !


!MyBitMap methodsFor: 'initialization' stamp: 'JosephHalfmoon 7/8/2024 17:26'!
width: anIntegerW height: anIntegerH
    | temp |

    temp := MyBitMapColor new.
    width := anIntegerW.
    height := anIntegerH.
    buf := Array new: (width * height) withAll: temp.! !

!MyBitMap methodsFor: 'initialization' stamp: 'JosephHalfmoon 7/8/2024 17:31'!
initialize 
    | temp |
    
    super initialize.
    temp := MyBitMapColor new.
    width := 256.
    height := 256.
    cmax := 256.
    buf := Array new: (width * height) withAll: temp.
    
    ! !


!MyBitMap methodsFor: 'utilities' stamp: 'JosephHalfmoon 7/8/2024 17:33'!
writePPM: filename
    | fref stream | 

    fref := filename asFileReference.
    stream := fref writeStream.
    stream nextPutAll: 'P3';
        cr;
        print: width;
        nextPutAll: ' ';
        print: height;
        cr;
        print: cmax;
        cr.
    buf do: [ :pix | stream 
        print: (pix red); nextPutAll: ' ';
        print: (pix green); nextPutAll: ' ';
        print: (pix blue); cr. ].
    stream close.
    
    
    
     
    ! !

!MyBitMap methodsFor: 'utilities' stamp: 'JosephHalfmoon 7/8/2024 17:58'!
generateSamplePattern
            
    | tR tG tB |
    tR := MyBitMapColor new red: 255 green:   0 blue:   0.
    tG := MyBitMapColor new red:   0 green: 255 blue:   0.
    tB := MyBitMapColor new red:   0 green:   0 blue: 255.
    
    1 to: 256 do: [:j |
          1 to:  85 do: [:i | self dotX: i dotY: j color: tR.].
         86 to: 170 do: [:i | self dotX: i dotY: j color: tG.].
        171 to: 256 do: [:i | self dotX: i dotY: j color: tB.].		
    ]
    ! !

!MyBitMap methodsFor: 'utilities' stamp: 'JosephHalfmoon 7/9/2024 15:07'!
drawMandelbrot
    
    | pa pb |
            
    1 to: 256 do: [ :i |
        pa := -1.5 + (i / 128.0).
        1 to:  256 do: [ :j |
            pb := 1.0 - (j / 128.0).
            self dotX: i dotY: j color: (MyBitMapColor new setMandelbrotColorPIXa: pa b: pb)
        ]
    ]
    ! !


Object subclass: #MyBitMapColor
    instanceVariableNames: 'red green blue'
    classVariableNames: ''
    package: 'My-BitMap'!
!MyBitMapColor commentStamp: 'JosephHalfmoon 7/8/2024 15:10' prior: 0!
Bit Map color.
!


!MyBitMapColor methodsFor: 'initialization' stamp: 'JosephHalfmoon 7/8/2024 15:09'!
green

    ^green! !

!MyBitMapColor methodsFor: 'initialization' stamp: 'JosephHalfmoon 7/8/2024 15:09'!
blue

    ^blue! !

!MyBitMapColor methodsFor: 'initialization' stamp: 'JosephHalfmoon 7/8/2024 15:08'!
initialize

    super initialize.
    red := 0.
    green := 0.
    blue := 0.! !

!MyBitMapColor methodsFor: 'initialization' stamp: 'JosephHalfmoon 7/8/2024 15:11'!
red: r green: g blue: b

    red := r.
    green := g.
    blue := b.! !

!MyBitMapColor methodsFor: 'initialization' stamp: 'JosephHalfmoon 7/9/2024 14:52'!
setMandelbrotColorPIXa: afloatA b: afloatB
            
    | mdlev |
    mdlev := self class mandelbrotPA: afloatA pb: afloatB.
    mdlev > 999 ifTrue: [ self red:   0 green:   0 blue:   0. ^ self ].
    mdlev >  20 ifTrue: [ self red: 255 green: 255 blue: 255. ^ self ].
    mdlev >  15 ifTrue: [ self red: 200 green: 200 blue: 120. ^ self ].
    mdlev >  10 ifTrue: [ self red: 150 green: 150 blue: 120. ^ self ].
    mdlev >   5 ifTrue: [ self red: 100 green: 100 blue: 120. ^ self ].
    mdlev >   3 ifTrue: [ self red:  60 green:  60 blue: 120. ^ self ].
    mdlev >   2 ifTrue: [ self red:  20 green:  20 blue: 120. ^ self ].
    self red: 10 green: 10 blue: 80.
    ^ self
! !

!MyBitMapColor methodsFor: 'initialization' stamp: 'JosephHalfmoon 7/8/2024 15:08'!
red

    ^red! !

"-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "!

MyBitMapColor class
    instanceVariableNames: ''!

!MyBitMapColor class methodsFor: 'initialization' stamp: 'JosephHalfmoon 7/9/2024 14:43'!
mandelbrotPA: afloatA pb: afloatB
            
    | xn yn x y r cnt |
    x := 0.0.
    y := 0.0.
    r := 0.0.
    cnt := 0.
    1 to: 1000 do: [:i | 
        xn := (x * x) - (y * y) + afloatA.
        yn := 2.0 * x * y + afloatB.
        r := (xn * xn) + (yn * yn).
        x := xn.
        y := yn.
        cnt := i.
        r > 100.0 ifTrue: [ ^ cnt ]
    ].
    ^ cnt! !
PackageManifest subclass: #ManifestMyBitMap
    instanceVariableNames: ''
    classVariableNames: ''
    package: 'My-BitMap-Manifest'!
!ManifestMyBitMap commentStamp: '<historical>' prior: 0!
Please describe the package using the class comment of the included manifest class. The manifest class also includes other additional metadata for the package. These meta data are used by other tools such as the SmalllintManifestChecker and the critics Browser!

"-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "!

ManifestMyBitMap class
    instanceVariableNames: ''!

!ManifestMyBitMap class methodsFor: 'code-critics' stamp: 'JosephHalfmoon 7/9/2024 14:52'!
ruleLongMethodsRuleV1FalsePositive
    ^ #(#(#(#RGMethodDefinition #(#MyBitMapColor #setMandelbrotColorPIXa:b: #false)) #'2024-07-09T14:52:55.574211+09:00') )! !