Full Code of fogleman/primitive for AI

master 0373c216458b cached
26 files
63.6 KB
21.6k tokens
182 symbols
1 requests
Download .txt
Repository: fogleman/primitive
Branch: master
Commit: 0373c216458b
Files: 26
Total size: 63.6 KB

Directory structure:
gitextract_s2bcgc36/

├── .gitignore
├── LICENSE.md
├── README.md
├── bot/
│   ├── .gitignore
│   ├── main.py
│   └── requirements.txt
├── main.go
├── primitive/
│   ├── color.go
│   ├── core.go
│   ├── ellipse.go
│   ├── heatmap.go
│   ├── log.go
│   ├── model.go
│   ├── optimize.go
│   ├── polygon.go
│   ├── quadratic.go
│   ├── raster.go
│   ├── rectangle.go
│   ├── scanline.go
│   ├── shape.go
│   ├── state.go
│   ├── triangle.go
│   ├── util.go
│   └── worker.go
└── scripts/
    ├── html.py
    └── process.py

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
/*.png
/*.svg
/*.gif



================================================
FILE: LICENSE.md
================================================
Copyright (C) 2016 Michael Fogleman

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# Primitive Pictures

Reproducing images with geometric primitives.

![Example](https://www.michaelfogleman.com/static/primitive/examples/16550611738.200.128.4.5.png)

### 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](https://twitter.com/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](https://golang.org/doc/install).

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

<img src="https://www.michaelfogleman.com/static/primitive/examples/monalisa.3.2000.gif" width="440"/> <img src="https://www.michaelfogleman.com/static/primitive/examples/monalisa-original.png" width="440"/>

### 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](https://www.michaelfogleman.com/static/primitive/examples/pencils.gif)

### 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](https://www.michaelfogleman.com/static/primitive/examples/pyramids.png)

### Shape and Iteration Comparison Matrix

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

![Matrix](http://i.imgur.com/H5NYpL4.png)

### 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](https://en.wikipedia.org/wiki/Root-mean-square_deviation) 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](https://en.wikipedia.org/wiki/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](https://en.wikipedia.org/wiki/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:

```go
type Shape interface {
	Rasterize() []Scanline
	Copy() Shape
	Mutate()
	Draw(dc *gg.Context)
	SVG(attrs string) string
}
```

### Features

- [Hill Climbing](https://en.wikipedia.org/wiki/Hill_climbing) or [Simulated Annealing](https://en.wikipedia.org/wiki/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](https://rogeralsing.com/2008/12/07/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](https://www.michaelfogleman.com/static/primitive/examples/29167683201.png)
![Example](https://www.michaelfogleman.com/static/primitive/examples/26574286221.200.128.4.1.png)
![Example](https://www.michaelfogleman.com/static/primitive/examples/15011768709.200.128.4.1.png)
![Example](https://www.michaelfogleman.com/static/primitive/examples/27540729075.200.128.4.1.png)
![Example](https://www.michaelfogleman.com/static/primitive/examples/28896874003.png)
![Example](https://www.michaelfogleman.com/static/primitive/examples/20414282102.png)
![Example](https://www.michaelfogleman.com/static/primitive/examples/15199237095.200.128.4.1.png)
![Example](https://www.michaelfogleman.com/static/primitive/examples/11707819764.200.128.4.1.png)
![Example](https://www.michaelfogleman.com/static/primitive/examples/18270231645.200.128.4.3.png)
![Example](https://www.michaelfogleman.com/static/primitive/examples/15705764893.png)
![Example](https://www.michaelfogleman.com/static/primitive/examples/25213252889.png)
![Example](https://www.michaelfogleman.com/static/primitive/examples/15015411870.200.128.4.3.png)
![Example](https://www.michaelfogleman.com/static/primitive/examples/25766500104.png)
![Example](https://www.michaelfogleman.com/static/primitive/examples/27471731151.50.128.4.1.png)
![Example](https://www.michaelfogleman.com/static/primitive/examples/11720700033.200.128.4.3.png)
![Example](https://www.michaelfogleman.com/static/primitive/examples/18782606664.png)
![Example](https://www.michaelfogleman.com/static/primitive/examples/21374478713.png)
![Example](https://www.michaelfogleman.com/static/primitive/examples/15196426112.200.128.4.5.png)
![Example](https://www.michaelfogleman.com/static/primitive/examples/24696847962.png)
![Example](https://www.michaelfogleman.com/static/primitive/examples/18276676312.100.128.4.1.png)


================================================
FILE: bot/.gitignore
================================================
config.py
env
venv



================================================
FILE: bot/main.py
================================================
import datetime
import os
import random
import requests
import subprocess
import time
import traceback
import twitter

RATE = 60 * 30
MENTION_RATE = 65

INPUT_FOLDER = ''
OUTPUT_FOLDER = ''

FLICKR_API_KEY = None
TWITTER_CONSUMER_KEY = None
TWITTER_CONSUMER_SECRET = None
TWITTER_ACCESS_TOKEN_KEY = None
TWITTER_ACCESS_TOKEN_SECRET = None

MODE_NAMES = [
    'primitives', # 0
    'triangles',  # 1
    'rectangles', # 2
    'ellipses',   # 3
    'circles',    # 4
    'rectangles', # 5
    'beziers',    # 6
    'ellipses',   # 7
    'polygons',   # 8
]

SINCE_ID = None
START_DATETIME = datetime.datetime.utcnow()
USER_DATETIME = {}

try:
    from config import *
except ImportError:
    print 'no config found!'

class AttrDict(dict):
    def __init__(self, *args, **kwargs):
        super(AttrDict, self).__init__(*args, **kwargs)
        self.__dict__ = self

class Config(AttrDict):
    def randomize(self):
        self.m = random.choice([1, 5, 6, 7])
        self.n = random.randint(10, 50) * 10
        self.rep = 0
        self.a = 128
        self.r = 300
        self.s = 1200
    def parse(self, text):
        text = (text or '').lower()
        tokens = text.split()
        for i, name in enumerate(MODE_NAMES):
            if name in text:
                self.m = i
        for token in tokens:
            try:
                self.n = int(token)
            except Exception:
                pass
    def validate(self):
        self.m = clamp(self.m, 0, 8)
        if self.m == 6:
            self.a = 0
            self.rep = 19
            self.n = 100
        else:
            self.n = clamp(self.n, 1, 500)
    @property
    def description(self):
        total = self.n + self.n * self.rep
        return '%d %s' % (total, MODE_NAMES[self.m])

def clamp(x, lo, hi):
    if x < lo:
        x = lo
    if x > hi:
        x = hi
    return x

def random_date(max_days_ago=1000):
    today = datetime.date.today()
    days = random.randint(1, max_days_ago)
    d = today - datetime.timedelta(days=days)
    return d.strftime('%Y-%m-%d')

def interesting(date=None):
    url = 'https://api.flickr.com/services/rest/'
    params = dict(
        api_key=FLICKR_API_KEY,
        format='json',
        nojsoncallback=1,
        method='flickr.interestingness.getList',
    )
    if date:
        params['date'] = date
    r = requests.get(url, params=params)
    return r.json()['photos']['photo']

def photo_url(p, size=None):
    # See: https://www.flickr.com/services/api/misc.urls.html
    if size:
        url = 'https://farm%s.staticflickr.com/%s/%s_%s_%s.jpg'
        return url % (p['farm'], p['server'], p['id'], p['secret'], size)
    else:
        url = 'https://farm%s.staticflickr.com/%s/%s_%s.jpg'
        return url % (p['farm'], p['server'], p['id'], p['secret'])

def download_photo(url, path):
    r = requests.get(url)
    with open(path, 'wb') as fp:
        fp.write(r.content)

def primitive(**kwargs):
    args = []
    for k, v in kwargs.items():
        if v is None:
            continue
        args.append('-%s' % k)
        args.append(str(v))
    args = ' '.join(args)
    cmd = 'primitive %s' % args
    subprocess.call(cmd, shell=True)

def twitter_api():
    return twitter.Api(
        consumer_key=TWITTER_CONSUMER_KEY,
        consumer_secret=TWITTER_CONSUMER_SECRET,
        access_token_key=TWITTER_ACCESS_TOKEN_KEY,
        access_token_secret=TWITTER_ACCESS_TOKEN_SECRET)

def tweet(status, media, in_reply_to_status_id=None):
    api = twitter_api()
    api.PostUpdate(status, media, in_reply_to_status_id=in_reply_to_status_id)

def handle_mentions():
    global SINCE_ID
    print 'checking for mentions'
    api = twitter_api()
    statuses = api.GetMentions(200, SINCE_ID)
    for status in reversed(statuses):
        SINCE_ID = status.id
        print 'handling mention', status.id
        handle_mention(status)
    print 'done with mentions'

def handle_mention(status):
    mentions = status.user_mentions or []
    if len(mentions) != 1:
        print 'mention does not have exactly one mention'
        return
    media = status.media or []
    if len(media) != 1:
        print 'mention does not have exactly one media'
        return
    url = media[0].media_url or None
    if not url:
        print 'mention does not have a media_url'
        return
    created_at = datetime.datetime.strptime(
        status.created_at, '%a %b %d %H:%M:%S +0000 %Y')
    if created_at < START_DATETIME:
        print 'mention timestamp before bot started'
        return
    user_id = status.user.id
    now = datetime.datetime.utcnow()
    td = datetime.timedelta(minutes=5)
    if user_id in USER_DATETIME:
        if now - USER_DATETIME[user_id] < td:
            print 'user mentioned me too recently'
            return
    USER_DATETIME[user_id] = now
    in_path = os.path.join(INPUT_FOLDER, '%s.jpg' % status.id)
    out_path = os.path.join(OUTPUT_FOLDER, '%s.png' % status.id)
    print 'downloading', url
    download_photo(url, in_path)
    config = Config()
    config.randomize()
    config.parse(status.text)
    config.validate()
    status_text = '@%s %s.' % (status.user.screen_name, config.description)
    print status_text
    print 'running algorithm: %s' % config
    primitive(i=in_path, o=out_path, **config)
    if os.path.exists(out_path):
        print 'uploading to twitter'
        tweet(status_text, out_path, status.id)
        print 'done'
    else:
        print 'failed!'

def flickr_url(photo_id):
    alphabet = '123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ'
    return 'https://flic.kr/p/%s' % base_encode(alphabet, int(photo_id))

def base_encode(alphabet, number, suffix=''):
    base = len(alphabet)
    if number >= base:
        div, mod = divmod(number, base)
        return base_encode(alphabet, div, alphabet[mod] + suffix)
    else:
        return alphabet[number] + suffix

def generate():
    date = random_date()
    print 'finding an interesting photo from', date
    photos = interesting(date)
    photo = random.choice(photos)
    print 'picked photo', photo['id']
    in_path = os.path.join(INPUT_FOLDER, '%s.jpg' % photo['id'])
    out_path = os.path.join(OUTPUT_FOLDER, '%s.png' % photo['id'])
    url = photo_url(photo, 'z')
    print 'downloading', url
    download_photo(url, in_path)
    config = Config()
    config.randomize()
    config.validate()
    status_text = '%s. %s' % (config.description, flickr_url(photo['id']))
    print status_text
    print 'running algorithm: %s' % config
    primitive(i=in_path, o=out_path, **config)
    if os.path.exists(out_path):
        print 'uploading to twitter'
        tweet(status_text, out_path)
        print 'done'
    else:
        print 'failed!'

def main():
    previous = 0
    mention_previous = 0
    while True:
        now = time.time()
        if now - previous > RATE:
            previous = now
            try:
                generate()
            except Exception:
                traceback.print_exc()
        if now - mention_previous > MENTION_RATE:
            mention_previous = now
            try:
                handle_mentions()
            except Exception:
                traceback.print_exc()
        time.sleep(5)

def download_photos(folder, date=None):
    try:
        os.makedirs(folder)
    except Exception:
        pass
    date = date or random_date()
    photos = interesting(date)
    for photo in photos:
        url = photo_url(photo, 'z')
        path = '%s.jpg' % photo['id']
        path = os.path.join(folder, path)
        download_photo(url, path)

if __name__ == '__main__':
    main()


================================================
FILE: bot/requirements.txt
================================================
python-twitter==3.1
requests==2.11.1


================================================
FILE: main.go
================================================
package main

import (
	"flag"
	"fmt"
	"log"
	"math/rand"
	"os"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"time"

	"github.com/fogleman/primitive/primitive"
	"github.com/nfnt/resize"
)

var (
	Input      string
	Outputs    flagArray
	Background string
	Configs    shapeConfigArray
	Alpha      int
	InputSize  int
	OutputSize int
	Mode       int
	Workers    int
	Nth        int
	Repeat     int
	V, VV      bool
)

type flagArray []string

func (i *flagArray) String() string {
	return strings.Join(*i, ", ")
}

func (i *flagArray) Set(value string) error {
	*i = append(*i, value)
	return nil
}

type shapeConfig struct {
	Count  int
	Mode   int
	Alpha  int
	Repeat int
}

type shapeConfigArray []shapeConfig

func (i *shapeConfigArray) String() string {
	return ""
}

func (i *shapeConfigArray) Set(value string) error {
	n, _ := strconv.ParseInt(value, 0, 0)
	*i = append(*i, shapeConfig{int(n), Mode, Alpha, Repeat})
	return nil
}

func init() {
	flag.StringVar(&Input, "i", "", "input image path")
	flag.Var(&Outputs, "o", "output image path")
	flag.Var(&Configs, "n", "number of primitives")
	flag.StringVar(&Background, "bg", "", "background color (hex)")
	flag.IntVar(&Alpha, "a", 128, "alpha value")
	flag.IntVar(&InputSize, "r", 256, "resize large input images to this size")
	flag.IntVar(&OutputSize, "s", 1024, "output image size")
	flag.IntVar(&Mode, "m", 1, "0=combo 1=triangle 2=rect 3=ellipse 4=circle 5=rotatedrect 6=beziers 7=rotatedellipse 8=polygon")
	flag.IntVar(&Workers, "j", 0, "number of parallel workers (default uses all cores)")
	flag.IntVar(&Nth, "nth", 1, "save every Nth frame (put \"%d\" in path)")
	flag.IntVar(&Repeat, "rep", 0, "add N extra shapes per iteration with reduced search")
	flag.BoolVar(&V, "v", false, "verbose")
	flag.BoolVar(&VV, "vv", false, "very verbose")
}

func errorMessage(message string) bool {
	fmt.Fprintln(os.Stderr, message)
	return false
}

func check(err error) {
	if err != nil {
		log.Fatal(err)
	}
}

func main() {
	// parse and validate arguments
	flag.Parse()
	ok := true
	if Input == "" {
		ok = errorMessage("ERROR: input argument required")
	}
	if len(Outputs) == 0 {
		ok = errorMessage("ERROR: output argument required")
	}
	if len(Configs) == 0 {
		ok = errorMessage("ERROR: number argument required")
	}
	if len(Configs) == 1 {
		Configs[0].Mode = Mode
		Configs[0].Alpha = Alpha
		Configs[0].Repeat = Repeat
	}
	for _, config := range Configs {
		if config.Count < 1 {
			ok = errorMessage("ERROR: number argument must be > 0")
		}
	}
	if !ok {
		fmt.Println("Usage: primitive [OPTIONS] -i input -o output -n count")
		flag.PrintDefaults()
		os.Exit(1)
	}

	// set log level
	if V {
		primitive.LogLevel = 1
	}
	if VV {
		primitive.LogLevel = 2
	}

	// seed random number generator
	rand.Seed(time.Now().UTC().UnixNano())

	// determine worker count
	if Workers < 1 {
		Workers = runtime.NumCPU()
	}

	// read input image
	primitive.Log(1, "reading %s\n", Input)
	input, err := primitive.LoadImage(Input)
	check(err)

	// scale down input image if needed
	size := uint(InputSize)
	if size > 0 {
		input = resize.Thumbnail(size, size, input, resize.Bilinear)
	}

	// determine background color
	var bg primitive.Color
	if Background == "" {
		bg = primitive.MakeColor(primitive.AverageImageColor(input))
	} else {
		bg = primitive.MakeHexColor(Background)
	}

	// run algorithm
	model := primitive.NewModel(input, bg, OutputSize, Workers)
	primitive.Log(1, "%d: t=%.3f, score=%.6f\n", 0, 0.0, model.Score)
	start := time.Now()
	frame := 0
	for j, config := range Configs {
		primitive.Log(1, "count=%d, mode=%d, alpha=%d, repeat=%d\n",
			config.Count, config.Mode, config.Alpha, config.Repeat)

		for i := 0; i < config.Count; i++ {
			frame++

			// find optimal shape and add it to the model
			t := time.Now()
			n := model.Step(primitive.ShapeType(config.Mode), config.Alpha, config.Repeat)
			nps := primitive.NumberString(float64(n) / time.Since(t).Seconds())
			elapsed := time.Since(start).Seconds()
			primitive.Log(1, "%d: t=%.3f, score=%.6f, n=%d, n/s=%s\n", frame, elapsed, model.Score, n, nps)

			// write output image(s)
			for _, output := range Outputs {
				ext := strings.ToLower(filepath.Ext(output))
				if output == "-" {
					ext = ".svg"
				}
				percent := strings.Contains(output, "%")
				saveFrames := percent && ext != ".gif"
				saveFrames = saveFrames && frame%Nth == 0
				last := j == len(Configs)-1 && i == config.Count-1
				if saveFrames || last {
					path := output
					if percent {
						path = fmt.Sprintf(output, frame)
					}
					primitive.Log(1, "writing %s\n", path)
					switch ext {
					default:
						check(fmt.Errorf("unrecognized file extension: %s", ext))
					case ".png":
						check(primitive.SavePNG(path, model.Context.Image()))
					case ".jpg", ".jpeg":
						check(primitive.SaveJPG(path, model.Context.Image(), 95))
					case ".svg":
						check(primitive.SaveFile(path, model.SVG()))
					case ".gif":
						frames := model.Frames(0.001)
						check(primitive.SaveGIFImageMagick(path, frames, 50, 250))
					}
				}
			}
		}
	}
}


================================================
FILE: primitive/color.go
================================================
package primitive

import (
	"fmt"
	"image/color"
	"strings"
)

type Color struct {
	R, G, B, A int
}

func MakeColor(c color.Color) Color {
	r, g, b, a := c.RGBA()
	return Color{int(r / 257), int(g / 257), int(b / 257), int(a / 257)}
}

func MakeHexColor(x string) Color {
	x = strings.Trim(x, "#")
	var r, g, b, a int
	a = 255
	switch len(x) {
	case 3:
		fmt.Sscanf(x, "%1x%1x%1x", &r, &g, &b)
		r = (r << 4) | r
		g = (g << 4) | g
		b = (b << 4) | b
	case 4:
		fmt.Sscanf(x, "%1x%1x%1x%1x", &r, &g, &b, &a)
		r = (r << 4) | r
		g = (g << 4) | g
		b = (b << 4) | b
		a = (a << 4) | a
	case 6:
		fmt.Sscanf(x, "%02x%02x%02x", &r, &g, &b)
	case 8:
		fmt.Sscanf(x, "%02x%02x%02x%02x", &r, &g, &b, &a)
	}
	return Color{r, g, b, a}
}

func (c *Color) NRGBA() color.NRGBA {
	return color.NRGBA{uint8(c.R), uint8(c.G), uint8(c.B), uint8(c.A)}
}


================================================
FILE: primitive/core.go
================================================
package primitive

import (
	"image"
	"math"
)

func computeColor(target, current *image.RGBA, lines []Scanline, alpha int) Color {
	var rsum, gsum, bsum, count int64
	a := 0x101 * 255 / alpha
	for _, line := range lines {
		i := target.PixOffset(line.X1, line.Y)
		for x := line.X1; x <= line.X2; x++ {
			tr := int(target.Pix[i])
			tg := int(target.Pix[i+1])
			tb := int(target.Pix[i+2])
			cr := int(current.Pix[i])
			cg := int(current.Pix[i+1])
			cb := int(current.Pix[i+2])
			i += 4
			rsum += int64((tr-cr)*a + cr*0x101)
			gsum += int64((tg-cg)*a + cg*0x101)
			bsum += int64((tb-cb)*a + cb*0x101)
			count++
		}
	}
	if count == 0 {
		return Color{}
	}
	r := clampInt(int(rsum/count)>>8, 0, 255)
	g := clampInt(int(gsum/count)>>8, 0, 255)
	b := clampInt(int(bsum/count)>>8, 0, 255)
	return Color{r, g, b, alpha}
}

func copyLines(dst, src *image.RGBA, lines []Scanline) {
	for _, line := range lines {
		a := dst.PixOffset(line.X1, line.Y)
		b := a + (line.X2-line.X1+1)*4
		copy(dst.Pix[a:b], src.Pix[a:b])
	}
}

func drawLines(im *image.RGBA, c Color, lines []Scanline) {
	const m = 0xffff
	sr, sg, sb, sa := c.NRGBA().RGBA()
	for _, line := range lines {
		ma := line.Alpha
		a := (m - sa*ma/m) * 0x101
		i := im.PixOffset(line.X1, line.Y)
		for x := line.X1; x <= line.X2; x++ {
			dr := uint32(im.Pix[i+0])
			dg := uint32(im.Pix[i+1])
			db := uint32(im.Pix[i+2])
			da := uint32(im.Pix[i+3])
			im.Pix[i+0] = uint8((dr*a + sr*ma) / m >> 8)
			im.Pix[i+1] = uint8((dg*a + sg*ma) / m >> 8)
			im.Pix[i+2] = uint8((db*a + sb*ma) / m >> 8)
			im.Pix[i+3] = uint8((da*a + sa*ma) / m >> 8)
			i += 4
		}
	}
}

func differenceFull(a, b *image.RGBA) float64 {
	size := a.Bounds().Size()
	w, h := size.X, size.Y
	var total uint64
	for y := 0; y < h; y++ {
		i := a.PixOffset(0, y)
		for x := 0; x < w; x++ {
			ar := int(a.Pix[i])
			ag := int(a.Pix[i+1])
			ab := int(a.Pix[i+2])
			aa := int(a.Pix[i+3])
			br := int(b.Pix[i])
			bg := int(b.Pix[i+1])
			bb := int(b.Pix[i+2])
			ba := int(b.Pix[i+3])
			i += 4
			dr := ar - br
			dg := ag - bg
			db := ab - bb
			da := aa - ba
			total += uint64(dr*dr + dg*dg + db*db + da*da)
		}
	}
	return math.Sqrt(float64(total)/float64(w*h*4)) / 255
}

func differencePartial(target, before, after *image.RGBA, score float64, lines []Scanline) float64 {
	size := target.Bounds().Size()
	w, h := size.X, size.Y
	total := uint64(math.Pow(score*255, 2) * float64(w*h*4))
	for _, line := range lines {
		i := target.PixOffset(line.X1, line.Y)
		for x := line.X1; x <= line.X2; x++ {
			tr := int(target.Pix[i])
			tg := int(target.Pix[i+1])
			tb := int(target.Pix[i+2])
			ta := int(target.Pix[i+3])
			br := int(before.Pix[i])
			bg := int(before.Pix[i+1])
			bb := int(before.Pix[i+2])
			ba := int(before.Pix[i+3])
			ar := int(after.Pix[i])
			ag := int(after.Pix[i+1])
			ab := int(after.Pix[i+2])
			aa := int(after.Pix[i+3])
			i += 4
			dr1 := tr - br
			dg1 := tg - bg
			db1 := tb - bb
			da1 := ta - ba
			dr2 := tr - ar
			dg2 := tg - ag
			db2 := tb - ab
			da2 := ta - aa
			total -= uint64(dr1*dr1 + dg1*dg1 + db1*db1 + da1*da1)
			total += uint64(dr2*dr2 + dg2*dg2 + db2*db2 + da2*da2)
		}
	}
	return math.Sqrt(float64(total)/float64(w*h*4)) / 255
}


================================================
FILE: primitive/ellipse.go
================================================
package primitive

import (
	"fmt"
	"math"

	"github.com/fogleman/gg"
	"github.com/golang/freetype/raster"
)

type Ellipse struct {
	Worker *Worker
	X, Y   int
	Rx, Ry int
	Circle bool
}

func NewRandomEllipse(worker *Worker) *Ellipse {
	rnd := worker.Rnd
	x := rnd.Intn(worker.W)
	y := rnd.Intn(worker.H)
	rx := rnd.Intn(32) + 1
	ry := rnd.Intn(32) + 1
	return &Ellipse{worker, x, y, rx, ry, false}
}

func NewRandomCircle(worker *Worker) *Ellipse {
	rnd := worker.Rnd
	x := rnd.Intn(worker.W)
	y := rnd.Intn(worker.H)
	r := rnd.Intn(32) + 1
	return &Ellipse{worker, x, y, r, r, true}
}

func (c *Ellipse) Draw(dc *gg.Context, scale float64) {
	dc.DrawEllipse(float64(c.X), float64(c.Y), float64(c.Rx), float64(c.Ry))
	dc.Fill()
}

func (c *Ellipse) SVG(attrs string) string {
	return fmt.Sprintf(
		"<ellipse %s cx=\"%d\" cy=\"%d\" rx=\"%d\" ry=\"%d\" />",
		attrs, c.X, c.Y, c.Rx, c.Ry)
}

func (c *Ellipse) Copy() Shape {
	a := *c
	return &a
}

func (c *Ellipse) Mutate() {
	w := c.Worker.W
	h := c.Worker.H
	rnd := c.Worker.Rnd
	switch rnd.Intn(3) {
	case 0:
		c.X = clampInt(c.X+int(rnd.NormFloat64()*16), 0, w-1)
		c.Y = clampInt(c.Y+int(rnd.NormFloat64()*16), 0, h-1)
	case 1:
		c.Rx = clampInt(c.Rx+int(rnd.NormFloat64()*16), 1, w-1)
		if c.Circle {
			c.Ry = c.Rx
		}
	case 2:
		c.Ry = clampInt(c.Ry+int(rnd.NormFloat64()*16), 1, h-1)
		if c.Circle {
			c.Rx = c.Ry
		}
	}
}

func (c *Ellipse) Rasterize() []Scanline {
	w := c.Worker.W
	h := c.Worker.H
	lines := c.Worker.Lines[:0]
	aspect := float64(c.Rx) / float64(c.Ry)
	for dy := 0; dy < c.Ry; dy++ {
		y1 := c.Y - dy
		y2 := c.Y + dy
		if (y1 < 0 || y1 >= h) && (y2 < 0 || y2 >= h) {
			continue
		}
		s := int(math.Sqrt(float64(c.Ry*c.Ry-dy*dy)) * aspect)
		x1 := c.X - s
		x2 := c.X + s
		if x1 < 0 {
			x1 = 0
		}
		if x2 >= w {
			x2 = w - 1
		}
		if y1 >= 0 && y1 < h {
			lines = append(lines, Scanline{y1, x1, x2, 0xffff})
		}
		if y2 >= 0 && y2 < h && dy > 0 {
			lines = append(lines, Scanline{y2, x1, x2, 0xffff})
		}
	}
	return lines
}

type RotatedEllipse struct {
	Worker *Worker
	X, Y   float64
	Rx, Ry float64
	Angle  float64
}

func NewRandomRotatedEllipse(worker *Worker) *RotatedEllipse {
	rnd := worker.Rnd
	x := rnd.Float64() * float64(worker.W)
	y := rnd.Float64() * float64(worker.H)
	rx := rnd.Float64()*32 + 1
	ry := rnd.Float64()*32 + 1
	a := rnd.Float64() * 360
	return &RotatedEllipse{worker, x, y, rx, ry, a}
}

func (c *RotatedEllipse) Draw(dc *gg.Context, scale float64) {
	dc.Push()
	dc.RotateAbout(radians(c.Angle), c.X, c.Y)
	dc.DrawEllipse(c.X, c.Y, c.Rx, c.Ry)
	dc.Fill()
	dc.Pop()
}

func (c *RotatedEllipse) SVG(attrs string) string {
	return fmt.Sprintf(
		"<g transform=\"translate(%f %f) rotate(%f) scale(%f %f)\"><ellipse %s cx=\"0\" cy=\"0\" rx=\"1\" ry=\"1\" /></g>",
		c.X, c.Y, c.Angle, c.Rx, c.Ry, attrs)
}

func (c *RotatedEllipse) Copy() Shape {
	a := *c
	return &a
}

func (c *RotatedEllipse) Mutate() {
	w := c.Worker.W
	h := c.Worker.H
	rnd := c.Worker.Rnd
	switch rnd.Intn(3) {
	case 0:
		c.X = clamp(c.X+rnd.NormFloat64()*16, 0, float64(w-1))
		c.Y = clamp(c.Y+rnd.NormFloat64()*16, 0, float64(h-1))
	case 1:
		c.Rx = clamp(c.Rx+rnd.NormFloat64()*16, 1, float64(w-1))
		c.Ry = clamp(c.Ry+rnd.NormFloat64()*16, 1, float64(w-1))
	case 2:
		c.Angle = c.Angle + rnd.NormFloat64()*32
	}
}

func (c *RotatedEllipse) Rasterize() []Scanline {
	var path raster.Path
	const n = 16
	for i := 0; i < n; i++ {
		p1 := float64(i+0) / n
		p2 := float64(i+1) / n
		a1 := p1 * 2 * math.Pi
		a2 := p2 * 2 * math.Pi
		x0 := c.Rx * math.Cos(a1)
		y0 := c.Ry * math.Sin(a1)
		x1 := c.Rx * math.Cos(a1+(a2-a1)/2)
		y1 := c.Ry * math.Sin(a1+(a2-a1)/2)
		x2 := c.Rx * math.Cos(a2)
		y2 := c.Ry * math.Sin(a2)
		cx := 2*x1 - x0/2 - x2/2
		cy := 2*y1 - y0/2 - y2/2
		x0, y0 = rotate(x0, y0, radians(c.Angle))
		cx, cy = rotate(cx, cy, radians(c.Angle))
		x2, y2 = rotate(x2, y2, radians(c.Angle))
		if i == 0 {
			path.Start(fixp(x0+c.X, y0+c.Y))
		}
		path.Add2(fixp(cx+c.X, cy+c.Y), fixp(x2+c.X, y2+c.Y))
	}
	return fillPath(c.Worker, path)
}


================================================
FILE: primitive/heatmap.go
================================================
package primitive

import (
	"image"
	"image/color"
	"math"
)

type Heatmap struct {
	W, H  int
	Count []uint64
}

func NewHeatmap(w, h int) *Heatmap {
	count := make([]uint64, w*h)
	return &Heatmap{w, h, count}
}

func (h *Heatmap) Clear() {
	for i := range h.Count {
		h.Count[i] = 0
	}
}

func (h *Heatmap) Add(lines []Scanline) {
	for _, line := range lines {
		i := line.Y*h.W + line.X1
		for x := line.X1; x <= line.X2; x++ {
			h.Count[i] += uint64(line.Alpha)
			i++
		}
	}
}

func (h *Heatmap) AddHeatmap(a *Heatmap) {
	for i, x := range a.Count {
		h.Count[i] += x
	}
}

func (h *Heatmap) Image(gamma float64) *image.Gray16 {
	im := image.NewGray16(image.Rect(0, 0, h.W, h.H))
	var hi uint64
	for _, h := range h.Count {
		if h > hi {
			hi = h
		}
	}
	i := 0
	for y := 0; y < h.H; y++ {
		for x := 0; x < h.W; x++ {
			p := float64(h.Count[i]) / float64(hi)
			p = math.Pow(p, gamma)
			im.SetGray16(x, y, color.Gray16{uint16(p * 0xffff)})
			i++
		}
	}
	return im
}


================================================
FILE: primitive/log.go
================================================
package primitive

import "fmt"

var LogLevel int

func Log(level int, format string, a ...interface{}) {
	if LogLevel >= level {
		fmt.Printf(format, a...)
	}
}

func v(format string, a ...interface{}) {
	Log(1, format, a...)
}

func vv(format string, a ...interface{}) {
	Log(2, "  "+format, a...)
}

func vvv(format string, a ...interface{}) {
	Log(3, "    "+format, a...)
}


================================================
FILE: primitive/model.go
================================================
package primitive

import (
	"fmt"
	"image"
	"strings"

	"github.com/fogleman/gg"
)

type Model struct {
	Sw, Sh     int
	Scale      float64
	Background Color
	Target     *image.RGBA
	Current    *image.RGBA
	Context    *gg.Context
	Score      float64
	Shapes     []Shape
	Colors     []Color
	Scores     []float64
	Workers    []*Worker
}

func NewModel(target image.Image, background Color, size, numWorkers int) *Model {
	w := target.Bounds().Size().X
	h := target.Bounds().Size().Y
	aspect := float64(w) / float64(h)
	var sw, sh int
	var scale float64
	if aspect >= 1 {
		sw = size
		sh = int(float64(size) / aspect)
		scale = float64(size) / float64(w)
	} else {
		sw = int(float64(size) * aspect)
		sh = size
		scale = float64(size) / float64(h)
	}

	model := &Model{}
	model.Sw = sw
	model.Sh = sh
	model.Scale = scale
	model.Background = background
	model.Target = imageToRGBA(target)
	model.Current = uniformRGBA(target.Bounds(), background.NRGBA())
	model.Score = differenceFull(model.Target, model.Current)
	model.Context = model.newContext()
	for i := 0; i < numWorkers; i++ {
		worker := NewWorker(model.Target)
		model.Workers = append(model.Workers, worker)
	}
	return model
}

func (model *Model) newContext() *gg.Context {
	dc := gg.NewContext(model.Sw, model.Sh)
	dc.Scale(model.Scale, model.Scale)
	dc.Translate(0.5, 0.5)
	dc.SetColor(model.Background.NRGBA())
	dc.Clear()
	return dc
}

func (model *Model) Frames(scoreDelta float64) []image.Image {
	var result []image.Image
	dc := model.newContext()
	result = append(result, imageToRGBA(dc.Image()))
	previous := 10.0
	for i, shape := range model.Shapes {
		c := model.Colors[i]
		dc.SetRGBA255(c.R, c.G, c.B, c.A)
		shape.Draw(dc, model.Scale)
		dc.Fill()
		score := model.Scores[i]
		delta := previous - score
		if delta >= scoreDelta {
			previous = score
			result = append(result, imageToRGBA(dc.Image()))
		}
	}
	return result
}

func (model *Model) SVG() string {
	bg := model.Background
	var lines []string
	lines = append(lines, fmt.Sprintf("<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" width=\"%d\" height=\"%d\">", model.Sw, model.Sh))
	lines = append(lines, fmt.Sprintf("<rect x=\"0\" y=\"0\" width=\"%d\" height=\"%d\" fill=\"#%02x%02x%02x\" />", model.Sw, model.Sh, bg.R, bg.G, bg.B))
	lines = append(lines, fmt.Sprintf("<g transform=\"scale(%f) translate(0.5 0.5)\">", model.Scale))
	for i, shape := range model.Shapes {
		c := model.Colors[i]
		attrs := "fill=\"#%02x%02x%02x\" fill-opacity=\"%f\""
		attrs = fmt.Sprintf(attrs, c.R, c.G, c.B, float64(c.A)/255)
		lines = append(lines, shape.SVG(attrs))
	}
	lines = append(lines, "</g>")
	lines = append(lines, "</svg>")
	return strings.Join(lines, "\n")
}

func (model *Model) Add(shape Shape, alpha int) {
	before := copyRGBA(model.Current)
	lines := shape.Rasterize()
	color := computeColor(model.Target, model.Current, lines, alpha)
	drawLines(model.Current, color, lines)
	score := differencePartial(model.Target, before, model.Current, model.Score, lines)

	model.Score = score
	model.Shapes = append(model.Shapes, shape)
	model.Colors = append(model.Colors, color)
	model.Scores = append(model.Scores, score)

	model.Context.SetRGBA255(color.R, color.G, color.B, color.A)
	shape.Draw(model.Context, model.Scale)
}

func (model *Model) Step(shapeType ShapeType, alpha, repeat int) int {
	state := model.runWorkers(shapeType, alpha, 1000, 100, 16)
	// state = HillClimb(state, 1000).(*State)
	model.Add(state.Shape, state.Alpha)

	for i := 0; i < repeat; i++ {
		state.Worker.Init(model.Current, model.Score)
		a := state.Energy()
		state = HillClimb(state, 100).(*State)
		b := state.Energy()
		if a == b {
			break
		}
		model.Add(state.Shape, state.Alpha)
	}

	// for _, w := range model.Workers[1:] {
	// 	model.Workers[0].Heatmap.AddHeatmap(w.Heatmap)
	// }
	// SavePNG("heatmap.png", model.Workers[0].Heatmap.Image(0.5))

	counter := 0
	for _, worker := range model.Workers {
		counter += worker.Counter
	}
	return counter
}

func (model *Model) runWorkers(t ShapeType, a, n, age, m int) *State {
	wn := len(model.Workers)
	ch := make(chan *State, wn)
	wm := m / wn
	if m%wn != 0 {
		wm++
	}
	for i := 0; i < wn; i++ {
		worker := model.Workers[i]
		worker.Init(model.Current, model.Score)
		go model.runWorker(worker, t, a, n, age, wm, ch)
	}
	var bestEnergy float64
	var bestState *State
	for i := 0; i < wn; i++ {
		state := <-ch
		energy := state.Energy()
		if i == 0 || energy < bestEnergy {
			bestEnergy = energy
			bestState = state
		}
	}
	return bestState
}

func (model *Model) runWorker(worker *Worker, t ShapeType, a, n, age, m int, ch chan *State) {
	ch <- worker.BestHillClimbState(t, a, n, age, m)
}


================================================
FILE: primitive/optimize.go
================================================
package primitive

import (
	"math"
	"math/rand"
)

type Annealable interface {
	Energy() float64
	DoMove() interface{}
	UndoMove(interface{})
	Copy() Annealable
}

func HillClimb(state Annealable, maxAge int) Annealable {
	state = state.Copy()
	bestState := state.Copy()
	bestEnergy := state.Energy()
	step := 0
	for age := 0; age < maxAge; age++ {
		undo := state.DoMove()
		energy := state.Energy()
		if energy >= bestEnergy {
			state.UndoMove(undo)
		} else {
			// fmt.Printf("step: %d, energy: %.6f\n", step, energy)
			bestEnergy = energy
			bestState = state.Copy()
			age = -1
		}
		step++
	}
	return bestState
}

func PreAnneal(state Annealable, iterations int) float64 {
	state = state.Copy()
	previous := state.Energy()
	var total float64
	for i := 0; i < iterations; i++ {
		state.DoMove()
		energy := state.Energy()
		total += math.Abs(energy - previous)
		previous = energy
	}
	return total / float64(iterations)
}

func Anneal(state Annealable, maxTemp, minTemp float64, steps int) Annealable {
	factor := -math.Log(maxTemp / minTemp)
	state = state.Copy()
	bestState := state.Copy()
	bestEnergy := state.Energy()
	previousEnergy := bestEnergy
	for step := 0; step < steps; step++ {
		pct := float64(step) / float64(steps-1)
		temp := maxTemp * math.Exp(factor*pct)
		undo := state.DoMove()
		energy := state.Energy()
		change := energy - previousEnergy
		if change > 0 && math.Exp(-change/temp) < rand.Float64() {
			state.UndoMove(undo)
		} else {
			previousEnergy = energy
			if energy < bestEnergy {
				// pct := float64(step*100) / float64(steps)
				// fmt.Printf("step: %d of %d (%.1f%%), temp: %.3f, energy: %.6f\n",
				// 	step, steps, pct, temp, energy)
				bestEnergy = energy
				bestState = state.Copy()
			}
		}
	}
	return bestState
}


================================================
FILE: primitive/polygon.go
================================================
package primitive

import (
	"fmt"
	"strings"

	"github.com/fogleman/gg"
	"github.com/golang/freetype/raster"
)

type Polygon struct {
	Worker *Worker
	Order  int
	Convex bool
	X, Y   []float64
}

func NewRandomPolygon(worker *Worker, order int, convex bool) *Polygon {
	rnd := worker.Rnd
	x := make([]float64, order)
	y := make([]float64, order)
	x[0] = rnd.Float64() * float64(worker.W)
	y[0] = rnd.Float64() * float64(worker.H)
	for i := 1; i < order; i++ {
		x[i] = x[0] + rnd.Float64()*40 - 20
		y[i] = y[0] + rnd.Float64()*40 - 20
	}
	p := &Polygon{worker, order, convex, x, y}
	p.Mutate()
	return p
}

func (p *Polygon) Draw(dc *gg.Context, scale float64) {
	dc.NewSubPath()
	for i := 0; i < p.Order; i++ {
		dc.LineTo(p.X[i], p.Y[i])
	}
	dc.ClosePath()
	dc.Fill()
}

func (p *Polygon) SVG(attrs string) string {
	ret := fmt.Sprintf(
		"<polygon %s points=\"",
		attrs)
	points := make([]string, len(p.X))
	for i := 0; i < len(p.X); i++ {
		points[i] = fmt.Sprintf("%f,%f", p.X[i], p.Y[i])
	}

	return ret + strings.Join(points, ",") + "\" />"
}

func (p *Polygon) Copy() Shape {
	a := *p
	a.X = make([]float64, p.Order)
	a.Y = make([]float64, p.Order)
	copy(a.X, p.X)
	copy(a.Y, p.Y)
	return &a
}

func (p *Polygon) Mutate() {
	const m = 16
	w := p.Worker.W
	h := p.Worker.H
	rnd := p.Worker.Rnd
	for {
		if rnd.Float64() < 0.25 {
			i := rnd.Intn(p.Order)
			j := rnd.Intn(p.Order)
			p.X[i], p.Y[i], p.X[j], p.Y[j] = p.X[j], p.Y[j], p.X[i], p.Y[i]
		} else {
			i := rnd.Intn(p.Order)
			p.X[i] = clamp(p.X[i]+rnd.NormFloat64()*16, -m, float64(w-1+m))
			p.Y[i] = clamp(p.Y[i]+rnd.NormFloat64()*16, -m, float64(h-1+m))
		}
		if p.Valid() {
			break
		}
	}
}

func (p *Polygon) Valid() bool {
	if !p.Convex {
		return true
	}
	var sign bool
	for a := 0; a < p.Order; a++ {
		i := (a + 0) % p.Order
		j := (a + 1) % p.Order
		k := (a + 2) % p.Order
		c := cross3(p.X[i], p.Y[i], p.X[j], p.Y[j], p.X[k], p.Y[k])
		if a == 0 {
			sign = c > 0
		} else if c > 0 != sign {
			return false
		}
	}
	return true
}

func cross3(x1, y1, x2, y2, x3, y3 float64) float64 {
	dx1 := x2 - x1
	dy1 := y2 - y1
	dx2 := x3 - x2
	dy2 := y3 - y2
	return dx1*dy2 - dy1*dx2
}

func (p *Polygon) Rasterize() []Scanline {
	var path raster.Path
	for i := 0; i <= p.Order; i++ {
		f := fixp(p.X[i%p.Order], p.Y[i%p.Order])
		if i == 0 {
			path.Start(f)
		} else {
			path.Add1(f)
		}
	}
	return fillPath(p.Worker, path)
}


================================================
FILE: primitive/quadratic.go
================================================
package primitive

import (
	"fmt"
	"strings"

	"github.com/fogleman/gg"
	"github.com/golang/freetype/raster"
)

type Quadratic struct {
	Worker *Worker
	X1, Y1 float64
	X2, Y2 float64
	X3, Y3 float64
	Width  float64
}

func NewRandomQuadratic(worker *Worker) *Quadratic {
	rnd := worker.Rnd
	x1 := rnd.Float64() * float64(worker.W)
	y1 := rnd.Float64() * float64(worker.H)
	x2 := x1 + rnd.Float64()*40 - 20
	y2 := y1 + rnd.Float64()*40 - 20
	x3 := x2 + rnd.Float64()*40 - 20
	y3 := y2 + rnd.Float64()*40 - 20
	width := 1.0 / 2
	q := &Quadratic{worker, x1, y1, x2, y2, x3, y3, width}
	q.Mutate()
	return q
}

func (q *Quadratic) Draw(dc *gg.Context, scale float64) {
	dc.MoveTo(q.X1, q.Y1)
	dc.QuadraticTo(q.X2, q.Y2, q.X3, q.Y3)
	dc.SetLineWidth(q.Width * scale)
	dc.Stroke()
}

func (q *Quadratic) SVG(attrs string) string {
	// TODO: this is a little silly
	attrs = strings.Replace(attrs, "fill", "stroke", -1)
	return fmt.Sprintf(
		"<path %s fill=\"none\" d=\"M %f %f Q %f %f, %f %f\" stroke-width=\"%f\" />",
		attrs, q.X1, q.Y1, q.X2, q.Y2, q.X3, q.Y3, q.Width)
}

func (q *Quadratic) Copy() Shape {
	a := *q
	return &a
}

func (q *Quadratic) Mutate() {
	const m = 16
	w := q.Worker.W
	h := q.Worker.H
	rnd := q.Worker.Rnd
	for {
		switch rnd.Intn(3) {
		case 0:
			q.X1 = clamp(q.X1+rnd.NormFloat64()*16, -m, float64(w-1+m))
			q.Y1 = clamp(q.Y1+rnd.NormFloat64()*16, -m, float64(h-1+m))
		case 1:
			q.X2 = clamp(q.X2+rnd.NormFloat64()*16, -m, float64(w-1+m))
			q.Y2 = clamp(q.Y2+rnd.NormFloat64()*16, -m, float64(h-1+m))
		case 2:
			q.X3 = clamp(q.X3+rnd.NormFloat64()*16, -m, float64(w-1+m))
			q.Y3 = clamp(q.Y3+rnd.NormFloat64()*16, -m, float64(h-1+m))
		case 3:
			q.Width = clamp(q.Width+rnd.NormFloat64(), 1, 16)
		}
		if q.Valid() {
			break
		}
	}
}

func (q *Quadratic) Valid() bool {
	dx12 := int(q.X1 - q.X2)
	dy12 := int(q.Y1 - q.Y2)
	dx23 := int(q.X2 - q.X3)
	dy23 := int(q.Y2 - q.Y3)
	dx13 := int(q.X1 - q.X3)
	dy13 := int(q.Y1 - q.Y3)
	d12 := dx12*dx12 + dy12*dy12
	d23 := dx23*dx23 + dy23*dy23
	d13 := dx13*dx13 + dy13*dy13
	return d13 > d12 && d13 > d23
}

func (q *Quadratic) Rasterize() []Scanline {
	var path raster.Path
	p1 := fixp(q.X1, q.Y1)
	p2 := fixp(q.X2, q.Y2)
	p3 := fixp(q.X3, q.Y3)
	path.Start(p1)
	path.Add2(p2, p3)
	width := fix(q.Width)
	return strokePath(q.Worker, path, width, raster.RoundCapper, raster.RoundJoiner)
}


================================================
FILE: primitive/raster.go
================================================
package primitive

import (
	"github.com/golang/freetype/raster"
	"golang.org/x/image/math/fixed"
)

func fix(x float64) fixed.Int26_6 {
	return fixed.Int26_6(x * 64)
}

func fixp(x, y float64) fixed.Point26_6 {
	return fixed.Point26_6{fix(x), fix(y)}
}

type painter struct {
	Lines []Scanline
}

func (p *painter) Paint(spans []raster.Span, done bool) {
	for _, span := range spans {
		p.Lines = append(p.Lines, Scanline{span.Y, span.X0, span.X1 - 1, span.Alpha})
	}
}

func fillPath(worker *Worker, path raster.Path) []Scanline {
	r := worker.Rasterizer
	r.Clear()
	r.UseNonZeroWinding = true
	r.AddPath(path)
	var p painter
	p.Lines = worker.Lines[:0]
	r.Rasterize(&p)
	return p.Lines
}

func strokePath(worker *Worker, path raster.Path, width fixed.Int26_6, cr raster.Capper, jr raster.Joiner) []Scanline {
	r := worker.Rasterizer
	r.Clear()
	r.UseNonZeroWinding = true
	r.AddStroke(path, width, cr, jr)
	var p painter
	p.Lines = worker.Lines[:0]
	r.Rasterize(&p)
	return p.Lines
}


================================================
FILE: primitive/rectangle.go
================================================
package primitive

import (
	"fmt"
	"math"

	"github.com/fogleman/gg"
)

type Rectangle struct {
	Worker *Worker
	X1, Y1 int
	X2, Y2 int
}

func NewRandomRectangle(worker *Worker) *Rectangle {
	rnd := worker.Rnd
	x1 := rnd.Intn(worker.W)
	y1 := rnd.Intn(worker.H)
	x2 := clampInt(x1+rnd.Intn(32)+1, 0, worker.W-1)
	y2 := clampInt(y1+rnd.Intn(32)+1, 0, worker.H-1)
	return &Rectangle{worker, x1, y1, x2, y2}
}

func (r *Rectangle) bounds() (x1, y1, x2, y2 int) {
	x1, y1 = r.X1, r.Y1
	x2, y2 = r.X2, r.Y2
	if x1 > x2 {
		x1, x2 = x2, x1
	}
	if y1 > y2 {
		y1, y2 = y2, y1
	}
	return
}

func (r *Rectangle) Draw(dc *gg.Context, scale float64) {
	x1, y1, x2, y2 := r.bounds()
	dc.DrawRectangle(float64(x1), float64(y1), float64(x2-x1+1), float64(y2-y1+1))
	dc.Fill()
}

func (r *Rectangle) SVG(attrs string) string {
	x1, y1, x2, y2 := r.bounds()
	w := x2 - x1 + 1
	h := y2 - y1 + 1
	return fmt.Sprintf(
		"<rect %s x=\"%d\" y=\"%d\" width=\"%d\" height=\"%d\" />",
		attrs, x1, y1, w, h)
}

func (r *Rectangle) Copy() Shape {
	a := *r
	return &a
}

func (r *Rectangle) Mutate() {
	w := r.Worker.W
	h := r.Worker.H
	rnd := r.Worker.Rnd
	switch rnd.Intn(2) {
	case 0:
		r.X1 = clampInt(r.X1+int(rnd.NormFloat64()*16), 0, w-1)
		r.Y1 = clampInt(r.Y1+int(rnd.NormFloat64()*16), 0, h-1)
	case 1:
		r.X2 = clampInt(r.X2+int(rnd.NormFloat64()*16), 0, w-1)
		r.Y2 = clampInt(r.Y2+int(rnd.NormFloat64()*16), 0, h-1)
	}
}

func (r *Rectangle) Rasterize() []Scanline {
	x1, y1, x2, y2 := r.bounds()
	lines := r.Worker.Lines[:0]
	for y := y1; y <= y2; y++ {
		lines = append(lines, Scanline{y, x1, x2, 0xffff})
	}
	return lines
}

type RotatedRectangle struct {
	Worker *Worker
	X, Y   int
	Sx, Sy int
	Angle  int
}

func NewRandomRotatedRectangle(worker *Worker) *RotatedRectangle {
	rnd := worker.Rnd
	x := rnd.Intn(worker.W)
	y := rnd.Intn(worker.H)
	sx := rnd.Intn(32) + 1
	sy := rnd.Intn(32) + 1
	a := rnd.Intn(360)
	r := &RotatedRectangle{worker, x, y, sx, sy, a}
	r.Mutate()
	return r
}

func (r *RotatedRectangle) Draw(dc *gg.Context, scale float64) {
	sx, sy := float64(r.Sx), float64(r.Sy)
	dc.Push()
	dc.Translate(float64(r.X), float64(r.Y))
	dc.Rotate(radians(float64(r.Angle)))
	dc.DrawRectangle(-sx/2, -sy/2, sx, sy)
	dc.Pop()
	dc.Fill()
}

func (r *RotatedRectangle) SVG(attrs string) string {
	return fmt.Sprintf(
		"<g transform=\"translate(%d %d) rotate(%d) scale(%d %d)\"><rect %s x=\"-0.5\" y=\"-0.5\" width=\"1\" height=\"1\" /></g>",
		r.X, r.Y, r.Angle, r.Sx, r.Sy, attrs)
}

func (r *RotatedRectangle) Copy() Shape {
	a := *r
	return &a
}

func (r *RotatedRectangle) Mutate() {
	w := r.Worker.W
	h := r.Worker.H
	rnd := r.Worker.Rnd
	switch rnd.Intn(3) {
	case 0:
		r.X = clampInt(r.X+int(rnd.NormFloat64()*16), 0, w-1)
		r.Y = clampInt(r.Y+int(rnd.NormFloat64()*16), 0, h-1)
	case 1:
		r.Sx = clampInt(r.Sx+int(rnd.NormFloat64()*16), 1, w-1)
		r.Sy = clampInt(r.Sy+int(rnd.NormFloat64()*16), 1, h-1)
	case 2:
		r.Angle = r.Angle + int(rnd.NormFloat64()*32)
	}
	// for !r.Valid() {
	// 	r.Sx = clampInt(r.Sx+int(rnd.NormFloat64()*16), 0, w-1)
	// 	r.Sy = clampInt(r.Sy+int(rnd.NormFloat64()*16), 0, h-1)
	// }
}

func (r *RotatedRectangle) Valid() bool {
	a, b := r.Sx, r.Sy
	if a < b {
		a, b = b, a
	}
	aspect := float64(a) / float64(b)
	return aspect <= 5
}

func (r *RotatedRectangle) Rasterize() []Scanline {
	w := r.Worker.W
	h := r.Worker.H
	sx, sy := float64(r.Sx), float64(r.Sy)
	angle := radians(float64(r.Angle))
	rx1, ry1 := rotate(-sx/2, -sy/2, angle)
	rx2, ry2 := rotate(sx/2, -sy/2, angle)
	rx3, ry3 := rotate(sx/2, sy/2, angle)
	rx4, ry4 := rotate(-sx/2, sy/2, angle)
	x1, y1 := int(rx1)+r.X, int(ry1)+r.Y
	x2, y2 := int(rx2)+r.X, int(ry2)+r.Y
	x3, y3 := int(rx3)+r.X, int(ry3)+r.Y
	x4, y4 := int(rx4)+r.X, int(ry4)+r.Y
	miny := minInt(y1, minInt(y2, minInt(y3, y4)))
	maxy := maxInt(y1, maxInt(y2, maxInt(y3, y4)))
	n := maxy - miny + 1
	min := make([]int, n)
	max := make([]int, n)
	for i := range min {
		min[i] = w
	}
	xs := []int{x1, x2, x3, x4, x1}
	ys := []int{y1, y2, y3, y4, y1}
	// TODO: this could be better probably
	for i := 0; i < 4; i++ {
		x, y := float64(xs[i]), float64(ys[i])
		dx, dy := float64(xs[i+1]-xs[i]), float64(ys[i+1]-ys[i])
		count := int(math.Sqrt(dx*dx+dy*dy)) * 2
		for j := 0; j < count; j++ {
			t := float64(j) / float64(count-1)
			xi := int(x + dx*t)
			yi := int(y+dy*t) - miny
			min[yi] = minInt(min[yi], xi)
			max[yi] = maxInt(max[yi], xi)
		}
	}
	lines := r.Worker.Lines[:0]
	for i := 0; i < n; i++ {
		y := miny + i
		if y < 0 || y >= h {
			continue
		}
		a := maxInt(min[i], 0)
		b := minInt(max[i], w-1)
		if b >= a {
			lines = append(lines, Scanline{y, a, b, 0xffff})
		}
	}
	return lines
}


================================================
FILE: primitive/scanline.go
================================================
package primitive

type Scanline struct {
	Y, X1, X2 int
	Alpha     uint32
}

func cropScanlines(lines []Scanline, w, h int) []Scanline {
	i := 0
	for _, line := range lines {
		if line.Y < 0 || line.Y >= h {
			continue
		}
		if line.X1 >= w {
			continue
		}
		if line.X2 < 0 {
			continue
		}
		line.X1 = clampInt(line.X1, 0, w-1)
		line.X2 = clampInt(line.X2, 0, w-1)
		if line.X1 > line.X2 {
			continue
		}
		lines[i] = line
		i++
	}
	return lines[:i]
}


================================================
FILE: primitive/shape.go
================================================
package primitive

import "github.com/fogleman/gg"

type Shape interface {
	Rasterize() []Scanline
	Copy() Shape
	Mutate()
	Draw(dc *gg.Context, scale float64)
	SVG(attrs string) string
}

type ShapeType int

const (
	ShapeTypeAny ShapeType = iota
	ShapeTypeTriangle
	ShapeTypeRectangle
	ShapeTypeEllipse
	ShapeTypeCircle
	ShapeTypeRotatedRectangle
	ShapeTypeQuadratic
	ShapeTypeRotatedEllipse
	ShapeTypePolygon
)


================================================
FILE: primitive/state.go
================================================
package primitive

type State struct {
	Worker      *Worker
	Shape       Shape
	Alpha       int
	MutateAlpha bool
	Score       float64
}

func NewState(worker *Worker, shape Shape, alpha int) *State {
	var mutateAlpha bool
	if alpha == 0 {
		alpha = 128
		mutateAlpha = true
	}
	return &State{worker, shape, alpha, mutateAlpha, -1}
}

func (state *State) Energy() float64 {
	if state.Score < 0 {
		state.Score = state.Worker.Energy(state.Shape, state.Alpha)
	}
	return state.Score
}

func (state *State) DoMove() interface{} {
	rnd := state.Worker.Rnd
	oldState := state.Copy()
	state.Shape.Mutate()
	if state.MutateAlpha {
		state.Alpha = clampInt(state.Alpha+rnd.Intn(21)-10, 1, 255)
	}
	state.Score = -1
	return oldState
}

func (state *State) UndoMove(undo interface{}) {
	oldState := undo.(*State)
	state.Shape = oldState.Shape
	state.Alpha = oldState.Alpha
	state.Score = oldState.Score
}

func (state *State) Copy() Annealable {
	return &State{
		state.Worker, state.Shape.Copy(), state.Alpha, state.MutateAlpha, state.Score}
}


================================================
FILE: primitive/triangle.go
================================================
package primitive

import (
	"fmt"
	"math"

	"github.com/fogleman/gg"
)

type Triangle struct {
	Worker *Worker
	X1, Y1 int
	X2, Y2 int
	X3, Y3 int
}

func NewRandomTriangle(worker *Worker) *Triangle {
	rnd := worker.Rnd
	x1 := rnd.Intn(worker.W)
	y1 := rnd.Intn(worker.H)
	x2 := x1 + rnd.Intn(31) - 15
	y2 := y1 + rnd.Intn(31) - 15
	x3 := x1 + rnd.Intn(31) - 15
	y3 := y1 + rnd.Intn(31) - 15
	t := &Triangle{worker, x1, y1, x2, y2, x3, y3}
	t.Mutate()
	return t
}

func (t *Triangle) Draw(dc *gg.Context, scale float64) {
	dc.LineTo(float64(t.X1), float64(t.Y1))
	dc.LineTo(float64(t.X2), float64(t.Y2))
	dc.LineTo(float64(t.X3), float64(t.Y3))
	dc.ClosePath()
	dc.Fill()
}

func (t *Triangle) SVG(attrs string) string {
	return fmt.Sprintf(
		"<polygon %s points=\"%d,%d %d,%d %d,%d\" />",
		attrs, t.X1, t.Y1, t.X2, t.Y2, t.X3, t.Y3)
}

func (t *Triangle) Copy() Shape {
	a := *t
	return &a
}

func (t *Triangle) Mutate() {
	w := t.Worker.W
	h := t.Worker.H
	rnd := t.Worker.Rnd
	const m = 16
	for {
		switch rnd.Intn(3) {
		case 0:
			t.X1 = clampInt(t.X1+int(rnd.NormFloat64()*16), -m, w-1+m)
			t.Y1 = clampInt(t.Y1+int(rnd.NormFloat64()*16), -m, h-1+m)
		case 1:
			t.X2 = clampInt(t.X2+int(rnd.NormFloat64()*16), -m, w-1+m)
			t.Y2 = clampInt(t.Y2+int(rnd.NormFloat64()*16), -m, h-1+m)
		case 2:
			t.X3 = clampInt(t.X3+int(rnd.NormFloat64()*16), -m, w-1+m)
			t.Y3 = clampInt(t.Y3+int(rnd.NormFloat64()*16), -m, h-1+m)
		}
		if t.Valid() {
			break
		}
	}
}

func (t *Triangle) Valid() bool {
	const minDegrees = 15
	var a1, a2, a3 float64
	{
		x1 := float64(t.X2 - t.X1)
		y1 := float64(t.Y2 - t.Y1)
		x2 := float64(t.X3 - t.X1)
		y2 := float64(t.Y3 - t.Y1)
		d1 := math.Sqrt(x1*x1 + y1*y1)
		d2 := math.Sqrt(x2*x2 + y2*y2)
		x1 /= d1
		y1 /= d1
		x2 /= d2
		y2 /= d2
		a1 = degrees(math.Acos(x1*x2 + y1*y2))
	}
	{
		x1 := float64(t.X1 - t.X2)
		y1 := float64(t.Y1 - t.Y2)
		x2 := float64(t.X3 - t.X2)
		y2 := float64(t.Y3 - t.Y2)
		d1 := math.Sqrt(x1*x1 + y1*y1)
		d2 := math.Sqrt(x2*x2 + y2*y2)
		x1 /= d1
		y1 /= d1
		x2 /= d2
		y2 /= d2
		a2 = degrees(math.Acos(x1*x2 + y1*y2))
	}
	a3 = 180 - a1 - a2
	return a1 > minDegrees && a2 > minDegrees && a3 > minDegrees
}

func (t *Triangle) Rasterize() []Scanline {
	buf := t.Worker.Lines[:0]
	lines := rasterizeTriangle(t.X1, t.Y1, t.X2, t.Y2, t.X3, t.Y3, buf)
	return cropScanlines(lines, t.Worker.W, t.Worker.H)
}

func rasterizeTriangle(x1, y1, x2, y2, x3, y3 int, buf []Scanline) []Scanline {
	if y1 > y3 {
		x1, x3 = x3, x1
		y1, y3 = y3, y1
	}
	if y1 > y2 {
		x1, x2 = x2, x1
		y1, y2 = y2, y1
	}
	if y2 > y3 {
		x2, x3 = x3, x2
		y2, y3 = y3, y2
	}
	if y2 == y3 {
		return rasterizeTriangleBottom(x1, y1, x2, y2, x3, y3, buf)
	} else if y1 == y2 {
		return rasterizeTriangleTop(x1, y1, x2, y2, x3, y3, buf)
	} else {
		x4 := x1 + int((float64(y2-y1)/float64(y3-y1))*float64(x3-x1))
		y4 := y2
		buf = rasterizeTriangleBottom(x1, y1, x2, y2, x4, y4, buf)
		buf = rasterizeTriangleTop(x2, y2, x4, y4, x3, y3, buf)
		return buf
	}
}

func rasterizeTriangleBottom(x1, y1, x2, y2, x3, y3 int, buf []Scanline) []Scanline {
	s1 := float64(x2-x1) / float64(y2-y1)
	s2 := float64(x3-x1) / float64(y3-y1)
	ax := float64(x1)
	bx := float64(x1)
	for y := y1; y <= y2; y++ {
		a := int(ax)
		b := int(bx)
		ax += s1
		bx += s2
		if a > b {
			a, b = b, a
		}
		buf = append(buf, Scanline{y, a, b, 0xffff})
	}
	return buf
}

func rasterizeTriangleTop(x1, y1, x2, y2, x3, y3 int, buf []Scanline) []Scanline {
	s1 := float64(x3-x1) / float64(y3-y1)
	s2 := float64(x3-x2) / float64(y3-y2)
	ax := float64(x3)
	bx := float64(x3)
	for y := y3; y > y1; y-- {
		ax -= s1
		bx -= s2
		a := int(ax)
		b := int(bx)
		if a > b {
			a, b = b, a
		}
		buf = append(buf, Scanline{y, a, b, 0xffff})
	}
	return buf
}


================================================
FILE: primitive/util.go
================================================
package primitive

import (
	"fmt"
	"image"
	"image/color"
	"image/color/palette"
	"image/draw"
	"image/gif"
	"image/jpeg"
	"image/png"
	"io/ioutil"
	"math"
	"os"
	"os/exec"
	"path/filepath"
)

func LoadImage(path string) (image.Image, error) {
	if path == "-" {
		im, _, err := image.Decode(os.Stdin)
		return im, err
	} else {
		file, err := os.Open(path)
		if err != nil {
			return nil, err
		}
		defer file.Close()
		im, _, err := image.Decode(file)
		return im, err
	}
}

func SaveFile(path, contents string) error {
	if path == "-" {
		_, err := fmt.Fprint(os.Stdout, contents)
		return err
	} else {
		file, err := os.Create(path)
		if err != nil {
			return err
		}
		defer file.Close()
		_, err = file.WriteString(contents)
		return err
	}
}

func SavePNG(path string, im image.Image) error {
	file, err := os.Create(path)
	if err != nil {
		return err
	}
	defer file.Close()
	return png.Encode(file, im)
}

func SaveJPG(path string, im image.Image, quality int) error {
	file, err := os.Create(path)
	if err != nil {
		return err
	}
	defer file.Close()
	return jpeg.Encode(file, im, &jpeg.Options{quality})
}

func SaveGIF(path string, frames []image.Image, delay, lastDelay int) error {
	g := gif.GIF{}
	for i, src := range frames {
		dst := image.NewPaletted(src.Bounds(), palette.Plan9)
		draw.Draw(dst, dst.Rect, src, image.ZP, draw.Src)
		g.Image = append(g.Image, dst)
		if i == len(frames)-1 {
			g.Delay = append(g.Delay, lastDelay)
		} else {
			g.Delay = append(g.Delay, delay)
		}
	}
	file, err := os.Create(path)
	if err != nil {
		return err
	}
	defer file.Close()
	return gif.EncodeAll(file, &g)
}

func SaveGIFImageMagick(path string, frames []image.Image, delay, lastDelay int) error {
	dir, err := ioutil.TempDir("", "")
	if err != nil {
		return err
	}
	for i, im := range frames {
		path := filepath.Join(dir, fmt.Sprintf("%06d.png", i))
		SavePNG(path, im)
	}
	args := []string{
		"-loop", "0",
		"-delay", fmt.Sprint(delay),
		filepath.Join(dir, "*.png"),
		"-delay", fmt.Sprint(lastDelay - delay),
		filepath.Join(dir, fmt.Sprintf("%06d.png", len(frames)-1)),
		path,
	}
	cmd := exec.Command("convert", args...)
	if err := cmd.Run(); err != nil {
		return err
	}
	return os.RemoveAll(dir)
}

func NumberString(x float64) string {
	suffixes := []string{"", "k", "M", "G"}
	for _, suffix := range suffixes {
		if x < 1000 {
			return fmt.Sprintf("%.1f%s", x, suffix)
		}
		x /= 1000
	}
	return fmt.Sprintf("%.1f%s", x, "T")
}

func radians(degrees float64) float64 {
	return degrees * math.Pi / 180
}

func degrees(radians float64) float64 {
	return radians * 180 / math.Pi
}

func clamp(x, lo, hi float64) float64 {
	if x < lo {
		return lo
	}
	if x > hi {
		return hi
	}
	return x
}

func clampInt(x, lo, hi int) int {
	if x < lo {
		return lo
	}
	if x > hi {
		return hi
	}
	return x
}

func minInt(a, b int) int {
	if a < b {
		return a
	}
	return b
}

func maxInt(a, b int) int {
	if a > b {
		return a
	}
	return b
}

func rotate(x, y, theta float64) (rx, ry float64) {
	rx = x*math.Cos(theta) - y*math.Sin(theta)
	ry = x*math.Sin(theta) + y*math.Cos(theta)
	return
}

func imageToRGBA(src image.Image) *image.RGBA {
	dst := image.NewRGBA(src.Bounds())
	draw.Draw(dst, dst.Rect, src, image.ZP, draw.Src)
	return dst
}

func copyRGBA(src *image.RGBA) *image.RGBA {
	dst := image.NewRGBA(src.Bounds())
	copy(dst.Pix, src.Pix)
	return dst
}

func uniformRGBA(r image.Rectangle, c color.Color) *image.RGBA {
	im := image.NewRGBA(r)
	draw.Draw(im, im.Bounds(), &image.Uniform{c}, image.ZP, draw.Src)
	return im
}

func AverageImageColor(im image.Image) color.NRGBA {
	rgba := imageToRGBA(im)
	size := rgba.Bounds().Size()
	w, h := size.X, size.Y
	var r, g, b int
	for y := 0; y < h; y++ {
		for x := 0; x < w; x++ {
			c := rgba.RGBAAt(x, y)
			r += int(c.R)
			g += int(c.G)
			b += int(c.B)
		}
	}
	r /= w * h
	g /= w * h
	b /= w * h
	return color.NRGBA{uint8(r), uint8(g), uint8(b), 255}
}


================================================
FILE: primitive/worker.go
================================================
package primitive

import (
	"image"
	"math/rand"
	"time"

	"github.com/golang/freetype/raster"
)

type Worker struct {
	W, H       int
	Target     *image.RGBA
	Current    *image.RGBA
	Buffer     *image.RGBA
	Rasterizer *raster.Rasterizer
	Lines      []Scanline
	Heatmap    *Heatmap
	Rnd        *rand.Rand
	Score      float64
	Counter    int
}

func NewWorker(target *image.RGBA) *Worker {
	w := target.Bounds().Size().X
	h := target.Bounds().Size().Y
	worker := Worker{}
	worker.W = w
	worker.H = h
	worker.Target = target
	worker.Buffer = image.NewRGBA(target.Bounds())
	worker.Rasterizer = raster.NewRasterizer(w, h)
	worker.Lines = make([]Scanline, 0, 4096) // TODO: based on height
	worker.Heatmap = NewHeatmap(w, h)
	worker.Rnd = rand.New(rand.NewSource(time.Now().UnixNano()))
	return &worker
}

func (worker *Worker) Init(current *image.RGBA, score float64) {
	worker.Current = current
	worker.Score = score
	worker.Counter = 0
	worker.Heatmap.Clear()
}

func (worker *Worker) Energy(shape Shape, alpha int) float64 {
	worker.Counter++
	lines := shape.Rasterize()
	// worker.Heatmap.Add(lines)
	color := computeColor(worker.Target, worker.Current, lines, alpha)
	copyLines(worker.Buffer, worker.Current, lines)
	drawLines(worker.Buffer, color, lines)
	return differencePartial(worker.Target, worker.Current, worker.Buffer, worker.Score, lines)
}

func (worker *Worker) BestHillClimbState(t ShapeType, a, n, age, m int) *State {
	var bestEnergy float64
	var bestState *State
	for i := 0; i < m; i++ {
		state := worker.BestRandomState(t, a, n)
		before := state.Energy()
		state = HillClimb(state, age).(*State)
		energy := state.Energy()
		vv("%dx random: %.6f -> %dx hill climb: %.6f\n", n, before, age, energy)
		if i == 0 || energy < bestEnergy {
			bestEnergy = energy
			bestState = state
		}
	}
	return bestState
}

func (worker *Worker) BestRandomState(t ShapeType, a, n int) *State {
	var bestEnergy float64
	var bestState *State
	for i := 0; i < n; i++ {
		state := worker.RandomState(t, a)
		energy := state.Energy()
		if i == 0 || energy < bestEnergy {
			bestEnergy = energy
			bestState = state
		}
	}
	return bestState
}

func (worker *Worker) RandomState(t ShapeType, a int) *State {
	switch t {
	default:
		return worker.RandomState(ShapeType(worker.Rnd.Intn(8)+1), a)
	case ShapeTypeTriangle:
		return NewState(worker, NewRandomTriangle(worker), a)
	case ShapeTypeRectangle:
		return NewState(worker, NewRandomRectangle(worker), a)
	case ShapeTypeEllipse:
		return NewState(worker, NewRandomEllipse(worker), a)
	case ShapeTypeCircle:
		return NewState(worker, NewRandomCircle(worker), a)
	case ShapeTypeRotatedRectangle:
		return NewState(worker, NewRandomRotatedRectangle(worker), a)
	case ShapeTypeQuadratic:
		return NewState(worker, NewRandomQuadratic(worker), a)
	case ShapeTypeRotatedEllipse:
		return NewState(worker, NewRandomRotatedEllipse(worker), a)
	case ShapeTypePolygon:
		return NewState(worker, NewRandomPolygon(worker, 4, false), a)
	}
}


================================================
FILE: scripts/html.py
================================================
import os
import sys

def run(in_folder, out_folder):
    seen = set()
    for name in os.listdir(out_folder):
        if not name.endswith('.png'):
            continue
        seen.add(name.split('.')[0])
    for name in os.listdir(in_folder):
        if not name.endswith('.jpg'):
            continue
        name = name[:-4]
        if name not in seen:
            continue
        for m in [1, 3, 5]:
            print '<tr>'
            path = '%s.jpg' % name
            print '<td><img src="%s"></td>' % os.path.join(in_folder, path)
            for n in [50, 100, 200]:
                path = '%s.%d.128.4.%d.png' % (name, n, m)
                print '<td><img src="%s"></td>' % os.path.join(out_folder, path)
            print '</tr>'

def main():
    args = sys.argv[1:]
    print HEADER
    run(args[0], args[1])
    print FOOTER

HEADER = '''
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>PrimitivePic</title>
<style>
body {
    margin: 0;
    padding: 0;
    font-family: sans-serif;
}
table {
    border-collapse: collapse;
    margin: 4px;
}
img {
    width: 400px;
    display: block;
    margin: 4px;
}
td {
    padding: 0;
}
</style>
</head>
<body>
<table>

<tr>
<th>original</th>
<th>50 shapes</th>
<th>100 shapes</th>
<th>200 shapes</th>
</tr>
'''

FOOTER = '''
</table>
</body>
</html>
'''

if __name__ == '__main__':
    main()


================================================
FILE: scripts/process.py
================================================
from Queue import Queue
import itertools
import os
import subprocess
import sys
import threading

def makedirs(x):
    try:
        os.makedirs(x)
    except Exception:
        pass

def primitive(i, o, n, a, m):
    makedirs(os.path.split(o)[0])
    args = (i, o, n, a, m)
    cmd = 'primitive -r 128 -s 512 -i %s -o %s -n %d -a %d -m %d' % args
    subprocess.call(cmd, shell=True)

def create_jobs(in_folder, out_folder, n, a, m):
    result = []
    for name in os.listdir(in_folder):
        base, ext = os.path.splitext(name)
        if ext.lower() not in ['.jpg', '.jpeg', '.png']:
            continue
        out_name = '%d.%%d.png' % (m)
        in_path = os.path.join(in_folder, name)
        out_path = os.path.join(out_folder, base, out_name)
        if os.path.exists(out_path):
            continue
        key = (base, n, m)
        args = (in_path, out_path, n, a, m)
        result.append((key, args))
    return result

def worker(jobs, done):
    while True:
        job = jobs.get()
        log(job)
        primitive(*job)
        done.put(True)

def process(in_folder, out_folder, nlist, alist, mlist, nworkers):
    jobs = Queue()
    done = Queue()
    for i in xrange(nworkers):
        t = threading.Thread(target=worker, args=(jobs, done))
        t.setDaemon(True)
        t.start()
    count = 0
    items = []
    for n, a, m in itertools.product(nlist, alist, mlist):
        for item in create_jobs(in_folder, out_folder, n, a, m):
            items.append(item)
    items.sort()
    for _, job in items:
        jobs.put(job)
        count += 1
    for i in xrange(count):
        done.get()

log_lock = threading.Lock()

def log(x):
    with log_lock:
        print x

if __name__ == '__main__':
    args = sys.argv[1:]
    nlist = [500]
    alist = [128]
    mlist = [0, 1, 3, 5]
    nworkers = 4
    process(args[0], args[1], nlist, alist, mlist, nworkers)
Download .txt
gitextract_s2bcgc36/

├── .gitignore
├── LICENSE.md
├── README.md
├── bot/
│   ├── .gitignore
│   ├── main.py
│   └── requirements.txt
├── main.go
├── primitive/
│   ├── color.go
│   ├── core.go
│   ├── ellipse.go
│   ├── heatmap.go
│   ├── log.go
│   ├── model.go
│   ├── optimize.go
│   ├── polygon.go
│   ├── quadratic.go
│   ├── raster.go
│   ├── rectangle.go
│   ├── scanline.go
│   ├── shape.go
│   ├── state.go
│   ├── triangle.go
│   ├── util.go
│   └── worker.go
└── scripts/
    ├── html.py
    └── process.py
Download .txt
SYMBOL INDEX (182 symbols across 21 files)

FILE: bot/main.py
  class AttrDict (line 43) | class AttrDict(dict):
    method __init__ (line 44) | def __init__(self, *args, **kwargs):
  class Config (line 48) | class Config(AttrDict):
    method randomize (line 49) | def randomize(self):
    method parse (line 56) | def parse(self, text):
    method validate (line 67) | def validate(self):
    method description (line 76) | def description(self):
  function clamp (line 80) | def clamp(x, lo, hi):
  function random_date (line 87) | def random_date(max_days_ago=1000):
  function interesting (line 93) | def interesting(date=None):
  function photo_url (line 106) | def photo_url(p, size=None):
  function download_photo (line 115) | def download_photo(url, path):
  function primitive (line 120) | def primitive(**kwargs):
  function twitter_api (line 131) | def twitter_api():
  function tweet (line 138) | def tweet(status, media, in_reply_to_status_id=None):
  function handle_mentions (line 142) | def handle_mentions():
  function handle_mention (line 153) | def handle_mention(status):
  function flickr_url (line 198) | def flickr_url(photo_id):
  function base_encode (line 202) | def base_encode(alphabet, number, suffix=''):
  function generate (line 210) | def generate():
  function main (line 235) | def main():
  function download_photos (line 254) | def download_photos(folder, date=None):

FILE: main.go
  type flagArray (line 34) | type flagArray
    method String (line 36) | func (i *flagArray) String() string {
    method Set (line 40) | func (i *flagArray) Set(value string) error {
  type shapeConfig (line 45) | type shapeConfig struct
  type shapeConfigArray (line 52) | type shapeConfigArray
    method String (line 54) | func (i *shapeConfigArray) String() string {
    method Set (line 58) | func (i *shapeConfigArray) Set(value string) error {
  function init (line 64) | func init() {
  function errorMessage (line 80) | func errorMessage(message string) bool {
  function check (line 85) | func check(err error) {
  function main (line 91) | func main() {

FILE: primitive/color.go
  type Color (line 9) | type Color struct
    method NRGBA (line 42) | func (c *Color) NRGBA() color.NRGBA {
  function MakeColor (line 13) | func MakeColor(c color.Color) Color {
  function MakeHexColor (line 18) | func MakeHexColor(x string) Color {

FILE: primitive/core.go
  function computeColor (line 8) | func computeColor(target, current *image.RGBA, lines []Scanline, alpha i...
  function copyLines (line 36) | func copyLines(dst, src *image.RGBA, lines []Scanline) {
  function drawLines (line 44) | func drawLines(im *image.RGBA, c Color, lines []Scanline) {
  function differenceFull (line 65) | func differenceFull(a, b *image.RGBA) float64 {
  function differencePartial (line 91) | func differencePartial(target, before, after *image.RGBA, score float64,...

FILE: primitive/ellipse.go
  type Ellipse (line 11) | type Ellipse struct
    method Draw (line 35) | func (c *Ellipse) Draw(dc *gg.Context, scale float64) {
    method SVG (line 40) | func (c *Ellipse) SVG(attrs string) string {
    method Copy (line 46) | func (c *Ellipse) Copy() Shape {
    method Mutate (line 51) | func (c *Ellipse) Mutate() {
    method Rasterize (line 72) | func (c *Ellipse) Rasterize() []Scanline {
  function NewRandomEllipse (line 18) | func NewRandomEllipse(worker *Worker) *Ellipse {
  function NewRandomCircle (line 27) | func NewRandomCircle(worker *Worker) *Ellipse {
  type RotatedEllipse (line 102) | type RotatedEllipse struct
    method Draw (line 119) | func (c *RotatedEllipse) Draw(dc *gg.Context, scale float64) {
    method SVG (line 127) | func (c *RotatedEllipse) SVG(attrs string) string {
    method Copy (line 133) | func (c *RotatedEllipse) Copy() Shape {
    method Mutate (line 138) | func (c *RotatedEllipse) Mutate() {
    method Rasterize (line 154) | func (c *RotatedEllipse) Rasterize() []Scanline {
  function NewRandomRotatedEllipse (line 109) | func NewRandomRotatedEllipse(worker *Worker) *RotatedEllipse {

FILE: primitive/heatmap.go
  type Heatmap (line 9) | type Heatmap struct
    method Clear (line 19) | func (h *Heatmap) Clear() {
    method Add (line 25) | func (h *Heatmap) Add(lines []Scanline) {
    method AddHeatmap (line 35) | func (h *Heatmap) AddHeatmap(a *Heatmap) {
    method Image (line 41) | func (h *Heatmap) Image(gamma float64) *image.Gray16 {
  function NewHeatmap (line 14) | func NewHeatmap(w, h int) *Heatmap {

FILE: primitive/log.go
  function Log (line 7) | func Log(level int, format string, a ...interface{}) {
  function v (line 13) | func v(format string, a ...interface{}) {
  function vv (line 17) | func vv(format string, a ...interface{}) {
  function vvv (line 21) | func vvv(format string, a ...interface{}) {

FILE: primitive/model.go
  type Model (line 11) | type Model struct
    method newContext (line 57) | func (model *Model) newContext() *gg.Context {
    method Frames (line 66) | func (model *Model) Frames(scoreDelta float64) []image.Image {
    method SVG (line 86) | func (model *Model) SVG() string {
    method Add (line 103) | func (model *Model) Add(shape Shape, alpha int) {
    method Step (line 119) | func (model *Model) Step(shapeType ShapeType, alpha, repeat int) int {
    method runWorkers (line 147) | func (model *Model) runWorkers(t ShapeType, a, n, age, m int) *State {
    method runWorker (line 172) | func (model *Model) runWorker(worker *Worker, t ShapeType, a, n, age, ...
  function NewModel (line 25) | func NewModel(target image.Image, background Color, size, numWorkers int...

FILE: primitive/optimize.go
  type Annealable (line 8) | type Annealable interface
  function HillClimb (line 15) | func HillClimb(state Annealable, maxAge int) Annealable {
  function PreAnneal (line 36) | func PreAnneal(state Annealable, iterations int) float64 {
  function Anneal (line 49) | func Anneal(state Annealable, maxTemp, minTemp float64, steps int) Annea...

FILE: primitive/polygon.go
  type Polygon (line 11) | type Polygon struct
    method Draw (line 33) | func (p *Polygon) Draw(dc *gg.Context, scale float64) {
    method SVG (line 42) | func (p *Polygon) SVG(attrs string) string {
    method Copy (line 54) | func (p *Polygon) Copy() Shape {
    method Mutate (line 63) | func (p *Polygon) Mutate() {
    method Valid (line 84) | func (p *Polygon) Valid() bool {
    method Rasterize (line 111) | func (p *Polygon) Rasterize() []Scanline {
  function NewRandomPolygon (line 18) | func NewRandomPolygon(worker *Worker, order int, convex bool) *Polygon {
  function cross3 (line 103) | func cross3(x1, y1, x2, y2, x3, y3 float64) float64 {

FILE: primitive/quadratic.go
  type Quadratic (line 11) | type Quadratic struct
    method Draw (line 33) | func (q *Quadratic) Draw(dc *gg.Context, scale float64) {
    method SVG (line 40) | func (q *Quadratic) SVG(attrs string) string {
    method Copy (line 48) | func (q *Quadratic) Copy() Shape {
    method Mutate (line 53) | func (q *Quadratic) Mutate() {
    method Valid (line 78) | func (q *Quadratic) Valid() bool {
    method Rasterize (line 91) | func (q *Quadratic) Rasterize() []Scanline {
  function NewRandomQuadratic (line 19) | func NewRandomQuadratic(worker *Worker) *Quadratic {

FILE: primitive/raster.go
  function fix (line 8) | func fix(x float64) fixed.Int26_6 {
  function fixp (line 12) | func fixp(x, y float64) fixed.Point26_6 {
  type painter (line 16) | type painter struct
    method Paint (line 20) | func (p *painter) Paint(spans []raster.Span, done bool) {
  function fillPath (line 26) | func fillPath(worker *Worker, path raster.Path) []Scanline {
  function strokePath (line 37) | func strokePath(worker *Worker, path raster.Path, width fixed.Int26_6, c...

FILE: primitive/rectangle.go
  type Rectangle (line 10) | type Rectangle struct
    method bounds (line 25) | func (r *Rectangle) bounds() (x1, y1, x2, y2 int) {
    method Draw (line 37) | func (r *Rectangle) Draw(dc *gg.Context, scale float64) {
    method SVG (line 43) | func (r *Rectangle) SVG(attrs string) string {
    method Copy (line 52) | func (r *Rectangle) Copy() Shape {
    method Mutate (line 57) | func (r *Rectangle) Mutate() {
    method Rasterize (line 71) | func (r *Rectangle) Rasterize() []Scanline {
  function NewRandomRectangle (line 16) | func NewRandomRectangle(worker *Worker) *Rectangle {
  type RotatedRectangle (line 80) | type RotatedRectangle struct
    method Draw (line 99) | func (r *RotatedRectangle) Draw(dc *gg.Context, scale float64) {
    method SVG (line 109) | func (r *RotatedRectangle) SVG(attrs string) string {
    method Copy (line 115) | func (r *RotatedRectangle) Copy() Shape {
    method Mutate (line 120) | func (r *RotatedRectangle) Mutate() {
    method Valid (line 140) | func (r *RotatedRectangle) Valid() bool {
    method Rasterize (line 149) | func (r *RotatedRectangle) Rasterize() []Scanline {
  function NewRandomRotatedRectangle (line 87) | func NewRandomRotatedRectangle(worker *Worker) *RotatedRectangle {

FILE: primitive/scanline.go
  type Scanline (line 3) | type Scanline struct
  function cropScanlines (line 8) | func cropScanlines(lines []Scanline, w, h int) []Scanline {

FILE: primitive/shape.go
  type Shape (line 5) | type Shape interface
  type ShapeType (line 13) | type ShapeType
  constant ShapeTypeAny (line 16) | ShapeTypeAny ShapeType = iota
  constant ShapeTypeTriangle (line 17) | ShapeTypeTriangle
  constant ShapeTypeRectangle (line 18) | ShapeTypeRectangle
  constant ShapeTypeEllipse (line 19) | ShapeTypeEllipse
  constant ShapeTypeCircle (line 20) | ShapeTypeCircle
  constant ShapeTypeRotatedRectangle (line 21) | ShapeTypeRotatedRectangle
  constant ShapeTypeQuadratic (line 22) | ShapeTypeQuadratic
  constant ShapeTypeRotatedEllipse (line 23) | ShapeTypeRotatedEllipse
  constant ShapeTypePolygon (line 24) | ShapeTypePolygon

FILE: primitive/state.go
  type State (line 3) | type State struct
    method Energy (line 20) | func (state *State) Energy() float64 {
    method DoMove (line 27) | func (state *State) DoMove() interface{} {
    method UndoMove (line 38) | func (state *State) UndoMove(undo interface{}) {
    method Copy (line 45) | func (state *State) Copy() Annealable {
  function NewState (line 11) | func NewState(worker *Worker, shape Shape, alpha int) *State {

FILE: primitive/triangle.go
  type Triangle (line 10) | type Triangle struct
    method Draw (line 30) | func (t *Triangle) Draw(dc *gg.Context, scale float64) {
    method SVG (line 38) | func (t *Triangle) SVG(attrs string) string {
    method Copy (line 44) | func (t *Triangle) Copy() Shape {
    method Mutate (line 49) | func (t *Triangle) Mutate() {
    method Valid (line 72) | func (t *Triangle) Valid() bool {
    method Rasterize (line 105) | func (t *Triangle) Rasterize() []Scanline {
  function NewRandomTriangle (line 17) | func NewRandomTriangle(worker *Worker) *Triangle {
  function rasterizeTriangle (line 111) | func rasterizeTriangle(x1, y1, x2, y2, x3, y3 int, buf []Scanline) []Sca...
  function rasterizeTriangleBottom (line 137) | func rasterizeTriangleBottom(x1, y1, x2, y2, x3, y3 int, buf []Scanline)...
  function rasterizeTriangleTop (line 155) | func rasterizeTriangleTop(x1, y1, x2, y2, x3, y3 int, buf []Scanline) []...

FILE: primitive/util.go
  function LoadImage (line 19) | func LoadImage(path string) (image.Image, error) {
  function SaveFile (line 34) | func SaveFile(path, contents string) error {
  function SavePNG (line 49) | func SavePNG(path string, im image.Image) error {
  function SaveJPG (line 58) | func SaveJPG(path string, im image.Image, quality int) error {
  function SaveGIF (line 67) | func SaveGIF(path string, frames []image.Image, delay, lastDelay int) er...
  function SaveGIFImageMagick (line 87) | func SaveGIFImageMagick(path string, frames []image.Image, delay, lastDe...
  function NumberString (line 111) | func NumberString(x float64) string {
  function radians (line 122) | func radians(degrees float64) float64 {
  function degrees (line 126) | func degrees(radians float64) float64 {
  function clamp (line 130) | func clamp(x, lo, hi float64) float64 {
  function clampInt (line 140) | func clampInt(x, lo, hi int) int {
  function minInt (line 150) | func minInt(a, b int) int {
  function maxInt (line 157) | func maxInt(a, b int) int {
  function rotate (line 164) | func rotate(x, y, theta float64) (rx, ry float64) {
  function imageToRGBA (line 170) | func imageToRGBA(src image.Image) *image.RGBA {
  function copyRGBA (line 176) | func copyRGBA(src *image.RGBA) *image.RGBA {
  function uniformRGBA (line 182) | func uniformRGBA(r image.Rectangle, c color.Color) *image.RGBA {
  function AverageImageColor (line 188) | func AverageImageColor(im image.Image) color.NRGBA {

FILE: primitive/worker.go
  type Worker (line 11) | type Worker struct
    method Init (line 39) | func (worker *Worker) Init(current *image.RGBA, score float64) {
    method Energy (line 46) | func (worker *Worker) Energy(shape Shape, alpha int) float64 {
    method BestHillClimbState (line 56) | func (worker *Worker) BestHillClimbState(t ShapeType, a, n, age, m int...
    method BestRandomState (line 73) | func (worker *Worker) BestRandomState(t ShapeType, a, n int) *State {
    method RandomState (line 87) | func (worker *Worker) RandomState(t ShapeType, a int) *State {
  function NewWorker (line 24) | func NewWorker(target *image.RGBA) *Worker {

FILE: scripts/html.py
  function run (line 4) | def run(in_folder, out_folder):
  function main (line 25) | def main():

FILE: scripts/process.py
  function makedirs (line 8) | def makedirs(x):
  function primitive (line 14) | def primitive(i, o, n, a, m):
  function create_jobs (line 20) | def create_jobs(in_folder, out_folder, n, a, m):
  function worker (line 36) | def worker(jobs, done):
  function process (line 43) | def process(in_folder, out_folder, nlist, alist, mlist, nworkers):
  function log (line 64) | def log(x):
Condensed preview — 26 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (72K chars).
[
  {
    "path": ".gitignore",
    "chars": 22,
    "preview": "/*.png\n/*.svg\n/*.gif\n\n"
  },
  {
    "path": "LICENSE.md",
    "chars": 1060,
    "preview": "Copyright (C) 2016 Michael Fogleman\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof thi"
  },
  {
    "path": "README.md",
    "chars": 9113,
    "preview": "# Primitive Pictures\n\nReproducing images with geometric primitives.\n\n![Example](https://www.michaelfogleman.com/static/p"
  },
  {
    "path": "bot/.gitignore",
    "chars": 20,
    "preview": "config.py\nenv\nvenv\n\n"
  },
  {
    "path": "bot/main.py",
    "chars": 7620,
    "preview": "import datetime\nimport os\nimport random\nimport requests\nimport subprocess\nimport time\nimport traceback\nimport twitter\n\nR"
  },
  {
    "path": "bot/requirements.txt",
    "chars": 37,
    "preview": "python-twitter==3.1\nrequests==2.11.1\n"
  },
  {
    "path": "main.go",
    "chars": 5077,
    "preview": "package main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"log\"\n\t\"math/rand\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time"
  },
  {
    "path": "primitive/color.go",
    "chars": 840,
    "preview": "package primitive\n\nimport (\n\t\"fmt\"\n\t\"image/color\"\n\t\"strings\"\n)\n\ntype Color struct {\n\tR, G, B, A int\n}\n\nfunc MakeColor(c "
  },
  {
    "path": "primitive/core.go",
    "chars": 3217,
    "preview": "package primitive\n\nimport (\n\t\"image\"\n\t\"math\"\n)\n\nfunc computeColor(target, current *image.RGBA, lines []Scanline, alpha i"
  },
  {
    "path": "primitive/ellipse.go",
    "chars": 4037,
    "preview": "package primitive\n\nimport (\n\t\"fmt\"\n\t\"math\"\n\n\t\"github.com/fogleman/gg\"\n\t\"github.com/golang/freetype/raster\"\n)\n\ntype Ellip"
  },
  {
    "path": "primitive/heatmap.go",
    "chars": 978,
    "preview": "package primitive\n\nimport (\n\t\"image\"\n\t\"image/color\"\n\t\"math\"\n)\n\ntype Heatmap struct {\n\tW, H  int\n\tCount []uint64\n}\n\nfunc "
  },
  {
    "path": "primitive/log.go",
    "chars": 378,
    "preview": "package primitive\n\nimport \"fmt\"\n\nvar LogLevel int\n\nfunc Log(level int, format string, a ...interface{}) {\n\tif LogLevel >"
  },
  {
    "path": "primitive/model.go",
    "chars": 4673,
    "preview": "package primitive\n\nimport (\n\t\"fmt\"\n\t\"image\"\n\t\"strings\"\n\n\t\"github.com/fogleman/gg\"\n)\n\ntype Model struct {\n\tSw, Sh     int"
  },
  {
    "path": "primitive/optimize.go",
    "chars": 1769,
    "preview": "package primitive\n\nimport (\n\t\"math\"\n\t\"math/rand\"\n)\n\ntype Annealable interface {\n\tEnergy() float64\n\tDoMove() interface{}\n"
  },
  {
    "path": "primitive/polygon.go",
    "chars": 2405,
    "preview": "package primitive\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/fogleman/gg\"\n\t\"github.com/golang/freetype/raster\"\n)\n\ntype Po"
  },
  {
    "path": "primitive/quadratic.go",
    "chars": 2367,
    "preview": "package primitive\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/fogleman/gg\"\n\t\"github.com/golang/freetype/raster\"\n)\n\ntype Qu"
  },
  {
    "path": "primitive/raster.go",
    "chars": 987,
    "preview": "package primitive\n\nimport (\n\t\"github.com/golang/freetype/raster\"\n\t\"golang.org/x/image/math/fixed\"\n)\n\nfunc fix(x float64)"
  },
  {
    "path": "primitive/rectangle.go",
    "chars": 4667,
    "preview": "package primitive\n\nimport (\n\t\"fmt\"\n\t\"math\"\n\n\t\"github.com/fogleman/gg\"\n)\n\ntype Rectangle struct {\n\tWorker *Worker\n\tX1, Y1"
  },
  {
    "path": "primitive/scanline.go",
    "chars": 460,
    "preview": "package primitive\n\ntype Scanline struct {\n\tY, X1, X2 int\n\tAlpha     uint32\n}\n\nfunc cropScanlines(lines []Scanline, w, h "
  },
  {
    "path": "primitive/shape.go",
    "chars": 414,
    "preview": "package primitive\n\nimport \"github.com/fogleman/gg\"\n\ntype Shape interface {\n\tRasterize() []Scanline\n\tCopy() Shape\n\tMutate"
  },
  {
    "path": "primitive/state.go",
    "chars": 1035,
    "preview": "package primitive\n\ntype State struct {\n\tWorker      *Worker\n\tShape       Shape\n\tAlpha       int\n\tMutateAlpha bool\n\tScore"
  },
  {
    "path": "primitive/triangle.go",
    "chars": 3752,
    "preview": "package primitive\n\nimport (\n\t\"fmt\"\n\t\"math\"\n\n\t\"github.com/fogleman/gg\"\n)\n\ntype Triangle struct {\n\tWorker *Worker\n\tX1, Y1 "
  },
  {
    "path": "primitive/util.go",
    "chars": 3925,
    "preview": "package primitive\n\nimport (\n\t\"fmt\"\n\t\"image\"\n\t\"image/color\"\n\t\"image/color/palette\"\n\t\"image/draw\"\n\t\"image/gif\"\n\t\"image/jpe"
  },
  {
    "path": "primitive/worker.go",
    "chars": 2980,
    "preview": "package primitive\n\nimport (\n\t\"image\"\n\t\"math/rand\"\n\t\"time\"\n\n\t\"github.com/golang/freetype/raster\"\n)\n\ntype Worker struct {\n"
  },
  {
    "path": "scripts/html.py",
    "chars": 1377,
    "preview": "import os\nimport sys\n\ndef run(in_folder, out_folder):\n    seen = set()\n    for name in os.listdir(out_folder):\n        i"
  },
  {
    "path": "scripts/process.py",
    "chars": 1894,
    "preview": "from Queue import Queue\nimport itertools\nimport os\nimport subprocess\nimport sys\nimport threading\n\ndef makedirs(x):\n    t"
  }
]

About this extraction

This page contains the full source code of the fogleman/primitive GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 26 files (63.6 KB), approximately 21.6k tokens, and a symbol index with 182 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!