mtsatellite/cmd/mtseeder/pyramid.go
2014-09-14 21:56:41 +02:00

234 lines
4.3 KiB
Go

// 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
}