From 91efa7b4babc85f206dbdfd974fd6a007bcbdf2d Mon Sep 17 00:00:00 2001 From: David Vogel Date: Thu, 24 Oct 2019 21:11:23 +0200 Subject: [PATCH] Stitching improvements - Add integer downscale parameter - Output information about the total bounding box - Build workload chunks in a hilbert spiral to reduce memory consumption --- .vscode/settings.json | 3 +++ README.md | 2 +- bin/stitch/imagetile.go | 10 +++++++++- bin/stitch/imagetiles.go | 19 ++++++++++++++----- bin/stitch/stitch.go | 16 +++++++++++++--- bin/stitch/util.go | 39 +++++++++++++++++++++++++++++++++++++-- go.mod | 2 ++ go.sum | 4 ++++ 8 files changed, 83 insertions(+), 12 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 9cdaefa..e9e1230 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,8 +1,11 @@ { "cSpell.words": [ + "Lanczos", "Vogel", + "executables", "gridify", "hacky", + "hilbertify", "kbinani", "mapcap", "noita", diff --git a/README.md b/README.md index d2d685a..2193466 100644 --- a/README.md +++ b/README.md @@ -39,4 +39,4 @@ If you use `noita_dev.exe`, you can enable the debug mode by pressing `F5`. Once ## License -[MIT](LICENSE) \ No newline at end of file +[MIT](LICENSE) diff --git a/bin/stitch/imagetile.go b/bin/stitch/imagetile.go index 3b2d490..8593a05 100644 --- a/bin/stitch/imagetile.go +++ b/bin/stitch/imagetile.go @@ -12,11 +12,15 @@ import ( "os" "sync" "time" + + "github.com/nfnt/resize" ) type imageTile struct { fileName string + scaleDivider int // Downscales the coordinates and images on the fly. + offset image.Point // Correction offset of the image, so that it aligns pixel perfect with other images. Determined by image matching. image image.Image // Either a rectangle or an RGBA image. The bounds of this image are determined by the filename. @@ -59,6 +63,10 @@ func (it *imageTile) GetImage() (*image.RGBA, error) { return &image.RGBA{}, err } + if it.scaleDivider > 1 { + img = resize.Resize(uint(oldRect.Dx()), uint(oldRect.Dy()), img, resize.NearestNeighbor) + } + imgRGBA, ok := img.(*image.RGBA) if !ok { return &image.RGBA{}, fmt.Errorf("Expected an RGBA image, got %T instead", img) @@ -72,7 +80,7 @@ func (it *imageTile) GetImage() (*image.RGBA, error) { // Free the image after some time go func() { for it.imageUsedFlag { - time.Sleep(5 * time.Second) + time.Sleep(1 * time.Second) it.imageUsedFlag = false } diff --git a/bin/stitch/imagetiles.go b/bin/stitch/imagetiles.go index ab7b747..943298b 100644 --- a/bin/stitch/imagetiles.go +++ b/bin/stitch/imagetiles.go @@ -37,9 +37,13 @@ type tilePairs map[tileAlignmentKeys]tileAlignment var regexFileParse = regexp.MustCompile(`^(-?\d+),(-?\d+).png$`) -func loadImages(path string) ([]imageTile, error) { +func loadImages(path string, scaleDivider int) ([]imageTile, error) { var imageTiles []imageTile + if scaleDivider < 1 { + return nil, fmt.Errorf("Invalid scale of %v", scaleDivider) + } + files, err := filepath.Glob(filepath.Join(inputPath, "*.png")) if err != nil { return nil, err @@ -66,9 +70,10 @@ func loadImages(path string) ([]imageTile, error) { } imageTiles = append(imageTiles, imageTile{ - fileName: file, - image: image.Rect(x, y, x+width, y+height), - imageMutex: &sync.RWMutex{}, + fileName: file, + scaleDivider: scaleDivider, + image: image.Rect(x/scaleDivider, y/scaleDivider, (x+width)/scaleDivider, (y+height)/scaleDivider), + imageMutex: &sync.RWMutex{}, }) } @@ -250,7 +255,11 @@ func (tp tilePairs) Stitch(tiles []imageTile, destImage *image.RGBA) error { // StitchGrid calls stitch, but divides the workload into a grid of chunks. // Additionally it runs the workload multithreaded. func (tp tilePairs) StitchGrid(tiles []imageTile, destImage *image.RGBA, gridSize int) (errResult error) { - workloads := gridifyRectangle(destImage.Bounds(), gridSize) + //workloads := gridifyRectangle(destImage.Bounds(), gridSize) + workloads, err := hilbertifyRectangle(destImage.Bounds(), gridSize) + if err != nil { + return err + } bar := progressbar.New(len(workloads)) bar.RenderBlank() diff --git a/bin/stitch/stitch.go b/bin/stitch/stitch.go index 3f691ab..7375d04 100644 --- a/bin/stitch/stitch.go +++ b/bin/stitch/stitch.go @@ -17,12 +17,22 @@ var inputPath = filepath.Join(".", "..", "..", "output") func main() { log.Printf("Starting to read tile information at \"%v\"", inputPath) - tiles, err := loadImages(inputPath) + tiles, err := loadImages(inputPath, 2) if err != nil { log.Panic(err) } log.Printf("Got %v tiles", len(tiles)) + totalBounds := image.Rectangle{} + for i, tile := range tiles { + if i == 0 { + totalBounds = tile.Bounds() + } else { + totalBounds = totalBounds.Union(tile.Bounds()) + } + } + log.Printf("Total size of the possible output space is %v", totalBounds) + /*profFile, err := os.Create("cpu.prof") if err != nil { log.Panicf("could not create CPU profile: %v", err) @@ -35,14 +45,14 @@ func main() { // TODO: Flags / Program arguments - outputRect := image.Rect(-35000, -35000, 35000, 35000) + outputRect := image.Rect(-1900*2, -1900*2, 1900*2, 1900*2) log.Printf("Creating output image with a size of %v", outputRect.Size()) outputImage := image.NewRGBA(outputRect) log.Printf("Stitching %v tiles into an image at %v", len(tiles), outputImage.Bounds()) tp := make(tilePairs) - if err := tp.StitchGrid(tiles, outputImage, 1024); err != nil { + if err := tp.StitchGrid(tiles, outputImage, 512); err != nil { log.Panic(err) } diff --git a/bin/stitch/util.go b/bin/stitch/util.go index f640247..7edd6ab 100644 --- a/bin/stitch/util.go +++ b/bin/stitch/util.go @@ -11,10 +11,13 @@ import ( "image/color" "math" "os" + "sort" "golang.org/x/image/font" "golang.org/x/image/font/basicfont" "golang.org/x/image/math/fixed" + + "github.com/google/hilbert" ) // Source: https://gist.github.com/sergiotapia/7882944 @@ -75,6 +78,31 @@ func gridifyRectangle(rect image.Rectangle, gridSize int) (result []image.Rectan return } +func hilbertifyRectangle(rect image.Rectangle, gridSize int) ([]image.Rectangle, error) { + grid := gridifyRectangle(rect, gridSize) + + gridX := divideFloor(rect.Min.X, gridSize) + gridY := divideFloor(rect.Min.Y, gridSize) + + // Size of the grid in chunks + gridWidth := divideCeil(rect.Max.X, gridSize) - divideFloor(rect.Min.X, gridSize) + gridHeight := divideCeil(rect.Max.Y, gridSize) - divideFloor(rect.Min.Y, gridSize) + + s, err := hilbert.NewHilbert(int(math.Pow(2, math.Ceil(math.Log2(math.Max(float64(gridWidth), float64(gridHeight))))))) + if err != nil { + return nil, err + } + + sort.Slice(grid, func(i, j int) bool { + // Ignore out of range errors, as they shouldn't happen. + hilbertIndexA, _ := s.MapInverse(grid[i].Min.X/gridSize-gridX, grid[i].Min.Y/gridSize-gridY) + hilbertIndexB, _ := s.MapInverse(grid[j].Min.X/gridSize-gridX, grid[j].Min.Y/gridSize-gridY) + return hilbertIndexA < hilbertIndexB + }) + + return grid, nil +} + func drawLabel(img *image.RGBA, x, y int, label string) { col := color.RGBA{200, 100, 0, 255} point := fixed.Point26_6{X: fixed.Int26_6(x * 64), Y: fixed.Int26_6(y * 64)} @@ -105,7 +133,7 @@ func pointAbs(p image.Point) image.Point { return p } -// Integer division that rounds to the next integer towards negative infinity +// Integer division that rounds to the next integer towards negative infinity. func divideFloor(a, b int) int { temp := a / b @@ -116,7 +144,7 @@ func divideFloor(a, b int) int { return temp } -// Integer division that rounds to the next integer towards positive infinity +// Integer division that rounds to the next integer towards positive infinity. func divideCeil(a, b int) int { temp := a / b @@ -126,3 +154,10 @@ func divideCeil(a, b int) int { return temp } + +func maxInt(x, y int) int { + if x > y { + return x + } + return y +} diff --git a/go.mod b/go.mod index e9e16fc..82e9b5f 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,10 @@ go 1.13 require ( github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 // indirect github.com/gen2brain/shm v0.0.0-20180314170312-6c18ff7f8b90 // indirect + github.com/google/hilbert v0.0.0-20181122061418-320f2e35a565 github.com/kbinani/screenshot v0.0.0-20190719135742-f06580e30cdc github.com/lxn/win v0.0.0-20190919090605-24c5960b03d8 // indirect + github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 github.com/schollz/progressbar/v2 v2.14.0 golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 ) diff --git a/go.sum b/go.sum index 4c3db04..93cb8be 100644 --- a/go.sum +++ b/go.sum @@ -5,12 +5,16 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gen2brain/shm v0.0.0-20180314170312-6c18ff7f8b90 h1:QagTG5rauLt6pVVEhnVSrlIX4ifhVIZOwmw6x6D8TUw= github.com/gen2brain/shm v0.0.0-20180314170312-6c18ff7f8b90/go.mod h1:uF6rMu/1nvu+5DpiRLwusA6xB8zlkNoGzKn8lmYONUo= +github.com/google/hilbert v0.0.0-20181122061418-320f2e35a565 h1:KBAlCAY6eLC44FiEwbzEbHnpVlw15iVM4ZK8QpRIp4U= +github.com/google/hilbert v0.0.0-20181122061418-320f2e35a565/go.mod h1:xn6EodFfRzV6j8NXQRPjngeHWlrpOrsZPKuuLRThU1k= github.com/kbinani/screenshot v0.0.0-20190719135742-f06580e30cdc h1:kGFotla6Dyr6a2ILeExAHlttPgJtnoP/GIw2uVN/4h4= github.com/kbinani/screenshot v0.0.0-20190719135742-f06580e30cdc/go.mod h1:f8GY5V3lRzakvEyr49P7hHRYoHtPr8zvj/7JodCoRzw= github.com/lxn/win v0.0.0-20190919090605-24c5960b03d8 h1:RVMGIuuNgrpGB7I79f6xfhGCkpN47IaEGh8VTM0p7Xc= github.com/lxn/win v0.0.0-20190919090605-24c5960b03d8/go.mod h1:ouWl4wViUNh8tPSIwxTVMuS014WakR1hqvBc2I0bMoA= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/schollz/progressbar/v2 v2.14.0 h1:vo7bdkI9E4/CIk9DnL5uVIaybLQiVtiCC2vO+u9j5IM=