Mandelbrot

 

スクリプト

MandelbrotWorker.java

import java.util.concurrent.BlockingQueue;
 
import com.sun.javafx.runtime.async.AbstractAsyncOperation;
import com.sun.javafx.runtime.async.AsyncOperationListener;
 
public class MandelbrotWorker extends AbstractAsyncOperation<Object> {
    // イベントディスパッチスレッドと居有するキュー
    // スレッドセーフなBlockingQueueインタフェースを使用する
    private BlockingQueue<Point> queue;
    
    public MandelbrotWorker(BlockingQueue<Point> queue, AsyncOperationListener<Object> listener) {
        super(listener);
         
        // イベントディスパッチスレッドとキューを共有
        this.queue = queue;
    }
 
    // 非同期に行う処理
    @Override
    public Object call() throws InterruptedException {
        for (double x = -2.0; x <= 0.6; x += 0.005) {
            for (double y = -1.2; y <= 1.2; y += 0.005) {
                Point c = new Point(x, y, 0);
                Point z = new Point(0.0, 0.0, 0);
 
                while (true) {
                    // 漸化式の計算
                    z = calcNextPoint(z, c);
 
                    // zの大きさが100,000以上であれば無限大と見なす
                    // また、215回以上のイテレーションを繰り返しても
                    // 100,000以上にならなければマンデルブロ集合に属していると見なす
                    if (z.length() > 100000 || z.number > 215) {
                        break;
                    }
                }
                // イテレーション数をコピーする
                c.number = z.number;
 
                // 計算結果をキューに入れる
                queue.put(c);
            }
        }
 
        return null;
    }
 
    // マンデルブロの漸化式
    private Point calcNextPoint(Point z, Point c) {
        double x = z.x * z.x - z.y * z.y + c.x;
        double y = 2 * z.x * z.y + c.y;
 
        return new Point(x, y, z.number + 1);
    }
}

Point.java

// マンデルブロの計算結果を格納するクラス
public class Point {
    public double x;
    public double y;
 
    // 無限大と見なしたイテレーション回数
    // 最大215回とする
    public int number;
 
    public Point(double x, double y, int number) {
        this.x = x;
        this.y = y;
        this.number = number;
    }
 
    public double length() {
        return x * x + y * y;
    }
}

Mandelbrot.fx

import javafx.async.AbstractAsyncOperation;
 
import java.lang.Object;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
 
import com.sun.javafx.runtime.async.AsyncOperationListener;
 
public class Mandelbrot extends AbstractAsyncOperation {
    // スレッド間でデータを受け渡しするキュー
    // スレッドセーフなBlockingQueueインタフェースを使用する
    var queue: BlockingQueue;
     
    // 非同期処理を行うJavaクラス
    var worker: MandelbrotWorker;
 
    public override function start(): Void {
        // スレッド間の計算結果の引渡しにキューを使用する
        // BlockingQueueインタフェースのコンクリートクラスとして
        // LinkedBlockingQueueクラスを使用する
        queue = new LinkedBlockingQueue();
 
        // Javaの非同期処理部分
        worker = new MandelbrotWorker(queue, listener);
        // 非同期処理の開始
        worker.start();
    }
 
    public override function cancel(): Void {
        // 非同期処理のキャンセル
        worker.cancel();
    }
 
    public override function onCompletion(value: Object): Void {}
 
    // キューから計算結果を取り出す
    public function poll(): Point {
        queue.poll() as Point;
    }
}

MandelbrotViewer.fx

import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.ext.swing.SwingButton;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
 
import java.awt.image.BufferedImage;
 
// 色のインデックス
var colors: Integer[] = for(i in [0..7]) {
    for (blue in [0, 0x66, 0xff], green in [0, 0x66, 0xff], red in [0, 0x66,0xff]) {
        var color = (0xff - red) * 0x10000 + (0xff - green) * 0x100 + (0xff - blue);
    }
}
 
// Imageクラスはピクセルにアクセスできないので、BufferedImageクラスを使用する
var bufferedImage: BufferedImage;
var image: Image;
 
// 非同期でマンデルブロの計算を行うクラス
var mandelbrot: Mandelbrot;
 
// 非同期で行われる処理から結果を受け取るためのタイムライン
var timeline: Timeline;
timeline = Timeline {
    repeatCount: Timeline.INDEFINITE
    keyFrames: [
        KeyFrame {
            time: 0.0s
            action: function() {
                while(true) {
                    // キューから計算結果の取り出し
                    var point = mandelbrot.poll();
                    if (point != null) {
                        // 計算結果からイメージに色をセットする
                        var x = (point.x * 200 + 400) as Integer;
                        var y = (point.y * 200 + 240) as Integer;
                        var color = colors[point.number];
                        bufferedImage.setRGB(x, y, color);
                    } else {
                        // キューが空であれば、0.1秒待つ
                        break;
                    }
                }
                // BufferedImageからImageオブジェクトを生成する
                image = Image.fromBufferedImage(bufferedImage);
            }
        },
        KeyFrame {
            time: 0.1s
            action: function() {
                // 非同期処理が終了していたら、timelineもストップさせる
                if (mandelbrot.done) {
                    timeline.stop();
                }
            }
        }
    ]
}
 
Stage {
    title: "Mandelbrot"
    scene: Scene {
        width: 520
        height: 480
        fill: Color.BLACK
        content: [
            ImageView {
                image: bind image
            },
            SwingButton {
                text: "DRAW"
                action: function() {
                    timeline.stop();
                    if (mandelbrot != null) {
                        // 非同期で処理を行っている場合はキャンセル
                        mandelbrot.cancel();
                    }
 
                    // イメージとMandelbrotオブジェクトを新たに生成し、
                    // 非同期処理を開始する
                    bufferedImage = new BufferedImage(520, 480, BufferedImage.TYPE_INT_RGB);
                    mandelbrot = Mandelbrot {};
                    timeline.play();
                }
            }
        ]
    }
}