Skip to content

Instantly share code, notes, and snippets.

@sausheong
sausheong / .go
Created December 28, 2019 12:57
photo mosaic blog 10
// build up the tiles database
db := cloneTilesDB()
// source point for each tile, which starts with 0, 0 of each tile
sp := image.Point{0, 0}
@sausheong
sausheong / .go
Created December 28, 2019 12:59
photo mosaic blog 11
for y := bounds.Min.Y; y < bounds.Max.Y; y = y + tileSize {
for x := bounds.Min.X; x < bounds.Max.X; x = x + tileSize {
// use the top left most pixel color as the average color
r, g, b, _ := original.At(x, y).RGBA()
color := [3]float64{float64(r), float64(g), float64(b)}
// get the closest tile from the tiles DB
nearest := nearest(color, &db)
file, err := os.Open(nearest)
if err == nil {
img, _, err := image.Decode(file)
@sausheong
sausheong / .go
Created December 28, 2019 12:59
photo mosaic blog 12
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,
@sausheong
sausheong / mosaic.html
Created December 28, 2019 13:00
photo mosaic blog 13
<!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">
@sausheong
sausheong / .go
Created December 28, 2019 13:02
photo mosaic blog 14
func mosaic(w http.ResponseWriter, r *http.Request) {
t0 := time.Now()
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()
db := cloneTilesDB()
@sausheong
sausheong / .go
Created December 28, 2019 13:03
photo mosaic 15
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)
@sausheong
sausheong / .go
Created December 28, 2019 13:03
photo mosaic 16
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)
@sausheong
sausheong / .go
Created December 28, 2019 13:04
photo mosaic blog 17
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
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)
@sausheong
sausheong / .go
Created December 28, 2019 13:05
photo mosaic blog 18
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})
@sausheong
sausheong / .go
Created December 28, 2019 13:06
photo mosaic blog 19
func main() {
// this prints out the number of CPUs in my system
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)