[
  {
    "path": ".gitignore",
    "content": "/*.png\n/*.svg\n/*.gif\n\n"
  },
  {
    "path": "LICENSE.md",
    "content": "Copyright (C) 2016 Michael Fogleman\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Primitive Pictures\n\nReproducing images with geometric primitives.\n\n![Example](https://www.michaelfogleman.com/static/primitive/examples/16550611738.200.128.4.5.png)\n\n### How it Works\n\nA 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.\n\n### Primitive for macOS\n\nNow available as a native Mac application!\n\nhttps://primitive.lol/\n\n### Twitter\n\nFollow [@PrimitivePic](https://twitter.com/PrimitivePic) on Twitter to see a new primitive picture every 30 minutes!\n\nThe Twitter bot looks for interesting photos using the Flickr API, runs the algorithm using randomized parameters, and\nposts the picture using the Twitter API.\n\nYou can tweet a picture to the bot and it will process it for you.\n\n### Command-line Usage\n\nRun it on your own images! First, [install Go](https://golang.org/doc/install).\n\n    go get -u github.com/fogleman/primitive\n    primitive -i input.png -o output.png -n 100\n\nSmall input images should be used (like 256x256px). You don't need the detail anyway and the code will run faster.\n\n| Flag | Default | Description |\n| --- | --- | --- |\n| `i` | n/a | input file |\n| `o` | n/a | output file |\n| `n` | n/a | number of shapes |\n| `m` | 1 | mode: 0=combo, 1=triangle, 2=rect, 3=ellipse, 4=circle, 5=rotatedrect, 6=beziers, 7=rotatedellipse, 8=polygon |\n| `rep` | 0 | add N extra shapes each iteration with reduced search (mostly good for beziers) |\n| `nth` | 1 | save every Nth frame (only when `%d` is in output path) |\n| `r` | 256 | resize large input images to this size before processing |\n| `s` | 1024 | output image size |\n| `a` | 128 | color alpha (use `0` to let the algorithm choose alpha for each shape) |\n| `bg` | avg | starting background color (hex) |\n| `j` | 0 | number of parallel workers (default uses all cores) |\n| `v` | off | verbose output |\n| `vv` | off | very verbose output |\n\n### Output Formats\n\nDepending on the output filename extension provided, you can produce different types of output.\n\n- `PNG`: raster output\n- `JPG`: raster output\n- `SVG`: vector output\n- `GIF`: animated output showing shapes being added - requires ImageMagick (specifically the `convert` command)\n\nFor PNG and SVG outputs, you can also include `%d`, `%03d`, etc. in the filename. In this case, each frame will be saved separately.\n\nYou can use the `-o` flag multiple times. This way you can save both a PNG and an SVG, for example.\n\n### Progression\n\nThis 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!)\n\n<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\"/>\n\n### Static Animation\n\nSince 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.\n\n![Pencils](https://www.michaelfogleman.com/static/primitive/examples/pencils.gif)\n\n### Creative Constraints\n\nIf 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.\n\n![Pyramids](https://www.michaelfogleman.com/static/primitive/examples/pyramids.png)\n\n### Shape and Iteration Comparison Matrix\n\nThe matrix below shows triangles, ellipses and rectangles at 50, 100 and 200 iterations each.\n\n![Matrix](http://i.imgur.com/H5NYpL4.png)\n\n### How it Works, Part II\n\nSay 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.\n\n    Current Image + Shape => New Image\n    RMSE(New Image, Target Image) => Score\n\nThe 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.\n\nOnce 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.\n\n### Primitives\n\nThe following primitives are supported:\n\n- Triangle\n- Rectangle (axis-aligned)\n- Ellipse (axis-aligned)\n- Circle\n- Rotated Rectangle\n- Combo (a mix of the above in a single image)\n\nMore shapes can be added by implementing the following interface:\n\n```go\ntype Shape interface {\n\tRasterize() []Scanline\n\tCopy() Shape\n\tMutate()\n\tDraw(dc *gg.Context)\n\tSVG(attrs string) string\n}\n```\n\n### Features\n\n- [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)\n- Scanline rasterization of shapes in pure Go (preferable for implementing the features below)\n- Optimal color computation based on affected pixels for each shape (color is directly computed, not optimized for)\n- Partial image difference for faster scoring (only pixels that change need be considered)\n- Anti-aliased output rendering\n\n### Inspiration\n\nThis 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.\n\nIt 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.\n\n### Examples\n\nHere are more examples from interesting photos found on Flickr.\n\n![Example](https://www.michaelfogleman.com/static/primitive/examples/29167683201.png)\n![Example](https://www.michaelfogleman.com/static/primitive/examples/26574286221.200.128.4.1.png)\n![Example](https://www.michaelfogleman.com/static/primitive/examples/15011768709.200.128.4.1.png)\n![Example](https://www.michaelfogleman.com/static/primitive/examples/27540729075.200.128.4.1.png)\n![Example](https://www.michaelfogleman.com/static/primitive/examples/28896874003.png)\n![Example](https://www.michaelfogleman.com/static/primitive/examples/20414282102.png)\n![Example](https://www.michaelfogleman.com/static/primitive/examples/15199237095.200.128.4.1.png)\n![Example](https://www.michaelfogleman.com/static/primitive/examples/11707819764.200.128.4.1.png)\n![Example](https://www.michaelfogleman.com/static/primitive/examples/18270231645.200.128.4.3.png)\n![Example](https://www.michaelfogleman.com/static/primitive/examples/15705764893.png)\n![Example](https://www.michaelfogleman.com/static/primitive/examples/25213252889.png)\n![Example](https://www.michaelfogleman.com/static/primitive/examples/15015411870.200.128.4.3.png)\n![Example](https://www.michaelfogleman.com/static/primitive/examples/25766500104.png)\n![Example](https://www.michaelfogleman.com/static/primitive/examples/27471731151.50.128.4.1.png)\n![Example](https://www.michaelfogleman.com/static/primitive/examples/11720700033.200.128.4.3.png)\n![Example](https://www.michaelfogleman.com/static/primitive/examples/18782606664.png)\n![Example](https://www.michaelfogleman.com/static/primitive/examples/21374478713.png)\n![Example](https://www.michaelfogleman.com/static/primitive/examples/15196426112.200.128.4.5.png)\n![Example](https://www.michaelfogleman.com/static/primitive/examples/24696847962.png)\n![Example](https://www.michaelfogleman.com/static/primitive/examples/18276676312.100.128.4.1.png)\n"
  },
  {
    "path": "bot/.gitignore",
    "content": "config.py\nenv\nvenv\n\n"
  },
  {
    "path": "bot/main.py",
    "content": "import datetime\nimport os\nimport random\nimport requests\nimport subprocess\nimport time\nimport traceback\nimport twitter\n\nRATE = 60 * 30\nMENTION_RATE = 65\n\nINPUT_FOLDER = ''\nOUTPUT_FOLDER = ''\n\nFLICKR_API_KEY = None\nTWITTER_CONSUMER_KEY = None\nTWITTER_CONSUMER_SECRET = None\nTWITTER_ACCESS_TOKEN_KEY = None\nTWITTER_ACCESS_TOKEN_SECRET = None\n\nMODE_NAMES = [\n    'primitives', # 0\n    'triangles',  # 1\n    'rectangles', # 2\n    'ellipses',   # 3\n    'circles',    # 4\n    'rectangles', # 5\n    'beziers',    # 6\n    'ellipses',   # 7\n    'polygons',   # 8\n]\n\nSINCE_ID = None\nSTART_DATETIME = datetime.datetime.utcnow()\nUSER_DATETIME = {}\n\ntry:\n    from config import *\nexcept ImportError:\n    print 'no config found!'\n\nclass AttrDict(dict):\n    def __init__(self, *args, **kwargs):\n        super(AttrDict, self).__init__(*args, **kwargs)\n        self.__dict__ = self\n\nclass Config(AttrDict):\n    def randomize(self):\n        self.m = random.choice([1, 5, 6, 7])\n        self.n = random.randint(10, 50) * 10\n        self.rep = 0\n        self.a = 128\n        self.r = 300\n        self.s = 1200\n    def parse(self, text):\n        text = (text or '').lower()\n        tokens = text.split()\n        for i, name in enumerate(MODE_NAMES):\n            if name in text:\n                self.m = i\n        for token in tokens:\n            try:\n                self.n = int(token)\n            except Exception:\n                pass\n    def validate(self):\n        self.m = clamp(self.m, 0, 8)\n        if self.m == 6:\n            self.a = 0\n            self.rep = 19\n            self.n = 100\n        else:\n            self.n = clamp(self.n, 1, 500)\n    @property\n    def description(self):\n        total = self.n + self.n * self.rep\n        return '%d %s' % (total, MODE_NAMES[self.m])\n\ndef clamp(x, lo, hi):\n    if x < lo:\n        x = lo\n    if x > hi:\n        x = hi\n    return x\n\ndef random_date(max_days_ago=1000):\n    today = datetime.date.today()\n    days = random.randint(1, max_days_ago)\n    d = today - datetime.timedelta(days=days)\n    return d.strftime('%Y-%m-%d')\n\ndef interesting(date=None):\n    url = 'https://api.flickr.com/services/rest/'\n    params = dict(\n        api_key=FLICKR_API_KEY,\n        format='json',\n        nojsoncallback=1,\n        method='flickr.interestingness.getList',\n    )\n    if date:\n        params['date'] = date\n    r = requests.get(url, params=params)\n    return r.json()['photos']['photo']\n\ndef photo_url(p, size=None):\n    # See: https://www.flickr.com/services/api/misc.urls.html\n    if size:\n        url = 'https://farm%s.staticflickr.com/%s/%s_%s_%s.jpg'\n        return url % (p['farm'], p['server'], p['id'], p['secret'], size)\n    else:\n        url = 'https://farm%s.staticflickr.com/%s/%s_%s.jpg'\n        return url % (p['farm'], p['server'], p['id'], p['secret'])\n\ndef download_photo(url, path):\n    r = requests.get(url)\n    with open(path, 'wb') as fp:\n        fp.write(r.content)\n\ndef primitive(**kwargs):\n    args = []\n    for k, v in kwargs.items():\n        if v is None:\n            continue\n        args.append('-%s' % k)\n        args.append(str(v))\n    args = ' '.join(args)\n    cmd = 'primitive %s' % args\n    subprocess.call(cmd, shell=True)\n\ndef twitter_api():\n    return twitter.Api(\n        consumer_key=TWITTER_CONSUMER_KEY,\n        consumer_secret=TWITTER_CONSUMER_SECRET,\n        access_token_key=TWITTER_ACCESS_TOKEN_KEY,\n        access_token_secret=TWITTER_ACCESS_TOKEN_SECRET)\n\ndef tweet(status, media, in_reply_to_status_id=None):\n    api = twitter_api()\n    api.PostUpdate(status, media, in_reply_to_status_id=in_reply_to_status_id)\n\ndef handle_mentions():\n    global SINCE_ID\n    print 'checking for mentions'\n    api = twitter_api()\n    statuses = api.GetMentions(200, SINCE_ID)\n    for status in reversed(statuses):\n        SINCE_ID = status.id\n        print 'handling mention', status.id\n        handle_mention(status)\n    print 'done with mentions'\n\ndef handle_mention(status):\n    mentions = status.user_mentions or []\n    if len(mentions) != 1:\n        print 'mention does not have exactly one mention'\n        return\n    media = status.media or []\n    if len(media) != 1:\n        print 'mention does not have exactly one media'\n        return\n    url = media[0].media_url or None\n    if not url:\n        print 'mention does not have a media_url'\n        return\n    created_at = datetime.datetime.strptime(\n        status.created_at, '%a %b %d %H:%M:%S +0000 %Y')\n    if created_at < START_DATETIME:\n        print 'mention timestamp before bot started'\n        return\n    user_id = status.user.id\n    now = datetime.datetime.utcnow()\n    td = datetime.timedelta(minutes=5)\n    if user_id in USER_DATETIME:\n        if now - USER_DATETIME[user_id] < td:\n            print 'user mentioned me too recently'\n            return\n    USER_DATETIME[user_id] = now\n    in_path = os.path.join(INPUT_FOLDER, '%s.jpg' % status.id)\n    out_path = os.path.join(OUTPUT_FOLDER, '%s.png' % status.id)\n    print 'downloading', url\n    download_photo(url, in_path)\n    config = Config()\n    config.randomize()\n    config.parse(status.text)\n    config.validate()\n    status_text = '@%s %s.' % (status.user.screen_name, config.description)\n    print status_text\n    print 'running algorithm: %s' % config\n    primitive(i=in_path, o=out_path, **config)\n    if os.path.exists(out_path):\n        print 'uploading to twitter'\n        tweet(status_text, out_path, status.id)\n        print 'done'\n    else:\n        print 'failed!'\n\ndef flickr_url(photo_id):\n    alphabet = '123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ'\n    return 'https://flic.kr/p/%s' % base_encode(alphabet, int(photo_id))\n\ndef base_encode(alphabet, number, suffix=''):\n    base = len(alphabet)\n    if number >= base:\n        div, mod = divmod(number, base)\n        return base_encode(alphabet, div, alphabet[mod] + suffix)\n    else:\n        return alphabet[number] + suffix\n\ndef generate():\n    date = random_date()\n    print 'finding an interesting photo from', date\n    photos = interesting(date)\n    photo = random.choice(photos)\n    print 'picked photo', photo['id']\n    in_path = os.path.join(INPUT_FOLDER, '%s.jpg' % photo['id'])\n    out_path = os.path.join(OUTPUT_FOLDER, '%s.png' % photo['id'])\n    url = photo_url(photo, 'z')\n    print 'downloading', url\n    download_photo(url, in_path)\n    config = Config()\n    config.randomize()\n    config.validate()\n    status_text = '%s. %s' % (config.description, flickr_url(photo['id']))\n    print status_text\n    print 'running algorithm: %s' % config\n    primitive(i=in_path, o=out_path, **config)\n    if os.path.exists(out_path):\n        print 'uploading to twitter'\n        tweet(status_text, out_path)\n        print 'done'\n    else:\n        print 'failed!'\n\ndef main():\n    previous = 0\n    mention_previous = 0\n    while True:\n        now = time.time()\n        if now - previous > RATE:\n            previous = now\n            try:\n                generate()\n            except Exception:\n                traceback.print_exc()\n        if now - mention_previous > MENTION_RATE:\n            mention_previous = now\n            try:\n                handle_mentions()\n            except Exception:\n                traceback.print_exc()\n        time.sleep(5)\n\ndef download_photos(folder, date=None):\n    try:\n        os.makedirs(folder)\n    except Exception:\n        pass\n    date = date or random_date()\n    photos = interesting(date)\n    for photo in photos:\n        url = photo_url(photo, 'z')\n        path = '%s.jpg' % photo['id']\n        path = os.path.join(folder, path)\n        download_photo(url, path)\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "bot/requirements.txt",
    "content": "python-twitter==3.1\nrequests==2.11.1\n"
  },
  {
    "path": "main.go",
    "content": "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\"\n\n\t\"github.com/fogleman/primitive/primitive\"\n\t\"github.com/nfnt/resize\"\n)\n\nvar (\n\tInput      string\n\tOutputs    flagArray\n\tBackground string\n\tConfigs    shapeConfigArray\n\tAlpha      int\n\tInputSize  int\n\tOutputSize int\n\tMode       int\n\tWorkers    int\n\tNth        int\n\tRepeat     int\n\tV, VV      bool\n)\n\ntype flagArray []string\n\nfunc (i *flagArray) String() string {\n\treturn strings.Join(*i, \", \")\n}\n\nfunc (i *flagArray) Set(value string) error {\n\t*i = append(*i, value)\n\treturn nil\n}\n\ntype shapeConfig struct {\n\tCount  int\n\tMode   int\n\tAlpha  int\n\tRepeat int\n}\n\ntype shapeConfigArray []shapeConfig\n\nfunc (i *shapeConfigArray) String() string {\n\treturn \"\"\n}\n\nfunc (i *shapeConfigArray) Set(value string) error {\n\tn, _ := strconv.ParseInt(value, 0, 0)\n\t*i = append(*i, shapeConfig{int(n), Mode, Alpha, Repeat})\n\treturn nil\n}\n\nfunc init() {\n\tflag.StringVar(&Input, \"i\", \"\", \"input image path\")\n\tflag.Var(&Outputs, \"o\", \"output image path\")\n\tflag.Var(&Configs, \"n\", \"number of primitives\")\n\tflag.StringVar(&Background, \"bg\", \"\", \"background color (hex)\")\n\tflag.IntVar(&Alpha, \"a\", 128, \"alpha value\")\n\tflag.IntVar(&InputSize, \"r\", 256, \"resize large input images to this size\")\n\tflag.IntVar(&OutputSize, \"s\", 1024, \"output image size\")\n\tflag.IntVar(&Mode, \"m\", 1, \"0=combo 1=triangle 2=rect 3=ellipse 4=circle 5=rotatedrect 6=beziers 7=rotatedellipse 8=polygon\")\n\tflag.IntVar(&Workers, \"j\", 0, \"number of parallel workers (default uses all cores)\")\n\tflag.IntVar(&Nth, \"nth\", 1, \"save every Nth frame (put \\\"%d\\\" in path)\")\n\tflag.IntVar(&Repeat, \"rep\", 0, \"add N extra shapes per iteration with reduced search\")\n\tflag.BoolVar(&V, \"v\", false, \"verbose\")\n\tflag.BoolVar(&VV, \"vv\", false, \"very verbose\")\n}\n\nfunc errorMessage(message string) bool {\n\tfmt.Fprintln(os.Stderr, message)\n\treturn false\n}\n\nfunc check(err error) {\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n\nfunc main() {\n\t// parse and validate arguments\n\tflag.Parse()\n\tok := true\n\tif Input == \"\" {\n\t\tok = errorMessage(\"ERROR: input argument required\")\n\t}\n\tif len(Outputs) == 0 {\n\t\tok = errorMessage(\"ERROR: output argument required\")\n\t}\n\tif len(Configs) == 0 {\n\t\tok = errorMessage(\"ERROR: number argument required\")\n\t}\n\tif len(Configs) == 1 {\n\t\tConfigs[0].Mode = Mode\n\t\tConfigs[0].Alpha = Alpha\n\t\tConfigs[0].Repeat = Repeat\n\t}\n\tfor _, config := range Configs {\n\t\tif config.Count < 1 {\n\t\t\tok = errorMessage(\"ERROR: number argument must be > 0\")\n\t\t}\n\t}\n\tif !ok {\n\t\tfmt.Println(\"Usage: primitive [OPTIONS] -i input -o output -n count\")\n\t\tflag.PrintDefaults()\n\t\tos.Exit(1)\n\t}\n\n\t// set log level\n\tif V {\n\t\tprimitive.LogLevel = 1\n\t}\n\tif VV {\n\t\tprimitive.LogLevel = 2\n\t}\n\n\t// seed random number generator\n\trand.Seed(time.Now().UTC().UnixNano())\n\n\t// determine worker count\n\tif Workers < 1 {\n\t\tWorkers = runtime.NumCPU()\n\t}\n\n\t// read input image\n\tprimitive.Log(1, \"reading %s\\n\", Input)\n\tinput, err := primitive.LoadImage(Input)\n\tcheck(err)\n\n\t// scale down input image if needed\n\tsize := uint(InputSize)\n\tif size > 0 {\n\t\tinput = resize.Thumbnail(size, size, input, resize.Bilinear)\n\t}\n\n\t// determine background color\n\tvar bg primitive.Color\n\tif Background == \"\" {\n\t\tbg = primitive.MakeColor(primitive.AverageImageColor(input))\n\t} else {\n\t\tbg = primitive.MakeHexColor(Background)\n\t}\n\n\t// run algorithm\n\tmodel := primitive.NewModel(input, bg, OutputSize, Workers)\n\tprimitive.Log(1, \"%d: t=%.3f, score=%.6f\\n\", 0, 0.0, model.Score)\n\tstart := time.Now()\n\tframe := 0\n\tfor j, config := range Configs {\n\t\tprimitive.Log(1, \"count=%d, mode=%d, alpha=%d, repeat=%d\\n\",\n\t\t\tconfig.Count, config.Mode, config.Alpha, config.Repeat)\n\n\t\tfor i := 0; i < config.Count; i++ {\n\t\t\tframe++\n\n\t\t\t// find optimal shape and add it to the model\n\t\t\tt := time.Now()\n\t\t\tn := model.Step(primitive.ShapeType(config.Mode), config.Alpha, config.Repeat)\n\t\t\tnps := primitive.NumberString(float64(n) / time.Since(t).Seconds())\n\t\t\telapsed := time.Since(start).Seconds()\n\t\t\tprimitive.Log(1, \"%d: t=%.3f, score=%.6f, n=%d, n/s=%s\\n\", frame, elapsed, model.Score, n, nps)\n\n\t\t\t// write output image(s)\n\t\t\tfor _, output := range Outputs {\n\t\t\t\text := strings.ToLower(filepath.Ext(output))\n\t\t\t\tif output == \"-\" {\n\t\t\t\t\text = \".svg\"\n\t\t\t\t}\n\t\t\t\tpercent := strings.Contains(output, \"%\")\n\t\t\t\tsaveFrames := percent && ext != \".gif\"\n\t\t\t\tsaveFrames = saveFrames && frame%Nth == 0\n\t\t\t\tlast := j == len(Configs)-1 && i == config.Count-1\n\t\t\t\tif saveFrames || last {\n\t\t\t\t\tpath := output\n\t\t\t\t\tif percent {\n\t\t\t\t\t\tpath = fmt.Sprintf(output, frame)\n\t\t\t\t\t}\n\t\t\t\t\tprimitive.Log(1, \"writing %s\\n\", path)\n\t\t\t\t\tswitch ext {\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tcheck(fmt.Errorf(\"unrecognized file extension: %s\", ext))\n\t\t\t\t\tcase \".png\":\n\t\t\t\t\t\tcheck(primitive.SavePNG(path, model.Context.Image()))\n\t\t\t\t\tcase \".jpg\", \".jpeg\":\n\t\t\t\t\t\tcheck(primitive.SaveJPG(path, model.Context.Image(), 95))\n\t\t\t\t\tcase \".svg\":\n\t\t\t\t\t\tcheck(primitive.SaveFile(path, model.SVG()))\n\t\t\t\t\tcase \".gif\":\n\t\t\t\t\t\tframes := model.Frames(0.001)\n\t\t\t\t\t\tcheck(primitive.SaveGIFImageMagick(path, frames, 50, 250))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "primitive/color.go",
    "content": "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 color.Color) Color {\n\tr, g, b, a := c.RGBA()\n\treturn Color{int(r / 257), int(g / 257), int(b / 257), int(a / 257)}\n}\n\nfunc MakeHexColor(x string) Color {\n\tx = strings.Trim(x, \"#\")\n\tvar r, g, b, a int\n\ta = 255\n\tswitch len(x) {\n\tcase 3:\n\t\tfmt.Sscanf(x, \"%1x%1x%1x\", &r, &g, &b)\n\t\tr = (r << 4) | r\n\t\tg = (g << 4) | g\n\t\tb = (b << 4) | b\n\tcase 4:\n\t\tfmt.Sscanf(x, \"%1x%1x%1x%1x\", &r, &g, &b, &a)\n\t\tr = (r << 4) | r\n\t\tg = (g << 4) | g\n\t\tb = (b << 4) | b\n\t\ta = (a << 4) | a\n\tcase 6:\n\t\tfmt.Sscanf(x, \"%02x%02x%02x\", &r, &g, &b)\n\tcase 8:\n\t\tfmt.Sscanf(x, \"%02x%02x%02x%02x\", &r, &g, &b, &a)\n\t}\n\treturn Color{r, g, b, a}\n}\n\nfunc (c *Color) NRGBA() color.NRGBA {\n\treturn color.NRGBA{uint8(c.R), uint8(c.G), uint8(c.B), uint8(c.A)}\n}\n"
  },
  {
    "path": "primitive/core.go",
    "content": "package primitive\n\nimport (\n\t\"image\"\n\t\"math\"\n)\n\nfunc computeColor(target, current *image.RGBA, lines []Scanline, alpha int) Color {\n\tvar rsum, gsum, bsum, count int64\n\ta := 0x101 * 255 / alpha\n\tfor _, line := range lines {\n\t\ti := target.PixOffset(line.X1, line.Y)\n\t\tfor x := line.X1; x <= line.X2; x++ {\n\t\t\ttr := int(target.Pix[i])\n\t\t\ttg := int(target.Pix[i+1])\n\t\t\ttb := int(target.Pix[i+2])\n\t\t\tcr := int(current.Pix[i])\n\t\t\tcg := int(current.Pix[i+1])\n\t\t\tcb := int(current.Pix[i+2])\n\t\t\ti += 4\n\t\t\trsum += int64((tr-cr)*a + cr*0x101)\n\t\t\tgsum += int64((tg-cg)*a + cg*0x101)\n\t\t\tbsum += int64((tb-cb)*a + cb*0x101)\n\t\t\tcount++\n\t\t}\n\t}\n\tif count == 0 {\n\t\treturn Color{}\n\t}\n\tr := clampInt(int(rsum/count)>>8, 0, 255)\n\tg := clampInt(int(gsum/count)>>8, 0, 255)\n\tb := clampInt(int(bsum/count)>>8, 0, 255)\n\treturn Color{r, g, b, alpha}\n}\n\nfunc copyLines(dst, src *image.RGBA, lines []Scanline) {\n\tfor _, line := range lines {\n\t\ta := dst.PixOffset(line.X1, line.Y)\n\t\tb := a + (line.X2-line.X1+1)*4\n\t\tcopy(dst.Pix[a:b], src.Pix[a:b])\n\t}\n}\n\nfunc drawLines(im *image.RGBA, c Color, lines []Scanline) {\n\tconst m = 0xffff\n\tsr, sg, sb, sa := c.NRGBA().RGBA()\n\tfor _, line := range lines {\n\t\tma := line.Alpha\n\t\ta := (m - sa*ma/m) * 0x101\n\t\ti := im.PixOffset(line.X1, line.Y)\n\t\tfor x := line.X1; x <= line.X2; x++ {\n\t\t\tdr := uint32(im.Pix[i+0])\n\t\t\tdg := uint32(im.Pix[i+1])\n\t\t\tdb := uint32(im.Pix[i+2])\n\t\t\tda := uint32(im.Pix[i+3])\n\t\t\tim.Pix[i+0] = uint8((dr*a + sr*ma) / m >> 8)\n\t\t\tim.Pix[i+1] = uint8((dg*a + sg*ma) / m >> 8)\n\t\t\tim.Pix[i+2] = uint8((db*a + sb*ma) / m >> 8)\n\t\t\tim.Pix[i+3] = uint8((da*a + sa*ma) / m >> 8)\n\t\t\ti += 4\n\t\t}\n\t}\n}\n\nfunc differenceFull(a, b *image.RGBA) float64 {\n\tsize := a.Bounds().Size()\n\tw, h := size.X, size.Y\n\tvar total uint64\n\tfor y := 0; y < h; y++ {\n\t\ti := a.PixOffset(0, y)\n\t\tfor x := 0; x < w; x++ {\n\t\t\tar := int(a.Pix[i])\n\t\t\tag := int(a.Pix[i+1])\n\t\t\tab := int(a.Pix[i+2])\n\t\t\taa := int(a.Pix[i+3])\n\t\t\tbr := int(b.Pix[i])\n\t\t\tbg := int(b.Pix[i+1])\n\t\t\tbb := int(b.Pix[i+2])\n\t\t\tba := int(b.Pix[i+3])\n\t\t\ti += 4\n\t\t\tdr := ar - br\n\t\t\tdg := ag - bg\n\t\t\tdb := ab - bb\n\t\t\tda := aa - ba\n\t\t\ttotal += uint64(dr*dr + dg*dg + db*db + da*da)\n\t\t}\n\t}\n\treturn math.Sqrt(float64(total)/float64(w*h*4)) / 255\n}\n\nfunc differencePartial(target, before, after *image.RGBA, score float64, lines []Scanline) float64 {\n\tsize := target.Bounds().Size()\n\tw, h := size.X, size.Y\n\ttotal := uint64(math.Pow(score*255, 2) * float64(w*h*4))\n\tfor _, line := range lines {\n\t\ti := target.PixOffset(line.X1, line.Y)\n\t\tfor x := line.X1; x <= line.X2; x++ {\n\t\t\ttr := int(target.Pix[i])\n\t\t\ttg := int(target.Pix[i+1])\n\t\t\ttb := int(target.Pix[i+2])\n\t\t\tta := int(target.Pix[i+3])\n\t\t\tbr := int(before.Pix[i])\n\t\t\tbg := int(before.Pix[i+1])\n\t\t\tbb := int(before.Pix[i+2])\n\t\t\tba := int(before.Pix[i+3])\n\t\t\tar := int(after.Pix[i])\n\t\t\tag := int(after.Pix[i+1])\n\t\t\tab := int(after.Pix[i+2])\n\t\t\taa := int(after.Pix[i+3])\n\t\t\ti += 4\n\t\t\tdr1 := tr - br\n\t\t\tdg1 := tg - bg\n\t\t\tdb1 := tb - bb\n\t\t\tda1 := ta - ba\n\t\t\tdr2 := tr - ar\n\t\t\tdg2 := tg - ag\n\t\t\tdb2 := tb - ab\n\t\t\tda2 := ta - aa\n\t\t\ttotal -= uint64(dr1*dr1 + dg1*dg1 + db1*db1 + da1*da1)\n\t\t\ttotal += uint64(dr2*dr2 + dg2*dg2 + db2*db2 + da2*da2)\n\t\t}\n\t}\n\treturn math.Sqrt(float64(total)/float64(w*h*4)) / 255\n}\n"
  },
  {
    "path": "primitive/ellipse.go",
    "content": "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 Ellipse struct {\n\tWorker *Worker\n\tX, Y   int\n\tRx, Ry int\n\tCircle bool\n}\n\nfunc NewRandomEllipse(worker *Worker) *Ellipse {\n\trnd := worker.Rnd\n\tx := rnd.Intn(worker.W)\n\ty := rnd.Intn(worker.H)\n\trx := rnd.Intn(32) + 1\n\try := rnd.Intn(32) + 1\n\treturn &Ellipse{worker, x, y, rx, ry, false}\n}\n\nfunc NewRandomCircle(worker *Worker) *Ellipse {\n\trnd := worker.Rnd\n\tx := rnd.Intn(worker.W)\n\ty := rnd.Intn(worker.H)\n\tr := rnd.Intn(32) + 1\n\treturn &Ellipse{worker, x, y, r, r, true}\n}\n\nfunc (c *Ellipse) Draw(dc *gg.Context, scale float64) {\n\tdc.DrawEllipse(float64(c.X), float64(c.Y), float64(c.Rx), float64(c.Ry))\n\tdc.Fill()\n}\n\nfunc (c *Ellipse) SVG(attrs string) string {\n\treturn fmt.Sprintf(\n\t\t\"<ellipse %s cx=\\\"%d\\\" cy=\\\"%d\\\" rx=\\\"%d\\\" ry=\\\"%d\\\" />\",\n\t\tattrs, c.X, c.Y, c.Rx, c.Ry)\n}\n\nfunc (c *Ellipse) Copy() Shape {\n\ta := *c\n\treturn &a\n}\n\nfunc (c *Ellipse) Mutate() {\n\tw := c.Worker.W\n\th := c.Worker.H\n\trnd := c.Worker.Rnd\n\tswitch rnd.Intn(3) {\n\tcase 0:\n\t\tc.X = clampInt(c.X+int(rnd.NormFloat64()*16), 0, w-1)\n\t\tc.Y = clampInt(c.Y+int(rnd.NormFloat64()*16), 0, h-1)\n\tcase 1:\n\t\tc.Rx = clampInt(c.Rx+int(rnd.NormFloat64()*16), 1, w-1)\n\t\tif c.Circle {\n\t\t\tc.Ry = c.Rx\n\t\t}\n\tcase 2:\n\t\tc.Ry = clampInt(c.Ry+int(rnd.NormFloat64()*16), 1, h-1)\n\t\tif c.Circle {\n\t\t\tc.Rx = c.Ry\n\t\t}\n\t}\n}\n\nfunc (c *Ellipse) Rasterize() []Scanline {\n\tw := c.Worker.W\n\th := c.Worker.H\n\tlines := c.Worker.Lines[:0]\n\taspect := float64(c.Rx) / float64(c.Ry)\n\tfor dy := 0; dy < c.Ry; dy++ {\n\t\ty1 := c.Y - dy\n\t\ty2 := c.Y + dy\n\t\tif (y1 < 0 || y1 >= h) && (y2 < 0 || y2 >= h) {\n\t\t\tcontinue\n\t\t}\n\t\ts := int(math.Sqrt(float64(c.Ry*c.Ry-dy*dy)) * aspect)\n\t\tx1 := c.X - s\n\t\tx2 := c.X + s\n\t\tif x1 < 0 {\n\t\t\tx1 = 0\n\t\t}\n\t\tif x2 >= w {\n\t\t\tx2 = w - 1\n\t\t}\n\t\tif y1 >= 0 && y1 < h {\n\t\t\tlines = append(lines, Scanline{y1, x1, x2, 0xffff})\n\t\t}\n\t\tif y2 >= 0 && y2 < h && dy > 0 {\n\t\t\tlines = append(lines, Scanline{y2, x1, x2, 0xffff})\n\t\t}\n\t}\n\treturn lines\n}\n\ntype RotatedEllipse struct {\n\tWorker *Worker\n\tX, Y   float64\n\tRx, Ry float64\n\tAngle  float64\n}\n\nfunc NewRandomRotatedEllipse(worker *Worker) *RotatedEllipse {\n\trnd := worker.Rnd\n\tx := rnd.Float64() * float64(worker.W)\n\ty := rnd.Float64() * float64(worker.H)\n\trx := rnd.Float64()*32 + 1\n\try := rnd.Float64()*32 + 1\n\ta := rnd.Float64() * 360\n\treturn &RotatedEllipse{worker, x, y, rx, ry, a}\n}\n\nfunc (c *RotatedEllipse) Draw(dc *gg.Context, scale float64) {\n\tdc.Push()\n\tdc.RotateAbout(radians(c.Angle), c.X, c.Y)\n\tdc.DrawEllipse(c.X, c.Y, c.Rx, c.Ry)\n\tdc.Fill()\n\tdc.Pop()\n}\n\nfunc (c *RotatedEllipse) SVG(attrs string) string {\n\treturn fmt.Sprintf(\n\t\t\"<g transform=\\\"translate(%f %f) rotate(%f) scale(%f %f)\\\"><ellipse %s cx=\\\"0\\\" cy=\\\"0\\\" rx=\\\"1\\\" ry=\\\"1\\\" /></g>\",\n\t\tc.X, c.Y, c.Angle, c.Rx, c.Ry, attrs)\n}\n\nfunc (c *RotatedEllipse) Copy() Shape {\n\ta := *c\n\treturn &a\n}\n\nfunc (c *RotatedEllipse) Mutate() {\n\tw := c.Worker.W\n\th := c.Worker.H\n\trnd := c.Worker.Rnd\n\tswitch rnd.Intn(3) {\n\tcase 0:\n\t\tc.X = clamp(c.X+rnd.NormFloat64()*16, 0, float64(w-1))\n\t\tc.Y = clamp(c.Y+rnd.NormFloat64()*16, 0, float64(h-1))\n\tcase 1:\n\t\tc.Rx = clamp(c.Rx+rnd.NormFloat64()*16, 1, float64(w-1))\n\t\tc.Ry = clamp(c.Ry+rnd.NormFloat64()*16, 1, float64(w-1))\n\tcase 2:\n\t\tc.Angle = c.Angle + rnd.NormFloat64()*32\n\t}\n}\n\nfunc (c *RotatedEllipse) Rasterize() []Scanline {\n\tvar path raster.Path\n\tconst n = 16\n\tfor i := 0; i < n; i++ {\n\t\tp1 := float64(i+0) / n\n\t\tp2 := float64(i+1) / n\n\t\ta1 := p1 * 2 * math.Pi\n\t\ta2 := p2 * 2 * math.Pi\n\t\tx0 := c.Rx * math.Cos(a1)\n\t\ty0 := c.Ry * math.Sin(a1)\n\t\tx1 := c.Rx * math.Cos(a1+(a2-a1)/2)\n\t\ty1 := c.Ry * math.Sin(a1+(a2-a1)/2)\n\t\tx2 := c.Rx * math.Cos(a2)\n\t\ty2 := c.Ry * math.Sin(a2)\n\t\tcx := 2*x1 - x0/2 - x2/2\n\t\tcy := 2*y1 - y0/2 - y2/2\n\t\tx0, y0 = rotate(x0, y0, radians(c.Angle))\n\t\tcx, cy = rotate(cx, cy, radians(c.Angle))\n\t\tx2, y2 = rotate(x2, y2, radians(c.Angle))\n\t\tif i == 0 {\n\t\t\tpath.Start(fixp(x0+c.X, y0+c.Y))\n\t\t}\n\t\tpath.Add2(fixp(cx+c.X, cy+c.Y), fixp(x2+c.X, y2+c.Y))\n\t}\n\treturn fillPath(c.Worker, path)\n}\n"
  },
  {
    "path": "primitive/heatmap.go",
    "content": "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 NewHeatmap(w, h int) *Heatmap {\n\tcount := make([]uint64, w*h)\n\treturn &Heatmap{w, h, count}\n}\n\nfunc (h *Heatmap) Clear() {\n\tfor i := range h.Count {\n\t\th.Count[i] = 0\n\t}\n}\n\nfunc (h *Heatmap) Add(lines []Scanline) {\n\tfor _, line := range lines {\n\t\ti := line.Y*h.W + line.X1\n\t\tfor x := line.X1; x <= line.X2; x++ {\n\t\t\th.Count[i] += uint64(line.Alpha)\n\t\t\ti++\n\t\t}\n\t}\n}\n\nfunc (h *Heatmap) AddHeatmap(a *Heatmap) {\n\tfor i, x := range a.Count {\n\t\th.Count[i] += x\n\t}\n}\n\nfunc (h *Heatmap) Image(gamma float64) *image.Gray16 {\n\tim := image.NewGray16(image.Rect(0, 0, h.W, h.H))\n\tvar hi uint64\n\tfor _, h := range h.Count {\n\t\tif h > hi {\n\t\t\thi = h\n\t\t}\n\t}\n\ti := 0\n\tfor y := 0; y < h.H; y++ {\n\t\tfor x := 0; x < h.W; x++ {\n\t\t\tp := float64(h.Count[i]) / float64(hi)\n\t\t\tp = math.Pow(p, gamma)\n\t\t\tim.SetGray16(x, y, color.Gray16{uint16(p * 0xffff)})\n\t\t\ti++\n\t\t}\n\t}\n\treturn im\n}\n"
  },
  {
    "path": "primitive/log.go",
    "content": "package primitive\n\nimport \"fmt\"\n\nvar LogLevel int\n\nfunc Log(level int, format string, a ...interface{}) {\n\tif LogLevel >= level {\n\t\tfmt.Printf(format, a...)\n\t}\n}\n\nfunc v(format string, a ...interface{}) {\n\tLog(1, format, a...)\n}\n\nfunc vv(format string, a ...interface{}) {\n\tLog(2, \"  \"+format, a...)\n}\n\nfunc vvv(format string, a ...interface{}) {\n\tLog(3, \"    \"+format, a...)\n}\n"
  },
  {
    "path": "primitive/model.go",
    "content": "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\n\tScale      float64\n\tBackground Color\n\tTarget     *image.RGBA\n\tCurrent    *image.RGBA\n\tContext    *gg.Context\n\tScore      float64\n\tShapes     []Shape\n\tColors     []Color\n\tScores     []float64\n\tWorkers    []*Worker\n}\n\nfunc NewModel(target image.Image, background Color, size, numWorkers int) *Model {\n\tw := target.Bounds().Size().X\n\th := target.Bounds().Size().Y\n\taspect := float64(w) / float64(h)\n\tvar sw, sh int\n\tvar scale float64\n\tif aspect >= 1 {\n\t\tsw = size\n\t\tsh = int(float64(size) / aspect)\n\t\tscale = float64(size) / float64(w)\n\t} else {\n\t\tsw = int(float64(size) * aspect)\n\t\tsh = size\n\t\tscale = float64(size) / float64(h)\n\t}\n\n\tmodel := &Model{}\n\tmodel.Sw = sw\n\tmodel.Sh = sh\n\tmodel.Scale = scale\n\tmodel.Background = background\n\tmodel.Target = imageToRGBA(target)\n\tmodel.Current = uniformRGBA(target.Bounds(), background.NRGBA())\n\tmodel.Score = differenceFull(model.Target, model.Current)\n\tmodel.Context = model.newContext()\n\tfor i := 0; i < numWorkers; i++ {\n\t\tworker := NewWorker(model.Target)\n\t\tmodel.Workers = append(model.Workers, worker)\n\t}\n\treturn model\n}\n\nfunc (model *Model) newContext() *gg.Context {\n\tdc := gg.NewContext(model.Sw, model.Sh)\n\tdc.Scale(model.Scale, model.Scale)\n\tdc.Translate(0.5, 0.5)\n\tdc.SetColor(model.Background.NRGBA())\n\tdc.Clear()\n\treturn dc\n}\n\nfunc (model *Model) Frames(scoreDelta float64) []image.Image {\n\tvar result []image.Image\n\tdc := model.newContext()\n\tresult = append(result, imageToRGBA(dc.Image()))\n\tprevious := 10.0\n\tfor i, shape := range model.Shapes {\n\t\tc := model.Colors[i]\n\t\tdc.SetRGBA255(c.R, c.G, c.B, c.A)\n\t\tshape.Draw(dc, model.Scale)\n\t\tdc.Fill()\n\t\tscore := model.Scores[i]\n\t\tdelta := previous - score\n\t\tif delta >= scoreDelta {\n\t\t\tprevious = score\n\t\t\tresult = append(result, imageToRGBA(dc.Image()))\n\t\t}\n\t}\n\treturn result\n}\n\nfunc (model *Model) SVG() string {\n\tbg := model.Background\n\tvar lines []string\n\tlines = append(lines, fmt.Sprintf(\"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" version=\\\"1.1\\\" width=\\\"%d\\\" height=\\\"%d\\\">\", model.Sw, model.Sh))\n\tlines = 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))\n\tlines = append(lines, fmt.Sprintf(\"<g transform=\\\"scale(%f) translate(0.5 0.5)\\\">\", model.Scale))\n\tfor i, shape := range model.Shapes {\n\t\tc := model.Colors[i]\n\t\tattrs := \"fill=\\\"#%02x%02x%02x\\\" fill-opacity=\\\"%f\\\"\"\n\t\tattrs = fmt.Sprintf(attrs, c.R, c.G, c.B, float64(c.A)/255)\n\t\tlines = append(lines, shape.SVG(attrs))\n\t}\n\tlines = append(lines, \"</g>\")\n\tlines = append(lines, \"</svg>\")\n\treturn strings.Join(lines, \"\\n\")\n}\n\nfunc (model *Model) Add(shape Shape, alpha int) {\n\tbefore := copyRGBA(model.Current)\n\tlines := shape.Rasterize()\n\tcolor := computeColor(model.Target, model.Current, lines, alpha)\n\tdrawLines(model.Current, color, lines)\n\tscore := differencePartial(model.Target, before, model.Current, model.Score, lines)\n\n\tmodel.Score = score\n\tmodel.Shapes = append(model.Shapes, shape)\n\tmodel.Colors = append(model.Colors, color)\n\tmodel.Scores = append(model.Scores, score)\n\n\tmodel.Context.SetRGBA255(color.R, color.G, color.B, color.A)\n\tshape.Draw(model.Context, model.Scale)\n}\n\nfunc (model *Model) Step(shapeType ShapeType, alpha, repeat int) int {\n\tstate := model.runWorkers(shapeType, alpha, 1000, 100, 16)\n\t// state = HillClimb(state, 1000).(*State)\n\tmodel.Add(state.Shape, state.Alpha)\n\n\tfor i := 0; i < repeat; i++ {\n\t\tstate.Worker.Init(model.Current, model.Score)\n\t\ta := state.Energy()\n\t\tstate = HillClimb(state, 100).(*State)\n\t\tb := state.Energy()\n\t\tif a == b {\n\t\t\tbreak\n\t\t}\n\t\tmodel.Add(state.Shape, state.Alpha)\n\t}\n\n\t// for _, w := range model.Workers[1:] {\n\t// \tmodel.Workers[0].Heatmap.AddHeatmap(w.Heatmap)\n\t// }\n\t// SavePNG(\"heatmap.png\", model.Workers[0].Heatmap.Image(0.5))\n\n\tcounter := 0\n\tfor _, worker := range model.Workers {\n\t\tcounter += worker.Counter\n\t}\n\treturn counter\n}\n\nfunc (model *Model) runWorkers(t ShapeType, a, n, age, m int) *State {\n\twn := len(model.Workers)\n\tch := make(chan *State, wn)\n\twm := m / wn\n\tif m%wn != 0 {\n\t\twm++\n\t}\n\tfor i := 0; i < wn; i++ {\n\t\tworker := model.Workers[i]\n\t\tworker.Init(model.Current, model.Score)\n\t\tgo model.runWorker(worker, t, a, n, age, wm, ch)\n\t}\n\tvar bestEnergy float64\n\tvar bestState *State\n\tfor i := 0; i < wn; i++ {\n\t\tstate := <-ch\n\t\tenergy := state.Energy()\n\t\tif i == 0 || energy < bestEnergy {\n\t\t\tbestEnergy = energy\n\t\t\tbestState = state\n\t\t}\n\t}\n\treturn bestState\n}\n\nfunc (model *Model) runWorker(worker *Worker, t ShapeType, a, n, age, m int, ch chan *State) {\n\tch <- worker.BestHillClimbState(t, a, n, age, m)\n}\n"
  },
  {
    "path": "primitive/optimize.go",
    "content": "package primitive\n\nimport (\n\t\"math\"\n\t\"math/rand\"\n)\n\ntype Annealable interface {\n\tEnergy() float64\n\tDoMove() interface{}\n\tUndoMove(interface{})\n\tCopy() Annealable\n}\n\nfunc HillClimb(state Annealable, maxAge int) Annealable {\n\tstate = state.Copy()\n\tbestState := state.Copy()\n\tbestEnergy := state.Energy()\n\tstep := 0\n\tfor age := 0; age < maxAge; age++ {\n\t\tundo := state.DoMove()\n\t\tenergy := state.Energy()\n\t\tif energy >= bestEnergy {\n\t\t\tstate.UndoMove(undo)\n\t\t} else {\n\t\t\t// fmt.Printf(\"step: %d, energy: %.6f\\n\", step, energy)\n\t\t\tbestEnergy = energy\n\t\t\tbestState = state.Copy()\n\t\t\tage = -1\n\t\t}\n\t\tstep++\n\t}\n\treturn bestState\n}\n\nfunc PreAnneal(state Annealable, iterations int) float64 {\n\tstate = state.Copy()\n\tprevious := state.Energy()\n\tvar total float64\n\tfor i := 0; i < iterations; i++ {\n\t\tstate.DoMove()\n\t\tenergy := state.Energy()\n\t\ttotal += math.Abs(energy - previous)\n\t\tprevious = energy\n\t}\n\treturn total / float64(iterations)\n}\n\nfunc Anneal(state Annealable, maxTemp, minTemp float64, steps int) Annealable {\n\tfactor := -math.Log(maxTemp / minTemp)\n\tstate = state.Copy()\n\tbestState := state.Copy()\n\tbestEnergy := state.Energy()\n\tpreviousEnergy := bestEnergy\n\tfor step := 0; step < steps; step++ {\n\t\tpct := float64(step) / float64(steps-1)\n\t\ttemp := maxTemp * math.Exp(factor*pct)\n\t\tundo := state.DoMove()\n\t\tenergy := state.Energy()\n\t\tchange := energy - previousEnergy\n\t\tif change > 0 && math.Exp(-change/temp) < rand.Float64() {\n\t\t\tstate.UndoMove(undo)\n\t\t} else {\n\t\t\tpreviousEnergy = energy\n\t\t\tif energy < bestEnergy {\n\t\t\t\t// pct := float64(step*100) / float64(steps)\n\t\t\t\t// fmt.Printf(\"step: %d of %d (%.1f%%), temp: %.3f, energy: %.6f\\n\",\n\t\t\t\t// \tstep, steps, pct, temp, energy)\n\t\t\t\tbestEnergy = energy\n\t\t\t\tbestState = state.Copy()\n\t\t\t}\n\t\t}\n\t}\n\treturn bestState\n}\n"
  },
  {
    "path": "primitive/polygon.go",
    "content": "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 Polygon struct {\n\tWorker *Worker\n\tOrder  int\n\tConvex bool\n\tX, Y   []float64\n}\n\nfunc NewRandomPolygon(worker *Worker, order int, convex bool) *Polygon {\n\trnd := worker.Rnd\n\tx := make([]float64, order)\n\ty := make([]float64, order)\n\tx[0] = rnd.Float64() * float64(worker.W)\n\ty[0] = rnd.Float64() * float64(worker.H)\n\tfor i := 1; i < order; i++ {\n\t\tx[i] = x[0] + rnd.Float64()*40 - 20\n\t\ty[i] = y[0] + rnd.Float64()*40 - 20\n\t}\n\tp := &Polygon{worker, order, convex, x, y}\n\tp.Mutate()\n\treturn p\n}\n\nfunc (p *Polygon) Draw(dc *gg.Context, scale float64) {\n\tdc.NewSubPath()\n\tfor i := 0; i < p.Order; i++ {\n\t\tdc.LineTo(p.X[i], p.Y[i])\n\t}\n\tdc.ClosePath()\n\tdc.Fill()\n}\n\nfunc (p *Polygon) SVG(attrs string) string {\n\tret := fmt.Sprintf(\n\t\t\"<polygon %s points=\\\"\",\n\t\tattrs)\n\tpoints := make([]string, len(p.X))\n\tfor i := 0; i < len(p.X); i++ {\n\t\tpoints[i] = fmt.Sprintf(\"%f,%f\", p.X[i], p.Y[i])\n\t}\n\n\treturn ret + strings.Join(points, \",\") + \"\\\" />\"\n}\n\nfunc (p *Polygon) Copy() Shape {\n\ta := *p\n\ta.X = make([]float64, p.Order)\n\ta.Y = make([]float64, p.Order)\n\tcopy(a.X, p.X)\n\tcopy(a.Y, p.Y)\n\treturn &a\n}\n\nfunc (p *Polygon) Mutate() {\n\tconst m = 16\n\tw := p.Worker.W\n\th := p.Worker.H\n\trnd := p.Worker.Rnd\n\tfor {\n\t\tif rnd.Float64() < 0.25 {\n\t\t\ti := rnd.Intn(p.Order)\n\t\t\tj := rnd.Intn(p.Order)\n\t\t\tp.X[i], p.Y[i], p.X[j], p.Y[j] = p.X[j], p.Y[j], p.X[i], p.Y[i]\n\t\t} else {\n\t\t\ti := rnd.Intn(p.Order)\n\t\t\tp.X[i] = clamp(p.X[i]+rnd.NormFloat64()*16, -m, float64(w-1+m))\n\t\t\tp.Y[i] = clamp(p.Y[i]+rnd.NormFloat64()*16, -m, float64(h-1+m))\n\t\t}\n\t\tif p.Valid() {\n\t\t\tbreak\n\t\t}\n\t}\n}\n\nfunc (p *Polygon) Valid() bool {\n\tif !p.Convex {\n\t\treturn true\n\t}\n\tvar sign bool\n\tfor a := 0; a < p.Order; a++ {\n\t\ti := (a + 0) % p.Order\n\t\tj := (a + 1) % p.Order\n\t\tk := (a + 2) % p.Order\n\t\tc := cross3(p.X[i], p.Y[i], p.X[j], p.Y[j], p.X[k], p.Y[k])\n\t\tif a == 0 {\n\t\t\tsign = c > 0\n\t\t} else if c > 0 != sign {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc cross3(x1, y1, x2, y2, x3, y3 float64) float64 {\n\tdx1 := x2 - x1\n\tdy1 := y2 - y1\n\tdx2 := x3 - x2\n\tdy2 := y3 - y2\n\treturn dx1*dy2 - dy1*dx2\n}\n\nfunc (p *Polygon) Rasterize() []Scanline {\n\tvar path raster.Path\n\tfor i := 0; i <= p.Order; i++ {\n\t\tf := fixp(p.X[i%p.Order], p.Y[i%p.Order])\n\t\tif i == 0 {\n\t\t\tpath.Start(f)\n\t\t} else {\n\t\t\tpath.Add1(f)\n\t\t}\n\t}\n\treturn fillPath(p.Worker, path)\n}\n"
  },
  {
    "path": "primitive/quadratic.go",
    "content": "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 Quadratic struct {\n\tWorker *Worker\n\tX1, Y1 float64\n\tX2, Y2 float64\n\tX3, Y3 float64\n\tWidth  float64\n}\n\nfunc NewRandomQuadratic(worker *Worker) *Quadratic {\n\trnd := worker.Rnd\n\tx1 := rnd.Float64() * float64(worker.W)\n\ty1 := rnd.Float64() * float64(worker.H)\n\tx2 := x1 + rnd.Float64()*40 - 20\n\ty2 := y1 + rnd.Float64()*40 - 20\n\tx3 := x2 + rnd.Float64()*40 - 20\n\ty3 := y2 + rnd.Float64()*40 - 20\n\twidth := 1.0 / 2\n\tq := &Quadratic{worker, x1, y1, x2, y2, x3, y3, width}\n\tq.Mutate()\n\treturn q\n}\n\nfunc (q *Quadratic) Draw(dc *gg.Context, scale float64) {\n\tdc.MoveTo(q.X1, q.Y1)\n\tdc.QuadraticTo(q.X2, q.Y2, q.X3, q.Y3)\n\tdc.SetLineWidth(q.Width * scale)\n\tdc.Stroke()\n}\n\nfunc (q *Quadratic) SVG(attrs string) string {\n\t// TODO: this is a little silly\n\tattrs = strings.Replace(attrs, \"fill\", \"stroke\", -1)\n\treturn fmt.Sprintf(\n\t\t\"<path %s fill=\\\"none\\\" d=\\\"M %f %f Q %f %f, %f %f\\\" stroke-width=\\\"%f\\\" />\",\n\t\tattrs, q.X1, q.Y1, q.X2, q.Y2, q.X3, q.Y3, q.Width)\n}\n\nfunc (q *Quadratic) Copy() Shape {\n\ta := *q\n\treturn &a\n}\n\nfunc (q *Quadratic) Mutate() {\n\tconst m = 16\n\tw := q.Worker.W\n\th := q.Worker.H\n\trnd := q.Worker.Rnd\n\tfor {\n\t\tswitch rnd.Intn(3) {\n\t\tcase 0:\n\t\t\tq.X1 = clamp(q.X1+rnd.NormFloat64()*16, -m, float64(w-1+m))\n\t\t\tq.Y1 = clamp(q.Y1+rnd.NormFloat64()*16, -m, float64(h-1+m))\n\t\tcase 1:\n\t\t\tq.X2 = clamp(q.X2+rnd.NormFloat64()*16, -m, float64(w-1+m))\n\t\t\tq.Y2 = clamp(q.Y2+rnd.NormFloat64()*16, -m, float64(h-1+m))\n\t\tcase 2:\n\t\t\tq.X3 = clamp(q.X3+rnd.NormFloat64()*16, -m, float64(w-1+m))\n\t\t\tq.Y3 = clamp(q.Y3+rnd.NormFloat64()*16, -m, float64(h-1+m))\n\t\tcase 3:\n\t\t\tq.Width = clamp(q.Width+rnd.NormFloat64(), 1, 16)\n\t\t}\n\t\tif q.Valid() {\n\t\t\tbreak\n\t\t}\n\t}\n}\n\nfunc (q *Quadratic) Valid() bool {\n\tdx12 := int(q.X1 - q.X2)\n\tdy12 := int(q.Y1 - q.Y2)\n\tdx23 := int(q.X2 - q.X3)\n\tdy23 := int(q.Y2 - q.Y3)\n\tdx13 := int(q.X1 - q.X3)\n\tdy13 := int(q.Y1 - q.Y3)\n\td12 := dx12*dx12 + dy12*dy12\n\td23 := dx23*dx23 + dy23*dy23\n\td13 := dx13*dx13 + dy13*dy13\n\treturn d13 > d12 && d13 > d23\n}\n\nfunc (q *Quadratic) Rasterize() []Scanline {\n\tvar path raster.Path\n\tp1 := fixp(q.X1, q.Y1)\n\tp2 := fixp(q.X2, q.Y2)\n\tp3 := fixp(q.X3, q.Y3)\n\tpath.Start(p1)\n\tpath.Add2(p2, p3)\n\twidth := fix(q.Width)\n\treturn strokePath(q.Worker, path, width, raster.RoundCapper, raster.RoundJoiner)\n}\n"
  },
  {
    "path": "primitive/raster.go",
    "content": "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) fixed.Int26_6 {\n\treturn fixed.Int26_6(x * 64)\n}\n\nfunc fixp(x, y float64) fixed.Point26_6 {\n\treturn fixed.Point26_6{fix(x), fix(y)}\n}\n\ntype painter struct {\n\tLines []Scanline\n}\n\nfunc (p *painter) Paint(spans []raster.Span, done bool) {\n\tfor _, span := range spans {\n\t\tp.Lines = append(p.Lines, Scanline{span.Y, span.X0, span.X1 - 1, span.Alpha})\n\t}\n}\n\nfunc fillPath(worker *Worker, path raster.Path) []Scanline {\n\tr := worker.Rasterizer\n\tr.Clear()\n\tr.UseNonZeroWinding = true\n\tr.AddPath(path)\n\tvar p painter\n\tp.Lines = worker.Lines[:0]\n\tr.Rasterize(&p)\n\treturn p.Lines\n}\n\nfunc strokePath(worker *Worker, path raster.Path, width fixed.Int26_6, cr raster.Capper, jr raster.Joiner) []Scanline {\n\tr := worker.Rasterizer\n\tr.Clear()\n\tr.UseNonZeroWinding = true\n\tr.AddStroke(path, width, cr, jr)\n\tvar p painter\n\tp.Lines = worker.Lines[:0]\n\tr.Rasterize(&p)\n\treturn p.Lines\n}\n"
  },
  {
    "path": "primitive/rectangle.go",
    "content": "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 int\n\tX2, Y2 int\n}\n\nfunc NewRandomRectangle(worker *Worker) *Rectangle {\n\trnd := worker.Rnd\n\tx1 := rnd.Intn(worker.W)\n\ty1 := rnd.Intn(worker.H)\n\tx2 := clampInt(x1+rnd.Intn(32)+1, 0, worker.W-1)\n\ty2 := clampInt(y1+rnd.Intn(32)+1, 0, worker.H-1)\n\treturn &Rectangle{worker, x1, y1, x2, y2}\n}\n\nfunc (r *Rectangle) bounds() (x1, y1, x2, y2 int) {\n\tx1, y1 = r.X1, r.Y1\n\tx2, y2 = r.X2, r.Y2\n\tif x1 > x2 {\n\t\tx1, x2 = x2, x1\n\t}\n\tif y1 > y2 {\n\t\ty1, y2 = y2, y1\n\t}\n\treturn\n}\n\nfunc (r *Rectangle) Draw(dc *gg.Context, scale float64) {\n\tx1, y1, x2, y2 := r.bounds()\n\tdc.DrawRectangle(float64(x1), float64(y1), float64(x2-x1+1), float64(y2-y1+1))\n\tdc.Fill()\n}\n\nfunc (r *Rectangle) SVG(attrs string) string {\n\tx1, y1, x2, y2 := r.bounds()\n\tw := x2 - x1 + 1\n\th := y2 - y1 + 1\n\treturn fmt.Sprintf(\n\t\t\"<rect %s x=\\\"%d\\\" y=\\\"%d\\\" width=\\\"%d\\\" height=\\\"%d\\\" />\",\n\t\tattrs, x1, y1, w, h)\n}\n\nfunc (r *Rectangle) Copy() Shape {\n\ta := *r\n\treturn &a\n}\n\nfunc (r *Rectangle) Mutate() {\n\tw := r.Worker.W\n\th := r.Worker.H\n\trnd := r.Worker.Rnd\n\tswitch rnd.Intn(2) {\n\tcase 0:\n\t\tr.X1 = clampInt(r.X1+int(rnd.NormFloat64()*16), 0, w-1)\n\t\tr.Y1 = clampInt(r.Y1+int(rnd.NormFloat64()*16), 0, h-1)\n\tcase 1:\n\t\tr.X2 = clampInt(r.X2+int(rnd.NormFloat64()*16), 0, w-1)\n\t\tr.Y2 = clampInt(r.Y2+int(rnd.NormFloat64()*16), 0, h-1)\n\t}\n}\n\nfunc (r *Rectangle) Rasterize() []Scanline {\n\tx1, y1, x2, y2 := r.bounds()\n\tlines := r.Worker.Lines[:0]\n\tfor y := y1; y <= y2; y++ {\n\t\tlines = append(lines, Scanline{y, x1, x2, 0xffff})\n\t}\n\treturn lines\n}\n\ntype RotatedRectangle struct {\n\tWorker *Worker\n\tX, Y   int\n\tSx, Sy int\n\tAngle  int\n}\n\nfunc NewRandomRotatedRectangle(worker *Worker) *RotatedRectangle {\n\trnd := worker.Rnd\n\tx := rnd.Intn(worker.W)\n\ty := rnd.Intn(worker.H)\n\tsx := rnd.Intn(32) + 1\n\tsy := rnd.Intn(32) + 1\n\ta := rnd.Intn(360)\n\tr := &RotatedRectangle{worker, x, y, sx, sy, a}\n\tr.Mutate()\n\treturn r\n}\n\nfunc (r *RotatedRectangle) Draw(dc *gg.Context, scale float64) {\n\tsx, sy := float64(r.Sx), float64(r.Sy)\n\tdc.Push()\n\tdc.Translate(float64(r.X), float64(r.Y))\n\tdc.Rotate(radians(float64(r.Angle)))\n\tdc.DrawRectangle(-sx/2, -sy/2, sx, sy)\n\tdc.Pop()\n\tdc.Fill()\n}\n\nfunc (r *RotatedRectangle) SVG(attrs string) string {\n\treturn fmt.Sprintf(\n\t\t\"<g transform=\\\"translate(%d %d) rotate(%d) scale(%d %d)\\\"><rect %s x=\\\"-0.5\\\" y=\\\"-0.5\\\" width=\\\"1\\\" height=\\\"1\\\" /></g>\",\n\t\tr.X, r.Y, r.Angle, r.Sx, r.Sy, attrs)\n}\n\nfunc (r *RotatedRectangle) Copy() Shape {\n\ta := *r\n\treturn &a\n}\n\nfunc (r *RotatedRectangle) Mutate() {\n\tw := r.Worker.W\n\th := r.Worker.H\n\trnd := r.Worker.Rnd\n\tswitch rnd.Intn(3) {\n\tcase 0:\n\t\tr.X = clampInt(r.X+int(rnd.NormFloat64()*16), 0, w-1)\n\t\tr.Y = clampInt(r.Y+int(rnd.NormFloat64()*16), 0, h-1)\n\tcase 1:\n\t\tr.Sx = clampInt(r.Sx+int(rnd.NormFloat64()*16), 1, w-1)\n\t\tr.Sy = clampInt(r.Sy+int(rnd.NormFloat64()*16), 1, h-1)\n\tcase 2:\n\t\tr.Angle = r.Angle + int(rnd.NormFloat64()*32)\n\t}\n\t// for !r.Valid() {\n\t// \tr.Sx = clampInt(r.Sx+int(rnd.NormFloat64()*16), 0, w-1)\n\t// \tr.Sy = clampInt(r.Sy+int(rnd.NormFloat64()*16), 0, h-1)\n\t// }\n}\n\nfunc (r *RotatedRectangle) Valid() bool {\n\ta, b := r.Sx, r.Sy\n\tif a < b {\n\t\ta, b = b, a\n\t}\n\taspect := float64(a) / float64(b)\n\treturn aspect <= 5\n}\n\nfunc (r *RotatedRectangle) Rasterize() []Scanline {\n\tw := r.Worker.W\n\th := r.Worker.H\n\tsx, sy := float64(r.Sx), float64(r.Sy)\n\tangle := radians(float64(r.Angle))\n\trx1, ry1 := rotate(-sx/2, -sy/2, angle)\n\trx2, ry2 := rotate(sx/2, -sy/2, angle)\n\trx3, ry3 := rotate(sx/2, sy/2, angle)\n\trx4, ry4 := rotate(-sx/2, sy/2, angle)\n\tx1, y1 := int(rx1)+r.X, int(ry1)+r.Y\n\tx2, y2 := int(rx2)+r.X, int(ry2)+r.Y\n\tx3, y3 := int(rx3)+r.X, int(ry3)+r.Y\n\tx4, y4 := int(rx4)+r.X, int(ry4)+r.Y\n\tminy := minInt(y1, minInt(y2, minInt(y3, y4)))\n\tmaxy := maxInt(y1, maxInt(y2, maxInt(y3, y4)))\n\tn := maxy - miny + 1\n\tmin := make([]int, n)\n\tmax := make([]int, n)\n\tfor i := range min {\n\t\tmin[i] = w\n\t}\n\txs := []int{x1, x2, x3, x4, x1}\n\tys := []int{y1, y2, y3, y4, y1}\n\t// TODO: this could be better probably\n\tfor i := 0; i < 4; i++ {\n\t\tx, y := float64(xs[i]), float64(ys[i])\n\t\tdx, dy := float64(xs[i+1]-xs[i]), float64(ys[i+1]-ys[i])\n\t\tcount := int(math.Sqrt(dx*dx+dy*dy)) * 2\n\t\tfor j := 0; j < count; j++ {\n\t\t\tt := float64(j) / float64(count-1)\n\t\t\txi := int(x + dx*t)\n\t\t\tyi := int(y+dy*t) - miny\n\t\t\tmin[yi] = minInt(min[yi], xi)\n\t\t\tmax[yi] = maxInt(max[yi], xi)\n\t\t}\n\t}\n\tlines := r.Worker.Lines[:0]\n\tfor i := 0; i < n; i++ {\n\t\ty := miny + i\n\t\tif y < 0 || y >= h {\n\t\t\tcontinue\n\t\t}\n\t\ta := maxInt(min[i], 0)\n\t\tb := minInt(max[i], w-1)\n\t\tif b >= a {\n\t\t\tlines = append(lines, Scanline{y, a, b, 0xffff})\n\t\t}\n\t}\n\treturn lines\n}\n"
  },
  {
    "path": "primitive/scanline.go",
    "content": "package primitive\n\ntype Scanline struct {\n\tY, X1, X2 int\n\tAlpha     uint32\n}\n\nfunc cropScanlines(lines []Scanline, w, h int) []Scanline {\n\ti := 0\n\tfor _, line := range lines {\n\t\tif line.Y < 0 || line.Y >= h {\n\t\t\tcontinue\n\t\t}\n\t\tif line.X1 >= w {\n\t\t\tcontinue\n\t\t}\n\t\tif line.X2 < 0 {\n\t\t\tcontinue\n\t\t}\n\t\tline.X1 = clampInt(line.X1, 0, w-1)\n\t\tline.X2 = clampInt(line.X2, 0, w-1)\n\t\tif line.X1 > line.X2 {\n\t\t\tcontinue\n\t\t}\n\t\tlines[i] = line\n\t\ti++\n\t}\n\treturn lines[:i]\n}\n"
  },
  {
    "path": "primitive/shape.go",
    "content": "package primitive\n\nimport \"github.com/fogleman/gg\"\n\ntype Shape interface {\n\tRasterize() []Scanline\n\tCopy() Shape\n\tMutate()\n\tDraw(dc *gg.Context, scale float64)\n\tSVG(attrs string) string\n}\n\ntype ShapeType int\n\nconst (\n\tShapeTypeAny ShapeType = iota\n\tShapeTypeTriangle\n\tShapeTypeRectangle\n\tShapeTypeEllipse\n\tShapeTypeCircle\n\tShapeTypeRotatedRectangle\n\tShapeTypeQuadratic\n\tShapeTypeRotatedEllipse\n\tShapeTypePolygon\n)\n"
  },
  {
    "path": "primitive/state.go",
    "content": "package primitive\n\ntype State struct {\n\tWorker      *Worker\n\tShape       Shape\n\tAlpha       int\n\tMutateAlpha bool\n\tScore       float64\n}\n\nfunc NewState(worker *Worker, shape Shape, alpha int) *State {\n\tvar mutateAlpha bool\n\tif alpha == 0 {\n\t\talpha = 128\n\t\tmutateAlpha = true\n\t}\n\treturn &State{worker, shape, alpha, mutateAlpha, -1}\n}\n\nfunc (state *State) Energy() float64 {\n\tif state.Score < 0 {\n\t\tstate.Score = state.Worker.Energy(state.Shape, state.Alpha)\n\t}\n\treturn state.Score\n}\n\nfunc (state *State) DoMove() interface{} {\n\trnd := state.Worker.Rnd\n\toldState := state.Copy()\n\tstate.Shape.Mutate()\n\tif state.MutateAlpha {\n\t\tstate.Alpha = clampInt(state.Alpha+rnd.Intn(21)-10, 1, 255)\n\t}\n\tstate.Score = -1\n\treturn oldState\n}\n\nfunc (state *State) UndoMove(undo interface{}) {\n\toldState := undo.(*State)\n\tstate.Shape = oldState.Shape\n\tstate.Alpha = oldState.Alpha\n\tstate.Score = oldState.Score\n}\n\nfunc (state *State) Copy() Annealable {\n\treturn &State{\n\t\tstate.Worker, state.Shape.Copy(), state.Alpha, state.MutateAlpha, state.Score}\n}\n"
  },
  {
    "path": "primitive/triangle.go",
    "content": "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 int\n\tX2, Y2 int\n\tX3, Y3 int\n}\n\nfunc NewRandomTriangle(worker *Worker) *Triangle {\n\trnd := worker.Rnd\n\tx1 := rnd.Intn(worker.W)\n\ty1 := rnd.Intn(worker.H)\n\tx2 := x1 + rnd.Intn(31) - 15\n\ty2 := y1 + rnd.Intn(31) - 15\n\tx3 := x1 + rnd.Intn(31) - 15\n\ty3 := y1 + rnd.Intn(31) - 15\n\tt := &Triangle{worker, x1, y1, x2, y2, x3, y3}\n\tt.Mutate()\n\treturn t\n}\n\nfunc (t *Triangle) Draw(dc *gg.Context, scale float64) {\n\tdc.LineTo(float64(t.X1), float64(t.Y1))\n\tdc.LineTo(float64(t.X2), float64(t.Y2))\n\tdc.LineTo(float64(t.X3), float64(t.Y3))\n\tdc.ClosePath()\n\tdc.Fill()\n}\n\nfunc (t *Triangle) SVG(attrs string) string {\n\treturn fmt.Sprintf(\n\t\t\"<polygon %s points=\\\"%d,%d %d,%d %d,%d\\\" />\",\n\t\tattrs, t.X1, t.Y1, t.X2, t.Y2, t.X3, t.Y3)\n}\n\nfunc (t *Triangle) Copy() Shape {\n\ta := *t\n\treturn &a\n}\n\nfunc (t *Triangle) Mutate() {\n\tw := t.Worker.W\n\th := t.Worker.H\n\trnd := t.Worker.Rnd\n\tconst m = 16\n\tfor {\n\t\tswitch rnd.Intn(3) {\n\t\tcase 0:\n\t\t\tt.X1 = clampInt(t.X1+int(rnd.NormFloat64()*16), -m, w-1+m)\n\t\t\tt.Y1 = clampInt(t.Y1+int(rnd.NormFloat64()*16), -m, h-1+m)\n\t\tcase 1:\n\t\t\tt.X2 = clampInt(t.X2+int(rnd.NormFloat64()*16), -m, w-1+m)\n\t\t\tt.Y2 = clampInt(t.Y2+int(rnd.NormFloat64()*16), -m, h-1+m)\n\t\tcase 2:\n\t\t\tt.X3 = clampInt(t.X3+int(rnd.NormFloat64()*16), -m, w-1+m)\n\t\t\tt.Y3 = clampInt(t.Y3+int(rnd.NormFloat64()*16), -m, h-1+m)\n\t\t}\n\t\tif t.Valid() {\n\t\t\tbreak\n\t\t}\n\t}\n}\n\nfunc (t *Triangle) Valid() bool {\n\tconst minDegrees = 15\n\tvar a1, a2, a3 float64\n\t{\n\t\tx1 := float64(t.X2 - t.X1)\n\t\ty1 := float64(t.Y2 - t.Y1)\n\t\tx2 := float64(t.X3 - t.X1)\n\t\ty2 := float64(t.Y3 - t.Y1)\n\t\td1 := math.Sqrt(x1*x1 + y1*y1)\n\t\td2 := math.Sqrt(x2*x2 + y2*y2)\n\t\tx1 /= d1\n\t\ty1 /= d1\n\t\tx2 /= d2\n\t\ty2 /= d2\n\t\ta1 = degrees(math.Acos(x1*x2 + y1*y2))\n\t}\n\t{\n\t\tx1 := float64(t.X1 - t.X2)\n\t\ty1 := float64(t.Y1 - t.Y2)\n\t\tx2 := float64(t.X3 - t.X2)\n\t\ty2 := float64(t.Y3 - t.Y2)\n\t\td1 := math.Sqrt(x1*x1 + y1*y1)\n\t\td2 := math.Sqrt(x2*x2 + y2*y2)\n\t\tx1 /= d1\n\t\ty1 /= d1\n\t\tx2 /= d2\n\t\ty2 /= d2\n\t\ta2 = degrees(math.Acos(x1*x2 + y1*y2))\n\t}\n\ta3 = 180 - a1 - a2\n\treturn a1 > minDegrees && a2 > minDegrees && a3 > minDegrees\n}\n\nfunc (t *Triangle) Rasterize() []Scanline {\n\tbuf := t.Worker.Lines[:0]\n\tlines := rasterizeTriangle(t.X1, t.Y1, t.X2, t.Y2, t.X3, t.Y3, buf)\n\treturn cropScanlines(lines, t.Worker.W, t.Worker.H)\n}\n\nfunc rasterizeTriangle(x1, y1, x2, y2, x3, y3 int, buf []Scanline) []Scanline {\n\tif y1 > y3 {\n\t\tx1, x3 = x3, x1\n\t\ty1, y3 = y3, y1\n\t}\n\tif y1 > y2 {\n\t\tx1, x2 = x2, x1\n\t\ty1, y2 = y2, y1\n\t}\n\tif y2 > y3 {\n\t\tx2, x3 = x3, x2\n\t\ty2, y3 = y3, y2\n\t}\n\tif y2 == y3 {\n\t\treturn rasterizeTriangleBottom(x1, y1, x2, y2, x3, y3, buf)\n\t} else if y1 == y2 {\n\t\treturn rasterizeTriangleTop(x1, y1, x2, y2, x3, y3, buf)\n\t} else {\n\t\tx4 := x1 + int((float64(y2-y1)/float64(y3-y1))*float64(x3-x1))\n\t\ty4 := y2\n\t\tbuf = rasterizeTriangleBottom(x1, y1, x2, y2, x4, y4, buf)\n\t\tbuf = rasterizeTriangleTop(x2, y2, x4, y4, x3, y3, buf)\n\t\treturn buf\n\t}\n}\n\nfunc rasterizeTriangleBottom(x1, y1, x2, y2, x3, y3 int, buf []Scanline) []Scanline {\n\ts1 := float64(x2-x1) / float64(y2-y1)\n\ts2 := float64(x3-x1) / float64(y3-y1)\n\tax := float64(x1)\n\tbx := float64(x1)\n\tfor y := y1; y <= y2; y++ {\n\t\ta := int(ax)\n\t\tb := int(bx)\n\t\tax += s1\n\t\tbx += s2\n\t\tif a > b {\n\t\t\ta, b = b, a\n\t\t}\n\t\tbuf = append(buf, Scanline{y, a, b, 0xffff})\n\t}\n\treturn buf\n}\n\nfunc rasterizeTriangleTop(x1, y1, x2, y2, x3, y3 int, buf []Scanline) []Scanline {\n\ts1 := float64(x3-x1) / float64(y3-y1)\n\ts2 := float64(x3-x2) / float64(y3-y2)\n\tax := float64(x3)\n\tbx := float64(x3)\n\tfor y := y3; y > y1; y-- {\n\t\tax -= s1\n\t\tbx -= s2\n\t\ta := int(ax)\n\t\tb := int(bx)\n\t\tif a > b {\n\t\t\ta, b = b, a\n\t\t}\n\t\tbuf = append(buf, Scanline{y, a, b, 0xffff})\n\t}\n\treturn buf\n}\n"
  },
  {
    "path": "primitive/util.go",
    "content": "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/jpeg\"\n\t\"image/png\"\n\t\"io/ioutil\"\n\t\"math\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n)\n\nfunc LoadImage(path string) (image.Image, error) {\n\tif path == \"-\" {\n\t\tim, _, err := image.Decode(os.Stdin)\n\t\treturn im, err\n\t} else {\n\t\tfile, err := os.Open(path)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdefer file.Close()\n\t\tim, _, err := image.Decode(file)\n\t\treturn im, err\n\t}\n}\n\nfunc SaveFile(path, contents string) error {\n\tif path == \"-\" {\n\t\t_, err := fmt.Fprint(os.Stdout, contents)\n\t\treturn err\n\t} else {\n\t\tfile, err := os.Create(path)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer file.Close()\n\t\t_, err = file.WriteString(contents)\n\t\treturn err\n\t}\n}\n\nfunc SavePNG(path string, im image.Image) error {\n\tfile, err := os.Create(path)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer file.Close()\n\treturn png.Encode(file, im)\n}\n\nfunc SaveJPG(path string, im image.Image, quality int) error {\n\tfile, err := os.Create(path)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer file.Close()\n\treturn jpeg.Encode(file, im, &jpeg.Options{quality})\n}\n\nfunc SaveGIF(path string, frames []image.Image, delay, lastDelay int) error {\n\tg := gif.GIF{}\n\tfor i, src := range frames {\n\t\tdst := image.NewPaletted(src.Bounds(), palette.Plan9)\n\t\tdraw.Draw(dst, dst.Rect, src, image.ZP, draw.Src)\n\t\tg.Image = append(g.Image, dst)\n\t\tif i == len(frames)-1 {\n\t\t\tg.Delay = append(g.Delay, lastDelay)\n\t\t} else {\n\t\t\tg.Delay = append(g.Delay, delay)\n\t\t}\n\t}\n\tfile, err := os.Create(path)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer file.Close()\n\treturn gif.EncodeAll(file, &g)\n}\n\nfunc SaveGIFImageMagick(path string, frames []image.Image, delay, lastDelay int) error {\n\tdir, err := ioutil.TempDir(\"\", \"\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor i, im := range frames {\n\t\tpath := filepath.Join(dir, fmt.Sprintf(\"%06d.png\", i))\n\t\tSavePNG(path, im)\n\t}\n\targs := []string{\n\t\t\"-loop\", \"0\",\n\t\t\"-delay\", fmt.Sprint(delay),\n\t\tfilepath.Join(dir, \"*.png\"),\n\t\t\"-delay\", fmt.Sprint(lastDelay - delay),\n\t\tfilepath.Join(dir, fmt.Sprintf(\"%06d.png\", len(frames)-1)),\n\t\tpath,\n\t}\n\tcmd := exec.Command(\"convert\", args...)\n\tif err := cmd.Run(); err != nil {\n\t\treturn err\n\t}\n\treturn os.RemoveAll(dir)\n}\n\nfunc NumberString(x float64) string {\n\tsuffixes := []string{\"\", \"k\", \"M\", \"G\"}\n\tfor _, suffix := range suffixes {\n\t\tif x < 1000 {\n\t\t\treturn fmt.Sprintf(\"%.1f%s\", x, suffix)\n\t\t}\n\t\tx /= 1000\n\t}\n\treturn fmt.Sprintf(\"%.1f%s\", x, \"T\")\n}\n\nfunc radians(degrees float64) float64 {\n\treturn degrees * math.Pi / 180\n}\n\nfunc degrees(radians float64) float64 {\n\treturn radians * 180 / math.Pi\n}\n\nfunc clamp(x, lo, hi float64) float64 {\n\tif x < lo {\n\t\treturn lo\n\t}\n\tif x > hi {\n\t\treturn hi\n\t}\n\treturn x\n}\n\nfunc clampInt(x, lo, hi int) int {\n\tif x < lo {\n\t\treturn lo\n\t}\n\tif x > hi {\n\t\treturn hi\n\t}\n\treturn x\n}\n\nfunc minInt(a, b int) int {\n\tif a < b {\n\t\treturn a\n\t}\n\treturn b\n}\n\nfunc maxInt(a, b int) int {\n\tif a > b {\n\t\treturn a\n\t}\n\treturn b\n}\n\nfunc rotate(x, y, theta float64) (rx, ry float64) {\n\trx = x*math.Cos(theta) - y*math.Sin(theta)\n\try = x*math.Sin(theta) + y*math.Cos(theta)\n\treturn\n}\n\nfunc imageToRGBA(src image.Image) *image.RGBA {\n\tdst := image.NewRGBA(src.Bounds())\n\tdraw.Draw(dst, dst.Rect, src, image.ZP, draw.Src)\n\treturn dst\n}\n\nfunc copyRGBA(src *image.RGBA) *image.RGBA {\n\tdst := image.NewRGBA(src.Bounds())\n\tcopy(dst.Pix, src.Pix)\n\treturn dst\n}\n\nfunc uniformRGBA(r image.Rectangle, c color.Color) *image.RGBA {\n\tim := image.NewRGBA(r)\n\tdraw.Draw(im, im.Bounds(), &image.Uniform{c}, image.ZP, draw.Src)\n\treturn im\n}\n\nfunc AverageImageColor(im image.Image) color.NRGBA {\n\trgba := imageToRGBA(im)\n\tsize := rgba.Bounds().Size()\n\tw, h := size.X, size.Y\n\tvar r, g, b int\n\tfor y := 0; y < h; y++ {\n\t\tfor x := 0; x < w; x++ {\n\t\t\tc := rgba.RGBAAt(x, y)\n\t\t\tr += int(c.R)\n\t\t\tg += int(c.G)\n\t\t\tb += int(c.B)\n\t\t}\n\t}\n\tr /= w * h\n\tg /= w * h\n\tb /= w * h\n\treturn color.NRGBA{uint8(r), uint8(g), uint8(b), 255}\n}\n"
  },
  {
    "path": "primitive/worker.go",
    "content": "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\tW, H       int\n\tTarget     *image.RGBA\n\tCurrent    *image.RGBA\n\tBuffer     *image.RGBA\n\tRasterizer *raster.Rasterizer\n\tLines      []Scanline\n\tHeatmap    *Heatmap\n\tRnd        *rand.Rand\n\tScore      float64\n\tCounter    int\n}\n\nfunc NewWorker(target *image.RGBA) *Worker {\n\tw := target.Bounds().Size().X\n\th := target.Bounds().Size().Y\n\tworker := Worker{}\n\tworker.W = w\n\tworker.H = h\n\tworker.Target = target\n\tworker.Buffer = image.NewRGBA(target.Bounds())\n\tworker.Rasterizer = raster.NewRasterizer(w, h)\n\tworker.Lines = make([]Scanline, 0, 4096) // TODO: based on height\n\tworker.Heatmap = NewHeatmap(w, h)\n\tworker.Rnd = rand.New(rand.NewSource(time.Now().UnixNano()))\n\treturn &worker\n}\n\nfunc (worker *Worker) Init(current *image.RGBA, score float64) {\n\tworker.Current = current\n\tworker.Score = score\n\tworker.Counter = 0\n\tworker.Heatmap.Clear()\n}\n\nfunc (worker *Worker) Energy(shape Shape, alpha int) float64 {\n\tworker.Counter++\n\tlines := shape.Rasterize()\n\t// worker.Heatmap.Add(lines)\n\tcolor := computeColor(worker.Target, worker.Current, lines, alpha)\n\tcopyLines(worker.Buffer, worker.Current, lines)\n\tdrawLines(worker.Buffer, color, lines)\n\treturn differencePartial(worker.Target, worker.Current, worker.Buffer, worker.Score, lines)\n}\n\nfunc (worker *Worker) BestHillClimbState(t ShapeType, a, n, age, m int) *State {\n\tvar bestEnergy float64\n\tvar bestState *State\n\tfor i := 0; i < m; i++ {\n\t\tstate := worker.BestRandomState(t, a, n)\n\t\tbefore := state.Energy()\n\t\tstate = HillClimb(state, age).(*State)\n\t\tenergy := state.Energy()\n\t\tvv(\"%dx random: %.6f -> %dx hill climb: %.6f\\n\", n, before, age, energy)\n\t\tif i == 0 || energy < bestEnergy {\n\t\t\tbestEnergy = energy\n\t\t\tbestState = state\n\t\t}\n\t}\n\treturn bestState\n}\n\nfunc (worker *Worker) BestRandomState(t ShapeType, a, n int) *State {\n\tvar bestEnergy float64\n\tvar bestState *State\n\tfor i := 0; i < n; i++ {\n\t\tstate := worker.RandomState(t, a)\n\t\tenergy := state.Energy()\n\t\tif i == 0 || energy < bestEnergy {\n\t\t\tbestEnergy = energy\n\t\t\tbestState = state\n\t\t}\n\t}\n\treturn bestState\n}\n\nfunc (worker *Worker) RandomState(t ShapeType, a int) *State {\n\tswitch t {\n\tdefault:\n\t\treturn worker.RandomState(ShapeType(worker.Rnd.Intn(8)+1), a)\n\tcase ShapeTypeTriangle:\n\t\treturn NewState(worker, NewRandomTriangle(worker), a)\n\tcase ShapeTypeRectangle:\n\t\treturn NewState(worker, NewRandomRectangle(worker), a)\n\tcase ShapeTypeEllipse:\n\t\treturn NewState(worker, NewRandomEllipse(worker), a)\n\tcase ShapeTypeCircle:\n\t\treturn NewState(worker, NewRandomCircle(worker), a)\n\tcase ShapeTypeRotatedRectangle:\n\t\treturn NewState(worker, NewRandomRotatedRectangle(worker), a)\n\tcase ShapeTypeQuadratic:\n\t\treturn NewState(worker, NewRandomQuadratic(worker), a)\n\tcase ShapeTypeRotatedEllipse:\n\t\treturn NewState(worker, NewRandomRotatedEllipse(worker), a)\n\tcase ShapeTypePolygon:\n\t\treturn NewState(worker, NewRandomPolygon(worker, 4, false), a)\n\t}\n}\n"
  },
  {
    "path": "scripts/html.py",
    "content": "import os\nimport sys\n\ndef run(in_folder, out_folder):\n    seen = set()\n    for name in os.listdir(out_folder):\n        if not name.endswith('.png'):\n            continue\n        seen.add(name.split('.')[0])\n    for name in os.listdir(in_folder):\n        if not name.endswith('.jpg'):\n            continue\n        name = name[:-4]\n        if name not in seen:\n            continue\n        for m in [1, 3, 5]:\n            print '<tr>'\n            path = '%s.jpg' % name\n            print '<td><img src=\"%s\"></td>' % os.path.join(in_folder, path)\n            for n in [50, 100, 200]:\n                path = '%s.%d.128.4.%d.png' % (name, n, m)\n                print '<td><img src=\"%s\"></td>' % os.path.join(out_folder, path)\n            print '</tr>'\n\ndef main():\n    args = sys.argv[1:]\n    print HEADER\n    run(args[0], args[1])\n    print FOOTER\n\nHEADER = '''\n<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<title>PrimitivePic</title>\n<style>\nbody {\n    margin: 0;\n    padding: 0;\n    font-family: sans-serif;\n}\ntable {\n    border-collapse: collapse;\n    margin: 4px;\n}\nimg {\n    width: 400px;\n    display: block;\n    margin: 4px;\n}\ntd {\n    padding: 0;\n}\n</style>\n</head>\n<body>\n<table>\n\n<tr>\n<th>original</th>\n<th>50 shapes</th>\n<th>100 shapes</th>\n<th>200 shapes</th>\n</tr>\n'''\n\nFOOTER = '''\n</table>\n</body>\n</html>\n'''\n\nif __name__ == '__main__':\n    main()\n"
  },
  {
    "path": "scripts/process.py",
    "content": "from Queue import Queue\nimport itertools\nimport os\nimport subprocess\nimport sys\nimport threading\n\ndef makedirs(x):\n    try:\n        os.makedirs(x)\n    except Exception:\n        pass\n\ndef primitive(i, o, n, a, m):\n    makedirs(os.path.split(o)[0])\n    args = (i, o, n, a, m)\n    cmd = 'primitive -r 128 -s 512 -i %s -o %s -n %d -a %d -m %d' % args\n    subprocess.call(cmd, shell=True)\n\ndef create_jobs(in_folder, out_folder, n, a, m):\n    result = []\n    for name in os.listdir(in_folder):\n        base, ext = os.path.splitext(name)\n        if ext.lower() not in ['.jpg', '.jpeg', '.png']:\n            continue\n        out_name = '%d.%%d.png' % (m)\n        in_path = os.path.join(in_folder, name)\n        out_path = os.path.join(out_folder, base, out_name)\n        if os.path.exists(out_path):\n            continue\n        key = (base, n, m)\n        args = (in_path, out_path, n, a, m)\n        result.append((key, args))\n    return result\n\ndef worker(jobs, done):\n    while True:\n        job = jobs.get()\n        log(job)\n        primitive(*job)\n        done.put(True)\n\ndef process(in_folder, out_folder, nlist, alist, mlist, nworkers):\n    jobs = Queue()\n    done = Queue()\n    for i in xrange(nworkers):\n        t = threading.Thread(target=worker, args=(jobs, done))\n        t.setDaemon(True)\n        t.start()\n    count = 0\n    items = []\n    for n, a, m in itertools.product(nlist, alist, mlist):\n        for item in create_jobs(in_folder, out_folder, n, a, m):\n            items.append(item)\n    items.sort()\n    for _, job in items:\n        jobs.put(job)\n        count += 1\n    for i in xrange(count):\n        done.get()\n\nlog_lock = threading.Lock()\n\ndef log(x):\n    with log_lock:\n        print x\n\nif __name__ == '__main__':\n    args = sys.argv[1:]\n    nlist = [500]\n    alist = [128]\n    mlist = [0, 1, 3, 5]\n    nworkers = 4\n    process(args[0], args[1], nlist, alist, mlist, nworkers)\n"
  }
]