翻蚳ã®äžæ£ç¢ºãã«ã€ããŠã¯ãå人ã§æžããŠãã ããã
æ°ã¶æåãç§ã®èŠªåã§ãããµãã£ã·ã¥ã»ã¿ãªã ã¯çŽ æŽãããã¢ã€ãã¢ãææ¡ããŸãã-Goããã°ã©ããŒã®ã¹ãã«ãé«ããããã«ãGoã§ããã€ãã®ã³ã³ããäœæããããšã§ãã ãã®ãããžã§ã¯ãã®ã¢ã€ãã¢ã¯ãæ¯æïŒãŸãã¯ãããããïŒããã°ã©ãã³ã°ã¿ã¹ã¯ãèãåºãããšã§ããããã¯ãGoã³ãã¥ããã£ã«ãšã£ãŠæ°é®®ã§èå³æ·±ã課é¡ã§ãã åè³è ã¯è³åãåãåããŸãããããã«éèŠãªããšã¯ãããã¯äºãã«ããããŠç¹ã«èªåèªèº«ãå©ããè©Šã¿ã§ãã ãµãã£ã·ã¥ã¯ç§ã«ã¿ã¹ã¯ãèãåºãããã«é Œã¿ãŸããããããŠãç§ã¯åãã§3çªç®ã®ç«¶äºã®ããã«ãããæãã€ããŸããïŒãã£ã¬ã³ãžïŒ3ïŒã
ç§ã®ã»ãšãã©ã®ãã£ãªã¢ã§Webã¢ããªã±ãŒã·ã§ã³ããã°ã©ããŒã§ãã£ããããèªç¶ãªèãã¯Webã¢ããªã±ãŒã·ã§ã³ã®ç«¶äºãèãåºãããšã§ããã ãããŠæè¿ãããã«ãœã³ã§ã¢ã¶ã€ã¯ãçæããRubyã¹ã¯ãªãããæžããã®ã§ã ã¢ã¶ã€ã¯ãçæããWebã¢ããªã±ãŒã·ã§ã³ãäœæããã¿ã¹ã¯ã§ãããã®ã¢ã€ãã¢ãçµã¿åãããããšãèããŸããã

