mirror of
				https://bitbucket.org/s_l_teichmann/mtsatellite
				synced 2025-10-30 23:55:38 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			219 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			219 lines
		
	
	
		
			4.1 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 (
 | |
| 	"fmt"
 | |
| 	"image"
 | |
| 	"image/draw"
 | |
| 	"io/ioutil"
 | |
| 	"log"
 | |
| 	"math"
 | |
| 	"os"
 | |
| 	"path/filepath"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 	"sync"
 | |
| 
 | |
| 	"bitbucket.org/s_l_teichmann/mtsatellite/common"
 | |
| 
 | |
| 	"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 clip8(x int) int {
 | |
| 	switch {
 | |
| 	case x < 0:
 | |
| 		return 0
 | |
| 	case x > 256:
 | |
| 		return 256
 | |
| 	}
 | |
| 	return x
 | |
| }
 | |
| 
 | |
| func clipRect(r image.Rectangle) image.Rectangle {
 | |
| 	return image.Rectangle{
 | |
| 		Min: image.Point{X: clip8(r.Min.X), Y: clip8(r.Min.Y)},
 | |
| 		Max: image.Point{X: clip8(r.Max.X), Y: clip8(r.Max.Y)}}
 | |
| }
 | |
| 
 | |
| var dps = [4]image.Point{
 | |
| 	image.Pt(0, 256),
 | |
| 	image.Pt(0, 0),
 | |
| 	image.Pt(256, 256),
 | |
| 	image.Pt(256, 0)}
 | |
| 
 | |
| func fuseTile(scratch *image.RGBA, job *pyramidJob) error {
 | |
| 
 | |
| 	for i, path := range job.src {
 | |
| 
 | |
| 		img := common.LoadPNG(path)
 | |
| 
 | |
| 		sr := clipRect(img.Bounds())
 | |
| 		r := sr.Sub(sr.Min).Add(dps[i])
 | |
| 
 | |
| 		draw.Draw(scratch, r, img, sr.Min, draw.Src)
 | |
| 	}
 | |
| 
 | |
| 	resized := resize.Resize(256, 256, scratch, resize.Lanczos3)
 | |
| 
 | |
| 	return common.SaveAsPNG(job.dst, resized)
 | |
| }
 | |
| 
 | |
| 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
 | |
| }
 |