diff --git a/main.go b/main.go index 59e33452..d742c788 100644 --- a/main.go +++ b/main.go @@ -6,6 +6,7 @@ import ( "log" "math/rand" "os" + "os/exec" "path/filepath" "runtime" "strconv" @@ -18,6 +19,7 @@ import ( var ( Input string + Colors string Outputs flagArray Background string Configs shapeConfigArray @@ -62,7 +64,8 @@ func (i *shapeConfigArray) Set(value string) error { } func init() { - flag.StringVar(&Input, "i", "", "input image path") + flag.StringVar(&Input, "i", "", "Input image path") + flag.StringVar(&Colors, "c", "", "input image with example colors") flag.Var(&Outputs, "o", "output image path") flag.Var(&Configs, "n", "number of primitives") flag.StringVar(&Background, "bg", "", "background color (hex)") @@ -132,6 +135,19 @@ func main() { if Workers < 1 { Workers = runtime.NumCPU() } + if Colors != "" { + fmt.Println("here") + args := []string{ + Input, + "-dither", + "Riemersma", + "-remap", + Colors, + "dithered.png"} + cmd := exec.Command("convert", args...) + cmd.Run() + Input = "dithered.png" + } // read input image primitive.Log(1, "reading %s\n", Input) @@ -166,7 +182,7 @@ func main() { // find optimal shape and add it to the model t := time.Now() - n := model.Step(primitive.ShapeType(config.Mode), config.Alpha, config.Repeat) + n := model.Step(primitive.ShapeType(config.Mode), config.Alpha, config.Repeat, frame, config.Count) nps := primitive.NumberString(float64(n) / time.Since(t).Seconds()) elapsed := time.Since(start).Seconds() primitive.Log(1, "%d: t=%.3f, score=%.6f, n=%d, n/s=%s\n", frame, elapsed, model.Score, n, nps) diff --git a/primitive/core.go b/primitive/core.go index 5a0c0aa0..0a5f3641 100644 --- a/primitive/core.go +++ b/primitive/core.go @@ -5,6 +5,33 @@ import ( "math" ) +func closestColor(r1 int, g1 int, b1 int) Color { + palette := [8][3]int{ + {255, 255, 255}, + {255, 0, 25}, + {247, 236, 70}, + {0, 225, 132}, + {21, 54, 177}, + {102, 74, 41}, + {208, 0, 250}, + {0, 0, 0}} + + var closestDis = float64(10000) + var cc = Color{0, 0, 0, 0} + for _, rgb2 := range palette { + r2 := rgb2[0] + g2 := rgb2[1] + b2 := rgb2[2] + dis := float64(math.Pow(math.Pow(float64(r1-r2), 2)+math.Pow(float64(g1-g2), 2)+math.Pow(float64(b1-b2), 2), 0.5)) + if dis < closestDis { + closestDis = dis + cc = Color{r2, g2, b2, 255} + } + } + return cc + +} + func computeColor(target, current *image.RGBA, lines []Scanline, alpha int) Color { var rsum, gsum, bsum, count int64 a := 0x101 * 255 / alpha @@ -30,7 +57,7 @@ func computeColor(target, current *image.RGBA, lines []Scanline, alpha int) Colo 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} + return closestColor(r, g, b) } func copyLines(dst, src *image.RGBA, lines []Scanline) { diff --git a/primitive/ellipse.go b/primitive/ellipse.go index 4fd9601e..3eda1e3d 100644 --- a/primitive/ellipse.go +++ b/primitive/ellipse.go @@ -24,11 +24,25 @@ func NewRandomEllipse(worker *Worker) *Ellipse { return &Ellipse{worker, x, y, rx, ry, false} } +func Max(x, y int) int { + if x < y { + return y + } + return x +} + func NewRandomCircle(worker *Worker) *Ellipse { rnd := worker.Rnd x := rnd.Intn(worker.W) y := rnd.Intn(worker.H) - r := rnd.Intn(32) + 1 + var r = int(0) + if worker.Frame < (worker.N / 3) { + r = Max(worker.W, worker.H) * 3 / 100 + } else if worker.Frame < (worker.N * 2 / 3) { + r = Max(worker.W, worker.H) * 18 / 1000 + } else { + r = Max(worker.W, worker.H) * 74 / 10000 + } return &Ellipse{worker, x, y, r, r, true} } @@ -52,21 +66,8 @@ func (c *Ellipse) Mutate() { w := c.Worker.W h := c.Worker.H rnd := c.Worker.Rnd - switch rnd.Intn(3) { - case 0: - c.X = clampInt(c.X+int(rnd.NormFloat64()*16), 0, w-1) - c.Y = clampInt(c.Y+int(rnd.NormFloat64()*16), 0, h-1) - case 1: - c.Rx = clampInt(c.Rx+int(rnd.NormFloat64()*16), 1, w-1) - if c.Circle { - c.Ry = c.Rx - } - case 2: - c.Ry = clampInt(c.Ry+int(rnd.NormFloat64()*16), 1, h-1) - if c.Circle { - c.Rx = c.Ry - } - } + c.X = clampInt(c.X+int(rnd.NormFloat64()*16), 0, w-1) + c.Y = clampInt(c.Y+int(rnd.NormFloat64()*16), 0, h-1) } func (c *Ellipse) Rasterize() []Scanline { diff --git a/primitive/model.go b/primitive/model.go index 80f49ae8..f18cf04b 100644 --- a/primitive/model.go +++ b/primitive/model.go @@ -12,6 +12,7 @@ type Model struct { Sw, Sh int Scale float64 Background Color + Frame int Target *image.RGBA Current *image.RGBA Context *gg.Context @@ -116,13 +117,13 @@ func (model *Model) Add(shape Shape, alpha int) { shape.Draw(model.Context, model.Scale) } -func (model *Model) Step(shapeType ShapeType, alpha, repeat int) int { - state := model.runWorkers(shapeType, alpha, 1000, 100, 16) +func (model *Model) Step(shapeType ShapeType, alpha, repeat int, frame int, n int) int { + state := model.runWorkers(shapeType, alpha, 1000, 100, 16, frame, n) // state = HillClimb(state, 1000).(*State) model.Add(state.Shape, state.Alpha) for i := 0; i < repeat; i++ { - state.Worker.Init(model.Current, model.Score) + state.Worker.Init(model.Current, model.Score, frame, n) a := state.Energy() state = HillClimb(state, 100).(*State) b := state.Energy() @@ -144,7 +145,7 @@ func (model *Model) Step(shapeType ShapeType, alpha, repeat int) int { return counter } -func (model *Model) runWorkers(t ShapeType, a, n, age, m int) *State { +func (model *Model) runWorkers(t ShapeType, a, n, age, m int, frame int, totN int) *State { wn := len(model.Workers) ch := make(chan *State, wn) wm := m / wn @@ -153,7 +154,7 @@ func (model *Model) runWorkers(t ShapeType, a, n, age, m int) *State { } for i := 0; i < wn; i++ { worker := model.Workers[i] - worker.Init(model.Current, model.Score) + worker.Init(model.Current, model.Score, frame, totN) go model.runWorker(worker, t, a, n, age, wm, ch) } var bestEnergy float64 diff --git a/primitive/util.go b/primitive/util.go index 83446cdf..4242ac3b 100644 --- a/primitive/util.go +++ b/primitive/util.go @@ -185,6 +185,30 @@ func uniformRGBA(r image.Rectangle, c color.Color) *image.RGBA { return im } +func closestBGColor(r1 int, g1 int, b1 int) (int, int, int) { + palette := [5][3]int{{39, 79, 156}, + {95, 98, 103}, + {8, 8, 8}, + {117, 14, 30}, + {98, 92, 74}} + + var closestDis = float64(10000) + var cc = [3]int{0, 0, 0} + + for _, rgb2 := range palette { + r2 := rgb2[0] + g2 := rgb2[1] + b2 := rgb2[2] + dis := float64(math.Pow(math.Pow(float64(r1-r2), 2)+math.Pow(float64(g1-g2), 2)+math.Pow(float64(b1-b2), 2), 0.5)) + if dis < closestDis { + closestDis = dis + cc = [3]int{r2, g2, b2} + } + } + return cc[0], cc[1], cc[2] + +} + func AverageImageColor(im image.Image) color.NRGBA { rgba := imageToRGBA(im) size := rgba.Bounds().Size() @@ -198,8 +222,12 @@ func AverageImageColor(im image.Image) color.NRGBA { b += int(c.B) } } + r /= w * h g /= w * h b /= w * h - return color.NRGBA{uint8(r), uint8(g), uint8(b), 255} + + r1, g1, b1 := closestBGColor(r, g, b) + + return color.NRGBA{uint8(r1), uint8(g1), uint8(b1), 255} } diff --git a/primitive/worker.go b/primitive/worker.go index d99eed5d..cf6e1e88 100644 --- a/primitive/worker.go +++ b/primitive/worker.go @@ -10,6 +10,8 @@ import ( type Worker struct { W, H int + Frame int + N int Target *image.RGBA Current *image.RGBA Buffer *image.RGBA @@ -36,10 +38,12 @@ func NewWorker(target *image.RGBA) *Worker { return &worker } -func (worker *Worker) Init(current *image.RGBA, score float64) { +func (worker *Worker) Init(current *image.RGBA, score float64, frame int, n int) { worker.Current = current worker.Score = score worker.Counter = 0 + worker.N = n + worker.Frame = frame worker.Heatmap.Clear() }