Skip to content

Instantly share code, notes, and snippets.

@tivrfoa
Created June 15, 2025 17:41
Show Gist options
  • Save tivrfoa/2d6338740268166357ce018ceaa24ae1 to your computer and use it in GitHub Desktop.
Save tivrfoa/2d6338740268166357ce018ceaa24ae1 to your computer and use it in GitHub Desktop.
Mandelbrot Set JavaFX - Gemini
package com.example.mandelbrot;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.scene.image.PixelWriter;
import javafx.scene.input.MouseButton; // Import for mouse button detection
public class MandelbrotSet extends Application {
// --- Configuration ---
private static final int WIDTH = 800;
private static final int HEIGHT = 600;
private static final int MAX_ITERATIONS = 500; // Increase for more detail, decrease for faster rendering
private static final double ZOOM_FACTOR = 0.8; // Factor by which to zoom in (0.8 means view becomes 80% of original)
// Current view of the complex plane (these define the boundaries of the Mandelbrot rendering)
private double realMin;
private double realMax;
private double imaginaryMin;
private double imaginaryMax;
@Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Mandelbrot Set with Zoom");
// Initialize the view to the default Mandelbrot set range
resetView();
// Create a canvas to draw on
Canvas canvas = new Canvas(WIDTH, HEIGHT);
GraphicsContext gc = canvas.getGraphicsContext2D();
// Add mouse click event handler for zooming
canvas.setOnMouseClicked(event -> {
if (event.getButton() == MouseButton.PRIMARY) { // Left click to zoom in
// Get the clicked pixel coordinates on the canvas
double mouseX = event.getX();
double mouseY = event.getY();
// Convert the pixel coordinates to complex plane coordinates within the current view
// This determines the new center of our zoomed view.
double clickedReal = realMin + (mouseX / WIDTH) * (realMax - realMin);
double clickedImaginary = imaginaryMin + (mouseY / HEIGHT) * (imaginaryMax - imaginaryMin);
// Calculate the current width and height of the complex plane view
double currentRealRange = realMax - realMin;
double currentImaginaryRange = imaginaryMax - imaginaryMin;
// Determine the new, smaller ranges after applying the zoom factor
double newRealRange = currentRealRange * ZOOM_FACTOR;
double newImaginaryRange = currentImaginaryRange * ZOOM_FACTOR;
// Update the boundaries of the complex plane to center on the clicked point
// and use the new, smaller ranges.
realMin = clickedReal - (newRealRange / 2.0);
realMax = clickedReal + (newRealRange / 2.0);
imaginaryMin = clickedImaginary - (newImaginaryRange / 2.0);
imaginaryMax = clickedImaginary + (newImaginaryRange / 2.0);
// Redraw the Mandelbrot set with the new zoomed view
drawMandelbrot(gc);
} else if (event.getButton() == MouseButton.SECONDARY) { // Right click to reset view
// Reset the complex plane view to its initial, default state
resetView();
// Redraw the Mandelbrot set after resetting the view
drawMandelbrot(gc);
}
});
// Draw the initial Mandelbrot set when the application starts
drawMandelbrot(gc);
// Set up the scene and show the stage
Pane root = new Pane();
root.getChildren().add(canvas);
Scene scene = new Scene(root, WIDTH, HEIGHT);
primaryStage.setScene(scene);
primaryStage.setResizable(false); // Set to false to maintain fixed canvas size
primaryStage.show();
}
/**
* Resets the complex plane view to the default, wide range for the Mandelbrot set.
*/
private void resetView() {
realMin = -2.5; // Default minimum real value
realMax = 1.0; // Default maximum real value
imaginaryMin = -1.5; // Default minimum imaginary value
imaginaryMax = 1.5; // Default maximum imaginary value
}
/**
* Iterates through each pixel on the canvas, maps it to the current complex plane view,
* and colors it based on the Mandelbrot set algorithm.
* @param gc The GraphicsContext of the canvas to draw upon.
*/
private void drawMandelbrot(GraphicsContext gc) {
PixelWriter pixelWriter = gc.getPixelWriter();
// Calculate the current width and height of the complex plane that is being displayed
double realRange = realMax - realMin;
double imaginaryRange = imaginaryMax - imaginaryMin;
// Loop through every pixel on the canvas
for (int x = 0; x < WIDTH; x++) {
for (int y = 0; y < HEIGHT; y++) {
// Map the current pixel coordinates (x, y) to a point in the complex plane
// based on the current real and imaginary ranges.
double real = realMin + (double)x / WIDTH * realRange;
double imaginary = imaginaryMin + (double)y / HEIGHT * imaginaryRange;
// Initialize complex numbers for the Mandelbrot iteration
Complex c = new Complex(real, imaginary); // The constant 'c' for the current pixel
Complex z = new Complex(0, 0); // The iterated 'z' value, starts at (0,0)
int iterations = 0;
// The core Mandelbrot algorithm: z = z^2 + c
// Continue iterating as long as the magnitude of z is less than 2
// and the maximum number of iterations has not been reached.
while (z.abs() < 2 && iterations < MAX_ITERATIONS) {
z = z.multiply(z).add(c);
iterations++;
}
// Color the pixel based on the number of iterations it took to escape (or not escape)
if (iterations == MAX_ITERATIONS) {
// If MAX_ITERATIONS was reached, the point is likely in the Mandelbrot set.
// Color it black.
pixelWriter.setColor(x, y, Color.BLACK);
} else {
// If the point escaped, color it based on how quickly it escaped.
// Using HSB (Hue, Saturation, Brightness) color space creates a smooth gradient effect.
double hue = 360.0 * iterations / MAX_ITERATIONS; // Hue varies based on iterations
pixelWriter.setColor(x, y, Color.hsb(hue, 0.9, 0.9)); // Solid saturation and brightness
}
}
}
}
/**
* A helper class to represent complex numbers and their basic operations (addition, multiplication, absolute value).
* This makes the Mandelbrot calculation more readable and mathematically accurate.
*/
private static class Complex {
private final double real;
private final double imaginary;
public Complex(double real, double imaginary) {
this.real = real;
this.imaginary = imaginary;
}
// Returns the magnitude (absolute value) of the complex number: |a + bi| = sqrt(a^2 + b^2)
public double abs() {
return Math.sqrt(real * real + imaginary * imaginary);
}
// Returns the sum of this complex number and another complex number: (a + bi) + (c + di) = (a+c) + (b+d)i
public Complex add(Complex other) {
return new Complex(this.real + other.real, this.imaginary + other.imaginary);
}
// Returns the product of this complex number and another: (a + bi) * (c + di) = (ac - bd) + (ad + bc)i
public Complex multiply(Complex other) {
double newReal = this.real * other.real - this.imaginary * other.imaginary;
double newImaginary = this.real * other.imaginary + this.imaginary * other.real;
return new Complex(newReal, newImaginary);
}
}
public static void main(String[] args) {
launch(args);
}
}
@tivrfoa
Copy link
Author

