Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add route handler and example web server with primitive route #99

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ Now available as a native Mac application!

https://primitive.lol/

### Primitive for your Web Server

Process images using primitive as a route [in your web server](https://github.com/fogleman/primitive/tree/master/server/README.md).

### Twitter

Follow [@PrimitivePic](https://twitter.com/PrimitivePic) on Twitter to see a new primitive picture every 30 minutes!
Expand Down
62 changes: 62 additions & 0 deletions build/build.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package build

import (
"image"
"runtime"
"time"

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

// Config are options needed to build a primitive image
type Config struct {
Input image.Image
Background string
Alpha int
Count int
OutputSize int
Mode int
Repeat int
V, VV bool
}

// Build builds a primitive image from available options
func Build(conf Config, stepCallback func(*primitive.Model, int, int)) *primitive.Model {
start := time.Now()

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

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

input := resize.Thumbnail(uint(256), uint(256), conf.Input, resize.Bilinear)
model := primitive.NewModel(input, bg, conf.OutputSize, runtime.NumCPU())
primitive.Log(1, "%d: t=%.3f, score=%.6f\n", 0, 0.0, model.Score)

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

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

return model
}
36 changes: 14 additions & 22 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"strings"
"time"

"github.com/fogleman/primitive/build"
"github.com/fogleman/primitive/primitive"
"github.com/nfnt/resize"
)
Expand Down Expand Up @@ -144,33 +145,24 @@ func main() {
input = resize.Thumbnail(size, size, input, resize.Bilinear)
}

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

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

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

// find optimal shape and add it to the model
t := time.Now()
n := model.Step(primitive.ShapeType(config.Mode), config.Alpha, config.Repeat)
nps := primitive.NumberString(float64(n) / time.Since(t).Seconds())
elapsed := time.Since(start).Seconds()
primitive.Log(1, "%d: t=%.3f, score=%.6f, n=%d, n/s=%s\n", frame, elapsed, model.Score, n, nps)
buildConfig := build.Config{
Input: input,
Background: Background,
Alpha: config.Alpha,
Count: config.Count,
OutputSize: OutputSize,
Mode: config.Mode,
Repeat: config.Repeat,
V: V,
VV: VV,
}

build.Build(buildConfig, func(model *primitive.Model, frame int, i int) {
// write output image(s)
for _, output := range Outputs {
ext := strings.ToLower(filepath.Ext(output))
Expand Down Expand Up @@ -202,6 +194,6 @@ func main() {
}
}
}
}
})
}
}
43 changes: 43 additions & 0 deletions server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Primitive for your Web Server

## Add a route to your existing web server

The following will bind a new route to your server where users can
POST files to `/primitive` with the `file` form key and process them with
primitive. The response returns a raw PNG buffer with Content-Type `image/png`.

```go
import (
"net/http"

pr "github.com/fogleman/primitive/server/route"
)

handler := pr.PrimitiveRoute(pr.Config{
MaxUploadMb: 10,
FileKey: "file",
})
http.HandleFunc("/primitive", handler)
```

## Example web server

An examples web server you can start that has a `/primitive` route
bound that accepts file uploads to be processed with primitive
can be started with the following instructions.

```
$ go get github.com/fogleman/primitive/server

$ # run the web server on port :8080
$ go run $(go env GOPATH)/src/github.com/fogleman/primitive/server

$ # in another terminal window, make sure the server is running
$ curl localhost:8080
We're running!
```

## TODOS

- Make all parameters configurable (Background, Alpha, Mode, etc.)
- Dockerfile for example web server
64 changes: 64 additions & 0 deletions server/route/route.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package route

import (
"image"
"image/png"
"net/http"

"github.com/fogleman/primitive/build"
"github.com/fogleman/primitive/primitive"
)

// Config contains core settings one can
// configure to setup the primitive file processing route.
type Config struct {
MaxUploadMb int64
FileKey string
}

// PrimitiveRoute is an http route than can be used to consume
// an uploaded file and execute primitive on it.
func PrimitiveRoute(conf Config) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Parse our multipart form, 10 << 20 specifies a maximum upload of 10 MB files.
var maxUpload int64 = 10
if conf.MaxUploadMb > 0 {
maxUpload = conf.MaxUploadMb
}
r.ParseMultipartForm(maxUpload << 20)

// FormFile returns the first file for the given key `myFile`
// it also returns the FileHeader so we can get the Filename,
// the Header and the size of the file
fileKey := "file"
if conf.FileKey != "" {
fileKey = conf.FileKey
}
file, _, err := r.FormFile(fileKey)
if err != nil {
http.Error(w, err.Error(), 400)
return
}
defer file.Close()

img, _, err := image.Decode(file)
if err != nil {
http.Error(w, err.Error(), 400)
return
}

buildConfig := build.Config{
Input: img,
Alpha: 128,
Count: 100,
OutputSize: 1024,
Mode: 4,
}
model := build.Build(buildConfig, func(_ *primitive.Model, _ int, _ int) {})
finalImg := model.Context.Image()

// return final primitive image
w.Header().Set("Content-Type", "image/png")
png.Encode(w, finalImg)
}
}
33 changes: 33 additions & 0 deletions server/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// main.go
package main

import (
"log"
"net/http"
"os"

pr "github.com/fogleman/primitive/server/route"
)

func setupRoutes() {
handler := pr.PrimitiveRoute(pr.Config{
MaxUploadMb: 10,
})
http.HandleFunc("/primitive", handler)
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("We're running!"))
})
}

func main() {
setupRoutes()
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
err := http.ListenAndServe(":"+port, nil)
if err != nil {
log.Fatal("Error starting server - ", err)
}
}