Generating Mandelbrot Set Images with Kotlin
There's something deeply satisfying about generating fractal images through code. The Mandelbrot set, in particular, has fascinated mathematicians and programmers alike since Benoit Mandelbrot first visualized it in 1980. I built a Kotlin library to explore this mathematical wonder, and I'd like to share both the results and the approach.
What is the Mandelbrot Set?
The Mandelbrot set is defined by a deceptively simple formula: for each point c in the complex plane, iterate z = z² + c starting from z = 0. If the sequence stays bounded (doesn't escape to infinity), that point is in the Mandelbrot set.
The magic happens at the boundary - points that take longer to escape produce the intricate, infinitely detailed patterns that make fractals so mesmerizing.
Why Kotlin?
I chose Kotlin for this project for several reasons:
- Coroutines - Mandelbrot generation is embarrassingly parallel. Each pixel can be computed independently, making it perfect for concurrent processing. Kotlin's coroutines provide an elegant way to parallelize the computation.
- Clean syntax - Complex number operations and iteration logic read naturally in Kotlin.
- JVM performance - The heavy numerical computation benefits from JVM optimizations.
Sample Renders
Here are some images generated by the library at various zoom levels and color mappings:
The classic Mandelbrot set view - the iconic "bug" shape with infinite complexity at its edges.
A Julia set render - a close relative of the Mandelbrot set, also supported by the library.
Different color mappings highlight various aspects of the escape-time algorithm.
The deeper you zoom, the more intricate structures emerge.
How It Works
The core algorithm is straightforward:
fun isInMandelbrotSet(c: Complex, maxIterations: Int): Int {
var z = Complex(0.0, 0.0)
for (i in 0 until maxIterations) {
z = z * z + c
if (z.magnitudeSquared() > 4.0) {
return i // Escaped - return iteration count for coloring
}
}
return maxIterations // Didn't escape - in the set
}
The iteration count for escaped points determines the color, creating those beautiful gradient bands around the set.
Using Kotlin coroutines, the image is divided into chunks that are processed concurrently:
coroutineScope {
for (chunk in imageChunks) {
launch {
computeChunk(chunk)
}
}
}
This approach scales well across CPU cores, significantly reducing render times for high-resolution images.
Anti-Aliasing with Super Sampling
One challenge with fractal rendering is aliasing - the jagged edges and moiré patterns that appear when infinite detail meets finite pixels. This is especially noticeable at the boundary of the Mandelbrot set where detail exists at every scale.
To address this, I implemented super sampling anti-aliasing (SSAA). Instead of computing a single point per pixel, we sample multiple points within each pixel and take the median value:
fun computePixelWithSuperSampling(x: Int, y: Int, samples: Int): Double {
val values = mutableListOf<Double>()
for (sy in 0 until samples) {
for (sx in 0 until samples) {
// Sub-pixel offset
val offsetX = (sx + 0.5) / samples
val offsetY = (sy + 0.5) / samples
val c = pixelToComplex(x + offsetX, y + offsetY)
values.add(calculatePoint(c))
}
}
values.sort()
val mid = values.size / 2
return if (values.size % 2 == 0) {
(values[mid - 1] + values[mid]) / 2.0
} else {
values[mid]
}
}
With a 2x2 or 4x4 sample grid, edges become smooth and the fine details render more accurately. The trade-off is computation time - 4x4 super sampling means 16 times more calculations per pixel. But combined with coroutines for parallelization, the results are worth it for high-quality renders.
Try It Yourself
The full source code is available on GitHub: Mandelbrot-Kotlin
The project is licensed under Apache 2.0, so feel free to use it, modify it, or learn from it. The library is designed to be extensible - you can experiment with different fractal formulas, color schemes, and rendering parameters.
Interactive Features
The library includes an interactive viewer built with Java Swing that supports:
- Real-time zooming and panning - explore the infinite detail of these fractals
- Multiple color palettes - right-click to cycle through different color mappings with instant re-rendering
What's Next?
Ideas I'm considering for future enhancements:
- GPU acceleration - using Kotlin/Native or compute shaders for even faster rendering
- Arbitrary precision support - replacing Double with BigDecimal or similar to enable deep zooming. Currently, Double precision limits how far you can zoom before pixelation artifacts appear due to floating-point precision loss
Fractals remind us that simple rules can produce infinite complexity - a lesson that applies well beyond mathematics.
Happy coding, and happy exploring!