A high-performance Java port of Michael Fogleman's primitive. This project reproduces images using geometric primitives (triangles, lines, curves) by hill-climbing optimization.
It transforms raster images (PNG/JPG) into abstract vector art (SVG) and simplified raster approximations.
Imagine a "blind" painter trying to recreate the Mona Lisa by layering thousands of random, semi-transparent triangles onto a canvas. For every single shape added, the computer calculates the mathematical difference between the current canvas and the original photo using a metric called Root Mean Square Error. This metric squares the difference between the color values of corresponding pixels to penalize large errors heavily, condensing the total visual mismatch into a single number. If a random shape makes the canvas look even slightly more like the target by lowering this error score, it is kept; otherwise, it is immediately discarded. This process repeats millions of times and slowly evolves a chaotic mess into a recognizable reproduction through sheer brute force. In computer science, this is called Hill Climbing because the algorithm treats similarity as altitude, blindly taking steps uphill toward a better match and never accepting a step down. It is a greedy optimization method that relies on high-speed trial and error rather than artistic intent to find the perfect image.
- High Performance: Designed with a "Zero-Allocation" hot path. Uses a Structure of Arrays (SoA) pattern (ScanlineBuffer) to avoid Garbage Collector thrashing during the millions of mutations required for image generation.
- Multi-Core Parallelism: Uses an "embarrassingly parallel" architecture where independent workers race to find the best shape for each step.
- Multiple Primitives: Supports Triangles, Thick Lines, Quadratic Bezier Curves, Rotated Rectangles, Polylines, Ellipses, and a Combo mode.
- Dual Output: Generates both a raster preview (.png) and an infinite-resolution vector file (.svg) automatically.
- Graphical User Interface: A Swing-based interactive GUI for real-time visualization, configuration, and control.
- Analytical Color Solving: Mathematically calculates the optimal color for every shape to minimize Root Mean Square Error (RMSE), rather than guessing colors randomly.
- Java 17 or higher (Required for record types and modern switch syntax).
- Maven 3.6+
Clone the repository and build with Maven:
mvn clean package
You can run the resulting JAR file directly:
To enable the hardware-accelerated Vector API, you must add the incubator module flag:
java --add-modules jdk.incubator.vector -jar target/primitive-1.0-SNAPSHOT.jar [options] <input_file> <output_file> <num_shapes> [mode]Note: Without this flag, the application will crash.
| Argument | Description | Example |
|---|---|---|
| input_file | Path to the source image (PNG/JPG). | monalisa.png |
| output_file | Path for the result (PNG). An SVG will also be saved with the same name. | output.png |
| num_shapes | Number of geometric shapes to generate. | 200 |
| mode | (Optional) Shape type: TRIANGLE, LINE, BEZIER, RECT, POLYLINE, ELLIPSE, COMBO. Default: TRIANGLE | COMBO |
| Flag | Description | Default |
|---|---|---|
| `--cores <number | all>` | Number of worker threads to use. |
To launch the Graphical User Interface:
java --add-modules jdk.incubator.vector -cp target/primitive-1.0-SNAPSHOT.jar org.trostheide.primitive.gui.PrimitiveFrameGUI Features:
- Real-time Visualization: Watch the image evolve step-by-step.
- Drag & Drop: Simply drag an image file onto the window to open it.
- Process Log: A live scrolling log shows exactly what the optimizer is doing (scores, time per shape).
- Controls: Start/Stop button to pause or interrupt generation.
- Configuration: Easy-to-use sliders and dropdowns for Mode, Shapes, and Workers.
- Export: Save the current state to PNG at any time. (Automatically saves a matching .svg vector file!)
For convenience, use the provided shell scripts:
Run with default Triangle mode:
./run_primitive.sh 500
Run with specific Shape Modes:
./run_shapes.sh rect 200
./run_shapes.sh polyline 150
./run_shapes.sh combo 500
Run GUI:
./run_gui.sh
This project is not a direct syntax translation of the Go original; it is re-engineered for the JVM.
A naive Java port using objects (e.g., new Scanline()) inside the optimization loop would trigger massive Garbage Collection (GC) pauses, as the algorithm generates millions of candidate shapes per second.
- Solution: We use a ScanlineBuffer class that wraps three primitive integer arrays (y[], x1[], x2[]).
- Benefit: Zero object allocation during the rasterization and scoring phase. The buffer is reset() and reused for every mutation.
The image is constructed one shape at a time (Greedy selection).
- Initialization: A shape is generated with random coordinates.
- Mutation Loop:
- The shape is slightly mutated (vertex moved, width changed).
- It is rasterized into the ScanlineBuffer.
- The "Energy Delta" (improvement in RMSE) is calculated.
- If the mutation improves the image, it is kept; otherwise, it is reverted.
- Commit: After N iterations (e.g., 1000), the best shape found is permanently drawn onto the canvas.
The process is parallelized using a ThreadPoolExecutor.
-
If you request 1 shape, the system spawns N workers (where N = CPU cores).
-
Each worker runs an independent Hill Climbing simulation on its own thread-local buffers.
-
The main thread collects all results, picks the global best shape, and commits it.
-
The main thread collects all results, picks the global best shape, and commits it.
The "hot path" of the application—calculating the root-mean-square error between the target image and the generated candidates—has been optimized using the Java Vector API (Incubator).
- Mechanism: Processes 8 or 16 pixels simultaneously (depending on CPU AVX/AVX-512 support) instead of one by one.
- Result:
- ~2.6x speedup on 80-core machine.
- ~1.9x speedup on single core.
- Configuration: A
PrimitiveConfigrecord encapsulates all runtime parameters. - Events: An
OptimizationListenerinterface allows the runner to be decoupled from the CLI, enabling future UI integrations (e.g., Progress bars, Live Canvas).
src/main/java/org/trostheide/primitive/
├── Main.java # Entry point, CLI argument parsing
├── PrimitiveRunner.java # Orchestrates the thread pool and main loop
├── core/
│ ├── Optimizer.java # The Hill Climbing logic & Color Solver
│ └── ShapeResult.java # Record to pass results from Workers to Main
├── image/
│ └── RgbaImage.java # Int-packed pixel storage (0xAARRGGBB)
├── raster/
│ └── ScanlineBuffer.java # SoA container for raster data
├── gui/
│ ├── PrimitiveFrame.java # Main Swing Window
│ ├── SettingsPanel.java # Configuration Controls
│ └── ImagePanel.java # Custom Image Rendering Panel
└── shape/
├── Shape.java # Interface for geometric primitives
├── Triangle.java # Standard triangle rasterizer
├── Line.java # Composite shape (2 Triangles)
├── QuadraticBezier.java # Composite shape (Series of Lines)
├── RotatedRectangle.java # Composite shape (Rotated Quad)
└── Polyline.java # Composite shape (Series of Lines)
- Live UI: Add a Swing/JavaFX window to visualize the generation process in real-time.
- Rotated Rectangles: Add support for RotatedRectangle primitive.
- Polylines: Add support for Polyline primitive.
- Ellipse Support: Add Ellipse rasterization logic.
- Alpha Optimization: Currently uses a fixed alpha (0.5). Implement a search step to find the optimal alpha value per shape.
- SIMD Optimization: Implemented using Java Vector API (Incubator).
This project is licensed under the GNU Affero General Public License v3.0.
Original work by Michael Fogleman (fogleman/primitive) under MIT License.
