Skip to content

Commit

Permalink
minor tweaks to Chapter 2 and also push up rest of the code
Browse files Browse the repository at this point in the history
  • Loading branch information
sausheong committed Nov 28, 2015
1 parent b1ed316 commit 504d04c
Show file tree
Hide file tree
Showing 70,301 changed files with 29,783 additions and 1 deletion.
The diff you're trying to view is too large. We only load the first 3000 changed files.
19 changes: 19 additions & 0 deletions Chapter_10_Deploying_Go/mosaic-docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Start from a Debian image with the latest version of Go installed
# and a workspace (GOPATH) configured at /go.
FROM golang

# Copy the local package files to the container's workspace.
ADD . /go/src/github.com/sausheong/mosaicgo

WORKDIR /go/src/github.com/sausheong/mosaicgo

# Build the outyet command inside the container.
# (You may fetch or manage dependencies here,
# either manually or with a tool like "godep".)
RUN go install github.com/sausheong/mosaic-docker

# Run the outyet command by default when the container starts.
ENTRYPOINT /go/bin/mosaic-docker

# Document that the service listens on port 8080.
EXPOSE 8080
155 changes: 155 additions & 0 deletions Chapter_10_Deploying_Go/mosaic-docker/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package main

import (
"bytes"
"encoding/base64"
"fmt"
"html/template"
"image"
"image/draw"
"image/jpeg"
"net/http"
"os"
"runtime"
"strconv"
"sync"
"time"
)

func main() {
fmt.Println("Number of CPUs:", runtime.NumCPU())
runtime.GOMAXPROCS(runtime.NumCPU())
fmt.Println("Starting mosaic server ...")
mux := http.NewServeMux()
files := http.FileServer(http.Dir("public"))
mux.Handle("/static/", http.StripPrefix("/static/", files))

mux.HandleFunc("/", upload)
mux.HandleFunc("/mosaic", mosaic)

server := &http.Server{
Addr: ":" + os.Getenv("PORT"),
Handler: mux,
}
TILESDB = make(map[string][3]float64)
go tilesDB()
fmt.Println("Mosaic server started.")
server.ListenAndServe()

}

func upload(w http.ResponseWriter, r *http.Request) {
t, _ := template.ParseFiles("upload.html")
t.Execute(w, nil)
}

// cut out the image and return individual channels with image.Image
// no encoding of JPEG
func cut(original image.Image, db *map[string][3]float64, tileSize, x1, y1, x2, y2 int) <-chan image.Image {
c := make(chan image.Image)
sp := image.Point{0, 0}
go func() {
newimage := image.NewNRGBA(image.Rect(x1, y1, x2, y2))
for y := y1; y < y2; y = y + tileSize {
for x := x1; x < x2; x = x + tileSize {
r, g, b, _ := original.At(x, y).RGBA()
color := [3]float64{float64(r), float64(g), float64(b)}
nearest := nearest(color, db)
file, err := os.Open(nearest)
if err == nil {
img, _, err := image.Decode(file)
if err == nil {
t := resize(img, tileSize)
tile := t.SubImage(t.Bounds())
tileBounds := image.Rect(x, y, x+tileSize, y+tileSize)
draw.Draw(newimage, tileBounds, tile, sp, draw.Src)
} else {
fmt.Println("error in decoding nearest", err, nearest)
}
} else {
fmt.Println("error opening file when creating mosaic:", nearest)
}
file.Close()
}
}
c <- newimage.SubImage(newimage.Rect)
}()

return c
}

// combine the images and return the encoding string
func combine(r image.Rectangle, c1, c2, c3, c4 <-chan image.Image) <-chan string {
c := make(chan string)
// start a goroutine
go func() {
var wg sync.WaitGroup
newimage := image.NewNRGBA(r)
copy := func(dst draw.Image, r image.Rectangle, src image.Image, sp image.Point) {
draw.Draw(dst, r, src, sp, draw.Src)
wg.Done()
}
wg.Add(4)
var s1, s2, s3, s4 image.Image
var ok1, ok2, ok3, ok4 bool
for {
select {
case s1, ok1 = <-c1:
go copy(newimage, s1.Bounds(), s1, image.Point{r.Min.X, r.Min.Y})
case s2, ok2 = <-c2:
go copy(newimage, s2.Bounds(), s2, image.Point{r.Max.X / 2, r.Min.Y})
case s3, ok3 = <-c3:
go copy(newimage, s3.Bounds(), s3, image.Point{r.Min.X, r.Max.Y / 2})
case s4, ok4 = <-c4:
go copy(newimage, s4.Bounds(), s4, image.Point{r.Max.X / 2, r.Max.Y / 2})
}
if ok1 && ok2 && ok3 && ok4 {
break
}
}
// wait till all copy goroutines are complete
wg.Wait()
buf2 := new(bytes.Buffer)
jpeg.Encode(buf2, newimage, nil)
c <- base64.StdEncoding.EncodeToString(buf2.Bytes())
}()
return c
}

