-
Notifications
You must be signed in to change notification settings - Fork 758
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
minor tweaks to Chapter 2 and also push up rest of the code
- Loading branch information
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.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
7 changes: 7 additions & 0 deletions
7
Chapter_10_Deploying_Go/mosaic-docker/public/css/bootstrap.min.css
Large diffs are not rendered by default.
Oops, something went wrong.
4 changes: 4 additions & 0 deletions
4
Chapter_10_Deploying_Go/mosaic-docker/public/css/font-awesome.min.css
Large diffs are not rendered by default.
Oops, something went wrong.
Binary file not shown.
Binary file added
BIN
+70.8 KB
Chapter_10_Deploying_Go/mosaic-docker/public/fonts/fontawesome-webfont.eot
Binary file not shown.
Oops, something went wrong.