æ£çŽãªãšãããã¿ã¹ã¯ã®å ¬éæç¹ã§ã¯ãç§ã®Webã¢ããªã±ãŒã·ã§ã³ã¯ãŸã äœæãããŠããŸããã ã³ã³ãã¹ããçµãã£ãŠããæžãå§ããŸããã ã¢ã¶ã€ã¯ã®Webã¢ããªã±ãŒã·ã§ã³ãäœæããã®ã«çŽ2æ¥ããããŸããã ããããããã°ã©ã ãé«éåããããã«Goã«ããã«ç«¶äºåãè¿œå ãããã£ããããçµäºããŸããã§ããã ãã®ããã°æçš¿ã¯ç§ããã£ãããšã®çµæã§ãã
ãã®ãšã³ããªã®ãã¹ãŠã®ã³ãŒãã¯github.com/sausheong/mosaicã«ãããŸãã githubã®ã³ãŒãã¯ã以äžã«ç€ºããã®ãšãããã«ç°ãªãããšã«æ³šæããŠãã ããã
ãã¢ã¯ã mosaic.saush.comã§è¡šç€ºã§ããŸãã ïŒã¬ãŒã³ã«æ³šæããŠãã ãã-倧ããªç»åã§ã¢ããªã±ãŒã·ã§ã³ãæããããªãã§ãã ããããµãŒããŒã¯åŒ±ãã®ã§ã4 MBã®ç»åã§ãåãã§ã¯ã©ãã·ã¥ããŸãïŒã Tutumçµç±ã§Dockerã䜿çšããŠDigital Oceanã§ãã¹ããããŸãã ãµãŒããŒã§ã®ãã¢ã®ããã©ãŒãã³ã¹ã¯ããã®èšäºã»ã©é«ãã¯ãããŸãããããã¯ã1ã€ã®CPU VMãš512 MBãã䜿çšããªãããã§ãã
åçã¢ã¶ã€ã¯ãäœæããŸãã
åçã¢ã¶ã€ã¯ãŸãã¯ãã©ãã¢ã¶ã€ã¯ã¯ãé·æ¹åœ¢ã®ã»ã¯ã·ã§ã³ïŒéåžžã¯åããµã€ãºïŒã«åå²ãããåçïŒéåžžã¯åçïŒã§ããããããããæ°ããåçã«çœ®ãæããããŸãã é ãããããŸãã¯ç®ã现ããŠèŠããšãå ã®åçãèŠãããšãã§ããŸãã ããèŠããšã倧ããªç»åã¯å®éã«ã¯æ°çŸãŸãã¯æ°åãã®å°ããªã¿ã€ã«ç»åã§æ§æãããŠããããšãããããŸãã
åºæ¬çãªèãæ¹ã¯ç°¡åã§ã-Webã¢ããªã±ãŒã·ã§ã³ã䜿çšãããšããŠãŒã¶ãŒã¯å¿ èŠãªç»åãããŠã³ããŒãã§ããããã«åºã¥ããŠã¢ã¶ã€ã¯ãçæãããŸãã ç°¡åã«ããããã«ãåçã¯ãã£ã¬ã¯ããªã«ããªããŒããããé©åãªãµã€ãºã§ãããšæ³å®ããŸããã
ãã©ãã¢ã¶ã€ã¯ã®ã¢ã«ãŽãªãºã ããå§ããŸãããã Webã¢ããªã±ãŒã·ã§ã³ã¯ç°¡åãªæé ã§æ§æãããŠããããµãŒãããŒãã£ã®ã©ã€ãã©ãªã䜿çšããã«äœæã§ããŸãã
- ç»åã®ããŒã¿ããŒã¹ãã¿ã€ã«ã®ããã·ã¥ããã£ã¬ã¯ããªãåçã§ã¹ãã£ã³ããŠäœæããŸãã 次ã«ããã¡ã€ã«åãããŒã§ãããç»åã®å¹³åè²ãå€ã§ãããã£ã¹ãã¬ã€ãäœæããŸãã åãã¯ã»ã«ã®èµ€ãç·ãéïŒRGBïŒã®3ã€ã®èŠçŽ ã®ã¿ãã«ã«ã€ããŠå¹³åè²å€ãèšç®ããããã¹ãŠã®èµ€ãç·ãéã®ãã¯ã»ã«ã®å€ãåèšãã¯ã»ã«æ°ã§é€ç®ãããŠå ç®ãããŸãã ïŒã¬ãŒã³ã«æ³šæããŠãã ãã-èè ã¯ã¿ãã«ãšããçšèªã䜿çšããŸãããGoã«ã¯ã¿ãã«ã¯ãããŸãããé åã®ããããã£ã¯éåžžã«äŒŒãŠããŸããã¹ã©ã€ã¹ãšæ··åããªãã§ãã ããïŒã
- ã¿ãŒã²ããç»åãé©åãªãµã€ãºã®å°ããªã¿ã€ã«ã«åãåããŸãã
- ã¿ãŒã²ããç»åã®åã»ã¯ã·ã§ã³ã«ã€ããŠãå·Šäžã®ãã¯ã»ã«ã®å¹³åè²ãèšç®ãããããã»ã¯ã·ã§ã³å šäœã®å¹³åè²ã§ãããšä»®å®ããŸãã
- ã¿ãŒã²ããã€ã¡ãŒãžã®å¯Ÿå¿ããã»ã¯ã·ã§ã³ã®å¹³åè²ã«æãããäžèŽããã¿ã€ã«ã®ããŒã¿ããŒã¹ããé©åãªã¿ã€ã«ãæ¢ãããã©ããã¹ã¯ã®é©åãªå Žæã«é 眮ããŸãã æé©ãªäžèŽãèŠã€ããã«ã¯ãã¿ãã«ã®åè²[3]ã3次å 空éã®ãã€ã³ãã«å€æããããšã«ããã[3]ã¿ãã«ã®2è²éã®ãŠãŒã¯ãªããè·é¢ãèšç®ããŸãã
- èŠã€ãã£ãã¿ã€ã«ãããŒã¿ããŒã¹ããåé€ããŠãæ®ãã®ã¿ã€ã«ãäžæã«ãªãããã«ããŸãã
ã¢ã¶ã€ã¯ãäœæããããã«æžããããã¹ãŠã®ã³ãŒããåäžã®mosaic.goãã¡ã€ã«ã«é 眮ããŸããã ãã®ãã¡ã€ã«ããåæ©èœãç解ããŸãããã
// 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} }
ãŸããé¢æ°ã®colorColorããå§ããŸãããã®é¢æ°ã¯ãç»åã®ãã¹ãŠã®èµ€ãç·ãéã®ãã¯ã»ã«ã®å€ãèšç®ããããããåèšããŠïŒåå¥ã«ïŒãå€ã®åââåèšãç»åã®ãã¯ã»ã«ã®ç·æ°ã§é€ç®ããŸãã 次ã«ããããã®å€ã§æ§æããã[3]ã¿ãã«ïŒå®éã«ã¯3ã€ã®èŠçŽ ã®é åïŒãè¿ããŸãã
次ã«ã ãµã€ãºå€æŽæ©èœãèŠãŠãã ãã
// newWidth 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 }
tilesDBé¢æ°ã¯ãç»åãã£ã¬ã¯ããªãã¹ãã£ã³ããŠç»åã®ããŒã¿ããŒã¹ãäœæããŸãã
// tilesDB func tilesDB() map[string][3]float64 { fmt.Println("Start populating tiles db ...") db := make(map[string][3]float64) 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 { db[name] = averageColor(img) } else { fmt.Println(":", err, name) } } else { fmt.Println("cannot open file", name, err) } file.Close() } fmt.Println("Finished populating tiles db.") return db }
ã¿ã€ã«ããŒã¿ããŒã¹ã¯ãããŒã«æååãã[3]å€ã«ã¿ãã«ïŒãã®å Žåã¯3ã€ã®èŠçŽ ã®é åïŒãå«ããããã³ã°ã§ãã ãã£ã¬ã¯ããªå ã®åç»åãã¡ã€ã«ãéããç»åã®å¹³åè²ãèšç®ããŠè¡šç€ºå€ãèšå®ããŸãã ã¿ã€ã«ããŒã¿ããŒã¹ã¯ãç»åãã£ã¬ã¯ããªã§é©åãªã¿ã€ã«ãæ€çŽ¢ããããã«äœ¿çšãããŸãã ã¿ãã«ã®ã¿ãŒã²ããã«ã©ãŒå€[3]ãšãšãã«ãæãè¿ãé¢æ°ã«æž¡ãããŸãã
// 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 }
ããŒã¿ããŒã¹å ã®åå€ãã¿ãŒã²ããã«ã©ãŒãšæ¯èŒãããæå°ã®å·®ãæã€å€ãæé©ãªã¿ã€ã«ãšããŠè¿ãããŸãã èŠã€ãã£ãå€ã¯ããŒã¿ããŒã¹ããåé€ãããŸãã è·é¢é¢æ°ã¯ã2ã€ã®[3]ã¿ãã«éã®ãŠãŒã¯ãªããè·é¢ãèšç®ããŸãã
// 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])) } // func sq(n float64) float64 { return n * n }
æåŸã«ãåãã©ãã¢ã¶ã€ã¯ã®äœææã«ã¿ã€ã«ããŒã¿ããŒã¹ãã¹ãã£ã³ããŠããŒããããšãèŠèŠãããªããŸãã ãããäžåºŠã ãè¡ãããã©ãã¢ã¶ã€ã¯ãäœæãããšãã«ãã®ããŒã¿ããŒã¹ã®ã¯ããŒã³ãäœæããŸãã ãœãŒã¹TILESDBã¿ã€ã«ããŒã¿ããŒã¹ã¯ãã°ããŒãã«å€æ°ãšããŠäœæãããWebã¢ããªã±ãŒã·ã§ã³ã®éå§æã«å®£èšãããŸãã
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 }
ãã©ãã¢ã¶ã€ã¯Webã¢ããªã±ãŒã·ã§ã³
ãã©ãã¢ã¶ã€ã¯ãçæããããã®é¢æ°ãæºåããããWebã¢ããªã±ãŒã·ã§ã³ã®äœæãéå§ã§ããŸãã Webã¢ããªã±ãŒã·ã§ã³ã¯ã main.goãšããååã®ãœãŒã¹ãã¡ã€ã«ã«é 眮ãããŸãã
package main import ( "fmt" "html/template" "net/http" "bytes" "encoding/base64" "image" "image/draw" "image/jpeg" "os" "strconv" ) func main() { 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: "127.0.0.1:8080", Handler: mux, } // TILESDB = 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) } // HandlerFunc mosaic func mosaic(w http.ResponseWriter, r *http.Request) { t0 := time.Now() // POST r.ParseMultipartForm(10485760) // max body in memory is 10MB file, _, _ := r.FormFile("image") defer file.Close() tileSize, _ := strconv.Atoi(r.FormValue("tile_size")) // original, _, _ := image.Decode(file) bounds := original.Bounds() // newimage := image.NewNRGBA(image.Rect(bounds.Min.X, bounds.Min.Y, bounds.Max.X, bounds.Max.Y)) // db := cloneTilesDB() // source point 0, 0 sp := image.Point{0, 0} for y := bounds.Min.Y; y < bounds.Max.Y; y = y + tileSize { for x := bounds.Min.X; x < bounds.Max.X; 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:", err, nearest) } } else { fmt.Println("error:", nearest) } file.Close() } } buf1 := new(bytes.Buffer) jpeg.Encode(buf1, original, nil) originalStr := base64.StdEncoding.EncodeToString(buf1.Bytes()) buf2 := new(bytes.Buffer) jpeg.Encode(buf2, newimage, nil) mosaic := base64.StdEncoding.EncodeToString(buf2.Bytes()) t1 := time.Now() images := map[string]string{ "original": originalStr, "mosaic": mosaic, "duration": fmt.Sprintf("%v ", t1.Sub(t0)), } t, _ := template.ParseFiles("results.html") t.Execute(w, images) }
ãã©ãã¢ã¶ã€ã¯ãäœæããããã®åºæ¬çãªããžãã¯ã¯ããã³ãã©ãŒã§ããã¢ã¶ã€ã¯é¢æ°ã§èª¬æãããŠããŸãã æåã«ãããŠã³ããŒããããã¡ã€ã«ãšãã©ãŒã ã®ã¿ã€ã«ã®ãµã€ãºãååŸããŸãã
// POST r.ParseMultipartForm(10485760) // 10 file, _, _ := r.FormFile("image") defer file.Close() tileSize, _ := strconv.Atoi(r.FormValue("tile_size"))
次ã«ãããŠã³ããŒãããã¿ãŒã²ããã€ã¡ãŒãžããã³ãŒããããã©ãã¢ã¶ã€ã¯çšã®æ°ããã€ã¡ãŒãžãäœæããŸãã
// original, _, _ := image.Decode(file) bounds := original.Bounds() // newimage := image.NewNRGBA(image.Rect(bounds.Min.X, bounds.Min.Y, bounds.Max.X, bounds.Max.Y))
ãŸãããœãŒã¹ã¿ã€ã«ããŒã¿ããŒã¹ã®ã¯ããŒã³ãäœæããåã¿ã€ã«ã®ãœãŒã¹ãã€ã³ãã決å®ããŸãïŒåŸã§ã€ã¡ãŒãž/æç»ããã±ãŒãžã«å¿ èŠïŒ
// db := cloneTilesDB() // source point 0, 0 sp := image.Point{0, 0}
ããã§ãã¿ãŒã²ããã€ã¡ãŒãžå ã®éžæãããµã€ãºã®ãã¹ãŠã®ã¿ã€ã«ãå埩åŠçããæºåãã§ããŸããã
for y := bounds.Min.Y; y < bounds.Max.Y; y = y + tileSize { for x := bounds.Min.X; x < bounds.Max.X; 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:", err, nearest) } } else { fmt.Println("error:", nearest) } file.Close() } }
ã»ã¯ã·ã§ã³ããšã«ãå·Šäžã®ãã¯ã»ã«ãéžæãããããã»ã¯ã·ã§ã³ã®å¹³åè²ã§ããããšã決å®ããŸãã 次ã«ãããŒã¿ããŒã¹å ã§æãé©ããè²ã®ã¿ã€ã«ãæ¢ããŸãã ã¿ã€ã«åãããããŒã¿ããŒã¹ã¯ãã¡ã€ã«ã®ååãäžãããã®ãµã€ãºã¯æå®ããããã®ã«å€æŽããŸãã çµæã®ã¿ã€ã«ã¯ã以åã«äœæãããã¢ã¶ã€ã¯ïŒnewimageïŒã«é 眮ãããŸãã
ãã©ãã¢ã¶ã€ã¯ãäœæãããããJPEG圢åŒã§ãšã³ã³ãŒãããŠãããbase64æååã«ãšã³ã³ãŒãããŸãã
buf1 := new(bytes.Buffer) jpeg.Encode(buf1, original, nil) originalStr := base64.StdEncoding.EncodeToString(buf1.Bytes()) buf2 := new(bytes.Buffer) jpeg.Encode(buf2, newimage, nil) mosaic := base64.StdEncoding.EncodeToString(buf2.Bytes()) t1 := time.Now() images := map[string]string{ "original": originalStr, "mosaic": mosaic, "duration": fmt.Sprintf("%v ", t1.Sub(t0)), } t, _ := template.ParseFiles("results.html") t.Execute(w, images)
å ã®ã¿ãŒã²ããç»åãšãã©ãã¢ã¶ã€ã¯ã¯ã次ã®ããŒãžã«è¡šç€ºããããã«results.htmlãã³ãã¬ãŒãã«éä¿¡ãããŸãã ã芧ã®ãšãããç»åã¯base64 WebããŒãžã®ã³ã³ãã³ãã«çŽæ¥åã蟌ãŸããããŒã¿URLã«è¡šç€ºãããŸãã
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>Mosaic</title> ... </head> <body> <div class='container'> <div class="col-md-6"> <img src="data:image/jpg;base64,{{ .original }}" width="100%"> <div class="lead">Original</div> </div> <div class="col-md-6"> <img src="data:image/jpg;base64,{{ .mosaic }}" width="100%"> <div class="lead">Mosaic â {{ .duration }} </div> </div> <div class="col-md-12 center"> <a class="btn btn-lg btn-info" href="/">Go Back</a> </div> </div> <br> </body> </html>
äœæãããã¢ã¶ã€ã¯ã®ã¹ã¯ãªãŒã³ã·ã§ããïŒ

åºæ¬çãªãã©ãã¢ã¶ã€ã¯
ãã©ãã¢ã¶ã€ã¯ãçæããããã®åºæ¬çãªWebã¢ããªã±ãŒã·ã§ã³ãã§ããã®ã§ã軜éã¹ã¬ãããšé¢æ°ã®åæå®è¡ãåããããŒãžã§ã³ãè¿œå ããŸãããã
競äºåã®ããWebã¢ããªã±ãŒã·ã§ã³ã¢ã¶ã€ã¯
競äºåã䜿çšããéã®æãäžè¬çãªã¿ã¹ã¯ã®1ã€ã¯ãçç£æ§ã®åäžã§ãã ç§ãæžããWebã¢ããªã±ãŒã·ã§ã³ã¯ã2.25ç§ã§151 KBã®JPEGã€ã¡ãŒãžããåçã¢ã¶ã€ã¯ãäœæããŸãã é床ã¯ç®èŠãããã®ã§ã¯ãªãã軜éã¹ããªãŒã ãè¿œå ããããšã§ç¢ºãã«æ¹åã§ããŸãã ãã®äŸã§ã¯ãããªãåçŽãªãŽã«ãŒãã³ã®åæå®è¡ã¢ã«ãŽãªãºã ã䜿çšããŸãã
- å ã®ç»åã4ã€ã®éšåã«åå²ãã
- åæã«åŠçããŸã
- çµæã1ã€ã®ã¢ã¶ã€ã¯ã«æ»ã
ããã¯æ¬¡ã®ããã«è¡šãããšãã§ããŸãã

競åã¢ã«ãŽãªãºã
ããã¯ã軜éã¹ã¬ããã䜿çšããŠããã©ãŒãã³ã¹ãåäžãããå¯äžã®æ¹æ³ã§ã¯ãªããåçŽã§çŽæ¥çãªæ¹æ³ã§ããããšã«æ³šæããŠãã ããã
å€æŽããå¯äžã®é¢æ°ã¯ã¢ã¶ã€ã¯ãã³ãã©ãŒã§ãã 以åã¯ããã©ãã¢ã¶ã€ã¯ãäœæããããã®å¯äžã®ãã³ãã©ããããŸããã ãã©ãã¢ã¶ã€ã¯Webã¢ããªã±ãŒã·ã§ã³ã®ç«¶äºåã®ããããŒãžã§ã³ã§ã¯ãåçãã«ããããŠçµåãã2ã€ã®æ°ããæ©èœã«åå²ããå¿ èŠããããŸãã ã¢ã¶ã€ã¯é¢æ°ããã«ãããšçµåã®äž¡æ¹ãåŒã³åºããŸãã
func mosaic(w http.ResponseWriter, r *http.Request) { t0 := time.Now() r.ParseMultipartForm(10485760) // 10 file, _, _ := r.FormFile("image") defer file.Close() tileSize, _ := strconv.Atoi(r.FormValue("tile_size")) original, _, _ := image.Decode(file) bounds := original.Bounds() db := cloneTilesDB() // 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) // 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) }
ã«ããç»åã¯ã ã«ããæ©èœã«ãã£ãŠåŠçãããŸãã

ã¿ãŒã²ããç»åã4ã€ã®éšåã«åå²ãã
å ã®ç»åã¯ãåæã«åŠçããããã«4ã€ã®è±¡éã«åå²ãããŸãã
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)
ãããã¯ãŽã«ãŒãã³ã®ãªãéåžžã®æ©èœã§ããããšã«æ°ã¥ããã§ããããã©ã®ããã«åæã«å®è¡ã§ããŸããïŒ äºå®ãããèªäœã®äžã®cuté¢æ°ã¯å¿åã®ãŽã«ãŒãã³ãäœæãããã£ã³ãã«ãè¿ããŸãã
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:", err) } } else { fmt.Println("error:", nearest) } file.Close() } } c <- newimage.SubImage(newimage.Rect) }() return c }
ããžãã¯ã¯ãå ã®Webã¢ããªã±ãŒã·ã§ã³ãšãŸã£ããåãã§ãã cuté¢æ°ã§ãã£ãã«ãäœæããèšç®çµæããã®ãã£ãã«ã«éä¿¡ããå¿åãŽã«ãŒãã³ã宣èšããŸãã ãã®åŸããã®ãã£ã³ãã«ãè¿ããŸãã ãããã£ãŠããã£ãã«ã¯ããã«ã¢ã¶ã€ã¯ãã³ãã©ãŒé¢æ°ã«è¿ããããã©ãã¢ã¶ã€ã¯ã®ã»ã°ã¡ã³ãã¯ãå®å šã«åŠçããããšããã«ãã®ãã£ãã«ã«éä¿¡ãããŸãã
å ã®ç»åã4ã€ã®éšåã«åå²ããããããåå¥ã«ãã©ãã¢ã¶ã€ã¯ã«å€æããŸããã äœåãçµã¿ç«ãŠãŠ1ã€ã®åçã«æ»ããšããããŸããã
func combine(r image.Rectangle, c1, c2, c3, c4 <-chan image.Image) <-chan string { c := make(chan string) // go func() { var wg sync.WaitGroup img := 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(img, s1.Bounds(), s1, image.Point{r.Min.X, r.Min.Y}) case s2, ok2 = <-c2: go copy(img, s2.Bounds(), s2, image.Point{r.Max.X / 2, r.Min.Y}) case s3, ok3 = <-c3: go copy(img, s3.Bounds(), s3, image.Point{r.Min.X, r.Max.Y/2}) case s4, ok4 = <-c4: go copy(img, s4.Bounds(), s4, image.Point{r.Max.X / 2, r.Max.Y / 2}) } if (ok1 && ok2 && ok3 && ok4) { break } } // wg.Wait() buf2 := new(bytes.Buffer) jpeg.Encode(buf2, newimage, nil) c <- base64.StdEncoding.EncodeToString(buf2.Bytes()) }() return c }
ã«ããæ©èœãšåæ§ã«ãç»åæ¥ç¶ã®ã¡ã€ã³ããžãã¯ã¯ãŽã«ãŒãã³ã«ããããã£ãã«ã¯ããŒã¿ãåä¿¡ããããã ãã«äœæãããŸãã
å¿åãŽã«ãŒãã³ã§ã¯ã ã³ããŒå€æ°ãå²ãåœãŠãããå¥ã®å¿åé¢æ°ãäœæãããŸãã ãã®é¢æ°ã¯ããã©ãã¢ã¶ã€ã¯ã®ã»ã°ã¡ã³ããæçµçãªã¢ã¶ã€ã¯ã«ã³ããŒããŸãã åŸã§å¥ã®ãŽã«ãŒãã³ãšããŠèµ·åãããããããã€å®è¡ãããããç¥ãããšã¯ã§ããŸããã ãã®åé¡ã解決ããã³ããŒæ©èœã®å®è¡ãåæããã«ã¯ã WaitGroupã䜿çšããŸã ã WaitGroupã¿ã€ãã®wgå€æ°ãäœæãã Addã¡ãœããã䜿çšããŠãã«ãŠã³ã¿ãŒã4ã«èšå®ããŸãã ã³ããŒé¢æ°ãå®è¡ããããã³ã«ã Doneã¡ãœãããåŒã³åºãããã«ãŠã³ã¿ãŒã1æžå°ããŸããWaitã¡ãœããã¯ãç»åã®ãšã³ã³ãŒãåã«å³å¯ã«åŒã³åºãããã¢ã¶ã€ã¯ã®4ã€ã®éšåãã¹ãŠã®çæãä¿èšŒããŸãå šäœåãååŸããŸãã
çµåé¢æ°ã¯ãã¢ã¶ã€ã¯ã®ã»ã°ã¡ã³ããå«ãã«ããé¢æ°ãã4ã€ã®ãã£ãã«ãåãå ¥ããããšãæãåºããŠãã ããã å®éãã»ã°ã¡ã³ãããã£ãã«ã«ãã€è¡šç€ºããããã¯æ確ã§ã¯ãããŸããã ãããã®ã»ã°ã¡ã³ããé çªã«ååŸããããšããããšãã§ããŸãããããã¯ç«¶äºçãªã¢ãããŒãã®ããã«ã¯èŠããŸããã ãã ããselectã¹ããŒãã¡ã³ãã䜿çšããŠã»ã°ã¡ã³ããåä¿¡ãããããã«åŠçãéå§ããããšæããŸãã
var s1, s2, s3, s4 image.Image var ok1, ok2, ok3, ok4 bool for { select { case s1, ok1 = <-c1: go copy(img, s1.Bounds(), s1, image.Point{r.Min.X, r.Min.Y}) case s2, ok2 = <-c2: go copy(img, s2.Bounds(), s2, image.Point{r.Max.X / 2, r.Min.Y}) case s3, ok3 = <-c3: go copy(img, s3.Bounds(), s3, image.Point{r.Min.X, r.Max.Y / 2}) case s4, ok4 = <-c4: go copy(img, s4.Bounds(), s4, image.Point{r.Max.X / 2, r.Max.Y / 2}) } if (ok1 && ok2 && ok3 && ok4) { break } }
ããã¯ç¡éã«ãŒãã§ãããåå埩ã§æ¢è£œã®ããŒã¿ãå«ãã±ãŒã¹ãéžæããããšããŸãïŒããŒã¿ãåæã«è€æ°ã®ãã£ãã«ã«ããå ŽåãGoã¯èª€ã£ãŠ1ã€ã®ã±ãŒã¹ãéžæããŠå®è¡ããŸãïŒã ãã£ã³ãã«ã®image.Imageãåãåã£ããã ã³ããŒæ©èœã®ãŽã«ãŒãã³ãå®è¡ããŸãã ãã£ã³ãã«ã«ã¯ããã€ãã®å€ãããããšã«æ³šæããŠãã ããã 2çªç®ã®å€ïŒok1ãok2ãok3ããŸãã¯ok4ïŒã¯ããã£ãã«ããããŒã¿ãåä¿¡ãããšããäºå®ã瀺ããŠããŸãã 4ã€ã®ãã£ãã«ãã¹ãŠããããŒã¿ãåä¿¡ãããšãç¡éãµã€ã¯ã«ãäžæãããŸãã
次ã«ã以åã«äœ¿çšãããWaitGroupã¿ã€ããèŠããŠãããŠãã ããã çµåæ©èœã¯ããã©ãã¢ã¶ã€ã¯ã®çµæã®éšåãå¥ã ã®ãŽã«ãŒãã³ã«çµåããŸããããããã¯åæã«çµäºããªãå ŽåããããŸãã ãããã£ãŠãWaitGroupã¿ã€ãã®Waitã¡ãœããã¯ããã¹ãŠã®éšåãäžç·ã«çµã¿ç«ãŠããããŸã§ãã€ã¡ãŒãžã®ãšã³ã³ãŒãããããã¯ããŸãã
åãç»åãšã¢ã¶ã€ã¯çæã®çµæã®ã¹ã¯ãªãŒã³ã·ã§ããïŒ

競äºåã®ããWebã¢ããªã±ãŒã·ã§ã³ã¢ã¶ã€ã¯
éãç®ã¯ãçæããããã©ãã¢ã¶ã€ã¯ã®éãã«æ°ä»ãããšãã§ããŸãã æçµçãªã¢ã¶ã€ã¯ã¯4ã€ã®éšåããæ§æãããã¢ã«ãŽãªãºã ã¯ç²ããšããžãåãããŸããã ãã ããããã©ãŒãã³ã¹ã®éãã¯æããã§ããããŒã¹ã¢ããªã±ãŒã·ã§ã³ã¯2.25ç§ã§ã¢ã¶ã€ã¯ãçæãã競åå®è£ ã¯646ããªç§ã§4åéãåãããšãè¡ããŸãã
泚ææ·±ãèªè ã¯ãäž¡æ¹ã®Webã¢ããªã±ãŒã·ã§ã³ã1ã€ã®ããã»ããµã³ã¢ã§ã®ã¿å®è¡ãããããšã«æ°ä»ãã§ãããã Rob Pikeã圌ã®èšäºã 競äºåã¯äžŠåæ§ã§ã¯ãªã ãã§æžããŠããããã«ãããã¯åçŽãªã¢ã«ãŽãªãºã ãæ¡çšãã䞊ååŒã³åºããªãã§è»œéã¹ã¬ããã«åå²ããæ¹æ³ã§ãã ç¬ç«ããŠå®è¡ããããšããäºå®ã«ããããããããŽã«ãŒãã³ã¯ã©ãã䞊è¡ããŠå®è¡ãããŸããïŒçµå±ã䜿çšãããCPUã¯1ã€ã ãã§ãïŒã
ãã¡ãããæåŸã®ã¹ããããèžãŸãªãããšã¯æ®é ·ã§ããããã¯ãããããã¹ãŠãããã»ããµãŒã®ããã€ãã®ã³ã¢ã§å®è¡ããæ¹æ³ã瀺ããŸãã ãããè¡ãã«ã¯ã å®è¡æã«GOMAXPROCSãããã»ããµã®ã³ã¢ã®æ°ã«èšå®ããã ãã§ãã main.goãã¡ã€ã«ã«å€æŽãå ¥åããå¿ èŠããããŸãã å€æŽãå ããåã«ã ã©ã³ã¿ã€ã ããã±ãŒãžãã€ã³ããŒãããããšãå¿ããªãã§ãã ããã
ïŒæ³šã¬ãŒã³-ããŒãžã§ã³Go 1.5以éã§ã¯é¢ä¿ãããŸãããäºå®ãGOMAXPROCSã®ããã©ã«ãå€ã¯æ倧1.5ã§ãããèè ãèšãããã«ã1.5ãããããã©ã«ãã®GOMAXPROCSã¯ã³ã¢ã®æ°ã«çãããªããŸãïŒ
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: "127.0.0.1:8080", Handler: mux, } TILESDB = tilesDB() fmt.Println("Mosaic server started.") server.ListenAndServe() }
ç«ã®åçãå床ã³ã³ãã€ã«ããŠã¢ããããŒãããŸããã

