An open API service indexing awesome lists of open source software.

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

Awesome Lists containing this project

README

          

# Fractal Paint

[![CI](https://github.com/arcsymer/fractal-paint/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/arcsymer/fractal-paint/actions/workflows/ci.yml)
![tests](https://img.shields.io/badge/tests-174%20passing-brightgreen)
![java](https://img.shields.io/badge/Java-17-blue)
![maven](https://img.shields.io/badge/build-Maven-orange)

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 |
|---|---|
| ![Mandelbrot](docs/screenshots/mandelbrot.png) | ![Julia](docs/screenshots/julia.png) |

| Burning Ship | Newton |
|---|---|
| ![Burning Ship](docs/screenshots/burning_ship.png) | ![Newton](docs/screenshots/newton.png) |

| Sierpinski | Koch Snowflake | Fractal Tree |
|---|---|---|
| ![Sierpinski](docs/screenshots/sierpinski.png) | ![Koch](docs/screenshots/koch.png) | ![Tree](docs/screenshots/tree.png) |

| Barnsley Fern | Dragon Curve | Pythagoras Tree |
|---|---|---|
| ![Barnsley Fern](docs/screenshots/barnsley_fern.png) | ![Dragon Curve](docs/screenshots/dragon_curve.png) | ![Pythagoras Tree](docs/screenshots/pythagoras_tree.png) |

---

## License

MIT