tivrfoa commented Jun 15, 2025

package com.example.mandelbrot;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MandelbrotSetGrok extends Application {

    // Configuration
    private static final int WIDTH = 800;
    private static final int HEIGHT = 600;
    private static final int MAX_ITERATIONS = 500; // Adjust for detail vs. speed
    private static final double ZOOM_FACTOR = 0.5; // 50% zoom per click

    // Complex plane ranges
    private double realMin = -2.5;
    private double realMax = 1.0;
    private double imagMin = -1.5;
    private double imagMax = 1.5;

    private Canvas canvas;
    private GraphicsContext gc;
    private final ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

    @Override
    public void start(Stage primaryStage) {
        primaryStage.setTitle("Mandelbrot Set");

        // Initialize canvas
        canvas = new Canvas(WIDTH, HEIGHT);
        gc = canvas.getGraphicsContext2D();

        // Zoom functionality via mouse clicks
        canvas.setOnMouseClicked(event -> {
            double mouseX = event.getX();
            double mouseY = event.getY();

            // Map click to complex plane coordinates
            double real = realMin + (mouseX / WIDTH) * (realMax - realMin);
            double imag = imagMin + (mouseY / HEIGHT) * (imagMax - imagMin);

            // Zoom in (left-click) or out (right-click)
            double zoom = (event.getButton() == MouseButton.PRIMARY) ? ZOOM_FACTOR : 1 / ZOOM_FACTOR;
            double newWidth = (realMax - realMin) * zoom;
            double newHeight = (imagMax - imagMin) * zoom;

            // Update ranges
            realMin = real - (mouseX / WIDTH) * newWidth;
            realMax = real + (1 - mouseX / WIDTH) * newWidth;
            imagMin = imag - (mouseY / HEIGHT) * newHeight;
            imagMax = imag + (1 - mouseY / HEIGHT) * newHeight;

            // Re-render
            renderMandelbrot();
        });

        // Set up scene
        Pane root = new Pane(canvas);
        Scene scene = new Scene(root, WIDTH, HEIGHT);
        primaryStage.setScene(scene);
        primaryStage.setResizable(false);
        primaryStage.show();

        // Initial render
        renderMandelbrot();
    }

    /**
     * Launches a background thread to compute and draw the Mandelbrot set.
     */
    private void renderMandelbrot() {
        new Thread(() -> {
            WritableImage image = computeMandelbrotImage();
            Platform.runLater(() -> gc.drawImage(image, 0, 0));
        }).start();
    }

    /**
     * Computes the Mandelbrot set in parallel, returning a WritableImage.
     */
    private WritableImage computeMandelbrotImage() {
        WritableImage image = new WritableImage(WIDTH, HEIGHT);
        PixelWriter pixelWriter = image.getPixelWriter();
        int numThreads = Runtime.getRuntime().availableProcessors();
        List<Callable<Void>> tasks = new ArrayList<>();

        // Divide rows among threads
        for (int i = 0; i < numThreads; i++) {
            final int startRow = i * (HEIGHT / numThreads);
            final int endRow = (i == numThreads - 1) ? HEIGHT : (i + 1) * (HEIGHT / numThreads);
            tasks.add(() -> {
                for (int y = startRow; y < endRow; y++) {
                    for (int x = 0; x < WIDTH; x++) {
                        double real = realMin + (x / (double) WIDTH) * (realMax - realMin);
                        double imaginary = imagMin + (y / (double) HEIGHT) * (imagMax - imagMin);
                        Color color = computeColor(real, imaginary);
                        pixelWriter.setColor(x, y, color);
                    }
                }
                return null;
            });
        }

        // Execute tasks and wait for completion
        try {
            executor.invokeAll(tasks);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }

        return image;
    }

    /**
     * Calculates the color for a point based on Mandelbrot iteration count.
     */
    private Color computeColor(double cReal, double cImaginary) {
        double zReal = 0;
        double zImaginary = 0;
        int iterations = 0;

        // Iterate until escape or max iterations
        while (zReal * zReal + zImaginary * zImaginary < 4 && iterations < MAX_ITERATIONS) {
            double tempReal = zReal * zReal - zImaginary * zImaginary + cReal;
            zImaginary = 2 * zReal * zImaginary + cImaginary;
            zReal = tempReal;
            iterations++;
        }

        // Color based on escape time
        if (iterations == MAX_ITERATIONS) {
            return Color.BLACK;
        } else {
            double hue = 360.0 * iterations / MAX_ITERATIONS;
            return Color.hsb(hue, 0.9, 0.9);
        }
    }

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment