mirror of
				https://github.com/Dadido3/noita-mapcap.git
				synced 2025-11-04 07:19:34 +00:00 
			
		
		
		
	Update stitching program
- Divide workload into chunks - Add multithreading - Add console messages - Add progress bar - Unload tile images after some time - Fix median filter
This commit is contained in:
		
							parent
							
								
									a228b00815
								
							
						
					
					
						commit
						b11ba98db7
					
				
							
								
								
									
										5
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							@ -1,8 +1,11 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
    "cSpell.words": [
 | 
					    "cSpell.words": [
 | 
				
			||||||
        "Vogel",
 | 
					        "Vogel",
 | 
				
			||||||
 | 
					        "gridify",
 | 
				
			||||||
        "hacky",
 | 
					        "hacky",
 | 
				
			||||||
        "kbinani",
 | 
					        "kbinani",
 | 
				
			||||||
        "noita"
 | 
					        "mapcap",
 | 
				
			||||||
 | 
					        "noita",
 | 
				
			||||||
 | 
					        "schollz"
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -10,19 +10,26 @@ import (
 | 
				
			|||||||
	"image"
 | 
						"image"
 | 
				
			||||||
	_ "image/png"
 | 
						_ "image/png"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
 | 
						"sync"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type imageTile struct {
 | 
					type imageTile struct {
 | 
				
			||||||
	fileName string
 | 
						fileName string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	offset image.Point // Correction offset of the image, so that it aligns pixel perfect with other images. Determined by image matching.
 | 
						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.
 | 
						image      image.Image // Either a rectangle or an RGBA image. The bounds of this image are determined by the filename.
 | 
				
			||||||
 | 
						imageMutex *sync.Mutex
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (it *imageTile) loadImage() error {
 | 
					func (it *imageTile) GetImage() (*image.RGBA, error) {
 | 
				
			||||||
 | 
						it.imageMutex.Lock()
 | 
				
			||||||
 | 
						defer it.imageMutex.Unlock() // TODO: Use RWMutex
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Check if the image is already loaded
 | 
						// Check if the image is already loaded
 | 
				
			||||||
	if _, ok := it.image.(*image.RGBA); ok {
 | 
						if img, ok := it.image.(*image.RGBA); ok {
 | 
				
			||||||
		return nil
 | 
							return img, nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Store rectangle of the old image
 | 
						// Store rectangle of the old image
 | 
				
			||||||
@ -30,18 +37,18 @@ func (it *imageTile) loadImage() error {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	file, err := os.Open(it.fileName)
 | 
						file, err := os.Open(it.fileName)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return &image.RGBA{}, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	defer file.Close()
 | 
						defer file.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	img, _, err := image.Decode(file)
 | 
						img, _, err := image.Decode(file)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return &image.RGBA{}, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	imgRGBA, ok := img.(*image.RGBA)
 | 
						imgRGBA, ok := img.(*image.RGBA)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return fmt.Errorf("Expected an RGBA image, got %T instead", img)
 | 
							return &image.RGBA{}, fmt.Errorf("Expected an RGBA image, got %T instead", img)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Restore the position of the image rectangle
 | 
						// Restore the position of the image rectangle
 | 
				
			||||||
@ -49,11 +56,30 @@ func (it *imageTile) loadImage() error {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	it.image = imgRGBA
 | 
						it.image = imgRGBA
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return nil
 | 
						// Free the image after some time
 | 
				
			||||||
 | 
						go func() {
 | 
				
			||||||
 | 
							time.Sleep(5 * time.Second)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							it.imageMutex.Lock()
 | 
				
			||||||
 | 
							defer it.imageMutex.Unlock()
 | 
				
			||||||
 | 
							it.image = it.image.Bounds()
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return imgRGBA, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (it *imageTile) unloadImage() {
 | 
					func (it *imageTile) OffsetBounds() image.Rectangle {
 | 
				
			||||||
	it.image = it.image.Bounds()
 | 
						it.imageMutex.Lock()
 | 
				
			||||||
 | 
						defer it.imageMutex.Unlock() // TODO: Use RWMutex
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return it.image.Bounds().Add(it.offset)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (it *imageTile) Bounds() image.Rectangle {
 | 
				
			||||||
 | 
						it.imageMutex.Lock()
 | 
				
			||||||
 | 
						defer it.imageMutex.Unlock() // TODO: Use RWMutex
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return it.image.Bounds()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (it *imageTile) String() string {
 | 
					func (it *imageTile) String() string {
 | 
				
			||||||
 | 
				
			|||||||
@ -14,8 +14,12 @@ import (
 | 
				
			|||||||
	"math/rand"
 | 
						"math/rand"
 | 
				
			||||||
	"path/filepath"
 | 
						"path/filepath"
 | 
				
			||||||
	"regexp"
 | 
						"regexp"
 | 
				
			||||||
 | 
						"runtime"
 | 
				
			||||||
	"sort"
 | 
						"sort"
 | 
				
			||||||
	"strconv"
 | 
						"strconv"
 | 
				
			||||||
 | 
						"sync"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/schollz/progressbar/v2"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const tileAlignmentSearchRadius = 5
 | 
					const tileAlignmentSearchRadius = 5
 | 
				
			||||||
@ -64,25 +68,25 @@ func loadImages(path string) ([]imageTile, error) {
 | 
				
			|||||||
		imageTiles = append(imageTiles, imageTile{
 | 
							imageTiles = append(imageTiles, imageTile{
 | 
				
			||||||
			fileName:   file,
 | 
								fileName:   file,
 | 
				
			||||||
			image:      image.Rect(x, y, x+width, y+height),
 | 
								image:      image.Rect(x, y, x+width, y+height),
 | 
				
			||||||
 | 
								imageMutex: &sync.Mutex{},
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return imageTiles, nil
 | 
						return imageTiles, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// alignTilePair returns the pixel delta for the first tile, so that it aligns perfectly with the second.
 | 
					// AlignTilePair returns the pixel delta for the first tile, so that it aligns perfectly with the second.
 | 
				
			||||||
// This function will load images if needed.
 | 
					// This function will load images if needed.
 | 
				
			||||||
func alignTilePair(tileA, tileB *imageTile, searchRadius int) (image.Point, error) {
 | 
					func AlignTilePair(tileA, tileB *imageTile, searchRadius int) (image.Point, error) {
 | 
				
			||||||
	if err := tileA.loadImage(); err != nil {
 | 
						imgA, err := tileA.GetImage()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
		return image.Point{}, err
 | 
							return image.Point{}, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if err := tileB.loadImage(); err != nil {
 | 
						imgB, err := tileB.GetImage()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
		return image.Point{}, err
 | 
							return image.Point{}, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Type assertion.
 | 
					 | 
				
			||||||
	imgA, imgB := *tileA.image.(*image.RGBA), *tileB.image.(*image.RGBA)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	bestPoint := image.Point{}
 | 
						bestPoint := image.Point{}
 | 
				
			||||||
	bestValue := math.Inf(1)
 | 
						bestValue := math.Inf(1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -90,7 +94,7 @@ func alignTilePair(tileA, tileB *imageTile, searchRadius int) (image.Point, erro
 | 
				
			|||||||
		for x := -searchRadius; x <= searchRadius; x++ {
 | 
							for x := -searchRadius; x <= searchRadius; x++ {
 | 
				
			||||||
			point := image.Point{x, y} // Offset of the first image.
 | 
								point := image.Point{x, y} // Offset of the first image.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			value := getImageDifferenceValue(&imgA, &imgB, point)
 | 
								value := getImageDifferenceValue(imgA, imgB, point)
 | 
				
			||||||
			if bestValue > value {
 | 
								if bestValue > value {
 | 
				
			||||||
				bestValue, bestPoint = value, point
 | 
									bestValue, bestPoint = value, point
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
@ -100,7 +104,7 @@ func alignTilePair(tileA, tileB *imageTile, searchRadius int) (image.Point, erro
 | 
				
			|||||||
	return bestPoint, nil
 | 
						return bestPoint, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (tp tilePairs) alignTiles(tiles []*imageTile) error {
 | 
					func (tp tilePairs) AlignTiles(tiles []*imageTile) error {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	n := len(tiles)
 | 
						n := len(tiles)
 | 
				
			||||||
	maxOperations, operations := (n-1)*(n)/2, 0
 | 
						maxOperations, operations := (n-1)*(n)/2, 0
 | 
				
			||||||
@ -113,7 +117,7 @@ func (tp tilePairs) alignTiles(tiles []*imageTile) error {
 | 
				
			|||||||
			_, ok := tp[tileAlignmentKeys{tileA, tileB}]
 | 
								_, ok := tp[tileAlignmentKeys{tileA, tileB}]
 | 
				
			||||||
			if !ok {
 | 
								if !ok {
 | 
				
			||||||
				// Entry doesn't exist yet. Determine tile pair alignment.
 | 
									// Entry doesn't exist yet. Determine tile pair alignment.
 | 
				
			||||||
				offset, err := alignTilePair(tileA, tileB, tileAlignmentSearchRadius)
 | 
									offset, err := AlignTilePair(tileA, tileB, tileAlignmentSearchRadius)
 | 
				
			||||||
				if err != nil {
 | 
									if err != nil {
 | 
				
			||||||
					return fmt.Errorf("Failed to align tile pair %v %v: %w", tileA, tileB, err)
 | 
										return fmt.Errorf("Failed to align tile pair %v %v: %w", tileA, tileB, err)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
@ -200,13 +204,13 @@ func (tp tilePairs) alignTiles(tiles []*imageTile) error {
 | 
				
			|||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (tp tilePairs) stitch(tiles []imageTile, destImage *image.RGBA) error {
 | 
					func (tp tilePairs) Stitch(tiles []imageTile, destImage *image.RGBA) error {
 | 
				
			||||||
	intersectTiles := []*imageTile{}
 | 
						intersectTiles := []*imageTile{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Get only the tiles that intersect with the destination image bounds.
 | 
						// Get only the tiles that intersect with the destination image bounds.
 | 
				
			||||||
	// Ignore alignment here, doesn't matter if an image overlaps a few pixels anyways.
 | 
						// Ignore alignment here, doesn't matter if an image overlaps a few pixels anyways.
 | 
				
			||||||
	for i, tile := range tiles {
 | 
						for i, tile := range tiles {
 | 
				
			||||||
		if tile.image.Bounds().Add(tile.offset).Overlaps(destImage.Bounds()) {
 | 
							if tile.OffsetBounds().Overlaps(destImage.Bounds()) {
 | 
				
			||||||
			intersectTiles = append(intersectTiles, &tiles[i])
 | 
								intersectTiles = append(intersectTiles, &tiles[i])
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -225,44 +229,79 @@ func (tp tilePairs) stitch(tiles []imageTile, destImage *image.RGBA) error {
 | 
				
			|||||||
		draw.Draw(destImage, destImage.Bounds(), intersectTile.image, destImage.Bounds().Min, draw.Over)
 | 
							draw.Draw(destImage, destImage.Bounds(), intersectTile.image, destImage.Bounds().Min, draw.Over)
 | 
				
			||||||
	}*/
 | 
						}*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	drawMedianBlended(intersectTiles, destImage)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/*for _, intersectTile := range intersectTiles {
 | 
						/*for _, intersectTile := range intersectTiles {
 | 
				
			||||||
		drawLabel(destImage, intersectTile.image.Bounds().Min.X, intersectTile.image.Bounds().Min.Y, fmt.Sprintf("%v", intersectTile.fileName))
 | 
							drawLabel(destImage, intersectTile.image.Bounds().Min.X, intersectTile.image.Bounds().Min.Y, fmt.Sprintf("%v", intersectTile.fileName))
 | 
				
			||||||
	}*/
 | 
						}*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return nil
 | 
						return drawMedianBlended(intersectTiles, destImage)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func drawMedianBlended(tiles []*imageTile, destImage *image.RGBA) {
 | 
					// 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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						bar := progressbar.New(len(workloads))
 | 
				
			||||||
 | 
						bar.RenderBlank()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Start worker threads
 | 
				
			||||||
 | 
						wc := make(chan image.Rectangle)
 | 
				
			||||||
 | 
						wg := sync.WaitGroup{}
 | 
				
			||||||
 | 
						for i := 0; i < runtime.NumCPU(); i++ {
 | 
				
			||||||
 | 
							wg.Add(1)
 | 
				
			||||||
 | 
							go func() {
 | 
				
			||||||
 | 
								defer wg.Done()
 | 
				
			||||||
 | 
								for workload := range wc {
 | 
				
			||||||
 | 
									if err := tp.Stitch(tiles, destImage.SubImage(workload).(*image.RGBA)); err != nil {
 | 
				
			||||||
 | 
										errResult = err // This will not stop execution, but at least one of any errors is returned.
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									bar.Add(1)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Push workload to worker threads
 | 
				
			||||||
 | 
						for _, workload := range workloads {
 | 
				
			||||||
 | 
							wc <- workload
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Wait until all worker threads are done
 | 
				
			||||||
 | 
						close(wc)
 | 
				
			||||||
 | 
						wg.Wait()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Newline because of the progress bar
 | 
				
			||||||
 | 
						fmt.Println("")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func drawMedianBlended(tiles []*imageTile, destImage *image.RGBA) error {
 | 
				
			||||||
	bounds := destImage.Bounds()
 | 
						bounds := destImage.Bounds()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Make sure images are loaded.
 | 
					 | 
				
			||||||
	for _, tile := range tiles {
 | 
					 | 
				
			||||||
		tile.loadImage()
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	for iy := bounds.Min.Y; iy < bounds.Max.Y; iy++ {
 | 
						for iy := bounds.Min.Y; iy < bounds.Max.Y; iy++ {
 | 
				
			||||||
		for ix := bounds.Min.X; ix < bounds.Max.X; ix++ {
 | 
							for ix := bounds.Min.X; ix < bounds.Max.X; ix++ {
 | 
				
			||||||
			//colList := []color.RGBA{}
 | 
								rList, gList, bList := []int16{}, []int16{}, []int16{}
 | 
				
			||||||
			rList, gList, bList := []int{}, []int{}, []int{}
 | 
					 | 
				
			||||||
			point := image.Point{ix, iy}
 | 
								point := image.Point{ix, iy}
 | 
				
			||||||
 | 
								found := false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Iterate through all tiles, and create a list of colors.
 | 
								// Iterate through all tiles, and create a list of colors.
 | 
				
			||||||
			for _, tile := range tiles {
 | 
								for _, tile := range tiles {
 | 
				
			||||||
				tilePoint := point.Sub(tile.offset)
 | 
									tilePoint := point.Sub(tile.offset)
 | 
				
			||||||
				imageRGBA, ok := tile.image.(*image.RGBA)
 | 
									imageRGBA, err := tile.GetImage() // TODO: Optimize, as it's slow to get tiles and images every pixel
 | 
				
			||||||
				if ok && tilePoint.In(imageRGBA.Bounds()) {
 | 
									if err != nil {
 | 
				
			||||||
 | 
										return fmt.Errorf("Couldn't load image: %w", err)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if tilePoint.In(imageRGBA.Bounds().Inset(4)) { // Reduce image bounds by 4 pixels on each side, because otherwise there will be artifacts.
 | 
				
			||||||
					col := imageRGBA.RGBAAt(tilePoint.X, tilePoint.Y)
 | 
										col := imageRGBA.RGBAAt(tilePoint.X, tilePoint.Y)
 | 
				
			||||||
					//colList = append(colList, col)
 | 
										rList, gList, bList = append(rList, int16(col.R)), append(gList, int16(col.G)), append(bList, int16(col.B))
 | 
				
			||||||
					rList, gList, bList = append(rList, int(col.R)), append(gList, int(col.G)), append(bList, int(col.B))
 | 
										found = true
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Sort color list.
 | 
								// If there were no tiles to get data from, ignore the pixel.
 | 
				
			||||||
			/*sort.Slice(colList, func(i, j int) bool {
 | 
								if !found {
 | 
				
			||||||
				return rgbToHSV(colList[i]) < rgbToHSV(colList[j])
 | 
									continue
 | 
				
			||||||
			})*/
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Sort rList.
 | 
								// Sort rList.
 | 
				
			||||||
			sort.Slice(rList, func(i, j int) bool {
 | 
								sort.Slice(rList, func(i, j int) bool {
 | 
				
			||||||
@ -279,36 +318,26 @@ func drawMedianBlended(tiles []*imageTile, destImage *image.RGBA) {
 | 
				
			|||||||
				return bList[i] < bList[j]
 | 
									return bList[i] < bList[j]
 | 
				
			||||||
			})
 | 
								})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			//var col color.RGBA
 | 
								// Take the middle element of each color.
 | 
				
			||||||
 | 
					 | 
				
			||||||
			/*if len(colList)%2 == 0 {
 | 
					 | 
				
			||||||
				// Even
 | 
					 | 
				
			||||||
				a, b := colList[len(colList)/2-1], colList[len(colList)/2]
 | 
					 | 
				
			||||||
				col = color.RGBA{uint8((uint16(a.R) + uint16(b.R)) / 2), uint8((uint16(a.G) + uint16(b.G)) / 2), uint8((uint16(a.B) + uint16(b.B)) / 2), uint8((uint16(a.A) + uint16(b.A)) / 2)}
 | 
					 | 
				
			||||||
			} else if len(colList) > 0 {
 | 
					 | 
				
			||||||
				// Odd
 | 
					 | 
				
			||||||
				col = colList[(len(colList)-1)/2]
 | 
					 | 
				
			||||||
			}*/
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			var r, g, b uint8
 | 
								var r, g, b uint8
 | 
				
			||||||
			if len(rList)%2 == 0 {
 | 
								if len(rList)%2 == 0 {
 | 
				
			||||||
				// Even
 | 
									// Even
 | 
				
			||||||
				r = uint8((rList[len(rList)/2-1] + rList[len(rList)/2]) / 2)
 | 
									r = uint8((rList[len(rList)/2-1] + rList[len(rList)/2]) / 2)
 | 
				
			||||||
			} else if len(rList) > 0 {
 | 
								} else {
 | 
				
			||||||
				// Odd
 | 
									// Odd
 | 
				
			||||||
				r = uint8(rList[(len(rList)-1)/2])
 | 
									r = uint8(rList[(len(rList)-1)/2])
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			if len(gList)%2 == 0 {
 | 
								if len(gList)%2 == 0 {
 | 
				
			||||||
				// Even
 | 
									// Even
 | 
				
			||||||
				g = uint8((gList[len(gList)/2-1] + gList[len(gList)/2]) / 2)
 | 
									g = uint8((gList[len(gList)/2-1] + gList[len(gList)/2]) / 2)
 | 
				
			||||||
			} else if len(gList) > 0 {
 | 
								} else {
 | 
				
			||||||
				// Odd
 | 
									// Odd
 | 
				
			||||||
				g = uint8(gList[(len(gList)-1)/2])
 | 
									g = uint8(gList[(len(gList)-1)/2])
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			if len(bList)%2 == 0 {
 | 
								if len(bList)%2 == 0 {
 | 
				
			||||||
				// Even
 | 
									// Even
 | 
				
			||||||
				b = uint8((bList[len(bList)/2-1] + bList[len(bList)/2]) / 2)
 | 
									b = uint8((bList[len(bList)/2-1] + bList[len(bList)/2]) / 2)
 | 
				
			||||||
			} else if len(bList) > 0 {
 | 
								} else {
 | 
				
			||||||
				// Odd
 | 
									// Odd
 | 
				
			||||||
				b = uint8(bList[(len(bList)-1)/2])
 | 
									b = uint8(bList[(len(bList)-1)/2])
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
@ -316,4 +345,6 @@ func drawMedianBlended(tiles []*imageTile, destImage *image.RGBA) {
 | 
				
			|||||||
			destImage.SetRGBA(ix, iy, color.RGBA{r, g, b, 255})
 | 
								destImage.SetRGBA(ix, iy, color.RGBA{r, g, b, 255})
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -16,10 +16,12 @@ import (
 | 
				
			|||||||
var inputPath = filepath.Join(".", "..", "..", "output")
 | 
					var inputPath = filepath.Join(".", "..", "..", "output")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func main() {
 | 
					func main() {
 | 
				
			||||||
 | 
						log.Printf("Starting to read tile information at \"%v\"", inputPath)
 | 
				
			||||||
	tiles, err := loadImages(inputPath)
 | 
						tiles, err := loadImages(inputPath)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Panic(err)
 | 
							log.Panic(err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						log.Printf("Got %v tiles", len(tiles))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/*f, err := os.Create("cpu.prof")
 | 
						/*f, err := os.Create("cpu.prof")
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
@ -31,13 +33,18 @@ func main() {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	defer pprof.StopCPUProfile()*/
 | 
						defer pprof.StopCPUProfile()*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	outputImage := image.NewRGBA(image.Rect(-4000, -4000, 8000, 8000))
 | 
						outputRect := image.Rect(-10000, -10000, 10000, 10000)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						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)
 | 
						tp := make(tilePairs)
 | 
				
			||||||
	if err := tp.stitch(tiles, outputImage); err != nil {
 | 
						if err := tp.StitchGrid(tiles, outputImage, 256); err != nil {
 | 
				
			||||||
		log.Panic(err)
 | 
							log.Panic(err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						log.Printf("Creating output file \"%v\"", "output.png")
 | 
				
			||||||
	f, err := os.Create("output.png")
 | 
						f, err := os.Create("output.png")
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Panic(err)
 | 
							log.Panic(err)
 | 
				
			||||||
@ -51,4 +58,6 @@ func main() {
 | 
				
			|||||||
	if err := f.Close(); err != nil {
 | 
						if err := f.Close(); err != nil {
 | 
				
			||||||
		log.Panic(err)
 | 
							log.Panic(err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						log.Printf("Created output file \"%v\"", "output.png")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -62,9 +62,22 @@ func getImageDifferenceValue(a, b *image.RGBA, offsetA image.Point) float64 {
 | 
				
			|||||||
	return float64(value) / float64(intersectionWidth*intersectionHeight)
 | 
						return float64(value) / float64(intersectionWidth*intersectionHeight)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func gridifyRectangle(rect image.Rectangle, gridSize int) (result []image.Rectangle) {
 | 
				
			||||||
 | 
						for y := divideFloor(rect.Min.Y, gridSize); y < divideCeil(rect.Max.Y, gridSize); y++ {
 | 
				
			||||||
 | 
							for x := divideFloor(rect.Min.X, gridSize); x < divideCeil(rect.Max.X, gridSize); x++ {
 | 
				
			||||||
 | 
								tempRect := image.Rect(x*gridSize, y*gridSize, (x+1)*gridSize, (y+1)*gridSize)
 | 
				
			||||||
 | 
								if tempRect.Overlaps(rect) {
 | 
				
			||||||
 | 
									result = append(result, tempRect)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func drawLabel(img *image.RGBA, x, y int, label string) {
 | 
					func drawLabel(img *image.RGBA, x, y int, label string) {
 | 
				
			||||||
	col := color.RGBA{200, 100, 0, 255}
 | 
						col := color.RGBA{200, 100, 0, 255}
 | 
				
			||||||
	point := fixed.Point26_6{fixed.Int26_6(x * 64), fixed.Int26_6(y * 64)}
 | 
						point := fixed.Point26_6{X: fixed.Int26_6(x * 64), Y: fixed.Int26_6(y * 64)}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	d := &font.Drawer{
 | 
						d := &font.Drawer{
 | 
				
			||||||
		Dst:  img,
 | 
							Dst:  img,
 | 
				
			||||||
@ -91,3 +104,25 @@ func pointAbs(p image.Point) image.Point {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	return p
 | 
						return p
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Integer division that rounds to the next integer towards negative infinity
 | 
				
			||||||
 | 
					func divideFloor(a, b int) int {
 | 
				
			||||||
 | 
						temp := a / b
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if ((a ^ b) < 0) && (a%b != 0) {
 | 
				
			||||||
 | 
							return temp - 1
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return temp
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Integer division that rounds to the next integer towards positive infinity
 | 
				
			||||||
 | 
					func divideCeil(a, b int) int {
 | 
				
			||||||
 | 
						temp := a / b
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if ((a ^ b) >= 0) && (a%b != 0) {
 | 
				
			||||||
 | 
							return temp + 1
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return temp
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										1
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								go.mod
									
									
									
									
									
								
							@ -7,5 +7,6 @@ require (
 | 
				
			|||||||
	github.com/gen2brain/shm v0.0.0-20180314170312-6c18ff7f8b90 // indirect
 | 
						github.com/gen2brain/shm v0.0.0-20180314170312-6c18ff7f8b90 // indirect
 | 
				
			||||||
	github.com/kbinani/screenshot v0.0.0-20190719135742-f06580e30cdc
 | 
						github.com/kbinani/screenshot v0.0.0-20190719135742-f06580e30cdc
 | 
				
			||||||
	github.com/lxn/win v0.0.0-20190919090605-24c5960b03d8 // indirect
 | 
						github.com/lxn/win v0.0.0-20190919090605-24c5960b03d8 // indirect
 | 
				
			||||||
 | 
						github.com/schollz/progressbar/v2 v2.14.0
 | 
				
			||||||
	golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8
 | 
						golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										12
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								go.sum
									
									
									
									
									
								
							@ -1,11 +1,23 @@
 | 
				
			|||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 h1:1BDTz0u9nC3//pOCMdNH+CiXJVYJh5UQNCOBG7jbELc=
 | 
					github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 h1:1BDTz0u9nC3//pOCMdNH+CiXJVYJh5UQNCOBG7jbELc=
 | 
				
			||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 | 
					github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 | 
				
			||||||
 | 
					github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
				
			||||||
 | 
					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 h1:QagTG5rauLt6pVVEhnVSrlIX4ifhVIZOwmw6x6D8TUw=
 | 
				
			||||||
github.com/gen2brain/shm v0.0.0-20180314170312-6c18ff7f8b90/go.mod h1:uF6rMu/1nvu+5DpiRLwusA6xB8zlkNoGzKn8lmYONUo=
 | 
					github.com/gen2brain/shm v0.0.0-20180314170312-6c18ff7f8b90/go.mod h1:uF6rMu/1nvu+5DpiRLwusA6xB8zlkNoGzKn8lmYONUo=
 | 
				
			||||||
github.com/kbinani/screenshot v0.0.0-20190719135742-f06580e30cdc h1:kGFotla6Dyr6a2ILeExAHlttPgJtnoP/GIw2uVN/4h4=
 | 
					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/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 h1:RVMGIuuNgrpGB7I79f6xfhGCkpN47IaEGh8VTM0p7Xc=
 | 
				
			||||||
github.com/lxn/win v0.0.0-20190919090605-24c5960b03d8/go.mod h1:ouWl4wViUNh8tPSIwxTVMuS014WakR1hqvBc2I0bMoA=
 | 
					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/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=
 | 
				
			||||||
 | 
					github.com/schollz/progressbar/v2 v2.14.0/go.mod h1:6YZjqdthH6SCZKv2rqGryrxPtfmRB/DWZxSMfCXPyD8=
 | 
				
			||||||
 | 
					github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 | 
				
			||||||
 | 
					github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
 | 
				
			||||||
 | 
					github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 | 
				
			||||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U=
 | 
					golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U=
 | 
				
			||||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 | 
					golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 | 
				
			||||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd h1:DBH9mDw0zluJT/R+nGuV3jWFWLFaHyYZWD4tOT+cjn0=
 | 
					golang.org/x/sys v0.0.0-20190904154756-749cb33beabd h1:DBH9mDw0zluJT/R+nGuV3jWFWLFaHyYZWD4tOT+cjn0=
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user