diff --git a/cmd/mtseeder/main.go b/cmd/mtseeder/main.go index 9e99c84..521a955 100644 --- a/cmd/mtseeder/main.go +++ b/cmd/mtseeder/main.go @@ -20,6 +20,7 @@ func main() { outDir string numWorkers int skipBaseLevel bool + skipPyramid bool ) flag.IntVar(&port, "port", 6379, "port to of mtredisalize server") @@ -34,8 +35,10 @@ func main() { flag.StringVar(&outDir, "o", "map", "directory with the resulting image tree") flag.IntVar(&numWorkers, "worker", 1, "number of workers") flag.IntVar(&numWorkers, "w", 1, "number of workers (shorthand)") - flag.BoolVar(&skipBaseLevel, "skip-base-level", false, "Do not generate baselevel") - flag.BoolVar(&skipBaseLevel, "sb", false, "Do not generate baselevel (shorthand)") + flag.BoolVar(&skipBaseLevel, "skip-base-level", false, "Do not generate base level tiles") + flag.BoolVar(&skipBaseLevel, "sb", false, "Do not generate base level tiles (shorthand)") + flag.BoolVar(&skipPyramid, "skip-pyramid", false, "Do not generate pyramid tiles") + flag.BoolVar(&skipPyramid, "sp", false, "Do not generate pyramid tiles (shorthand)") flag.Parse() @@ -44,7 +47,12 @@ func main() { address := fmt.Sprintf("%s:%d", host, port) if err = createBaseLevel( address, xMin, zMin, xMax, zMax, colorsFile, outDir, numWorkers); err != nil { - log.Fatalf("Creating base level failed: %s", err) + log.Fatalf("Creating base level tiles failed: %s", err) + } + } + if !skipPyramid { + if err = createPyramid(outDir, numWorkers); err != nil { + log.Fatalf("Creating pyramid tiles failed: %s", err) } } } diff --git a/cmd/mtseeder/pyramid.go b/cmd/mtseeder/pyramid.go new file mode 100644 index 0000000..c2c9066 --- /dev/null +++ b/cmd/mtseeder/pyramid.go @@ -0,0 +1,233 @@ +// Copyright 2014 by Sascha L. Teichmann +// Use of this source code is governed by the MIT license +// that can be found in the LICENSE file. + +package main + +import ( + "bufio" + "fmt" + "image" + "image/draw" + "image/png" + "io/ioutil" + "log" + "math" + "os" + "path/filepath" + "strconv" + "strings" + "sync" + + "github.com/nfnt/resize" +) + +func findMaxDir(files []os.FileInfo) (min, max int) { + min, max = math.MaxInt32, math.MinInt32 + for _, file := range files { + if !file.Mode().IsDir() { + continue + } + if x, err := strconv.Atoi(file.Name()); err == nil { + if x > max { + max = x + } + if x < min { + min = x + } + } + } + return +} + +func findMaxFile(files []os.FileInfo) (min, max int) { + min, max = math.MaxInt32, math.MinInt32 + for _, file := range files { + if !file.Mode().IsRegular() { + continue + } + name := file.Name() + name = strings.TrimSuffix(name, filepath.Ext(name)) + if x, err := strconv.Atoi(name); err == nil { + if x > max { + max = x + } + if x < min { + min = x + } + } + } + return +} + +type pyramidJob struct { + src [4]string + dst string +} + +func createParentLevel(oldDir string, jobs chan pyramidJob) (newDir string, err error) { + oldName := filepath.Base(oldDir) + + var oldLevel int + if oldLevel, err = strconv.Atoi(oldName); err != nil { + return + } + + if oldLevel <= 0 { + return + } + + var files []os.FileInfo + if files, err = ioutil.ReadDir(oldDir); err != nil { + return + } + + xMin, xMax := findMaxDir(files) + if xMax == math.MinInt32 { + return + } + + newLevel := oldLevel - 1 + + log.Printf("Generating tiles of level %d", newLevel) + + parentDir := filepath.Dir(oldDir) + newDir = filepath.Join(parentDir, strconv.Itoa(newLevel)) + + if err = os.MkdirAll(newDir, os.ModePerm); err != nil { + return + } + + for ox, nx := xMin, xMin; ox <= xMax; ox += 2 { + + ox1Dir := filepath.Join(oldDir, strconv.Itoa(ox)) + ox2Dir := filepath.Join(oldDir, strconv.Itoa(ox+1)) + + if files, err = ioutil.ReadDir(ox1Dir); err != nil { + return + } + zMin, zMax := findMaxFile(files) + if zMax == math.MinInt32 { + nx++ + continue + } + + nxDir := filepath.Join(newDir, strconv.Itoa(nx)) + if err = os.MkdirAll(nxDir, os.ModePerm); err != nil { + return + } + + for oz, nz := zMin, zMin; oz <= zMax; oz += 2 { + oz1 := fmt.Sprintf("%d.png", oz) + oz2 := fmt.Sprintf("%d.png", oz+1) + s1 := filepath.Join(ox1Dir, oz1) + s2 := filepath.Join(ox1Dir, oz2) + s3 := filepath.Join(ox2Dir, oz1) + s4 := filepath.Join(ox2Dir, oz2) + d := filepath.Join(nxDir, fmt.Sprintf("%d.png", nz)) + jobs <- pyramidJob{src: [4]string{s1, s2, s3, s4}, dst: d} + nz++ + } + + nx++ + } + return +} + +func loadImage(path string) image.Image { + var err error + var file *os.File + if file, err = os.Open(path); err != nil { + //log.Printf("WARN: %s", err) + return image.White + } + defer file.Close() + reader := bufio.NewReader(file) + var img image.Image + if img, err = png.Decode(reader); err != nil { + log.Printf("WARN: %s", err) + return image.White + } + return img +} + +func fuseTile(scratch *image.RGBA, job *pyramidJob) (err error) { + + var ofs = [4][2]int{ + {0, 0}, + {0, 256}, + {256, 0}, + {256, 256}} + + for i, path := range job.src { + + img := loadImage(path) + + draw.Draw(scratch, + image.Rect(0, 0, 256, 256), + img, image.Pt(ofs[i][0], ofs[i][1]), draw.Src) + } + + resized := resize.Resize(256, 256, scratch, resize.Bicubic) + + var outFile *os.File + if outFile, err = os.Create(job.dst); err != nil { + return + } + + out := bufio.NewWriter(outFile) + + if err = png.Encode(out, resized); err != nil { + outFile.Close() + return + } + + out.Flush() + err = outFile.Close() + + return +} + +func fuseTiles(jobs chan pyramidJob, done *sync.WaitGroup) { + defer done.Done() + scratch := image.NewRGBA(image.Rect(0, 0, 512, 512)) + + for job := range jobs { + if err := fuseTile(scratch, &job); err != nil { + log.Printf("WARN: Writing image failed: %s", err) + } + } +} + +func createPyramid(outDir string, numWorker int) (err error) { + + for oldDir := filepath.Join(outDir, baseLevelDir); oldDir != ""; { + if oldDir, err = createLevel(oldDir, numWorker); err != nil { + return + } + } + return +} + +func createLevel(oldDir string, numWorker int) (newDir string, err error) { + + jobs := make(chan pyramidJob) + + var done sync.WaitGroup + + for i := 0; i < numWorker; i++ { + done.Add(1) + go fuseTiles(jobs, &done) + } + + newDir, err = createParentLevel(oldDir, jobs) + close(jobs) + + if err != nil { + return + } + + done.Wait() + + return +}