mirror of
https://github.com/Dadido3/noita-mapcap.git
synced 2024-12-22 02:17:33 +00:00
Remove incomplete alignment algorithm
This commit is contained in:
parent
e1fbda1053
commit
dc42ee1eb5
@ -9,9 +9,6 @@ import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"log"
|
||||
"math"
|
||||
"math/rand"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
@ -22,19 +19,6 @@ import (
|
||||
"github.com/schollz/progressbar/v2"
|
||||
)
|
||||
|
||||
const tileAlignmentSearchRadius = 5
|
||||
|
||||
type tileAlignment struct {
|
||||
offset image.Point // Contains the offset of the tile a, so that it aligns pixel perfect with tile b
|
||||
}
|
||||
|
||||
type tileAlignmentKeys struct {
|
||||
a, b *imageTile
|
||||
}
|
||||
|
||||
// tilePairs contains image pairs and their alignment.
|
||||
type tilePairs map[tileAlignmentKeys]tileAlignment
|
||||
|
||||
var regexFileParse = regexp.MustCompile(`^(-?\d+),(-?\d+).png$`)
|
||||
|
||||
func loadImages(path string, scaleDivider int) ([]imageTile, error) {
|
||||
@ -80,136 +64,9 @@ func loadImages(path string, scaleDivider int) ([]imageTile, error) {
|
||||
return imageTiles, nil
|
||||
}
|
||||
|
||||
// AlignTilePair returns the pixel delta for the first tile, so that it aligns perfectly with the second.
|
||||
// This function will load images if needed.
|
||||
func AlignTilePair(tileA, tileB *imageTile, searchRadius int) (image.Point, error) {
|
||||
imgA, err := tileA.GetImage()
|
||||
if err != nil {
|
||||
return image.Point{}, err
|
||||
}
|
||||
imgB, err := tileB.GetImage()
|
||||
if err != nil {
|
||||
return image.Point{}, err
|
||||
}
|
||||
|
||||
bestPoint := image.Point{}
|
||||
bestValue := math.Inf(1)
|
||||
|
||||
for y := -searchRadius; y <= searchRadius; y++ {
|
||||
for x := -searchRadius; x <= searchRadius; x++ {
|
||||
point := image.Point{x, y} // Offset of the first image.
|
||||
|
||||
value := getImageDifferenceValue(imgA, imgB, point)
|
||||
if bestValue > value {
|
||||
bestValue, bestPoint = value, point
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bestPoint, nil
|
||||
}
|
||||
|
||||
func (tp tilePairs) AlignTiles(tiles []*imageTile) error {
|
||||
|
||||
n := len(tiles)
|
||||
maxOperations, operations := (n-1)*(n)/2, 0
|
||||
|
||||
// Compare all n tiles with each other. (`(n-1)*(n)/2` comparisons)
|
||||
for i, tileA := range tiles {
|
||||
for j := i + 1; j < len(tiles); j++ {
|
||||
tileB := tiles[j]
|
||||
|
||||
_, ok := tp[tileAlignmentKeys{tileA, tileB}]
|
||||
if !ok {
|
||||
// Entry doesn't exist yet. Determine tile pair alignment.
|
||||
offset, err := AlignTilePair(tileA, tileB, tileAlignmentSearchRadius)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to align tile pair %v %v: %w", tileA, tileB, err)
|
||||
}
|
||||
|
||||
operations++
|
||||
log.Printf("(%v/%v)Got alignment for pair %v %v. Offset = %v", operations, maxOperations, tileA, tileB, offset)
|
||||
|
||||
// Store tile alignment pair, also reversed.
|
||||
tp[tileAlignmentKeys{tileA, tileB}] = tileAlignment{offset: offset}
|
||||
tp[tileAlignmentKeys{tileB, tileA}] = tileAlignment{offset: offset.Mul(-1)}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Silly and hacky method to determine the minimal error.
|
||||
// TODO: Use some mixed integer method or something similar to optimize the tile alignment
|
||||
|
||||
// The error function returns the x and y error. The axes are optimized independent of each other later on.
|
||||
errorFunction := func(tiles []*imageTile) (image.Point, error) {
|
||||
errorValue := image.Point{}
|
||||
|
||||
for i, tileA := range tiles {
|
||||
for j := i + 1; j < len(tiles); j++ {
|
||||
tileB := tiles[j]
|
||||
|
||||
tileAlignment, ok := tp[tileAlignmentKeys{tileA, tileB}]
|
||||
if !ok {
|
||||
return image.Point{}, fmt.Errorf("Offset of the tile pair %v %v is missing", tileA, tileB)
|
||||
}
|
||||
|
||||
// The error is the difference between the needed offset, and the actual offsets
|
||||
tempErrorValue := pointAbs(tileAlignment.offset.Sub(tileA.offset).Add(tileB.offset))
|
||||
|
||||
errorValue = errorValue.Add(tempErrorValue)
|
||||
}
|
||||
}
|
||||
return errorValue, nil
|
||||
}
|
||||
|
||||
errorValue, err := errorFunction(tiles)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to calculate error value: %w", err)
|
||||
}
|
||||
// Randomly select tiles, and move them in the direction where the error value is lower.
|
||||
// The "gradient" is basically caluclated by try and error.
|
||||
for i := 0; i < len(tiles)*tileAlignmentSearchRadius*5; i++ {
|
||||
tile := tiles[rand.Intn(len(tiles))]
|
||||
|
||||
// Calculate error value for positive shifting.
|
||||
tile.offset = tile.offset.Add(image.Point{1, 1})
|
||||
plusErrorValue, err := errorFunction(tiles)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to calculate error value: %w", err)
|
||||
}
|
||||
|
||||
// Calculate error value for negative shifting.
|
||||
tile.offset = tile.offset.Add(image.Point{-2, -2})
|
||||
minusErrorValue, err := errorFunction(tiles)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to calculate error value: %w", err)
|
||||
}
|
||||
|
||||
// Reset tile movement.
|
||||
tile.offset = tile.offset.Add(image.Point{1, 1})
|
||||
|
||||
// Move this tile towards the smaller error value.
|
||||
if plusErrorValue.X < errorValue.X {
|
||||
tile.offset = tile.offset.Add(image.Point{1, 0})
|
||||
}
|
||||
if minusErrorValue.X < errorValue.X {
|
||||
tile.offset = tile.offset.Add(image.Point{-1, 0})
|
||||
}
|
||||
if plusErrorValue.Y < errorValue.Y {
|
||||
tile.offset = tile.offset.Add(image.Point{0, 1})
|
||||
}
|
||||
if minusErrorValue.Y < errorValue.Y {
|
||||
tile.offset = tile.offset.Add(image.Point{0, -1})
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Move images in a way that the majority of images is positioned equal to their original position
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tp tilePairs) Stitch(tiles []imageTile, destImage *image.RGBA) error {
|
||||
// Stitch takes a list of tiles and stitches them together.
|
||||
// The destImage shouldn't be too large, or it gets too slow.
|
||||
func Stitch(tiles []imageTile, destImage *image.RGBA) error {
|
||||
intersectTiles := []*imageTile{}
|
||||
images := []*image.RGBA{}
|
||||
|
||||
@ -225,19 +82,12 @@ func (tp tilePairs) Stitch(tiles []imageTile, destImage *image.RGBA) error {
|
||||
}
|
||||
imgCopy := *img
|
||||
imgCopy.Rect = imgCopy.Rect.Add(tile.offset).Inset(4) // Reduce image bounds by 4 pixels on each side, because otherwise there will be artifacts.
|
||||
images = append(images, &imgCopy)
|
||||
images = append(images, &imgCopy) // TODO: Fix transparent pixels at the output image border because of Inset
|
||||
}
|
||||
}
|
||||
|
||||
//log.Printf("intersectTiles: %v", intersectTiles)
|
||||
|
||||
// Align those tiles
|
||||
/*if err := tp.alignTiles(intersectTiles); err != nil {
|
||||
return fmt.Errorf("Failed to align tiles: %w", err)
|
||||
}*/
|
||||
|
||||
// TODO: Add working aligning algorithm
|
||||
|
||||
/*for _, intersectTile := range intersectTiles {
|
||||
intersectTile.loadImage()
|
||||
draw.Draw(destImage, destImage.Bounds(), intersectTile.image, destImage.Bounds().Min, draw.Over)
|
||||
@ -254,7 +104,7 @@ 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) {
|
||||
func StitchGrid(tiles []imageTile, destImage *image.RGBA, gridSize int) (errResult error) {
|
||||
//workloads := gridifyRectangle(destImage.Bounds(), gridSize)
|
||||
workloads, err := hilbertifyRectangle(destImage.Bounds(), gridSize)
|
||||
if err != nil {
|
||||
@ -272,7 +122,7 @@ func (tp tilePairs) StitchGrid(tiles []imageTile, destImage *image.RGBA, gridSiz
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for workload := range wc {
|
||||
if err := tp.Stitch(tiles, destImage.SubImage(workload).(*image.RGBA)); err != nil {
|
||||
if err := 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)
|
||||
@ -318,6 +168,7 @@ func drawMedianBlended(images []*image.RGBA, destImage *image.RGBA) {
|
||||
|
||||
// If there were no images to get data from, ignore the pixel.
|
||||
if !found {
|
||||
//destImage.SetRGBA(ix, iy, color.RGBA{})
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -3,8 +3,6 @@
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
// TODO: Fix transparent pixels at the output image border
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
@ -159,8 +157,7 @@ func main() {
|
||||
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, 512); err != nil {
|
||||
if err := StitchGrid(tiles, outputImage, 512); err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user