Technology moves fast! ⚡ Don't get left behind.🚶 Subscribe to our mailing list to keep up with latest and greatest in open source projects! 🏆


Subscribe to our mailing list

primitive

Reproducing images with geometric primitives.

Subscribe to updates I use primitive


Statistics on primitive

Number of watchers on Github 8670
Number of open issues 34
Average time to close an issue about 21 hours
Main language Go
Average time to merge a PR about 8 hours
Open pull requests 17+
Closed pull requests 4+
Last commit almost 2 years ago
Repo Created almost 3 years ago
Repo Last Updated over 1 year ago
Size 2.29 MB
Homepage https://primitive...
Organization / Authorfogleman
Contributors1
Page Updated
Do you use primitive? Leave a review!
View open issues (34)
View primitive activity
View on github
Fresh, new opensource launches 🚀🚀🚀
Trendy new open source projects in your inbox! View examples

Subscribe to our mailing list

Evaluating primitive for your project? Score Explanation
Commits Score (?)
Issues & PR Score (?)

Primitive Pictures

Reproducing images with geometric primitives.

Example

How it Works

A target image is provided as input. The algorithm tries to find the single 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.

Primitive for macOS

Now available as a native Mac application!

https://primitive.lol/

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.

You can tweet a picture to the bot and it will process it for you.

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, 6=beziers, 7=rotatedellipse, 8=polygon
rep 0 add N extra shapes each iteration with reduced search (mostly good for beziers)
nth 1 save every Nth frame (only when %d is in output path)
r 256 resize large input images to this size before processing
s 1024 output image size
a 128 color alpha (use 0 to let the algorithm choose alpha for each shape)
bg avg starting background color (hex)
j 0 number of parallel workers (default uses all cores)
v off verbose output
vv off very verbose output

Output Formats

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

  • PNG: raster output
  • JPG: 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.

You can use the -o flag multiple times. This way you can save both a PNG and an SVG, for example.

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!)

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

How it Works, Part II

Say we have a Target Image. This is what we're working towards recreating. We start with a blank canvas, but we fill it with a single solid color. Currently, this is the average color of the Target Image. We call this new blank canvas the Current Image. Now, we start evaluating shapes. To evaluate a shape, we draw it on top of the Current Image, producing a New Image. This New Image is compared to the Target Image to compute a score. We use the root-mean-square error for the score.

Current Image + Shape => New Image
RMSE(New Image, Target Image) => Score

The shapes are generated randomly. We can generate a random shape and score it. Then we can mutate the shape (by tweaking a triangle vertex, tweaking an ellipse radius or center, etc.) and score it again. If the mutation improved the score, we keep it. Otherwise we rollback to the previous state. Repeating this process is known as hill climbing. Hill climbing is prone to getting stuck in local minima, so we actually do this many different times with several different starting shapes. We can also generate N random shapes and pick the best one before we start hill climbing. Simulated annealing is another good option, but in my tests I found the hill climbing technique just as good and faster, at least for this particular problem.

Once we have found a good-scoring shape, we add it to the Current Image, where it will remain unchanged. Then we start the process again to find the next shape to draw. This process is repeated as many times as desired.

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)
    SVG(attrs string) string
}

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.

It should be noted that there are significant differences in my implementation compared to Roger's original work. Mine is not a genetic algorithm. Mine only operates on one shape at a time. Mine is much faster (AFAIK) and supports many types of shapes.

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

primitive open issues Ask a question     (View All Issues)
  • over 2 years Feature request: continue computation
  • almost 3 years Feature suggestion: colour gradient
  • almost 3 years New code as of Oct 3 21h34 PM issues
  • almost 3 years Support source image transparency
  • almost 3 years What are you doing with primitive?
  • almost 3 years Possible new option
  • almost 3 years Adding modes for other combinations of shapes
primitive open pull requests (View All Pulls)
  • Documenting functions
  • Proposed score based cap
  • Implement ability to export model to json
  • Update main.go
  • First test at implementing chords
  • Use viewbox so svg is not a fixed size (easier to animate)
  • fix mutating ellipse ry
  • add mp4 output support
  • bugfix: get random stroke width in Quadratic
  • Adds a UI progress indicator
  • allow to pipe images to primitive and output result to stdio
  • Prevent recalculating the same thing over and over again
  • *grammar police*
  • Ref#45 add gif rate support
  • use magick convert if magick executable found
  • Reduce generated SVG file size
  • Adding -blur flag for SVG Blur filter
primitive questions on Stackoverflow (View All Questions)
  • Inconsistency of primitive specializations in Java 8
  • Unable to create a constant value of type 'Domain.DataModel.Hospital'. Only primitive types or enumeration types are supported in this context
  • Efficient finding primitive roots modulo n using Python?
  • Compare non-primitive Long values 127 and 128
  • Is declaring a variable with auto and initializing with a primitive literal defined behaviour?
  • (Un)boxing primitive arrays in Java
  • NewtonSoft JsonContract OnDeserializedCallbacks not called for primitive types?
  • Extending typed Arrays (of primitive types like Bool) in Swift 3?
  • Why is Java's boolean primitive size not defined?
  • How can I convert a Java HashSet<Integer> to a primitive int array?
  • Negative short primitive has wrong value on Android 24
  • Why do I get a "Null value was assigned to a property of primitive type setter of" error message when using HibernateCriteriaBuilder in Grails
  • Primitive / Non-Nullable typenames in C#
  • Swift: create object from primitive value
  • Float, Boolean , double are classes or primitive data types, in java..?
  • Unable to create a constant value of type 'XXXXXX'. Only primitive types or enumeration types are supported in this context
  • How to get class of primitive types with Javassist?
  • How to compare character ignoring case in primitive types
  • "Primitive value will be lost" warning in WebStorm
  • The primitive type of a variable does not have a field x during compilation
  • Counting primitive operations, Big O Notation
  • Create a List of primitive int?
  • Comparing wrapper class with primitive using equals() gives strange behavior
  • how to store both primitive data types and objects using a java object in firebase
  • Better using a single Map with a custom-made object as value or several Maps with unique "primitive" value?
  • Making custom devices behave like a primitive mouse
  • Objects with primitive data pointers and emscripten
  • Sort arrays of primitive types in descending order
  • Java Clone Class With Primitive Types
  • How do I declare and set the value of a primitive type variable in JSP in one step?
primitive list of languages used
Other projects in Go