8 CPUã§å®è¡ããã競äºåã®ããWebã¢ããªã±ãŒã·ã§ã³ã¢ã¶ã€ã¯
ã芧ã®ãšãããé床ã¯646ããªç§ãã216ããªç§ã«3åã«å¢å ããŠããŸãïŒ ãã®æéã2.25ç§ã§ã¢ã¶ã€ã¯ãçæãããªãªãžãã«ã®Webã¢ããªã±ãŒã·ã§ã³ãšæ¯èŒãããšãçç£æ§ã¯10åã«ãªããŸããïŒ ããã¯å®éã®æ¯èŒã§ãã æåã®ã¢ããªã±ãŒã·ã§ã³ã¯8ã³ã¢ã§å®è¡ããŸããã§ããããå®è¡ããŠãã競åã䜿çšããªãã£ããããããã©ãŒãã³ã¹ã®åäžã¯èŠãããŸããã
ãŸãããã©ãã¢ã¶ã€ã¯çæã¢ã«ãŽãªãºã ã®ã·ã³ã°ã«ã¹ã¬ããããã³ç«¶åå®è£ ã«åãã¢ã«ãŽãªãºã ã䜿çšããããšã«æ³šæããããšãèå³æ·±ãã§ãã å®éãmosaic.goãã¡ã€ã«ã«ã¯å€æŽãå ããŠããŸããã å¯äžã®éãã¯åæå®è¡æ§ã§ãããããã¯ãããã©ãã»ã©åŒ·åãã蚌æããŠããŸãã