// Handler function for fan-out and fan-in
func mosaic(w http.ResponseWriter, r *http.Request) {
t0 := time.Now()
// get the content from the POSTed form
r.ParseMultipartForm(10485760) // max body in memory is 10MB
file, _, _ := r.FormFile("image")
defer file.Close()
tileSize, _ := strconv.Atoi(r.FormValue("tile_size"))
//
// // decode and get original image
original, _, _ := image.Decode(file)
bounds := original.Bounds()
db := cloneTilesDB()

// fan-out
c1 := cut(original, &db, tileSize, bounds.Min.X, bounds.Min.Y, bounds.Max.X/2, bounds.Max.Y/2)
c2 := cut(original, &db, tileSize, bounds.Max.X/2, bounds.Min.Y, bounds.Max.X, bounds.Max.Y/2)
c3 := cut(original, &db, tileSize, bounds.Min.X, bounds.Max.Y/2, bounds.Max.X/2, bounds.Max.Y)
c4 := cut(original, &db, tileSize, bounds.Max.X/2, bounds.Max.Y/2, bounds.Max.X, bounds.Max.Y)

// fan-in
c := combine(bounds, c1, c2, c3, c4)

buf1 := new(bytes.Buffer)
jpeg.Encode(buf1, original, nil)
originalStr := base64.StdEncoding.EncodeToString(buf1.Bytes())

t1 := time.Now()
images := map[string]string{
"original": originalStr,
"mosaic": <-c,
"duration": fmt.Sprintf("%v ", t1.Sub(t0)),
}

t, _ := template.ParseFiles("results.html")
t.Execute(w, images)
}
95 changes: 95 additions & 0 deletions Chapter_10_Deploying_Go/mosaic-docker/mosaic.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package main

import (
"fmt"
"image"
"image/color"
"io/ioutil"
"math"
"os"
)

// resize an image by its ratio e.g. ratio 2 means reduce the size by 1/2, 10 means reduce the size by 1/10
func resize(in image.Image, newWidth int) image.NRGBA {
bounds := in.Bounds()
width := bounds.Max.X - bounds.Min.X
ratio := width / newWidth
out := image.NewNRGBA(image.Rect(bounds.Min.X/ratio, bounds.Min.X/ratio, bounds.Max.X/ratio, bounds.Max.Y/ratio))
for y, j := bounds.Min.Y, bounds.Min.Y; y < bounds.Max.Y; y, j = y+ratio, j+1 {
for x, i := bounds.Min.X, bounds.Min.X; x < bounds.Max.X; x, i = x+ratio, i+1 {
r, g, b, a := in.At(x, y).RGBA()
out.SetNRGBA(i, j, color.NRGBA{uint8(r), uint8(g), uint8(b), uint8(a)})
}
}
return *out
}

// find the average color of the picture
func averageColor(img image.Image) [3]float64 {
bounds := img.Bounds()
r, g, b := 0.0, 0.0, 0.0
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
for x := bounds.Min.X; x < bounds.Max.X; x++ {
r1, g1, b1, _ := img.At(x, y).RGBA()
r, g, b = r+float64(r1), g+float64(g1), b+float64(b1)
}
}
totalPixels := float64(bounds.Max.X * bounds.Max.Y)
return [3]float64{r / totalPixels, g / totalPixels, b / totalPixels}
}

var TILESDB map[string][3]float64

func cloneTilesDB() map[string][3]float64 {
db := make(map[string][3]float64)
for k, v := range TILESDB {
db[k] = v
}
return db
}

// populate a tiles database in memory
func tilesDB() {
fmt.Println("Start populating tiles db ...")
files, _ := ioutil.ReadDir("tiles")
for _, f := range files {
name := "tiles/" + f.Name()
file, err := os.Open(name)
if err == nil {
img, _, err := image.Decode(file)
if err == nil {
TILESDB[name] = averageColor(img)
} else {
fmt.Println("error in populating tiles db:", err, name)
}
} else {
fmt.Println("cannot open file", name, "when populating tiles db:", err)
}
file.Close()
}
fmt.Println("Finished populating tiles db.")
}

// find the nearest matching image
func nearest(target [3]float64, db *map[string][3]float64) string {
var filename string
smallest := 1000000.0
for k, v := range *db {
dist := distance(target, v)
if dist < smallest {
filename, smallest = k, dist
}
}
delete(*db, filename)
return filename
}

// find the Eucleadian distance between 2 points
func distance(p1 [3]float64, p2 [3]float64) float64 {
return math.Sqrt(sq(p2[0]-p1[0]) + sq(p2[1]-p1[1]) + sq(p2[2]-p1[2]))
}

// find the square
func sq(n float64) float64 {
return n * n
}

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Binary file not shown.
Binary file not shown.
Loading

0 comments on commit 504d04c

Please sign in to comment.