https://github.com/arcsymer/fractal-paint
Fractal drawing app (Java 17, Swing) — Mandelbrot, Julia, Sierpinski, Koch snowflake, fractal tree; pure deterministic core with JUnit 5 tests
https://github.com/arcsymer/fractal-paint
fractals java julia-set junit5 mandelbrot maven portfolio swing
Last synced: about 3 hours ago
JSON representation
Fractal drawing app (Java 17, Swing) — Mandelbrot, Julia, Sierpinski, Koch snowflake, fractal tree; pure deterministic core with JUnit 5 tests
- Host: GitHub
- URL: https://github.com/arcsymer/fractal-paint
- Owner: arcsymer
- Created: 2026-06-22T15:31:21.000Z (7 days ago)
- Default Branch: main
- Last Pushed: 2026-06-22T19:07:54.000Z (7 days ago)
- Last Synced: 2026-06-22T21:08:35.859Z (7 days ago)
- Topics: fractals, java, julia-set, junit5, mandelbrot, maven, portfolio, swing
- Language: Java
- Size: 565 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Fractal Paint
[](https://github.com/arcsymer/fractal-paint/actions/workflows/ci.yml)



An interactive drawing app for classic geometric fractals, built in Java 17 with Swing.
The computation core is a pure-function library with no GUI dependency, so it's testable headless.
Ten fractals are implemented across three algorithmic families: escape-time sets, geometric
subdivision, and stochastic/L-system iteration.
---
## Problem / Solution
**Problem:** Fractal geometry looks great but is easy to get wrong in code. Common pitfalls
are mixing rendering with the math, non-deterministic output, and coordinate systems that
break at higher zoom levels.
**Solution:** A layered architecture separates the pure math (`core`) from rendering (`render`)
and interaction (`app`). The core layer is covered by property-based unit tests that check
mathematical invariants (exact triangle counts, segment counts) before any pixel is drawn.
---
## Fractals implemented
| Fractal | Family | Description | Count property |
|---|---|---|---|
| **Mandelbrot set** | Escape-time | z → z² + c from z₀ = 0; smooth (normalized) colouring | — (pixel-wise) |
| **Julia set** | Escape-time | z → z² + c from varying z₀; fixed c parameter; smooth colouring | — (pixel-wise) |
| **Burning Ship** | Escape-time | z → (\|Re z\| + i\|Im z\|)² + c; smooth colouring | — (pixel-wise) |
| **Newton fractal** | Escape-time | Newton's method on z³ − 1; coloured by basin of attraction | — (pixel-wise) |
| **Sierpinski triangle** | Geometric subdivision | Recursive midpoint subdivision; centre removed | triangles = **3ⁿ** |
| **Koch snowflake** | Geometric subdivision | Each edge replaced by 4 Koch-rule segments | segments = **3 · 4ⁿ** |
| **Fractal tree** | Geometric recursive | Symmetric binary branching | segments = **2^(d+1) − 1** |
| **Barnsley fern** | IFS (random) | 4-rule affine IFS; fixed PRNG seed → deterministic | points = **n** (exact) |
| **Dragon curve** | L-system | Axiom F; rules F→F+H, H→F-H; 90° turns | segments = **2ⁿ** |
| **Pythagoras tree** | Geometric recursive | Symmetric 45-45-90 square tree | segments = **4 × (2^(n+1) − 1)** |
---
## Architecture
```
fractal-paint/
├── src/
│ ├── main/java/com/arcsymer/fractalpaint/
│ │ ├── core/ # Pure math — no GUI, fully headless-testable
│ │ │ ├── Mandelbrot.java
│ │ │ ├── Julia.java
│ │ │ ├── BurningShip.java
│ │ │ ├── Newton.java
│ │ │ ├── SmoothColor.java (normalized iteration count)
│ │ │ ├── Sierpinski.java + Triangle.java
│ │ │ ├── KochSnowflake.java + Segment.java
│ │ │ ├── FractalTree.java
│ │ │ ├── BarnsleyFern.java + Point2D.java (IFS)
│ │ │ ├── DragonCurve.java (L-system)
│ │ │ └── PythagorasTree.java (geometric recursive)
│ │ ├── app/ # Swing GUI (JFrame + canvas + controls)
│ │ │ ├── Main.java
│ │ │ ├── FractalCanvas.java
│ │ │ ├── ControlPanel.java
│ │ │ └── FractalType.java
│ │ └── render/ # Headless PNG renderer (used by CI)
│ │ └── Renderer.java
│ └── test/java/com/arcsymer/fractalpaint/
│ ├── core/ (MandelbrotTest, JuliaTest, BurningShipTest, NewtonTest,
│ │ SmoothColorTest, SierpinskiTest, KochSnowflakeTest,
│ │ FractalTreeTest, BarnsleyFernTest, DragonCurveTest,
│ │ PythagorasTreeTest)
│ └── render/ (RendererTest)
├── docs/screenshots/ # PNGs generated by the CI render step
├── pom.xml
└── .github/workflows/ci.yml
```
**Layer rules:**
- `core` has no dependencies (no Swing, no ImageIO), only `java.lang` and `java.util`.
- `render` depends on `core` plus `javax.imageio` (stdlib).
- `app` depends on `core` plus Swing (stdlib).
- Tests depend only on `core` plus JUnit 5.
---
## Build & Run
**Requirements:** JDK 17+, Maven 3.8+
### Build (produces a runnable fat-JAR)
```bash
mvn -B verify
```
This runs all tests and packages a self-contained executable JAR at
`target/fractal-paint.jar`.
### Run the interactive GUI
Double-click `target/fractal-paint.jar`, or:
```bash
java -jar target/fractal-paint.jar
```
### Render all fractals to PNG (headless, no display needed)
```bash
java -jar target/fractal-paint.jar --render docs/screenshots
```
`java -jar target/fractal-paint.jar --help` prints usage. The renderer can also
be invoked directly:
```bash
java -Djava.awt.headless=true \
-cp target/classes \
com.arcsymer.fractalpaint.render.Renderer docs/screenshots
```
Produces all 10 PNGs: `mandelbrot.png`, `julia.png`, `burning_ship.png`,
`newton.png`, `sierpinski.png`, `koch.png`, `tree.png`, `barnsley_fern.png`,
`dragon_curve.png`, `pythagoras_tree.png`
---
## Tests
All core tests verify deterministic mathematical properties, not pixel output:
| Test class | Property verified |
|---|---|
| `MandelbrotTest` | Origin `(0,0)` → `maxIter`; point `(2,2)` → escapes at iteration 1; result ≤ maxIter always; `maxIter<1` throws |
| `JuliaTest` | Origin inside the basilica set c=(-1,0) stays bounded (periodic 0→-1→0); `(2,0)` escapes at iteration 1; unit-circle boundary for c=0; `maxIter<1` throws |
| `BurningShipTest` | Interior/exterior escape behaviour; differs from Mandelbrot; `maxIter<1` throws; smooth-count consistency |
| `NewtonTest` | Convergence to the three cube-roots of unity; three-fold basin symmetry; `maxIter<1` throws |
| `SmoothColorTest` | Interior → 0.0; exterior smooth count in (0, maxIter); monotonic in escape time |
| `SierpinskiTest` | `triangles(n) == 3ⁿ` for n=0..5; all vertices in bounding box |
| `KochSnowflakeTest` | `segments(n) == 3·4ⁿ` for n=0..5; level-0 is closed triangle; no degenerate segments |
| `FractalTreeTest` | `segments(d) == 2^(d+1)−1` for d=0..5; trunk geometry; branch symmetry; error on invalid input |
| `BarnsleyFernTest` | `points(n) == n` exactly; bounding box [−2.182,2.658]×[0,10]; fully deterministic (fixed seed); all finite |
| `DragonCurveTest` | `segments(n) == 2ⁿ` for n=0..5; level-0 is a single horizontal segment; all segments connected; equal length at any level |
| `PythagorasTreeTest` | `segments(n) == 4×(2^(n+1)−1)` for n=0..5; root square exact geometry; all coordinates finite; no degenerate segments |
Run with: `mvn test`
---
## Screenshots
| Mandelbrot | Julia |
|---|---|
|  |  |
| Burning Ship | Newton |
|---|---|
|  |  |
| Sierpinski | Koch Snowflake | Fractal Tree |
|---|---|---|
|  |  |  |
| Barnsley Fern | Dragon Curve | Pythagoras Tree |
|---|---|---|
|  |  |  |
---
## License
MIT