使用自己的 Complex 在 JavaFX 中设置 Mandelbrot class

Mandelbrot Set in JavaFX using own Complex class

我正在尝试使用我自己的 Complex class 在 Java 中生成一个 Mandelbrot 集。它需要可缩放并具有着色功能,我还没有实现。我正在使用我的老师编写的算法,但出于某种原因,我得到的图像与 Mandelbrot 集不同。我尝试了各种修改,但无法找到解决问题的方法。

CanvasFX.java

package mandelbrot;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class CanvasFX extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {
        Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
        primaryStage.setTitle("Mandelbrot Set");
        primaryStage.setScene(new Scene(root));
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

Controller.java

package mandelbrot;

import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.effect.BlendMode;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.image.WritableImage;
import javafx.scene.image.PixelWriter;

public class Controller extends Complex {
    public Canvas canvas;
    private GraphicsContext gc;
    private double x1, y1, x2, y2;
    public void initialize() {
        final int size = 512;

        gc = canvas.getGraphicsContext2D();
        clear(gc);
        Complex a = new Complex(-2.5, 2.5);
        Complex b = new Complex(2.5, -2.5);
        WritableImage wr = new WritableImage(size, size);
        drawMandelbrot(wr, a, b,512, 512);
    }

    private void clear(GraphicsContext gc) {
        gc.setFill(Color.WHITE);
        gc.setGlobalBlendMode(BlendMode.SRC_OVER);
        gc.fillRect(0, 0, canvas.getWidth(), canvas.getHeight());
    }

    private void rect(GraphicsContext gc) {
        double x = x1;
        double y = y1;
        double width = x2 - x1;
        double height = y2 - y1;
        if (width < 0) {
            x = x2;
            width = -width;
        }
        if (height < 0) {
            y = y2;
            height = -height;
        }
        gc.strokeRect(x + 0.5, y + 0.5, width, height);
    }

    public void mouseMoves(MouseEvent mouseEvent) {
        double x = mouseEvent.getX();
        double y = mouseEvent.getY();
        gc.setGlobalBlendMode(BlendMode.DIFFERENCE);
        gc.setStroke(Color.WHITE);
        rect(gc);
        x2 = x;
        y2 = y;
        rect(gc);
    }

    public void mousePressed(MouseEvent mouseEvent) {
        x1 = mouseEvent.getX();
        y1 = mouseEvent.getY();
        x2 = x1;
        y2 = y1;
    }

    public void mouseReleased(MouseEvent mouseEvent) {
        rect(gc);
        System.out.format("%f %f %f %f\n", x1, y1, x2, y2);
    }

    private void drawMandelbrot(WritableImage wr, Complex a, Complex b, int height, int width) {
        final int maxIterations = 200;
        final double pixelWidth = (b.re() - a.re()) / width;
        final double pixelHeight = (b.im() - a.im()) / height;
        int iteration;
        PixelWriter pw = wr.getPixelWriter();

        for (int row = 0; row < height; row++) {
            for (int column = 0; column < width; column++) {
                Complex z = new Complex(); // z = 0
                Complex c = new Complex(); // c = 0
                c.setRe(a.re() + pixelWidth * column); // set real part of c
                c.setIm(a.im() + pixelHeight * row); // set imaginary part of c
                iteration = 0;

                while (Complex.abs(z) <= 4 && iteration < maxIterations) {
                    z.mul(z); // z to the power of 2
                    z.add(c); // z plus c
                    iteration++;
                }
                if (iteration < maxIterations)
                    pw.setArgb(column, row, 0xFFFFFFFF);
                else
                    pw.setArgb(row, column, 0xFF0000FF);
            }
        }

        gc.setGlobalBlendMode(BlendMode.SRC_OVER);
        gc.drawImage(wr, 0, 0, 512, 512);
    }
}

sample.fxml

<?import javafx.scene.canvas.Canvas?>
<?import javafx.scene.layout.*?>
<GridPane fx:controller="mandelbrot.Controller" xmlns:fx="http://javafx.com/fxml" alignment="center" hgap="10"
    vgap="10" stylesheets="/tutorial/main.css">

    <Canvas fx:id="canvas" GridPane.columnIndex="0" GridPane.rowSpan="6" width="512" height="512"
    onMouseDragged="#mouseMoves" onMousePressed="#mousePressed" onMouseReleased="#mouseReleased"/>

</GridPane>

Complex.java

package mandelbrot;

import java.text.DecimalFormat;
import java.text.NumberFormat;

public class Complex implements Field<Complex> {
    public double r, i; // Real and imaginary parts

    public Complex() {
        this.r = 0;
        this.i = 0;
    }

    public Complex(double real) {
        this.r = real;
        this.i = 0;
    }

    public Complex(double real, double imaginary) {
        this.r = real;
        this.i = imaginary;
    }

    public Complex(Complex c) {
        this.r = c.r;
        this.i = c.i;
    }

    public Complex(String s) {
        boolean isPositive = true;
        if(s.charAt(0) == '-') { // Check if the first part is negative
            isPositive = false;
            s = s.substring(1); // Remove first minus from the string
        }
        if(!(s.contains("+") || s.contains("-"))) { // Check if the complex number consists only of real or imaginary part
            if(s.contains("i")) { // If consists only of imaginary part
                // If imaginary part is equal to 1
                if(s.charAt(0) == 'i') this.i = Double.parseDouble((isPositive ? "+" : "-") + "1");
                else this.i = Double.parseDouble((isPositive ? "+" : "-") +
                        s.substring(0, s.length() - 1));
            }
            else this.r = Double.parseDouble((isPositive ? "+" : "-") + s);
        }
        else {
            boolean isImaginaryPositive = true;
            if (s.contains("-")) isImaginaryPositive = false; // Check if the imaginary part if negative
            String[] split = s.split("[+-]"); // Split the string to real and imaginary part
            this.r = Double.parseDouble((isPositive ? "+" : "-") + split[0]);
            // If imaginary part is equal to 1
            if(split[1].charAt(0) == 'i') this.i = Double.parseDouble((isImaginaryPositive ? "+" : "-") + "1");
            else this.i = Double.parseDouble((isImaginaryPositive ? "+" : "-") +
                    split[1].substring(0, split[1].length() - 1));
        }
    }

    @Override
    public Complex add(Complex b) {
        this.r = this.r + b.r;
        this.i = this.i + b.i;
        return this;
    }

    @Override
    public Complex sub(Complex b) {
        this.r = this.r - b.r;
        this.i = this.i - b.i;
        return this;
    }

    @Override
    public Complex mul(Complex b) {
        double real = this.r;
        double imaginary = this.i;
        this.r = real * b.r - imaginary * b.i;
        this.i = real * b.i + imaginary * b.r;
        return this;
    }

    @Override
    public Complex div(Complex b) throws IllegalArgumentException {
        if(b.r == 0 && b.i == 0) throw new IllegalArgumentException("Can't divide by 0.");
        double real = this.r;
        double imaginary = this.i;
        double divisor = b.r * b.r + b.i * b.i;
        this.setRe((real * b.r + imaginary * b.i) / divisor);
        this.setIm((imaginary * b.r - real * b.i) / divisor);
        return this;
    }

    public double abs() {
        return Math.hypot(r, i);
    }

    public double sqrAbs() {
        return Math.sqrt(abs());
    }

    public double phase() {
        if(this.r == 0 && this.i == 0) return -1;
        double phi = 0;
        if(this.r == 0) {
            if(this.i > 0) {
                phi = Math.PI / 2;
            }
            if(this.i < 0) {
                phi = -(Math.PI / 2);
            }
        }
        if(this.r > 0) {
            phi = Math.atan(this.i / this.r);
        }
        if(this.r < 0) {
            phi = Math.atan(this.i / this.r) + Math.PI;
        }
        return phi;
    }

    public double re() {
        return r;
    }

    public double im() {
        return i;
    }

    public static Complex add(Complex a, Complex b) {
        Complex c = new Complex();
        c.setRe(a.r + b.r);
        c.setIm(a.i + b.i);
        return c;
    }

    public static Complex sub(Complex a, Complex b) {
        Complex c = new Complex();
        c.setRe(a.r - b.r);
        c.setIm(a.i - b.i);
        return c;
    }

    public static Complex mul(Complex a, Complex b) {
        Complex c = new Complex();
        c.setRe(a.r * b.r - a.i * b.i);
        c.setIm(a.r * b.i + a.i * b.r);
        return c;
    }

    public static Complex div(Complex a, Complex b) throws IllegalArgumentException {
        if(b.r == 0 && b.i == 0) throw new IllegalArgumentException("Can't divide by 0.");
        Complex c = new Complex();
        double divisor = b.r * b.r + b.i * b.i;
        c.setRe((a.r * b.r + a.i * b.i) / divisor);
        c.setIm((a.i * b.r - a.r * b.i) / divisor);
        return c;
    }

    public static double abs(Complex a) {
        return Math.hypot(a.r, a.i);
    }

    public static double sqrtabs(Complex a) {
        return Math.sqrt(abs(a));
    }

    public static double phase(Complex a) {
        if(a.r == 0 && a.i == 0) return -1;
        double phi = 0;
        if(a.r == 0) {
            if(a.i > 0) {
                phi = Math.PI / 2;
            }
            if(a.i < 0) {
                phi = -(Math.PI / 2);
            }
        }
        if(a.r > 0) {
            phi = Math.atan(a.i / a.r);
        }
        if(a.r < 0) {
            phi = Math.atan(a.i / a.r) + Math.PI;
        }
        return phi;
    }

    public static double re(Complex a) {
        return a.r;
    }

    public static double im(Complex a) {
        return a.i;
    }

    @Override
    public String toString() {
        NumberFormat formatter = new DecimalFormat("#0.00");
        if(i == 0) return formatter.format(r);
        if(r == 0) return formatter.format(i) + "i";
        if(i > 0) return formatter.format(r) + "+" + formatter.format(i) + "i";
        return formatter.format(r) + formatter.format(i) + "i";
    }

    static Complex valueOf(String s) {
        Complex complex = new Complex(s);
        return complex;
    }

    void setRe(double real) {
        this.r = real;
    }

    void setIm(double imaginary) {
        this.i = imaginary;
    }

    void setVal(Complex c) {
        this.r = c.r;
        this.i = c.i;
    }

    void setVal(double real, double imaginary) {
        this.r = real;
        this.i = imaginary;
    }
}

Output

1) 在你的 Complex-class 中替换

@Override
public Complex mul(Complex b) {
    double real = this.r;
    double imaginary = this.i;
    this.r = real * b.r - imaginary * b.i;
    this.i = real * b.i + imaginary * b.r;
    return this;
}

@Override
public Complex mul(Complex b) {
    double real = this.r;
    double imaginary = this.i;
    double real_b = b.r;
    double imaginary_b = b.i;
    this.r = real * real_b - imaginary * imaginary_b;
    this.i = real * imaginary_b + imaginary * real_b;
    return this;
}

或者在您的 drawMandelbrot 方法中替换

z.mul(z);

z = Complex.mul(z, z);

这导致:

2) 如评论中所述,将 drawMandelbrot 方法替换为 maxIterations-case

pw.setArgb(row, column, 0xFF0000FF);

pw.setArgb(column, row, 0xFF0000FF);

这导致: