/primitive

Reproducing images with geometric primitives.

Primary LanguageGoMIT LicenseMIT

Primitive Pictures

Reproducing images with geometric primitives.

Example

How it Works

A target image is provided as input. The algorithm tries to find the most optimal shape that can be drawn to minimize the error between the target image and the drawn image. It repeats this process, adding one shape at a time. Around 50 to 200 shapes are needed to reach a result that is recognizable yet artistic and abstract.

Twitter

Follow @PrimitivePic on Twitter to see a new primitive picture every 30 minutes!

The Twitter bot looks for interesting photos using the Flickr API, runs the algorithm using randomized parameters, and posts the picture using the Twitter API.

Command-line Usage

Run it on your own images! First, install Go.

go get -u github.com/fogleman/primitive
primitive -i input.png -o output.png -n 100

Small input images should be used (like 256x256px). You don't need the detail anyway and the code will run faster.

Flag Default Description
-i n/a input file
-o n/a output file
-n n/a number of shapes
-m 1 mode: 0=combo, 1=triangle, 2=rect, 3=ellipse, 4=circle, 5=rotatedrect
-r 256 resize large input images to this size before processing
-s 1024 output image size
-a 128 color alpha
-v off verbose output

Output Formats

Depending on the output filename extension provided, you can produce different types of output.

  • PNG: raster output
  • SVG: vector output
  • GIF: animated output showing shapes being added - requires ImageMagick (specifically the convert command)

For PNG and SVG outputs, you can also include %d, %03d, etc. in the filename. In this case, each frame will be saved separately.

Primitives

The following primitives are supported:

  • Triangle
  • Rectangle (axis-aligned)
  • Ellipse (axis-aligned)
  • Circle
  • Rotated Rectangle
  • Combo (a mix of the above in a single image)

More shapes can be added by implementing the following interface:

type Shape interface {
	Rasterize() []Scanline
	Copy() Shape
	Mutate()
	Draw(dc *gg.Context)
}

Features

  • Hill Climbing or Simulated Annealing for optimization (hill climbing multiple random shapes is nearly as good as annealing and faster)
  • Scanline rasterization of shapes in pure Go (preferable for implementing the features below)
  • Optimal color computation based on affected pixels for each shape (color is directly computed, not optimized for)
  • Partial image difference for faster scoring (only pixels that change need be considered)
  • Anti-aliased output rendering

Inspiration

This project was originally inspired by the popular and excellent work of Roger Johansson - Genetic Programming: Evolution of Mona Lisa. Since seeing that article when it was quite new, I've tinkered with this problem here and there over the years. But only now am I satisfied with my results.

Progression

This GIF demonstrates the iterative nature of the algorithm, attempting to minimize the mean squared error by adding one shape at a time. (Use a ".gif" output file to generate one yourself!)

Mona Lisa

Static Animation

Since the algorithm has a random component to it, you can run it against the same input image multiple times to bring life to a static image.

Pencils

Creative Constraints

If you're willing to dabble in the code, you can enforce constraints on the shapes to produce even more interesting results. Here, the rectangles are constrained to point toward the sun in this picture of a pyramid sunset.

Pyramids

Shape and Iteration Comparison Matrix

The matrix below shows triangles, ellipses and rectangles at 50, 100 and 200 iterations each.

Matrix

Examples

Here are more examples from interesting photos found on Flickr.

Example Example Example Example Example Example Example Example Example Example Example Example Example Example Example Example Example Example Example Example