aboutsummaryrefslogtreecommitdiff
path: root/vendor/github.com/fogleman/primitive
diff options
context:
space:
mode:
authorChristine Dodrill <me@christine.website>2018-10-04 19:44:06 -0700
committerChristine Dodrill <me@christine.website>2018-10-04 19:44:06 -0700
commit1c1d089725dd9f98b7ac73276d07dbadb388b748 (patch)
treeb0e783f5e2d485050e0072faf3b303bdc66e3539 /vendor/github.com/fogleman/primitive
parente36f755db2198a96e2b87781f5f5432fcee092dd (diff)
downloadx-1c1d089725dd9f98b7ac73276d07dbadb388b748.tar.xz
x-1c1d089725dd9f98b7ac73276d07dbadb388b748.zip
add Dockerfile
Diffstat (limited to 'vendor/github.com/fogleman/primitive')
-rw-r--r--vendor/github.com/fogleman/primitive/LICENSE.md19
-rw-r--r--vendor/github.com/fogleman/primitive/primitive/color.go44
-rw-r--r--vendor/github.com/fogleman/primitive/primitive/core.go124
-rw-r--r--vendor/github.com/fogleman/primitive/primitive/ellipse.go179
-rw-r--r--vendor/github.com/fogleman/primitive/primitive/heatmap.go59
-rw-r--r--vendor/github.com/fogleman/primitive/primitive/log.go23
-rw-r--r--vendor/github.com/fogleman/primitive/primitive/model.go174
-rw-r--r--vendor/github.com/fogleman/primitive/primitive/optimize.go75
-rw-r--r--vendor/github.com/fogleman/primitive/primitive/polygon.go122
-rw-r--r--vendor/github.com/fogleman/primitive/primitive/quadratic.go100
-rw-r--r--vendor/github.com/fogleman/primitive/primitive/raster.go46
-rw-r--r--vendor/github.com/fogleman/primitive/primitive/rectangle.go198
-rw-r--r--vendor/github.com/fogleman/primitive/primitive/scanline.go29
-rw-r--r--vendor/github.com/fogleman/primitive/primitive/shape.go25
-rw-r--r--vendor/github.com/fogleman/primitive/primitive/state.go48
-rw-r--r--vendor/github.com/fogleman/primitive/primitive/triangle.go171
-rw-r--r--vendor/github.com/fogleman/primitive/primitive/util.go195
-rw-r--r--vendor/github.com/fogleman/primitive/primitive/worker.go108
18 files changed, 1739 insertions, 0 deletions
diff --git a/vendor/github.com/fogleman/primitive/LICENSE.md b/vendor/github.com/fogleman/primitive/LICENSE.md
new file mode 100644
index 0000000..d7b4099
--- /dev/null
+++ b/vendor/github.com/fogleman/primitive/LICENSE.md
@@ -0,0 +1,19 @@
+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.
diff --git a/vendor/github.com/fogleman/primitive/primitive/color.go b/vendor/github.com/fogleman/primitive/primitive/color.go
new file mode 100644
index 0000000..2828957
--- /dev/null
+++ b/vendor/github.com/fogleman/primitive/primitive/color.go
@@ -0,0 +1,44 @@
+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)}
+}
diff --git a/vendor/github.com/fogleman/primitive/primitive/core.go b/vendor/github.com/fogleman/primitive/primitive/core.go
new file mode 100644
index 0000000..5a0c0aa
--- /dev/null
+++ b/vendor/github.com/fogleman/primitive/primitive/core.go
@@ -0,0 +1,124 @@
+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
+}
diff --git a/vendor/github.com/fogleman/primitive/primitive/ellipse.go b/vendor/github.com/fogleman/primitive/primitive/ellipse.go
new file mode 100644
index 0000000..878df40
--- /dev/null
+++ b/vendor/github.com/fogleman/primitive/primitive/ellipse.go
@@ -0,0 +1,179 @@
+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, w-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)
+}
diff --git a/vendor/github.com/fogleman/primitive/primitive/heatmap.go b/vendor/github.com/fogleman/primitive/primitive/heatmap.go
new file mode 100644
index 0000000..7bef5fe
--- /dev/null
+++ b/vendor/github.com/fogleman/primitive/primitive/heatmap.go
@@ -0,0 +1,59 @@
+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
+}
diff --git a/vendor/github.com/fogleman/primitive/primitive/log.go b/vendor/github.com/fogleman/primitive/primitive/log.go
new file mode 100644
index 0000000..d162eff
--- /dev/null
+++ b/vendor/github.com/fogleman/primitive/primitive/log.go
@@ -0,0 +1,23 @@
+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...)
+}
diff --git a/vendor/github.com/fogleman/primitive/primitive/model.go b/vendor/github.com/fogleman/primitive/primitive/model.go
new file mode 100644
index 0000000..80f49ae
--- /dev/null
+++ b/vendor/github.com/fogleman/primitive/primitive/model.go
@@ -0,0 +1,174 @@
+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)
+}
diff --git a/vendor/github.com/fogleman/primitive/primitive/optimize.go b/vendor/github.com/fogleman/primitive/primitive/optimize.go
new file mode 100644
index 0000000..e15d2b6
--- /dev/null
+++ b/vendor/github.com/fogleman/primitive/primitive/optimize.go
@@ -0,0 +1,75 @@
+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
+}
diff --git a/vendor/github.com/fogleman/primitive/primitive/polygon.go b/vendor/github.com/fogleman/primitive/primitive/polygon.go
new file mode 100644
index 0000000..dee3a6c
--- /dev/null
+++ b/vendor/github.com/fogleman/primitive/primitive/polygon.go
@@ -0,0 +1,122 @@
+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)
+}
diff --git a/vendor/github.com/fogleman/primitive/primitive/quadratic.go b/vendor/github.com/fogleman/primitive/primitive/quadratic.go
new file mode 100644
index 0000000..740d44a
--- /dev/null
+++ b/vendor/github.com/fogleman/primitive/primitive/quadratic.go
@@ -0,0 +1,100 @@
+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)
+}
diff --git a/vendor/github.com/fogleman/primitive/primitive/raster.go b/vendor/github.com/fogleman/primitive/primitive/raster.go
new file mode 100644
index 0000000..a5d620a
--- /dev/null
+++ b/vendor/github.com/fogleman/primitive/primitive/raster.go
@@ -0,0 +1,46 @@
+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)